Compare commits

..

38 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
74b6e41cd4 feat: Add swagger decorators 2024-09-07 15:46:49 +02:00
Ahmed Bouhuolia
d239003047 feat: Document API endpoints using Swagger 2024-09-07 15:17:10 +02:00
Ahmed Bouhuolia
9f21f649f0 Merge pull request #662 from bigcapitalhq/refactor-expense-gl
refactor: The expense G/L writer
2024-09-04 21:10:51 +02:00
Ahmed Bouhuolia
d3d2112b8a refactor: The expense G/L writer 2024-09-04 21:10:13 +02:00
Ahmed Bouhuolia
3795322a65 Merge pull request #659 from bigcapitalhq/format-money-table-columns
feat: Tabular number of all money columns
2024-09-04 18:48:38 +02:00
Ahmed Bouhuolia
fe5cd5a8ea feat: Mark more columns as money columns 2024-09-04 18:46:42 +02:00
Ahmed Bouhuolia
c032a5db16 Merge pull request #661 from bigcapitalhq/fix-payment-made-full-amount
fix: Payment made filling the form full amount field
2024-09-04 17:35:54 +02:00
Ahmed Bouhuolia
3fcb6fefde fix: Payment made filling the form full amount field 2024-09-04 17:34:19 +02:00
Ahmed Bouhuolia
16f5cb713d Merge pull request #660 from bigcapitalhq/fix-cast-array-rule-ids
fix: Array cast of recognize function rule ids
2024-09-04 16:49:32 +02:00
Ahmed Bouhuolia
f7a7925028 fix: Array cast of recognize function rule ids 2024-09-04 16:48:34 +02:00
Ahmed Bouhuolia
66fb0c9fa3 feat: Tabular number of all money columns 2024-09-04 14:58:31 +02:00
Ahmed Bouhuolia
85acc85f17 Merge pull request #655 from bigcapitalhq/ui-tweaks
feat: Datatable UI improvements
2024-09-04 14:02:43 +02:00
Ahmed Bouhuolia
c76ce09191 feat: optimize the style of bank account transactions tables 2024-09-04 13:59:13 +02:00
Ahmed Bouhuolia
e3532098b2 Merge branch 'develop' into ui-tweaks 2024-09-04 09:53:55 +02:00
Ahmed Bouhuolia
b6783eb22e Merge pull request #658 from bigcapitalhq/banking-layout-breaking
feat: Bank pages layout breaking
2024-09-04 09:52:35 +02:00
Ahmed Bouhuolia
4ef00ab122 feat: Bank pages layout breaking 2024-09-04 09:51:35 +02:00
Ahmed Bouhuolia
3dd827417a fix: remove duplicated event types 2024-09-03 17:31:00 +02:00
Ahmed Bouhuolia
cbacd02aa2 Merge pull request #656 from bigcapitalhq/add-help-dropdown-menu
feat: Add help dropdown menu
2024-09-03 17:28:12 +02:00
Ahmed Bouhuolia
3b7e0fb78a Merge pull request #657 from bigcapitalhq/suspense-lazy-banking-pages
fix: Suspense the lazy loaded components in banking pages
2024-09-03 17:26:02 +02:00
Ahmed Bouhuolia
9add716395 fix: Suspense the lazy loaded components in banking pages 2024-09-03 17:24:11 +02:00
Ahmed Bouhuolia
083ea28a1f feat: Add help dropdown menu 2024-09-03 16:41:20 +02:00
Ahmed Bouhuolia
1b51742c36 feat: Datatable UI improvements 2024-09-03 16:39:13 +02:00
Ahmed Bouhuolia
0c6f23e770 Merge pull request #654 from bigcapitalhq/expense-credit-card
fix: Expense cannot accept credit card as payment account
2024-09-03 12:23:03 +02:00
Ahmed Bouhuolia
37a8ca4e97 fix: Expense cannot accept credit card as payment account 2024-09-03 12:22:07 +02:00
Ahmed Bouhuolia
795303c3a8 Merge pull request #653 from bigcapitalhq/tracking-more-events
feat: Tracking more Posthog events
2024-09-03 11:28:27 +02:00
Ahmed Bouhuolia
63ba3f0898 Merge branch 'develop' into tracking-more-events 2024-09-03 11:28:05 +02:00
Ahmed Bouhuolia
62594efa00 feat: Tracking more Posthog events 2024-09-03 11:26:24 +02:00
Ahmed Bouhuolia
9ac0dcbdc3 Merge pull request #651 from bigcapitalhq/bank-transactions-events
feat: Track banking service events
2024-09-02 17:13:49 +02:00
Ahmed Bouhuolia
df588dc4ce Merge branch 'develop' into bank-transactions-events 2024-09-02 17:13:43 +02:00
Ahmed Bouhuolia
d54f14a87a feat: Track banking service events 2024-09-02 17:12:37 +02:00
Ahmed Bouhuolia
a4d4be54c1 Merge pull request #650 from bigcapitalhq/cover-more-tracking-events
feat: Cover more tracking events.
2024-09-02 15:20:11 +02:00
Ahmed Bouhuolia
ddd17e74b5 feat: Cover more tracking events. 2024-09-02 15:19:01 +02:00
Ahmed Bouhuolia
81c0761fbe Merge pull request #649 from bigcapitalhq/import-multi-branches-expenses
fix: Integrate multiple branches with expense resource
2024-09-02 15:02:53 +02:00
Ahmed Bouhuolia
0812e3087e fix: Integrate multiple branches with expense resource 2024-09-02 15:02:02 +02:00
Ahmed Bouhuolia
0dd05493b2 Merge pull request #645 from bigcapitalhq/multi-branches-warehoues-in-importing
feat: integrate multiple branches and warehouses to resource importing
2024-09-02 14:46:08 +02:00
Ahmed Bouhuolia
bfb3909d26 feat: integrate multiple branches and warehouses with import 2024-09-02 14:42:05 +02:00
Ahmed Bouhuolia
266902026e Merge pull request #648 from bigcapitalhq/fix-bank-transactions-infinity-scrolling
fix: Bank transactions infinity scrolling
2024-09-02 10:41:54 +02:00
Ahmed Bouhuolia
791c4a4e9e fix: Bank transactions infinity scrolling 2024-09-02 10:41:16 +02:00
130 changed files with 1845 additions and 560 deletions

View File

@@ -109,6 +109,7 @@
"rtl-detect": "^1.0.4", "rtl-detect": "^1.0.4",
"socket.io": "^4.7.4", "socket.io": "^4.7.4",
"source-map-loader": "^4.0.1", "source-map-loader": "^4.0.1",
"swagger-ui-express": "^5.0.1",
"tmp-promise": "^3.0.3", "tmp-promise": "^3.0.3",
"ts-transformer-keys": "^0.4.2", "ts-transformer-keys": "^0.4.2",
"tsyringe": "^4.3.0", "tsyringe": "^4.3.0",

View File

@@ -5,7 +5,14 @@ import { body, param } from 'express-validator';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication'; import { AttachmentsApplication } from '@/services/Attachments/AttachmentsApplication';
import { AttachmentUploadPipeline } from '@/services/Attachments/S3UploadPipeline'; import { AttachmentUploadPipeline } from '@/services/Attachments/S3UploadPipeline';
import {
ApiOperation,
ApiResponse,
ApiTags,
Route,
} from '@/decorators/swagger-decorators';
@ApiTags('Attachments')
@Service() @Service()
export class AttachmentsController extends BaseController { export class AttachmentsController extends BaseController {
@Inject() @Inject()
@@ -121,6 +128,26 @@ export class AttachmentsController extends BaseController {
* @param {NextFunction} next * @param {NextFunction} next
* @returns {Promise<Response|void>} * @returns {Promise<Response|void>}
*/ */
@ApiResponse({
status: 200,
description: 'Details of the given attachement',
schema: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' },
},
},
},
})
@ApiOperation({
summary: 'Retrieve a specific details of attachment',
description: 'Get all registered users',
})
@Route('/attachments/:id')
private async getAttachment( private async getAttachment(
req: Request, req: Request,
res: Response, res: Response,

View File

@@ -8,6 +8,11 @@ import { IItemDTO, ItemAction, AbilitySubject } from '@/interfaces';
import { DATATYPES_LENGTH } from '@/data/DataTypes'; import { DATATYPES_LENGTH } from '@/data/DataTypes';
import CheckAbilities from '@/api/middleware/CheckPolicies'; import CheckAbilities from '@/api/middleware/CheckPolicies';
import { ItemsApplication } from '@/services/Items/ItemsApplication'; import { ItemsApplication } from '@/services/Items/ItemsApplication';
import {
ApiOperation,
ApiResponse,
Route,
} from '@/decorators/swagger-decorators';
@Service() @Service()
export default class ItemsController extends BaseController { export default class ItemsController extends BaseController {
@@ -198,6 +203,22 @@ export default class ItemsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
@ApiResponse({
status: 200,
description: 'Details of the given attachement',
schema: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
},
})
@ApiOperation({
summary: 'Creates a new item (inventory or service)',
description: 'Get all registered users',
})
@Route('/items')
private async newItem(req: Request, res: Response, next: NextFunction) { private async newItem(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const itemDTO: IItemDTO = this.matchedBodyData(req); const itemDTO: IItemDTO = this.matchedBodyData(req);

View File

@@ -34,6 +34,42 @@ export const ITEM_EVENT_DELETED = 'Item deleted';
export const AUTH_SIGNED_UP = 'Auth Signed-up'; export const AUTH_SIGNED_UP = 'Auth Signed-up';
export const AUTH_RESET_PASSWORD = 'Auth reset password'; export const AUTH_RESET_PASSWORD = 'Auth reset password';
export const SUBSCRIPTION_CANCELLED = 'Subscription cancelled';
export const SUBSCRIPTION_RESUMED = 'Subscription resumed';
export const SUBSCRIPTION_PLAN_CHANGED = 'Subscription plan changed';
export const CUSTOMER_CREATED = 'Customer created';
export const CUSTOMER_EDITED = 'Customer edited';
export const CUSTOMER_DELETED = 'Customer deleted';
export const VENDOR_CREATED = 'Vendor created';
export const VENDOR_EDITED = 'Vendor edited';
export const VENDOR_DELETED = 'Vendor deleted';
export const TRANSACTIONS_LOCKING_LOCKED = 'Transactions locking locked';
export const TRANSACTIONS_LOCKING_LOCKING_CANCELLED =
'Transactions locking cancelled';
export const TRANSACTIONS_LOCKING_PARTIALLY_UNLOCKED =
'Transactions locking partially unlocked';
export const TRANSACTIONS_LOCKING_PARTIALLY_UNLOCK_CANCELLED =
'Transactions locking partially unlock cancelled';
export const BANK_TRANSACTION_MATCHED = 'Bank transaction matching deleted';
export const BANK_TRANSACTION_EXCLUDED = 'Bank transaction excluded';
export const BANK_TRANSACTION_CATEGORIZED = 'Bank transaction categorized';
export const BANK_TRANSACTION_UNCATEGORIZED = 'Bank transaction uncategorized';
export const BANK_ACCOUNT_DISCONNECTED = 'Bank account disconnected';
export const MANUAL_JOURNAL_CREATED = 'Manual journal created';
export const MANUAL_JOURNAL_EDITED = 'Manual journal edited';
export const MANUAL_JOURNAL_DELETED = 'Manual journal deleted';
export const MANUAL_JOURNAL_PUBLISHED = 'Manual journal published';
export const BANK_RULE_CREATED = 'Bank rule created';
export const BANK_RULE_EDITED = 'Bank rule edited';
export const BANK_RULE_DELETED = 'Bank rule deleted';
// # Event Groups
export const ACCOUNT_GROUP = 'Account'; export const ACCOUNT_GROUP = 'Account';
export const ITEM_GROUP = 'Item'; export const ITEM_GROUP = 'Item';
export const AUTH_GROUP = 'Auth'; export const AUTH_GROUP = 'Auth';

View File

@@ -0,0 +1,63 @@
export const swaggerDocs = {
tags: {},
paths: {},
};
// Decorator to set a tag for a route
export function ApiTags(tag) {
return function (target) {
if (!swaggerDocs.tags[tag]) {
swaggerDocs.tags[tag] = { name: tag };
}
};
}
// Decorator to add an operation for a specific route
export function ApiOperation(options) {
return function (target, propertyKey, descriptor) {
const routePath = Reflect.getMetadata('path', target, propertyKey);
swaggerDocs.paths[routePath] = swaggerDocs.paths[routePath] || {};
swaggerDocs.paths[routePath].get = {
summary: options.summary,
description: options.description || '',
responses: options.responses || {
200: {
description: 'Successful Response',
},
},
};
};
}
// Decorator to define the route path
export function Route(path) {
return function (target, propertyKey, descriptor) {
Reflect.defineMetadata('path', path, target, propertyKey);
};
}
// Decorator to add a response schema for a specific route
export function ApiResponse(options) {
return function (target, propertyKey, descriptor) {
const routePath = Reflect.getMetadata('path', target, propertyKey);
if (!swaggerDocs.paths[routePath]) {
swaggerDocs.paths[routePath] = { get: {} };
}
swaggerDocs.paths[routePath].get.responses =
swaggerDocs.paths[routePath].get.responses || {};
swaggerDocs.paths[routePath].get.responses[options.status] = {
description: options.description || 'No description provided',
content: {
'application/json': {
schema: options.schema || {},
},
},
};
};
}

View File

@@ -99,6 +99,7 @@ export interface IBillsFilter extends IDynamicListFilterDTO {
stringifiedFilterRoles?: string; stringifiedFilterRoles?: string;
page: number; page: number;
pageSize: number; pageSize: number;
filterQuery?: (q: any) => void;
} }
export interface IBillsService { export interface IBillsService {

View File

@@ -241,6 +241,7 @@ export interface ICustomerEventCreatingPayload {
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface ICustomerEventEditedPayload { export interface ICustomerEventEditedPayload {
tenantId: number
customerId: number; customerId: number;
customer: ICustomer; customer: ICustomer;
trx: Knex.Transaction; trx: Knex.Transaction;

View File

@@ -130,7 +130,9 @@ export interface ICreditNoteOpenedPayload {
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface ICreditNotesQueryDTO {} export interface ICreditNotesQueryDTO {
filterQuery?: (query: any) => void;
}
export interface ICreditNotesQueryDTO extends IDynamicListFilter { export interface ICreditNotesQueryDTO extends IDynamicListFilter {
page: number; page: number;

View File

@@ -17,6 +17,7 @@ export interface IExpensesFilter {
columnSortBy: string; columnSortBy: string;
sortOrder: string; sortOrder: string;
viewSlug?: string; viewSlug?: string;
filterQuery?: (query: any) => void;
} }
export interface IExpense { export interface IExpense {

View File

@@ -151,6 +151,7 @@ export interface IModelMetaFieldCommon2 {
importHint?: string; importHint?: string;
order?: number; order?: number;
unique?: number; unique?: number;
features?: Array<any>
} }
export interface IModelMetaRelationField2 { export interface IModelMetaRelationField2 {

View File

@@ -44,6 +44,7 @@ export interface ISaleEstimateDTO {
export interface ISalesEstimatesFilter extends IDynamicListFilterDTO { export interface ISalesEstimatesFilter extends IDynamicListFilterDTO {
stringifiedFilterRoles?: string; stringifiedFilterRoles?: string;
filterQuery?: (q: any) => void;
} }
export interface ISalesEstimatesService { export interface ISalesEstimatesService {

View File

@@ -79,6 +79,7 @@ export interface ISalesInvoicesFilter extends IDynamicListFilter {
page: number; page: number;
pageSize: number; pageSize: number;
searchKeyword?: string; searchKeyword?: string;
filterQuery?: (q: Knex.QueryBuilder) => void;
} }
export interface ISalesInvoicesService { export interface ISalesInvoicesService {

View File

@@ -29,7 +29,9 @@ export interface ISaleReceipt {
entries?: IItemEntry[]; entries?: IItemEntry[];
} }
export interface ISalesReceiptsFilter {} export interface ISalesReceiptsFilter {
filterQuery?: (query: any) => void;
}
export interface ISaleReceiptDTO { export interface ISaleReceiptDTO {
customerId: number; customerId: number;

View File

@@ -106,6 +106,7 @@ export interface IVendorCreditsQueryDTO extends IDynamicListFilter {
page: number; page: number;
pageSize: number; pageSize: number;
searchKeyword?: string; searchKeyword?: string;
filterQuery?: (q: any) => void;
} }
export interface IVendorCreditEditingPayload { export interface IVendorCreditEditingPayload {

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
export default { export default {
defaultFilterField: 'vendor', defaultFilterField: 'vendor',
defaultSort: { defaultSort: {
@@ -167,6 +169,18 @@ export default {
}, },
}, },
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
billNumber: { billNumber: {
@@ -238,6 +252,22 @@ export default {
}, },
}, },
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true,
},
}, },
}; };

View File

@@ -402,6 +402,7 @@ export default class Bill extends mixin(TenantModel, [
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const BillLandedCost = require('models/BillLandedCost'); const BillLandedCost = require('models/BillLandedCost');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Warehouse = require('models/Warehouse');
const TaxRateTransaction = require('models/TaxRateTransaction'); const TaxRateTransaction = require('models/TaxRateTransaction');
const Document = require('models/Document'); const Document = require('models/Document');
const { MatchedBankTransaction } = require('models/MatchedBankTransaction'); const { MatchedBankTransaction } = require('models/MatchedBankTransaction');
@@ -453,6 +454,18 @@ export default class Bill extends mixin(TenantModel, [
}, },
}, },
/**
* Bill may has associated warehouse.
*/
warehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default,
join: {
from: 'bills.warehouseId',
to: 'warehouses.id',
},
},
/** /**
* Bill may has associated tax rate transactions. * Bill may has associated tax rate transactions.
*/ */

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
export default { export default {
defaultFilterField: 'vendor', defaultFilterField: 'vendor',
defaultSort: { defaultSort: {
@@ -141,6 +143,12 @@ export default {
}, },
}, },
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
vendorId: { vendorId: {
@@ -204,5 +212,13 @@ export default {
}, },
}, },
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
}, },
}; };

View File

@@ -0,0 +1,11 @@
export default {
fields2: {
name: {
name: 'Name',
fieldType: 'text',
required: true,
},
},
columns: {},
fields: {}
};

View File

@@ -1,7 +1,9 @@
import { Model } from 'objection'; import { Model, mixin } from 'objection';
import TenantModel from 'models/TenantModel'; import TenantModel from 'models/TenantModel';
import BranchMetadata from './Branch.settings';
import ModelSetting from './ModelSetting';
export default class Branch extends TenantModel { export default class Branch extends mixin(TenantModel, [ModelSetting]) {
/** /**
* Table name. * Table name.
*/ */
@@ -169,4 +171,11 @@ export default class Branch extends TenantModel {
}, },
}; };
} }
/**
* Model settings.
*/
static get meta() {
return BranchMetadata;
}
} }

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
function StatusFieldFilterQuery(query, role) { function StatusFieldFilterQuery(query, role) {
query.modify('filterByStatus', role.value); query.modify('filterByStatus', role.value);
} }
@@ -100,7 +102,7 @@ export default {
}, },
creditNoteDate: { creditNoteDate: {
name: 'Credit Note Date', name: 'Credit Note Date',
accessor: 'formattedCreditNoteDate' accessor: 'formattedCreditNoteDate',
}, },
referenceNo: { referenceNo: {
name: 'Reference No.', name: 'Reference No.',
@@ -147,6 +149,18 @@ export default {
}, },
}, },
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
customerId: { customerId: {
@@ -215,5 +229,21 @@ export default {
}, },
}, },
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true,
},
}, },
}; };

View File

@@ -175,6 +175,7 @@ export default class CreditNote extends mixin(TenantModel, [
const Customer = require('models/Customer'); const Customer = require('models/Customer');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document'); const Document = require('models/Document');
const Warehouse = require('models/Warehouse');
return { return {
/** /**
@@ -235,6 +236,18 @@ export default class CreditNote extends mixin(TenantModel, [
}, },
}, },
/**
* Credit note may has associated warehouse.
*/
warehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default,
join: {
from: 'credit_notes.warehouseId',
to: 'warehouses.id',
},
},
/** /**
* Credit note may has many attached attachments. * Credit note may has many attached attachments.
*/ */

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
/** /**
* Expense - Settings. * Expense - Settings.
*/ */
@@ -119,6 +121,12 @@ export default {
type: 'boolean', type: 'boolean',
printable: false, printable: false,
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
paymentAccountId: { paymentAccountId: {
@@ -178,6 +186,14 @@ export default {
name: 'expense.field.publish', name: 'expense.field.publish',
fieldType: 'boolean', fieldType: 'boolean',
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
}, },
}; };

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
export default { export default {
importable: true, importable: true,
@@ -128,6 +130,12 @@ export default {
type: 'date', type: 'date',
printable: false, printable: false,
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
customerId: { customerId: {
@@ -189,5 +197,13 @@ export default {
}, },
}, },
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
}, },
}; };

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
export default { export default {
defaultFilterField: 'estimate_date', defaultFilterField: 'estimate_date',
defaultSort: { defaultSort: {
@@ -174,6 +176,18 @@ export default {
}, },
}, },
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
customerId: { customerId: {
@@ -252,6 +266,22 @@ export default {
}, },
}, },
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true,
},
}, },
}; };

View File

@@ -182,6 +182,7 @@ export default class SaleEstimate extends mixin(TenantModel, [
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const Customer = require('models/Customer'); const Customer = require('models/Customer');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Warehouse = require('models/Warehouse');
const Document = require('models/Document'); const Document = require('models/Document');
return { return {
@@ -221,6 +222,18 @@ export default class SaleEstimate extends mixin(TenantModel, [
}, },
}, },
/**
* Sale estimate may has associated warehouse.
*/
warehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default,
join: {
from: 'sales_estimates.warehouseId',
to: 'warehouses.id',
},
},
/** /**
* Sale estimate transaction may has many attached attachments. * Sale estimate transaction may has many attached attachments.
*/ */

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
export default { export default {
defaultFilterField: 'customer', defaultFilterField: 'customer',
defaultSort: { defaultSort: {
@@ -185,6 +187,18 @@ export default {
}, },
}, },
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
invoiceDate: { invoiceDate: {
@@ -268,6 +282,22 @@ export default {
fieldType: 'boolean', fieldType: 'boolean',
printable: false, printable: false,
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true,
},
}, },
}; };

View File

@@ -408,6 +408,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
const InventoryCostLotTracker = require('models/InventoryCostLotTracker'); const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
const PaymentReceiveEntry = require('models/PaymentReceiveEntry'); const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Warehouse = require('models/Warehouse');
const Account = require('models/Account'); const Account = require('models/Account');
const TaxRateTransaction = require('models/TaxRateTransaction'); const TaxRateTransaction = require('models/TaxRateTransaction');
const Document = require('models/Document'); const Document = require('models/Document');
@@ -499,6 +500,18 @@ export default class SaleInvoice extends mixin(TenantModel, [
}, },
}, },
/**
* Invoice may has associated warehouse.
*/
warehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default,
join: {
from: 'sales_invoices.warehouseId',
to: 'warehouses.id',
}
},
/** /**
* Invoice may has associated written-off expense account. * Invoice may has associated written-off expense account.
*/ */

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
export default { export default {
defaultFilterField: 'receipt_date', defaultFilterField: 'receipt_date',
defaultSort: { defaultSort: {
@@ -169,6 +171,18 @@ export default {
type: 'date', type: 'date',
printable: false, printable: false,
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
receiptDate: { receiptDate: {
@@ -245,6 +259,22 @@ export default {
name: 'Receipt Message', name: 'Receipt Message',
fieldType: 'text', fieldType: 'text',
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true,
},
}, },
}; };

View File

@@ -109,6 +109,7 @@ export default class SaleReceipt extends mixin(TenantModel, [
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document'); const Document = require('models/Document');
const Warehouse = require('models/Warehouse');
return { return {
customer: { customer: {
@@ -169,6 +170,18 @@ export default class SaleReceipt extends mixin(TenantModel, [
}, },
}, },
/**
* Sale receipt may has associated warehouse.
*/
warehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default,
join: {
from: 'sales_receipts.warehouseId',
to: 'warehouses.id',
},
},
/** /**
* Sale receipt transaction may has many attached attachments. * Sale receipt transaction may has many attached attachments.
*/ */

View File

@@ -1,3 +1,5 @@
import { Features } from '@/interfaces';
function StatusFieldFilterQuery(query, role) { function StatusFieldFilterQuery(query, role) {
query.modify('filterByStatus', role.value); query.modify('filterByStatus', role.value);
} }
@@ -160,6 +162,18 @@ export default {
}, },
}, },
}, },
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
}, },
fields2: { fields2: {
vendorId: { vendorId: {
@@ -225,5 +239,21 @@ export default {
}, },
}, },
}, },
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true
},
}, },
}; };

View File

@@ -178,6 +178,7 @@ export default class VendorCredit extends mixin(TenantModel, [
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document'); const Document = require('models/Document');
const Warehouse = require('models/Warehouse');
return { return {
vendor: { vendor: {
@@ -217,6 +218,18 @@ export default class VendorCredit extends mixin(TenantModel, [
}, },
}, },
/**
* Vendor credit may has associated warehouse.
*/
warehouse: {
relation: Model.BelongsToOneRelation,
modelClass: Warehouse.default,
join: {
from: 'vendor_credits.warehouseId',
to: 'warehouses.id',
},
},
/** /**
* Vendor credit may has many attached attachments. * Vendor credit may has many attached attachments.
*/ */

View File

@@ -0,0 +1,11 @@
export default {
fields2: {
name: {
name: 'Name',
fieldType: 'text',
required: true,
},
},
columns: {},
fields: {}
};

View File

@@ -78,9 +78,9 @@ export class RecognizeTranasctionsService {
}); });
const bankRules = await BankRule.query(trx).onBuild((q) => { const bankRules = await BankRule.query(trx).onBuild((q) => {
const rulesIds = castArray(ruleId); const rulesIds = !isEmpty(ruleId) ? castArray(ruleId) : [];
if (!isEmpty(rulesIds)) { if (rulesIds?.length > 0) {
q.whereIn('id', rulesIds); q.whereIn('id', rulesIds);
} }
q.withGraphFetched('conditions'); q.withGraphFetched('conditions');

View File

@@ -65,6 +65,7 @@ export class EditCustomer {
}); });
// Triggers `onCustomerEdited` event. // Triggers `onCustomerEdited` event.
await this.eventPublisher.emitAsync(events.customers.onEdited, { await this.eventPublisher.emitAsync(events.customers.onEdited, {
tenantId,
customerId, customerId,
customer, customer,
trx, trx,

View File

@@ -15,12 +15,17 @@ export class CreditNotesExportable extends Exportable {
* @returns {} * @returns {}
*/ */
public exportable(tenantId: number, query: ICreditNotesQueryDTO) { public exportable(tenantId: number, query: ICreditNotesQueryDTO) {
const filterQuery = (query) => {
query.withGraphFetched('branch');
query.withGraphFetched('warehouse');
};
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
...query, ...query,
page: 1, page: 1,
pageSize: 12000, pageSize: 12000,
filterQuery,
} as ICreditNotesQueryDTO; } as ICreditNotesQueryDTO;
return this.getCreditNotes return this.getCreditNotes

View File

@@ -48,6 +48,7 @@ export default class ListCreditNotes extends BaseCreditNotes {
builder.withGraphFetched('entries.item'); builder.withGraphFetched('entries.item');
builder.withGraphFetched('customer'); builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
creditNotesQuery?.filterQuery && creditNotesQuery?.filterQuery(builder);
}) })
.pagination(filter.page - 1, filter.pageSize); .pagination(filter.page - 1, filter.pageSize);

View File

@@ -0,0 +1,68 @@
import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import {
IBankRuleEventCreatedPayload,
IBankRuleEventEditedPayload,
IBankRuleEventDeletedPayload,
} from '@/services/Banking/Rules/types'; // Updated import path for interfaces
import { PosthogService } from '../PostHog';
import events from '@/subscribers/events';
import {
BANK_RULE_CREATED,
BANK_RULE_EDITED,
BANK_RULE_DELETED,
} from '@/constants/event-tracker';
@Service()
export class BankRuleEventsTracker extends EventSubscriber {
@Inject()
private posthog: PosthogService;
/**
* Constructor method.
*/
public attach(bus) {
bus.subscribe(
events.bankRules.onCreated,
this.handleTrackBankRuleCreatedEvent
);
bus.subscribe(
events.bankRules.onEdited,
this.handleTrackEditedBankRuleEvent
);
bus.subscribe(
events.bankRules.onDeleted,
this.handleTrackDeletedBankRuleEvent
);
}
private handleTrackBankRuleCreatedEvent = ({
tenantId,
}: IBankRuleEventCreatedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_RULE_CREATED,
properties: {},
});
};
private handleTrackEditedBankRuleEvent = ({
tenantId,
}: IBankRuleEventEditedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_RULE_EDITED,
properties: {},
});
};
private handleTrackDeletedBankRuleEvent = ({
tenantId,
}: IBankRuleEventDeletedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_RULE_DELETED,
properties: {},
});
};
}

View File

@@ -0,0 +1,97 @@
import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import { PosthogService } from '../PostHog';
import events from '@/subscribers/events';
import {
BANK_TRANSACTION_MATCHED,
BANK_TRANSACTION_EXCLUDED,
BANK_TRANSACTION_CATEGORIZED,
BANK_TRANSACTION_UNCATEGORIZED,
BANK_ACCOUNT_DISCONNECTED,
} from '@/constants/event-tracker';
import { IBankTransactionMatchedEventPayload } from '@/services/Banking/Matching/types';
import { IBankAccountDisconnectedEventPayload } from '@/services/Banking/BankAccounts/types';
import {
ICashflowTransactionCategorizedPayload,
ICashflowTransactionUncategorizedPayload,
} from '@/interfaces';
import { IBankTransactionExcludedEventPayload } from '@/services/Banking/Exclude/_types';
@Service()
export class BankTransactionEventsTracker extends EventSubscriber {
@Inject()
private posthog: PosthogService;
public attach(bus) {
bus.subscribe(
events.bankMatch.onMatched,
this.handleTrackBankTransactionMatchedEvent
);
bus.subscribe(
events.bankTransactions.onExcluded,
this.handleTrackBankTransactionExcludedEvent
);
bus.subscribe(
events.cashflow.onTransactionCategorized,
this.handleTrackBankTransactionCategorizedEvent
);
bus.subscribe(
events.cashflow.onTransactionUncategorized,
this.handleTrackBankTransactionUncategorizedEvent
);
bus.subscribe(
events.bankAccount.onDisconnected,
this.handleTrackBankAccountDisconnectedEvent
);
}
private handleTrackBankTransactionMatchedEvent = ({
tenantId,
}: IBankTransactionMatchedEventPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_TRANSACTION_MATCHED,
properties: {},
});
};
private handleTrackBankTransactionExcludedEvent = ({
tenantId,
}: IBankTransactionExcludedEventPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_TRANSACTION_EXCLUDED,
properties: {},
});
};
private handleTrackBankTransactionCategorizedEvent = ({
tenantId,
}: ICashflowTransactionCategorizedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_TRANSACTION_CATEGORIZED,
properties: {},
});
};
private handleTrackBankTransactionUncategorizedEvent = ({
tenantId,
}: ICashflowTransactionUncategorizedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_TRANSACTION_UNCATEGORIZED,
properties: {},
});
};
private handleTrackBankAccountDisconnectedEvent = ({
tenantId,
}: IBankAccountDisconnectedEventPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: BANK_ACCOUNT_DISCONNECTED,
properties: {},
});
};
}

View File

@@ -0,0 +1,65 @@
import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import {
ICustomerEventCreatedPayload,
ICustomerEventEditedPayload,
ICustomerEventDeletedPayload,
} from '@/interfaces';
import { PosthogService } from '../PostHog';
import events from '@/subscribers/events';
import {
CUSTOMER_CREATED,
CUSTOMER_EDITED,
CUSTOMER_DELETED,
} from '@/constants/event-tracker';
@Service()
export class CustomerEventsTracker extends EventSubscriber {
@Inject()
private posthog: PosthogService;
public attach(bus) {
bus.subscribe(
events.customers.onCreated,
this.handleTrackCustomerCreatedEvent
);
bus.subscribe(
events.customers.onEdited,
this.handleTrackEditedCustomerEvent
);
bus.subscribe(
events.customers.onDeleted,
this.handleTrackDeletedCustomerEvent
);
}
private handleTrackCustomerCreatedEvent = ({
tenantId,
}: ICustomerEventCreatedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: CUSTOMER_CREATED,
properties: {},
});
};
private handleTrackEditedCustomerEvent = ({
tenantId,
}: ICustomerEventEditedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: CUSTOMER_EDITED,
properties: {},
});
};
private handleTrackDeletedCustomerEvent = ({
tenantId,
}: ICustomerEventDeletedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: CUSTOMER_DELETED,
properties: {},
});
};
}

View File

@@ -0,0 +1,68 @@
import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import {
IManualJournalEventCreatedPayload,
IManualJournalEventEditedPayload,
IManualJournalEventDeletedPayload,
} from '@/interfaces';
import { PosthogService } from '../PostHog';
import events from '@/subscribers/events';
import {
MANUAL_JOURNAL_CREATED,
MANUAL_JOURNAL_EDITED,
MANUAL_JOURNAL_DELETED,
} from '@/constants/event-tracker';
@Service()
export class ManualJournalEventsTracker extends EventSubscriber {
@Inject()
private posthog: PosthogService;
/**
* Constructor method.
*/
public attach(bus) {
bus.subscribe(
events.manualJournals.onCreated,
this.handleTrackManualJournalCreatedEvent
);
bus.subscribe(
events.manualJournals.onEdited,
this.handleTrackEditedManualJournalEvent
);
bus.subscribe(
events.manualJournals.onDeleted,
this.handleTrackDeletedManualJournalEvent
);
}
private handleTrackManualJournalCreatedEvent = ({
tenantId,
}: IManualJournalEventCreatedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: MANUAL_JOURNAL_CREATED,
properties: {},
});
};
private handleTrackEditedManualJournalEvent = ({
tenantId,
}: IManualJournalEventEditedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: MANUAL_JOURNAL_EDITED,
properties: {},
});
};
private handleTrackDeletedManualJournalEvent = ({
tenantId,
}: IManualJournalEventDeletedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: MANUAL_JOURNAL_DELETED,
properties: {},
});
};
}

View File

@@ -0,0 +1,76 @@
import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import { ITransactionsLockingPartialUnlocked } from '@/interfaces';
import { PosthogService } from '../PostHog';
import {
TRANSACTIONS_LOCKING_LOCKED,
TRANSACTIONS_LOCKING_LOCKING_CANCELLED,
TRANSACTIONS_LOCKING_PARTIALLY_UNLOCK_CANCELLED,
TRANSACTIONS_LOCKING_PARTIALLY_UNLOCKED,
} from '@/constants/event-tracker';
import events from '@/subscribers/events';
@Service()
export class TransactionsLockingEventsTracker extends EventSubscriber {
@Inject()
private posthog: PosthogService;
public attach(bus) {
bus.subscribe(
events.transactionsLocking.locked,
this.handleTransactionsLockingLockedEvent
);
bus.subscribe(
events.transactionsLocking.lockCanceled,
this.handleTransactionsLockingCancelledEvent
);
bus.subscribe(
events.transactionsLocking.partialUnlocked,
this.handleTransactionsLockingPartiallyUnlockedEvent
);
bus.subscribe(
events.transactionsLocking.partialUnlockCanceled,
this.handleTransactionsLockingPartiallyUnlockCancelledEvent
);
}
private handleTransactionsLockingLockedEvent = ({
tenantId,
}: ITransactionsLockingPartialUnlocked) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: TRANSACTIONS_LOCKING_LOCKED,
properties: {},
});
};
private handleTransactionsLockingCancelledEvent = ({
tenantId,
}: ITransactionsLockingPartialUnlocked) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: TRANSACTIONS_LOCKING_LOCKING_CANCELLED,
properties: {},
});
};
private handleTransactionsLockingPartiallyUnlockedEvent = ({
tenantId,
}: ITransactionsLockingPartialUnlocked) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: TRANSACTIONS_LOCKING_PARTIALLY_UNLOCKED,
properties: {},
});
};
private handleTransactionsLockingPartiallyUnlockCancelledEvent = ({
tenantId,
}: ITransactionsLockingPartialUnlocked) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: TRANSACTIONS_LOCKING_PARTIALLY_UNLOCK_CANCELLED,
properties: {},
});
};
}

View File

@@ -0,0 +1,55 @@
import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import { ITransactionsLockingPartialUnlocked } from '@/interfaces';
import { PosthogService } from '../PostHog';
import {
SUBSCRIPTION_CANCELLED,
SUBSCRIPTION_PLAN_CHANGED,
SUBSCRIPTION_RESUMED,
} from '@/constants/event-tracker';
import events from '@/subscribers/events';
@Service()
export class TransactionsLockingEventsTracker extends EventSubscriber {
@Inject()
private posthog: PosthogService;
public attach(bus) {
bus.subscribe(
events.subscription.onSubscriptionResumed,
this.handleSubscriptionResumedEvent
);
bus.subscribe(
events.subscription.onSubscriptionCancelled,
this.handleSubscriptionCancelledEvent
);
bus.subscribe(
events.subscription.onSubscriptionPlanChanged,
this.handleSubscriptionPlanChangedEvent
);
}
private handleSubscriptionResumedEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: SUBSCRIPTION_RESUMED,
properties: {},
});
};
private handleSubscriptionCancelledEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: SUBSCRIPTION_CANCELLED,
properties: {},
});
};
private handleSubscriptionPlanChangedEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: SUBSCRIPTION_PLAN_CHANGED,
properties: {},
});
};
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import { EventSubscriber } from '@/lib/EventPublisher/EventPublisher';
import {
IVendorEventCreatedPayload,
IVendorEventEditedPayload,
IVendorEventDeletedPayload,
} from '@/interfaces';
import { PosthogService } from '../PostHog';
import events from '@/subscribers/events';
import {
VENDOR_CREATED,
VENDOR_EDITED,
VENDOR_DELETED,
} from '@/constants/event-tracker';
@Service()
export class VendorEventsTracker extends EventSubscriber {
@Inject()
private posthog: PosthogService;
public attach(bus) {
bus.subscribe(events.vendors.onCreated, this.handleTrackVendorCreatedEvent);
bus.subscribe(events.vendors.onEdited, this.handleTrackEditedVendorEvent);
bus.subscribe(events.vendors.onDeleted, this.handleTrackDeletedVendorEvent);
}
private handleTrackVendorCreatedEvent = ({
tenantId,
}: IVendorEventCreatedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: VENDOR_CREATED,
properties: {},
});
};
private handleTrackEditedVendorEvent = ({
tenantId,
}: IVendorEventEditedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: VENDOR_EDITED,
properties: {},
});
};
private handleTrackDeletedVendorEvent = ({
tenantId,
}: IVendorEventDeletedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: VENDOR_DELETED,
properties: {},
});
};
}

View File

@@ -7,6 +7,11 @@ import { ExpenseEventsTracker } from './ExpenseEventsTracker';
import { AccountEventsTracker } from './AccountEventsTracker'; import { AccountEventsTracker } from './AccountEventsTracker';
import { AuthenticationEventsTracker } from './AuthenticationEventsTracker'; import { AuthenticationEventsTracker } from './AuthenticationEventsTracker';
import { ItemEventsTracker } from './ItemEventsTracker'; import { ItemEventsTracker } from './ItemEventsTracker';
import { BankTransactionEventsTracker } from './BankTransactionEventsTracker';
import { CustomerEventsTracker } from './CustomerEventsTracker';
import { VendorEventsTracker } from './VendorEventsTracker';
import { ManualJournalEventsTracker } from './ManualJournalEventsTracker';
import { BankRuleEventsTracker } from './BankRuleEventsTracker';
export const EventsTrackerListeners = [ export const EventsTrackerListeners = [
SaleInvoiceEventsTracker, SaleInvoiceEventsTracker,
@@ -17,5 +22,10 @@ export const EventsTrackerListeners = [
AccountEventsTracker, AccountEventsTracker,
ExpenseEventsTracker, ExpenseEventsTracker,
AuthenticationEventsTracker, AuthenticationEventsTracker,
ItemEventsTracker ItemEventsTracker,
BankTransactionEventsTracker,
CustomerEventsTracker,
VendorEventsTracker,
ManualJournalEventsTracker,
BankRuleEventsTracker,
]; ];

View File

@@ -1,7 +1,7 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { sumBy, difference } from 'lodash'; import { sumBy, difference } from 'lodash';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { ERRORS } from '../constants'; import { ERRORS, SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES } from '../constants';
import { import {
IAccount, IAccount,
IExpense, IExpense,
@@ -79,7 +79,9 @@ export class CommandExpenseValidator {
* @throws {ServiceError} * @throws {ServiceError}
*/ */
public validatePaymentAccountType = (paymentAccount: number[]) => { public validatePaymentAccountType = (paymentAccount: number[]) => {
if (!paymentAccount.isParentType(ACCOUNT_PARENT_TYPE.CURRENT_ASSET)) { if (
!paymentAccount.isAccountType(SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES)
) {
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_HAS_INVALID_TYPE); throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_HAS_INVALID_TYPE);
} }
}; };

View File

@@ -54,6 +54,7 @@ export class GetExpenses {
builder.withGraphFetched('categories.expenseAccount'); builder.withGraphFetched('categories.expenseAccount');
dynamicList.buildQuery()(builder); dynamicList.buildQuery()(builder);
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
}) })
.pagination(filter.page - 1, filter.pageSize); .pagination(filter.page - 1, filter.pageSize);

View File

@@ -0,0 +1,113 @@
import * as R from 'ramda';
import {
AccountNormal,
IExpenseCategory,
ILedger,
ILedgerEntry,
} from '@/interfaces';
import Ledger from '../Accounting/Ledger';
export class ExpenseGL {
private expense: any;
/**
* Constructor method.
*/
constructor(expense: any) {
this.expense = expense;
}
/**
* Retrieves the expense GL common entry.
* @param {IExpense} expense
* @returns {Partial<ILedgerEntry>}
*/
private getExpenseGLCommonEntry = (): Partial<ILedgerEntry> => {
return {
currencyCode: this.expense.currencyCode,
exchangeRate: this.expense.exchangeRate,
transactionType: 'Expense',
transactionId: this.expense.id,
date: this.expense.paymentDate,
userId: this.expense.userId,
debit: 0,
credit: 0,
branchId: this.expense.branchId,
};
};
/**
* Retrieves the expense GL payment entry.
* @param {IExpense} expense
* @returns {ILedgerEntry}
*/
private getExpenseGLPaymentEntry = (): ILedgerEntry => {
const commonEntry = this.getExpenseGLCommonEntry();
return {
...commonEntry,
credit: this.expense.localAmount,
accountId: this.expense.paymentAccountId,
accountNormal:
this.expense?.paymentAccount?.accountNormal === 'debit'
? AccountNormal.DEBIT
: AccountNormal.CREDIT,
index: 1,
};
};
/**
* Retrieves the expense GL category entry.
* @param {IExpense} expense -
* @param {IExpenseCategory} expenseCategory -
* @param {number} index
* @returns {ILedgerEntry}
*/
private getExpenseGLCategoryEntry = R.curry(
(category: IExpenseCategory, index: number): ILedgerEntry => {
const commonEntry = this.getExpenseGLCommonEntry();
const localAmount = category.amount * this.expense.exchangeRate;
return {
...commonEntry,
accountId: category.expenseAccountId,
accountNormal: AccountNormal.DEBIT,
debit: localAmount,
note: category.description,
index: index + 2,
projectId: category.projectId,
};
}
);
/**
* Retrieves the expense GL entries.
* @param {IExpense} expense
* @returns {ILedgerEntry[]}
*/
public getExpenseGLEntries = (): ILedgerEntry[] => {
const getCategoryEntry = this.getExpenseGLCategoryEntry();
const paymentEntry = this.getExpenseGLPaymentEntry();
const categoryEntries = this.expense.categories.map(getCategoryEntry);
return [paymentEntry, ...categoryEntries];
};
/**
* Retrieves the given expense ledger.
* @param {IExpense} expense
* @returns {ILedger}
*/
public getExpenseLedger = (): ILedger => {
const entries = this.getExpenseGLEntries();
console.log(entries, 'entries');
return new Ledger(entries);
};
}

View File

@@ -1,106 +0,0 @@
import * as R from 'ramda';
import { Service } from 'typedi';
import {
AccountNormal,
IExpense,
IExpenseCategory,
ILedger,
ILedgerEntry,
} from '@/interfaces';
import Ledger from '@/services/Accounting/Ledger';
@Service()
export class ExpenseGLEntries {
/**
* Retrieves the expense GL common entry.
* @param {IExpense} expense
* @returns
*/
private getExpenseGLCommonEntry = (expense: IExpense) => {
return {
currencyCode: expense.currencyCode,
exchangeRate: expense.exchangeRate,
transactionType: 'Expense',
transactionId: expense.id,
date: expense.paymentDate,
userId: expense.userId,
debit: 0,
credit: 0,
branchId: expense.branchId,
};
};
/**
* Retrieves the expense GL payment entry.
* @param {IExpense} expense
* @returns {ILedgerEntry}
*/
private getExpenseGLPaymentEntry = (expense: IExpense): ILedgerEntry => {
const commonEntry = this.getExpenseGLCommonEntry(expense);
return {
...commonEntry,
credit: expense.localAmount,
accountId: expense.paymentAccountId,
accountNormal: AccountNormal.DEBIT,
index: 1,
};
};
/**
* Retrieves the expense GL category entry.
* @param {IExpense} expense -
* @param {IExpenseCategory} expenseCategory -
* @param {number} index
* @returns {ILedgerEntry}
*/
private getExpenseGLCategoryEntry = R.curry(
(
expense: IExpense,
category: IExpenseCategory,
index: number
): ILedgerEntry => {
const commonEntry = this.getExpenseGLCommonEntry(expense);
const localAmount = category.amount * expense.exchangeRate;
return {
...commonEntry,
accountId: category.expenseAccountId,
accountNormal: AccountNormal.DEBIT,
debit: localAmount,
note: category.description,
index: index + 2,
projectId: category.projectId,
};
}
);
/**
* Retrieves the expense GL entries.
* @param {IExpense} expense
* @returns {ILedgerEntry[]}
*/
public getExpenseGLEntries = (expense: IExpense): ILedgerEntry[] => {
const getCategoryEntry = this.getExpenseGLCategoryEntry(expense);
const paymentEntry = this.getExpenseGLPaymentEntry(expense);
const categoryEntries = expense.categories.map(getCategoryEntry);
return [paymentEntry, ...categoryEntries];
};
/**
* Retrieves the given expense ledger.
* @param {IExpense} expense
* @returns {ILedger}
*/
public getExpenseLedger = (expense: IExpense): ILedger => {
const entries = this.getExpenseGLEntries(expense);
return new Ledger(entries);
};
}

View File

@@ -0,0 +1,45 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import { IExpense, ILedger } from '@/interfaces';
import { ExpenseGL } from './ExpenseGL';
import HasTenancyService from '../Tenancy/TenancyService';
@Service()
export class ExpenseGLEntries {
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves the expense G/L of the given id.
* @param {number} tenantId
* @param {number} expenseId
* @param {Knex.Transaction} trx
* @returns {Promise<ILedger>}
*/
public getExpenseLedgerById = async (
tenantId: number,
expenseId: number,
trx?: Knex.Transaction
): Promise<ILedger> => {
const { Expense } = await this.tenancy.models(tenantId);
const expense = await Expense.query(trx)
.findById(expenseId)
.withGraphFetched('categories')
.withGraphFetched('paymentAccount')
.throwIfNotFound();
return this.getExpenseLedger(expense);
};
/**
* Retrieves the given expense ledger.
* @param {IExpense} expense
* @returns {ILedger}
*/
public getExpenseLedger = (expense: IExpense): ILedger => {
const expenseGL = new ExpenseGL(expense);
return expenseGL.getExpenseLedger();
};
}

View File

@@ -2,7 +2,7 @@ import { Knex } from 'knex';
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ExpenseGLEntries } from './ExpenseGLEntries'; import { ExpenseGLEntries } from './ExpenseGLEntriesService';
@Service() @Service()
export class ExpenseGLEntriesStorage { export class ExpenseGLEntriesStorage {
@@ -12,9 +12,6 @@ export class ExpenseGLEntriesStorage {
@Inject() @Inject()
private ledgerStorage: LedgerStorageService; private ledgerStorage: LedgerStorageService;
@Inject()
private tenancy: HasTenancyService;
/** /**
* Writes the expense GL entries. * Writes the expense GL entries.
* @param {number} tenantId * @param {number} tenantId
@@ -26,15 +23,12 @@ export class ExpenseGLEntriesStorage {
expenseId: number, expenseId: number,
trx?: Knex.Transaction trx?: Knex.Transaction
) => { ) => {
const { Expense } = await this.tenancy.models(tenantId);
const expense = await Expense.query(trx)
.findById(expenseId)
.withGraphFetched('categories');
// Retrieves the given expense ledger. // Retrieves the given expense ledger.
const expenseLedger = this.expenseGLEntries.getExpenseLedger(expense); const expenseLedger = await this.expenseGLEntries.getExpenseLedgerById(
tenantId,
expenseId,
trx
);
// Commits the expense ledger entries. // Commits the expense ledger entries.
await this.ledgerStorage.commit(tenantId, expenseLedger, trx); await this.ledgerStorage.commit(tenantId, expenseLedger, trx);
}; };

View File

@@ -15,12 +15,16 @@ export class ExpensesExportable extends Exportable {
* @returns * @returns
*/ */
public exportable(tenantId: number, query: IExpensesFilter) { public exportable(tenantId: number, query: IExpensesFilter) {
const filterQuery = (query) => {
query.withGraphFetched('branch');
};
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
...query, ...query,
page: 1, page: 1,
pageSize: EXPORT_SIZE_LIMIT, pageSize: EXPORT_SIZE_LIMIT,
filterQuery,
} as IExpensesFilter; } as IExpensesFilter;
return this.expensesApplication return this.expensesApplication

View File

@@ -1,3 +1,5 @@
import { ACCOUNT_TYPE } from '@/data/AccountTypes';
export const DEFAULT_VIEW_COLUMNS = []; export const DEFAULT_VIEW_COLUMNS = [];
export const DEFAULT_VIEWS = [ export const DEFAULT_VIEWS = [
{ {
@@ -76,3 +78,12 @@ export const ExpensesSampleData = [
Publish: 'T', Publish: 'T',
}, },
]; ];
export const SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES = [
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CREDIT_CARD,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
ACCOUNT_TYPE.NON_CURRENT_ASSET,
ACCOUNT_TYPE.FIXED_ASSET,
];

View File

@@ -56,7 +56,10 @@ export class ExportResourceService {
) { ) {
const resource = sanitizeResourceName(resourceName); const resource = sanitizeResourceName(resourceName);
const resourceMeta = this.getResourceMeta(tenantId, resource); const resourceMeta = this.getResourceMeta(tenantId, resource);
const resourceColumns = this.resourceService.getResourceColumns(
tenantId,
resource
);
this.validateResourceMeta(resourceMeta); this.validateResourceMeta(resourceMeta);
const data = await this.getExportableData(tenantId, resource); const data = await this.getExportableData(tenantId, resource);
@@ -64,7 +67,7 @@ export class ExportResourceService {
// Returns the csv, xlsx format. // Returns the csv, xlsx format.
if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) { if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) {
const exportableColumns = this.getExportableColumns(resourceMeta); const exportableColumns = this.getExportableColumns(resourceColumns);
const workbook = this.createWorkbook(transformed, exportableColumns); const workbook = this.createWorkbook(transformed, exportableColumns);
return this.exportWorkbook(workbook, format); return this.exportWorkbook(workbook, format);
@@ -143,7 +146,7 @@ export class ExportResourceService {
* @param {IModelMeta} resourceMeta - The metadata of the resource. * @param {IModelMeta} resourceMeta - The metadata of the resource.
* @returns An array of exportable columns. * @returns An array of exportable columns.
*/ */
private getExportableColumns(resourceMeta: IModelMeta) { private getExportableColumns(resourceColumns: any) {
const processColumns = ( const processColumns = (
columns: { [key: string]: IModelMetaColumn }, columns: { [key: string]: IModelMetaColumn },
parent = '' parent = ''
@@ -166,7 +169,7 @@ export class ExportResourceService {
} }
}); });
}; };
return processColumns(resourceMeta.columns); return processColumns(resourceColumns);
} }
private getPrintableColumns(resourceMeta: IModelMeta) { private getPrintableColumns(resourceMeta: IModelMeta) {

View File

@@ -33,6 +33,8 @@ export class ImportFileMapping {
// Invalidate the from/to map attributes. // Invalidate the from/to map attributes.
this.validateMapsAttrs(tenantId, importFile, maps); this.validateMapsAttrs(tenantId, importFile, maps);
// @todo validate the required fields.
// Validate the diplicated relations of map attrs. // Validate the diplicated relations of map attrs.
this.validateDuplicatedMapAttrs(maps); this.validateDuplicatedMapAttrs(maps);

View File

@@ -16,6 +16,7 @@ export class BillPaymentExportable extends Exportable {
public exportable(tenantId: number, query: any) { public exportable(tenantId: number, query: any) {
const filterQuery = (builder) => { const filterQuery = (builder) => {
builder.withGraphFetched('entries.bill'); builder.withGraphFetched('entries.bill');
builder.withGraphFetched('branch');
}; };
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',

View File

@@ -1,8 +1,10 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { IBillsFilter } from '@/interfaces'; import { IBillsFilter } from '@/interfaces';
import { Exportable } from '@/services/Export/Exportable'; import { Exportable } from '@/services/Export/Exportable';
import { BillsApplication } from './BillsApplication'; import { BillsApplication } from './BillsApplication';
import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants'; import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
import Objection from 'objection';
@Service() @Service()
export class BillsExportable extends Exportable { export class BillsExportable extends Exportable {
@@ -15,12 +17,17 @@ export class BillsExportable extends Exportable {
* @returns * @returns
*/ */
public exportable(tenantId: number, query: IBillsFilter) { public exportable(tenantId: number, query: IBillsFilter) {
const filterQuery = (query) => {
query.withGraphFetched('branch');
query.withGraphFetched('warehouse');
};
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
...query, ...query,
page: 1, page: 1,
pageSize: EXPORT_SIZE_LIMIT, pageSize: EXPORT_SIZE_LIMIT,
filterQuery,
} as IBillsFilter; } as IBillsFilter;
return this.billsApplication return this.billsApplication

View File

@@ -51,6 +51,9 @@ export class GetBills {
builder.withGraphFetched('vendor'); builder.withGraphFetched('vendor');
builder.withGraphFetched('entries.item'); builder.withGraphFetched('entries.item');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
// Filter query.
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
}) })
.pagination(filter.page - 1, filter.pageSize); .pagination(filter.page - 1, filter.pageSize);

View File

@@ -48,6 +48,10 @@ export default class ListVendorCredits extends BaseVendorCredit {
builder.withGraphFetched('entries'); builder.withGraphFetched('entries');
builder.withGraphFetched('vendor'); builder.withGraphFetched('vendor');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
// Gives ability to inject custom query to filter results.
vendorCreditQuery?.filterQuery &&
vendorCreditQuery?.filterQuery(builder);
}) })
.pagination(filter.page - 1, filter.pageSize); .pagination(filter.page - 1, filter.pageSize);

View File

@@ -2,6 +2,7 @@ import { Inject, Service } from 'typedi';
import { IVendorCreditsQueryDTO } from '@/interfaces'; import { IVendorCreditsQueryDTO } from '@/interfaces';
import ListVendorCredits from './ListVendorCredits'; import ListVendorCredits from './ListVendorCredits';
import { Exportable } from '@/services/Export/Exportable'; import { Exportable } from '@/services/Export/Exportable';
import { QueryBuilder } from 'knex';
@Service() @Service()
export class VendorCreditsExportable extends Exportable { export class VendorCreditsExportable extends Exportable {
@@ -15,12 +16,17 @@ export class VendorCreditsExportable extends Exportable {
* @returns {} * @returns {}
*/ */
public exportable(tenantId: number, query: IVendorCreditsQueryDTO) { public exportable(tenantId: number, query: IVendorCreditsQueryDTO) {
const filterQuery = (query) => {
query.withGraphFetched('branch');
query.withGraphFetched('warehouse');
};
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
...query, ...query,
page: 1, page: 1,
pageSize: 12000, pageSize: 12000,
filterQuery,
} as IVendorCreditsQueryDTO; } as IVendorCreditsQueryDTO;
return this.getVendorCredits return this.getVendorCredits

View File

@@ -1,11 +1,18 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { camelCase, upperFirst, pickBy } from 'lodash'; import { camelCase, upperFirst, pickBy, isEmpty } from 'lodash';
import * as qim from 'qim'; import * as qim from 'qim';
import pluralize from 'pluralize'; import pluralize from 'pluralize';
import { IModelMeta, IModelMetaField, IModelMetaField2 } from '@/interfaces'; import {
Features,
IModelMeta,
IModelMetaField,
IModelMetaField2,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService'; import TenancyService from '@/services/Tenancy/TenancyService';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import I18nService from '@/services/I18n/I18nService'; import I18nService from '@/services/I18n/I18nService';
import { WarehousesSettings } from '../Warehouses/WarehousesSettings';
import { BranchesSettings } from '../Branches/BranchesSettings';
const ERRORS = { const ERRORS = {
RESOURCE_MODEL_NOT_FOUND: 'RESOURCE_MODEL_NOT_FOUND', RESOURCE_MODEL_NOT_FOUND: 'RESOURCE_MODEL_NOT_FOUND',
@@ -19,6 +26,12 @@ export default class ResourceService {
@Inject() @Inject()
i18nService: I18nService; i18nService: I18nService;
@Inject()
private branchesSettings: BranchesSettings;
@Inject()
private warehousesSettings: WarehousesSettings;
/** /**
* Transform resource to model name. * Transform resource to model name.
* @param {string} resourceName * @param {string} resourceName
@@ -74,13 +87,45 @@ export default class ResourceService {
return meta.fields; return meta.fields;
} }
public filterSupportFeatures = (
tenantId,
fields: { [key: string]: IModelMetaField2 }
) => {
const isMultiFeaturesEnabled =
this.branchesSettings.isMultiBranchesActive(tenantId);
const isMultiWarehousesEnabled =
this.warehousesSettings.isMultiWarehousesActive(tenantId);
return pickBy(fields, (field) => {
if (
!isMultiWarehousesEnabled &&
field.features?.includes(Features.WAREHOUSES)
) {
return false;
}
if (
!isMultiFeaturesEnabled &&
field.features?.includes(Features.BRANCHES)
) {
return false;
}
return true;
});
};
public getResourceFields2( public getResourceFields2(
tenantId: number, tenantId: number,
modelName: string modelName: string
): { [key: string]: IModelMetaField2 } { ): { [key: string]: IModelMetaField2 } {
const meta = this.getResourceMeta(tenantId, modelName); const meta = this.getResourceMeta(tenantId, modelName);
return meta.fields2; return this.filterSupportFeatures(tenantId, meta.fields2);
}
public getResourceColumns(tenantId: number, modelName: string) {
const meta = this.getResourceMeta(tenantId, modelName);
return this.filterSupportFeatures(tenantId, meta.columns);
} }
/** /**

View File

@@ -53,6 +53,7 @@ export class GetSaleEstimates {
builder.withGraphFetched('entries'); builder.withGraphFetched('entries');
builder.withGraphFetched('entries.item'); builder.withGraphFetched('entries.item');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
}) })
.pagination(filter.page - 1, filter.pageSize); .pagination(filter.page - 1, filter.pageSize);

View File

@@ -15,12 +15,17 @@ export class SaleEstimatesExportable extends Exportable {
* @returns * @returns
*/ */
public exportable(tenantId: number, query: ISalesInvoicesFilter) { public exportable(tenantId: number, query: ISalesInvoicesFilter) {
const filterQuery = (query) => {
query.withGraphFetched('branch');
query.withGraphFetched('warehouse');
};
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
...query, ...query,
page: 1, page: 1,
pageSize: EXPORT_SIZE_LIMIT, pageSize: EXPORT_SIZE_LIMIT,
filterQuery,
} as ISalesInvoicesFilter; } as ISalesInvoicesFilter;
return this.saleEstimatesApplication return this.saleEstimatesApplication

View File

@@ -52,6 +52,7 @@ export class GetSaleInvoices {
builder.withGraphFetched('entries.item'); builder.withGraphFetched('entries.item');
builder.withGraphFetched('customer'); builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
}) })
.pagination(filter.page - 1, filter.pageSize); .pagination(filter.page - 1, filter.pageSize);

View File

@@ -15,12 +15,17 @@ export class SaleInvoicesExportable extends Exportable {
* @returns * @returns
*/ */
public exportable(tenantId: number, query: ISalesInvoicesFilter) { public exportable(tenantId: number, query: ISalesInvoicesFilter) {
const filterQuery = (query) => {
query.withGraphFetched('branch');
query.withGraphFetched('warehouse');
};
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
...query, ...query,
page: 1, page: 1,
pageSize: EXPORT_SIZE_LIMIT, pageSize: EXPORT_SIZE_LIMIT,
filterQuery,
} as ISalesInvoicesFilter; } as ISalesInvoicesFilter;
return this.saleInvoicesApplication return this.saleInvoicesApplication

View File

@@ -18,6 +18,7 @@ export class PaymentsReceivedExportable extends Exportable {
public exportable(tenantId: number, query: IPaymentsReceivedFilter) { public exportable(tenantId: number, query: IPaymentsReceivedFilter) {
const filterQuery = (builder) => { const filterQuery = (builder) => {
builder.withGraphFetched('entries.invoice'); builder.withGraphFetched('entries.invoice');
builder.withGraphFetched('branch');
}; };
const parsedQuery = { const parsedQuery = {

View File

@@ -56,6 +56,7 @@ export class GetSaleReceipts {
builder.withGraphFetched('entries.item'); builder.withGraphFetched('entries.item');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
}) })
.pagination(filter.page - 1, filter.pageSize); .pagination(filter.page - 1, filter.pageSize);

View File

@@ -15,12 +15,17 @@ export class SaleReceiptsExportable extends Exportable {
* @returns * @returns
*/ */
public exportable(tenantId: number, query: ISalesReceiptsFilter) { public exportable(tenantId: number, query: ISalesReceiptsFilter) {
const filterQuery = (query) => {
query.withGraphFetched('branch');
query.withGraphFetched('warehouse');
};
const parsedQuery = { const parsedQuery = {
sortOrder: 'desc', sortOrder: 'desc',
columnSortBy: 'created_at', columnSortBy: 'created_at',
...query, ...query,
page: 1, page: 1,
pageSize: EXPORT_SIZE_LIMIT, pageSize: EXPORT_SIZE_LIMIT,
filterQuery,
} as ISalesReceiptsFilter; } as ISalesReceiptsFilter;
return this.saleReceiptsApp return this.saleReceiptsApp

View File

@@ -9,7 +9,12 @@ import {
Classes, Classes,
Tooltip, Tooltip,
Position, Position,
MenuItem,
Menu,
MenuDivider,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { Popover2 } from '@blueprintjs/popover2';
import { FormattedMessage as T, Icon, Hint, If } from '@/components'; import { FormattedMessage as T, Icon, Hint, If } from '@/components';
import DashboardTopbarUser from '@/components/Dashboard/TopbarUser'; import DashboardTopbarUser from '@/components/Dashboard/TopbarUser';
@@ -19,9 +24,20 @@ import DashboardBackLink from '@/components/Dashboard/DashboardBackLink';
import withUniversalSearchActions from '@/containers/UniversalSearch/withUniversalSearchActions'; import withUniversalSearchActions from '@/containers/UniversalSearch/withUniversalSearchActions';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions'; import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import withDashboard from '@/containers/Dashboard/withDashboard'; import withDashboard from '@/containers/Dashboard/withDashboard';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import QuickNewDropdown from '@/containers/QuickNewDropdown/QuickNewDropdown'; import QuickNewDropdown from '@/containers/QuickNewDropdown/QuickNewDropdown';
import { DashboardHamburgerButton, DashboardQuickSearchButton } from './_components'; import {
DashboardHamburgerButton,
DashboardQuickSearchButton,
} from './_components';
import { DialogsName } from '@/constants/dialogs';
import {
COMMUNITY_BIGCAPITAL_LINK,
DOCS_BIGCAPITAL_LINK,
} from '@/constants/routes';
import { compose } from '@/utils'; import { compose } from '@/utils';
/** /**
@@ -41,6 +57,9 @@ function DashboardTopbar({
// #withGlobalSearch // #withGlobalSearch
openGlobalSearch, openGlobalSearch,
// #withDialogActions
openDialog,
}) { }) {
const history = useHistory(); const history = useHistory();
@@ -112,11 +131,34 @@ function DashboardTopbar({
/> />
</Tooltip> </Tooltip>
<Button <Popover2
className={Classes.MINIMAL} content={
icon={<Icon icon={'help-24'} iconSize={20} />} <Menu>
text={<T id={'help'} />} <MenuItem
/> text={'Documents'}
onClick={() => window.open(DOCS_BIGCAPITAL_LINK)}
labelElement={<Icon icon={'share'} iconSize={16} />}
/>
<MenuItem
text={'Community support'}
onClick={() => window.open(COMMUNITY_BIGCAPITAL_LINK)}
labelElement={<Icon icon={'share'} iconSize={16} />}
/>
<MenuItem
text={'Keyboard shortcuts'}
onClick={() => openDialog(DialogsName.KeyboardShortcutForm)}
/>
<MenuDivider />
<MenuItem text={'Share feedback'} />
</Menu>
}
>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'help-24'} iconSize={20} />}
text={<T id={'help'} />}
/>
</Popover2>
<NavbarDivider /> <NavbarDivider />
</NavbarGroup> </NavbarGroup>
</Navbar> </Navbar>
@@ -138,4 +180,5 @@ export default compose(
pageHint, pageHint,
})), })),
withDashboardActions, withDashboardActions,
withDialogActions,
)(DashboardTopbar); )(DashboardTopbar);

View File

@@ -1,13 +1,14 @@
// @ts-nocheck // @ts-nocheck
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { camelCase} from 'lodash'; import { camelCase } from 'lodash';
import { If, Skeleton } from '@/components'; import { If, Skeleton } from '@/components';
import { useAppIntlContext } from '@/components/AppIntlProvider'; import { useAppIntlContext } from '@/components/AppIntlProvider';
import TableContext from './TableContext'; import TableContext from './TableContext';
import { saveInvoke, ignoreEventFromSelectors } from '@/utils'; import { saveInvoke, ignoreEventFromSelectors } from '@/utils';
import { isCellLoading } from './utils'; import { isCellLoading } from './utils';
import { MoneyDisplay } from '../Money/MoneyDisplay';
const ROW_CLICK_SELECTORS_INGORED = ['.expand-toggle', '.selection-checkbox']; const ROW_CLICK_SELECTORS_INGORED = ['.expand-toggle', '.selection-checkbox'];
@@ -58,7 +59,7 @@ export default function TableCell({ cell, row, index }) {
return; return;
} }
saveInvoke(onCellClick, cell, event); saveInvoke(onCellClick, cell, event);
}; };
const cellType = camelCase(cell.column.Cell.cellType) || 'text'; const cellType = camelCase(cell.column.Cell.cellType) || 'text';
return ( return (
@@ -109,7 +110,11 @@ export default function TableCell({ cell, row, index }) {
</span> </span>
</If> </If>
{cell.render('Cell')} {cell.column?.money ? (
<MoneyDisplay>{cell.render('Cell')}</MoneyDisplay>
) : (
<>{cell.render('Cell')}</>
)}
</div> </div>
</div> </div>
); );

View File

@@ -0,0 +1,4 @@
.root {
font-variant-numeric: tabular-nums;
}

View File

@@ -0,0 +1,9 @@
import styles from './MoneyDisplay.module.scss';
interface MoneyDisplayProps {
children: React.ReactNode;
}
export function MoneyDisplay({ children }: MoneyDisplayProps) {
return <span className={styles.root}>{children}</span>;
}

View File

@@ -0,0 +1,2 @@
export const DOCS_BIGCAPITAL_LINK = 'https://docs.bigcapital.app';
export const COMMUNITY_BIGCAPITAL_LINK = 'https://community.bigcapital.app';

View File

@@ -27,6 +27,7 @@ export const useManualJournalsColumns = () => {
accessor: 'formatted_amount', accessor: 'formatted_amount',
width: 115, width: 115,
clickable: true, clickable: true,
money: true,
align: 'right', align: 'right',
className: clsx(CLASSES.FONT_BOLD), className: clsx(CLASSES.FONT_BOLD),
}, },

View File

@@ -1,7 +1,8 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Intent, Tag } from '@blueprintjs/core'; import { Intent, Tag, Classes } from '@blueprintjs/core';
import clsx from 'classnames';
import { If, AppToaster } from '@/components'; import { If, AppToaster } from '@/components';
import { NormalCell, BalanceCell, BankBalanceCell } from './components'; import { NormalCell, BalanceCell, BankBalanceCell } from './components';
@@ -73,7 +74,7 @@ export const useAccountsTableColumns = () => {
id: 'type', id: 'type',
Header: intl.get('type'), Header: intl.get('type'),
accessor: 'account_type_label', accessor: 'account_type_label',
className: 'type', className: clsx('type', Classes.TEXT_MUTED),
width: 140, width: 140,
clickable: true, clickable: true,
textOverview: true, textOverview: true,
@@ -91,6 +92,7 @@ export const useAccountsTableColumns = () => {
id: 'currency', id: 'currency',
Header: intl.get('currency'), Header: intl.get('currency'),
accessor: 'currency_code', accessor: 'currency_code',
className: clsx(Classes.TEXT_MUTED),
width: 75, width: 75,
clickable: true, clickable: true,
}, },
@@ -102,6 +104,7 @@ export const useAccountsTableColumns = () => {
width: 150, width: 150,
clickable: true, clickable: true,
align: 'right', align: 'right',
money: true,
}, },
{ {
id: 'balance', id: 'balance',
@@ -110,6 +113,7 @@ export const useAccountsTableColumns = () => {
Cell: BalanceCell, Cell: BalanceCell,
width: 150, width: 150,
clickable: true, clickable: true,
money: true,
align: 'right', align: 'right',
}, },
], ],

View File

@@ -34,21 +34,23 @@ import {
} from '@/constants/cashflowOptions'; } from '@/constants/cashflowOptions';
import { useRefreshCashflowTransactions } from '@/hooks/query'; import { useRefreshCashflowTransactions } from '@/hooks/query';
import { useAccountTransactionsContext } from './AccountTransactionsProvider'; import { useAccountTransactionsContext } from './AccountTransactionsProvider';
import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useAppShellContext } from '@/components/AppShell/AppContentShell/AppContentShellProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions'; import withDialogActions from '@/containers/Dialog/withDialogActions';
import withSettings from '@/containers/Settings/withSettings'; import withSettings from '@/containers/Settings/withSettings';
import withSettingsActions from '@/containers/Settings/withSettingsActions'; import withSettingsActions from '@/containers/Settings/withSettingsActions';
import { withBankingActions } from '../withBankingActions';
import { compose } from '@/utils'; import { withBanking } from '../withBanking';
import withAlertActions from '@/containers/Alert/withAlertActions';
import { import {
useUpdateBankAccount, useUpdateBankAccount,
useExcludeUncategorizedTransactions, useExcludeUncategorizedTransactions,
useUnexcludeUncategorizedTransactions, useUnexcludeUncategorizedTransactions,
} from '@/hooks/query/bank-rules'; } from '@/hooks/query/bank-rules';
import { withBankingActions } from '../withBankingActions';
import { withBanking } from '../withBanking';
import withAlertActions from '@/containers/Alert/withAlertActions';
import { DialogsName } from '@/constants/dialogs'; import { DialogsName } from '@/constants/dialogs';
import { compose } from '@/utils';
function AccountTransactionsActionsBar({ function AccountTransactionsActionsBar({
// #withDialogActions // #withDialogActions
@@ -221,6 +223,13 @@ function AccountTransactionsActionsBar({
}); });
}; };
const { hideAside } = useAppShellContext();
const isMin1350Query = useMediaQuery('(min-width: 1350px)');
// Shrink actions to dropdown if the aside is open and matched the media query,
// To avoid actions overflow in small screens.
const shrinkActions = !hideAside && !isMin1350Query;
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
<NavbarGroup> <NavbarGroup>
@@ -241,23 +250,27 @@ function AccountTransactionsActionsBar({
}} }}
/> />
<NavbarDivider /> <NavbarDivider />
<Button
className={Classes.MINIMAL} <If condition={!shrinkActions}>
icon={<Icon icon="print-16" iconSize={16} />} <Button
text={<T id={'print'} />} className={Classes.MINIMAL}
/> icon={<Icon icon="print-16" iconSize={16} />}
<Button text={<T id={'print'} />}
className={Classes.MINIMAL} />
icon={<Icon icon="file-export-16" iconSize={16} />} <Button
text={<T id={'export'} />} className={Classes.MINIMAL}
/> icon={<Icon icon="file-export-16" iconSize={16} />}
<Button text={<T id={'export'} />}
className={Classes.MINIMAL} />
icon={<Icon icon="file-import-16" iconSize={16} />} <Button
text={<T id={'import'} />} className={Classes.MINIMAL}
onClick={handleImportBtnClick} icon={<Icon icon="file-import-16" iconSize={16} />}
/> text={<T id={'import'} />}
<NavbarDivider /> onClick={handleImportBtnClick}
/>
<NavbarDivider />
</If>
<DashboardRowsHeightButton <DashboardRowsHeightButton
initialValue={cashflowTansactionsTableSize} initialValue={cashflowTansactionsTableSize}
onChange={handleTableRowSizeChange} onChange={handleTableRowSizeChange}
@@ -290,6 +303,40 @@ function AccountTransactionsActionsBar({
</Tooltip> </Tooltip>
</If> </If>
<If condition={shrinkActions}>
<NavbarDivider />
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
icon={<Icon icon="print-16" iconSize={16} />}
text={<T id={'print'} />}
/>
<MenuItem
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<MenuItem
icon={<Icon icon="file-import-16" iconSize={16} />}
text={<T id={'import'} />}
onClick={handleImportBtnClick}
/>
</Menu>
}
>
<Button
icon={<Icon icon="more-h-16" iconSize={16} />}
minimal={true}
/>
</Popover>
</If>
{!isEmpty(uncategorizedTransationsIdsSelected) && ( {!isEmpty(uncategorizedTransationsIdsSelected) && (
<Button <Button
icon={<Icon icon="disable" iconSize={16} />} icon={<Icon icon="disable" iconSize={16} />}
@@ -368,7 +415,6 @@ function AccountTransactionsActionsBar({
</If> </If>
<MenuItem onClick={handleBankRulesClick} text={'Bank rules'} /> <MenuItem onClick={handleBankRulesClick} text={'Bank rules'} />
<MenuDivider /> <MenuDivider />
<If condition={isSyncingOwner && isFeedsActive}> <If condition={isSyncingOwner && isFeedsActive}>
<MenuItem <MenuItem

View File

@@ -0,0 +1,20 @@
// @ts-nocheck
import { Suspense, lazy } from 'react';
import { Spinner } from '@blueprintjs/core';
import { AppContentShell } from '@/components/AppShell';
const CategorizeTransactionAside = lazy(() =>
import('../CategorizeTransactionAside/CategorizeTransactionAside').then(
(module) => ({ default: module.CategorizeTransactionAside }),
),
);
export function AccountTransactionsAside() {
return (
<AppContentShell.Aside>
<Suspense fallback={<Spinner size={20} />}>
<CategorizeTransactionAside />
</Suspense>
</AppContentShell.Aside>
);
}

View File

@@ -22,6 +22,7 @@ import { withBankingActions } from '../withBankingActions';
import { useMemorizedColumnsWidths } from '@/hooks'; import { useMemorizedColumnsWidths } from '@/hooks';
import { useAccountTransactionsColumns, ActionsMenu } from './components'; import { useAccountTransactionsColumns, ActionsMenu } from './components';
import { useAccountTransactionsAllContext } from './AccountTransactionsAllBoot'; import { useAccountTransactionsAllContext } from './AccountTransactionsAllBoot';
import { useAccountTransactionsContext } from './AccountTransactionsProvider';
import { useUnmatchMatchedUncategorizedTransaction } from '@/hooks/query/bank-rules'; import { useUnmatchMatchedUncategorizedTransaction } from '@/hooks/query/bank-rules';
import { useUncategorizeTransaction } from '@/hooks/query'; import { useUncategorizeTransaction } from '@/hooks/query';
import { handleCashFlowTransactionType } from './utils'; import { handleCashFlowTransactionType } from './utils';
@@ -62,6 +63,8 @@ function AccountTransactionsDataTable({
const [initialColumnsWidths, , handleColumnResizing] = const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.CASHFLOW_Transactions); useMemorizedColumnsWidths(TABLES.CASHFLOW_Transactions);
const { scrollableRef } = useAccountTransactionsContext();
// Handle view details action. // Handle view details action.
const handleViewDetailCashflowTransaction = (referenceType) => { const handleViewDetailCashflowTransaction = (referenceType) => {
handleCashFlowTransactionType(referenceType, openDrawer); handleCashFlowTransactionType(referenceType, openDrawer);
@@ -132,7 +135,7 @@ function AccountTransactionsDataTable({
ContextMenu={ActionsMenu} ContextMenu={ActionsMenu}
onCellClick={handleCellClick} onCellClick={handleCellClick}
// #TableVirtualizedListRows props. // #TableVirtualizedListRows props.
vListrowHeight={cashflowTansactionsTableSize == 'small' ? 32 : 40} vListrowHeight={32}
vListOverscanRowCount={0} vListOverscanRowCount={0}
initialColumnsWidths={initialColumnsWidths} initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing} onColumnResizing={handleColumnResizing}
@@ -140,6 +143,7 @@ function AccountTransactionsDataTable({
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
noResults={<T id={'cash_flow.account_transactions.no_results'} />} noResults={<T id={'cash_flow.account_transactions.no_results'} />}
className="table-constrant" className="table-constrant"
windowScrollerProps={{ scrollElement: scrollableRef }}
payload={{ payload={{
onViewDetails: handleViewDetailCashflowTransaction, onViewDetails: handleViewDetailCashflowTransaction,
onUncategorize: handleUncategorizeTransaction, onUncategorize: handleUncategorizeTransaction,

View File

@@ -2,6 +2,8 @@
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import styled from 'styled-components'; import styled from 'styled-components';
import { curry } from 'lodash/fp';
import { useHistory } from 'react-router-dom';
import { import {
Popover, Popover,
Menu, Menu,
@@ -11,10 +13,9 @@ import {
Classes, Classes,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { Icon } from '@/components'; import { Icon } from '@/components';
import { useHistory } from 'react-router-dom';
import { curry } from 'lodash/fp';
import { useAccountTransactionsContext } from './AccountTransactionsProvider'; import { useAccountTransactionsContext } from './AccountTransactionsProvider';
import { useAppShellContext } from '@/components/AppShell/AppContentShell/AppContentShellProvider';
function AccountSwitchButton() { function AccountSwitchButton() {
const { currentAccount } = useAccountTransactionsContext(); const { currentAccount } = useAccountTransactionsContext();
@@ -22,7 +23,7 @@ function AccountSwitchButton() {
return ( return (
<AccountSwitchButtonBase <AccountSwitchButtonBase
minimal={true} minimal={true}
rightIcon={<Icon icon={'arrow-drop-down'} iconSize={24} />} rightIcon={<Icon icon={'caret-down-16'} iconSize={16} />}
> >
<AccountSwitchText>{currentAccount.name}</AccountSwitchText> <AccountSwitchText>{currentAccount.name}</AccountSwitchText>
</AccountSwitchButtonBase> </AccountSwitchButtonBase>
@@ -110,12 +111,16 @@ function AccountTransactionsDetailsBarSkeleton() {
} }
function AccountTransactionsDetailsContent() { function AccountTransactionsDetailsContent() {
const { hideAside } = useAppShellContext();
return ( return (
<React.Fragment> <React.Fragment>
<AccountSwitchItem /> <AccountSwitchItem />
<AccountNumberItem />
{/** Hide some details once the aside opens to preserve space on details bar. */}
{hideAside && <AccountNumberItem />}
<AccountBalanceItem /> <AccountBalanceItem />
<AccountBankBalanceItem /> {hideAside && <AccountBankBalanceItem />}
</React.Fragment> </React.Fragment>
); );
} }

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React, { Suspense } from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { Spinner } from '@blueprintjs/core'; import { Spinner } from '@blueprintjs/core';
import { Suspense, lazy } from 'react';
import '@/style/pages/CashFlow/AccountTransactions/List.scss'; import '@/style/pages/CashFlow/AccountTransactions/List.scss';
@@ -15,7 +15,7 @@ import {
import { AccountTransactionsDetailsBar } from './AccountTransactionsDetailsBar'; import { AccountTransactionsDetailsBar } from './AccountTransactionsDetailsBar';
import { AccountTransactionsFilterTabs } from './AccountTransactionsFilterTabs'; import { AccountTransactionsFilterTabs } from './AccountTransactionsFilterTabs';
import { AppContentShell } from '@/components/AppShell'; import { AppContentShell } from '@/components/AppShell';
import { CategorizeTransactionAside } from '../CategorizeTransactionAside/CategorizeTransactionAside'; import { AccountTransactionsAside } from './AccountTransactionsAside';
import { AccountTransactionsLoadingBar } from './components'; import { AccountTransactionsLoadingBar } from './components';
import { withBanking } from '../withBanking'; import { withBanking } from '../withBanking';
@@ -56,14 +56,6 @@ function AccountTransactionsMain() {
); );
} }
function AccountTransactionsAside() {
return (
<AppContentShell.Aside>
<CategorizeTransactionAside />
</AppContentShell.Aside>
);
}
export default R.compose( export default R.compose(
withBanking( withBanking(
({ selectedUncategorizedTransactionId, openMatchingTransactionAside }) => ({ ({ selectedUncategorizedTransactionId, openMatchingTransactionAside }) => ({
@@ -73,11 +65,8 @@ export default R.compose(
), ),
)(AccountTransactionsListRoot); )(AccountTransactionsListRoot);
const AccountsTransactionsAll = React.lazy( const AccountsTransactionsAll = lazy(() => import('./AccountsTransactionsAll'));
() => import('./AccountsTransactionsAll'), const AccountsTransactionsUncategorized = lazy(
);
const AccountsTransactionsUncategorized = React.lazy(
() => import('./AllTransactionsUncategorized'), () => import('./AllTransactionsUncategorized'),
); );

View File

@@ -1,10 +1,8 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import styled from 'styled-components';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import * as R from 'ramda'; import * as R from 'ramda';
import { import {
DataTable,
TableFastCell, TableFastCell,
TableSkeletonRows, TableSkeletonRows,
TableSkeletonHeader, TableSkeletonHeader,
@@ -17,9 +15,10 @@ import { useMemorizedColumnsWidths } from '@/hooks';
import { useExcludedTransactionsColumns } from './_utils'; import { useExcludedTransactionsColumns } from './_utils';
import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot'; import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
import { useAccountTransactionsContext } from '../AccountTransactionsProvider'; import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
import { ActionsMenu } from './_components'; import { ActionsMenu } from './_components';
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules'; import { BankAccountDataTable } from '../components/BankAccountDataTable';
import { import {
WithBankingActionsProps, WithBankingActionsProps,
withBankingActions, withBankingActions,
@@ -78,7 +77,7 @@ function ExcludedTransactionsTableRoot({
}; };
return ( return (
<CashflowTransactionsTable <BankAccountDataTable
noInitialFetch={true} noInitialFetch={true}
columns={columns} columns={columns}
data={excludedBankTransactions} data={excludedBankTransactions}
@@ -116,42 +115,3 @@ function ExcludedTransactionsTableRoot({
export const ExcludedTransactionsTable = R.compose(withBankingActions)( export const ExcludedTransactionsTable = R.compose(withBankingActions)(
ExcludedTransactionsTableRoot, ExcludedTransactionsTableRoot,
); );
const DashboardConstrantTable = styled(DataTable)`
.table {
.thead {
.th {
background: #fff;
letter-spacing: 1px;
text-transform: uppercase;
font-size: 13px;
}
}
.tbody {
.tr:last-child .td {
border-bottom: 0;
}
}
}
`;
const CashflowTransactionsTable = styled(DashboardConstrantTable)`
.table .tbody {
.tbody-inner .tr.no-results {
.td {
padding: 2rem 0;
font-size: 14px;
color: #888;
font-weight: 400;
border-bottom: 0;
}
}
.tbody-inner {
.tr .td {
border-bottom: 1px solid #e6e6e6;
}
}
}
`;

View File

@@ -54,12 +54,14 @@ export function useExcludedTransactionsColumns() {
accessor: 'formatted_deposit_amount', accessor: 'formatted_deposit_amount',
align: 'right', align: 'right',
width: depositWidth, width: depositWidth,
money: true
}, },
{ {
Header: 'Withdrawal', Header: 'Withdrawal',
accessor: 'formatted_withdrawal_amount', accessor: 'formatted_withdrawal_amount',
align: 'right', align: 'right',
width: withdrawalWidth, width: withdrawalWidth,
money: true
}, },
], ],
[], [],

View File

@@ -1,9 +1,6 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import clsx from 'classnames';
import styled from 'styled-components';
import { import {
DataTable,
TableFastCell, TableFastCell,
TableSkeletonRows, TableSkeletonRows,
TableSkeletonHeader, TableSkeletonHeader,
@@ -16,6 +13,7 @@ import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
import { usePendingTransactionsContext } from './PendingTransactionsTableBoot'; import { usePendingTransactionsContext } from './PendingTransactionsTableBoot';
import { usePendingTransactionsTableColumns } from './_hooks'; import { usePendingTransactionsTableColumns } from './_hooks';
import { BankAccountDataTable } from '../components/BankAccountDataTable';
import { compose } from '@/utils'; import { compose } from '@/utils';
/** /**
@@ -37,7 +35,7 @@ function PendingTransactionsDataTableRoot({
} = usePendingTransactionsContext(); } = usePendingTransactionsContext();
return ( return (
<CashflowTransactionsTable <BankAccountDataTable
noInitialFetch={true} noInitialFetch={true}
columns={columns} columns={columns}
data={pendingTransactions || []} data={pendingTransactions || []}
@@ -54,7 +52,6 @@ function PendingTransactionsDataTableRoot({
vListOverscanRowCount={0} vListOverscanRowCount={0}
noResults={'There is no pending transactions in the current account.'} noResults={'There is no pending transactions in the current account.'}
windowScrollerProps={{ scrollElement: scrollableRef }} windowScrollerProps={{ scrollElement: scrollableRef }}
className={clsx('table-constrant')}
/> />
); );
} }
@@ -65,47 +62,3 @@ export const PendingTransactionsDataTable = compose(
})), })),
withBankingActions, withBankingActions,
)(PendingTransactionsDataTableRoot); )(PendingTransactionsDataTableRoot);
const DashboardConstrantTable = styled(DataTable)`
.table {
.thead {
.th {
background: #fff;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 13px;i
font-weight: 500;
}
}
.tbody {
.tr:last-child .td {
border-bottom: 0;
}
}
}
`;
const CashflowTransactionsTable = styled(DashboardConstrantTable)`
.table .tbody {
.tbody-inner .tr.no-results {
.td {
padding: 2rem 0;
font-size: 14px;
color: #888;
font-weight: 400;
border-bottom: 0;
}
}
.tbody-inner {
.tr .td:not(:first-child) {
border-left: 1px solid #e6e6e6;
}
.td-description {
color: #5f6b7c;
}
}
}
`;

View File

@@ -48,6 +48,7 @@ export function usePendingTransactionsTableColumns() {
textOverview: true, textOverview: true,
align: 'right', align: 'right',
clickable: true, clickable: true,
money: true
}, },
{ {
id: 'withdrawal', id: 'withdrawal',
@@ -58,6 +59,7 @@ export function usePendingTransactionsTableColumns() {
textOverview: true, textOverview: true,
align: 'right', align: 'right',
clickable: true, clickable: true,
money: true
}, },
], ],
[], [],

View File

@@ -27,6 +27,7 @@ import {
withBankingActions, withBankingActions,
} from '../../withBankingActions'; } from '../../withBankingActions';
import styles from './RecognizedTransactionsTable.module.scss'; import styles from './RecognizedTransactionsTable.module.scss';
import { BankAccountDataTable } from '../components/BankAccountDataTable';
interface RecognizedTransactionsTableProps extends WithBankingActionsProps {} interface RecognizedTransactionsTableProps extends WithBankingActionsProps {}
@@ -83,7 +84,7 @@ function RecognizedTransactionsTableRoot({
}; };
return ( return (
<CashflowTransactionsTable <BankAccountDataTable
noInitialFetch={true} noInitialFetch={true}
columns={columns} columns={columns}
data={recognizedTransactions} data={recognizedTransactions}
@@ -100,14 +101,12 @@ function RecognizedTransactionsTableRoot({
ContextMenu={ActionsMenu} ContextMenu={ActionsMenu}
onCellClick={handleCellClick} onCellClick={handleCellClick}
// #TableVirtualizedListRows props. // #TableVirtualizedListRows props.
vListrowHeight={'small' == 'small' ? 32 : 40}
vListrowHeight={40} vListrowHeight={40}
vListOverscanRowCount={0} vListOverscanRowCount={0}
initialColumnsWidths={initialColumnsWidths} initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing} onColumnResizing={handleColumnResizing}
windowScrollerProps={{ scrollElement: scrollableRef }} windowScrollerProps={{ scrollElement: scrollableRef }}
noResults={<RecognizedTransactionsTableNoResults />} noResults={<RecognizedTransactionsTableNoResults />}
className="table-constrant"
payload={{ payload={{
onExclude: handleExcludeClick, onExclude: handleExcludeClick,
onCategorize: handleCategorizeClick, onCategorize: handleCategorizeClick,
@@ -120,45 +119,6 @@ export const RecognizedTransactionsTable = compose(withBankingActions)(
RecognizedTransactionsTableRoot, RecognizedTransactionsTableRoot,
); );
const DashboardConstrantTable = styled(DataTable)`
.table {
.thead {
.th {
background: #fff;
letter-spacing: 1px;
text-transform: uppercase;
font-size: 13px;
}
}
.tbody {
.tr:last-child .td {
border-bottom: 0;
}
}
}
`;
const CashflowTransactionsTable = styled(DashboardConstrantTable)`
.table .tbody {
.tbody-inner .tr.no-results {
.td {
padding: 2rem 0;
font-size: 14px;
color: #888;
font-weight: 400;
border-bottom: 0;
}
}
.tbody-inner {
.tr .td {
border-bottom: 1px solid #e6e6e6;
}
}
}
`;
function RecognizedTransactionsTableNoResults() { function RecognizedTransactionsTableNoResults() {
return ( return (
<Stack spacing={12} className={styles.emptyState}> <Stack spacing={12} className={styles.emptyState}>

View File

@@ -1,7 +1,9 @@
// @ts-nocheck // @ts-nocheck
import React from 'react';
import clsx from 'classnames';
import { Classes } from '@blueprintjs/core';
import { Group, Icon } from '@/components'; import { Group, Icon } from '@/components';
import { getColumnWidth } from '@/utils'; import { getColumnWidth } from '@/utils';
import React from 'react';
import { useRecognizedTransactionsBoot } from './RecognizedTransactionsTableBoot'; import { useRecognizedTransactionsBoot } from './RecognizedTransactionsTableBoot';
const getReportColWidth = (data, accessor, headerText) => { const getReportColWidth = (data, accessor, headerText) => {
@@ -28,10 +30,6 @@ const recognizeAccessor = (transaction) => {
); );
}; };
const descriptionAccessor = (transaction) => {
return <span style={{ color: '#5F6B7C' }}>{transaction.description}</span>;
};
/** /**
* Retrieve uncategorized transactions columns table. * Retrieve uncategorized transactions columns table.
*/ */
@@ -59,7 +57,8 @@ export function useUncategorizedTransactionsColumns() {
}, },
{ {
Header: 'Description', Header: 'Description',
accessor: descriptionAccessor, accessor: 'description',
className: clsx(Classes.TEXT_MUTED),
textOverview: true, textOverview: true,
}, },
{ {
@@ -82,12 +81,14 @@ export function useUncategorizedTransactionsColumns() {
accessor: 'formatted_deposit_amount', accessor: 'formatted_deposit_amount',
align: 'right', align: 'right',
width: depositWidth, width: depositWidth,
money: true
}, },
{ {
Header: 'Withdrawal', Header: 'Withdrawal',
accessor: 'formatted_withdrawal_amount', accessor: 'formatted_withdrawal_amount',
align: 'right', align: 'right',
width: withdrawalWidth, width: withdrawalWidth,
money: true
}, },
], ],
[], [],

View File

@@ -1,22 +1,21 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import clsx from 'classnames'; import clsx from 'classnames';
import styled from 'styled-components';
import { Intent } from '@blueprintjs/core'; import { Intent } from '@blueprintjs/core';
import { import {
DataTable,
TableFastCell, TableFastCell,
TableSkeletonRows, TableSkeletonRows,
TableSkeletonHeader, TableSkeletonHeader,
TableVirtualizedListRows, TableVirtualizedListRows,
FormattedMessage as T,
AppToaster, AppToaster,
} from '@/components'; } from '@/components';
import { TABLES } from '@/constants/tables'; import { TABLES } from '@/constants/tables';
import { ActionsMenu } from './components'; import { ActionsMenu } from './components';
import { BankAccountDataTable } from '../components/BankAccountDataTable';
import withSettings from '@/containers/Settings/withSettings'; import withSettings from '@/containers/Settings/withSettings';
import { withBankingActions } from '../../withBankingActions'; import { withBankingActions } from '../../withBankingActions';
import { withBanking } from '../../withBanking';
import { useMemorizedColumnsWidths } from '@/hooks'; import { useMemorizedColumnsWidths } from '@/hooks';
import { useAccountUncategorizedTransactionsContext } from '../AllTransactionsUncategorizedBoot'; import { useAccountUncategorizedTransactionsContext } from '../AllTransactionsUncategorizedBoot';
@@ -25,7 +24,6 @@ import { useAccountUncategorizedTransactionsColumns } from './hooks';
import { useAccountTransactionsContext } from '../AccountTransactionsProvider'; import { useAccountTransactionsContext } from '../AccountTransactionsProvider';
import { compose } from '@/utils'; import { compose } from '@/utils';
import { withBanking } from '../../withBanking';
import styles from './AccountTransactionsUncategorizedTable.module.scss'; import styles from './AccountTransactionsUncategorizedTable.module.scss';
/** /**
@@ -48,7 +46,6 @@ function AccountTransactionsDataTable({
}) { }) {
// Retrieve table columns. // Retrieve table columns.
const columns = useAccountUncategorizedTransactionsColumns(); const columns = useAccountUncategorizedTransactionsColumns();
const { scrollableRef } = useAccountTransactionsContext(); const { scrollableRef } = useAccountTransactionsContext();
// Retrieve list context. // Retrieve list context.
@@ -100,7 +97,7 @@ function AccountTransactionsDataTable({
}; };
return ( return (
<CashflowTransactionsTable <BankAccountDataTable
noInitialFetch={true} noInitialFetch={true}
columns={columns} columns={columns}
data={uncategorizedTransactions || []} data={uncategorizedTransactions || []}
@@ -119,7 +116,7 @@ function AccountTransactionsDataTable({
ContextMenu={ActionsMenu} ContextMenu={ActionsMenu}
onCellClick={handleCellClick} onCellClick={handleCellClick}
// #TableVirtualizedListRows props. // #TableVirtualizedListRows props.
vListrowHeight={cashflowTansactionsTableSize === 'small' ? 32 : 40} vListrowHeight={cashflowTansactionsTableSize === 'small' ? 34 : 40}
vListOverscanRowCount={0} vListOverscanRowCount={0}
initialColumnsWidths={initialColumnsWidths} initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing} onColumnResizing={handleColumnResizing}
@@ -132,7 +129,7 @@ function AccountTransactionsDataTable({
}} }}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
windowScrollerProps={{ scrollElement: scrollableRef }} windowScrollerProps={{ scrollElement: scrollableRef }}
className={clsx('table-constrant', styles.table, { className={clsx(styles.table, {
[styles.showCategorizeColumn]: enableMultipleCategorization, [styles.showCategorizeColumn]: enableMultipleCategorization,
})} })}
/> />
@@ -151,47 +148,3 @@ export default compose(
}), }),
), ),
)(AccountTransactionsDataTable); )(AccountTransactionsDataTable);
const DashboardConstrantTable = styled(DataTable)`
.table {
.thead {
.th {
background: #fff;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 13px;i
font-weight: 500;
}
}
.tbody {
.tr:last-child .td {
border-bottom: 0;
}
}
}
`;
const CashflowTransactionsTable = styled(DashboardConstrantTable)`
.table .tbody {
.tbody-inner .tr.no-results {
.td {
padding: 2rem 0;
font-size: 14px;
color: #888;
font-weight: 400;
border-bottom: 0;
}
}
.tbody-inner {
.tr .td:not(:first-child) {
border-left: 1px solid #e6e6e6;
}
.td-description {
color: #5f6b7c;
}
}
}
`;

View File

@@ -1,8 +1,10 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import clsx from 'classnames';
import { import {
Checkbox, Checkbox,
Classes,
Intent, Intent,
PopoverInteractionKind, PopoverInteractionKind,
Position, Position,
@@ -97,6 +99,7 @@ export function useAccountUncategorizedTransactionsColumns() {
width: 160, width: 160,
textOverview: true, textOverview: true,
clickable: true, clickable: true,
className: clsx(Classes.TEXT_MUTED),
}, },
{ {
id: 'payee', id: 'payee',
@@ -123,21 +126,21 @@ export function useAccountUncategorizedTransactionsColumns() {
id: 'deposit', id: 'deposit',
Header: intl.get('banking.label.deposit'), Header: intl.get('banking.label.deposit'),
accessor: 'formatted_deposit_amount', accessor: 'formatted_deposit_amount',
width: 40,
className: 'deposit',
textOverview: true,
align: 'right', align: 'right',
width: 40,
textOverview: true,
clickable: true, clickable: true,
money: true
}, },
{ {
id: 'withdrawal', id: 'withdrawal',
Header: intl.get('banking.label.withdrawal'), Header: intl.get('banking.label.withdrawal'),
accessor: 'formatted_withdrawal_amount', accessor: 'formatted_withdrawal_amount',
className: 'withdrawal',
width: 40, width: 40,
textOverview: true, textOverview: true,
align: 'right', align: 'right',
clickable: true, clickable: true,
money: true
}, },
{ {
id: 'categorize_include', id: 'categorize_include',

View File

@@ -123,6 +123,7 @@ export function useAccountTransactionsColumns() {
textOverview: true, textOverview: true,
align: 'right', align: 'right',
clickable: true, clickable: true,
money: true,
}, },
{ {
id: 'withdrawal', id: 'withdrawal',
@@ -133,16 +134,18 @@ export function useAccountTransactionsColumns() {
textOverview: true, textOverview: true,
align: 'right', align: 'right',
clickable: true, clickable: true,
money: true,
}, },
{ {
id: 'running_balance', id: 'running_balance',
Header: intl.get('banking.label.running_balance'), Header: intl.get('banking.label.running_balance'),
accessor: 'formatted_running_balance', accessor: 'formatted_running_balance',
className: 'running_balance', className: 'running_balance',
align: 'right',
width: 150, width: 150,
textOverview: true, textOverview: true,
align: 'right',
clickable: true, clickable: true,
money: true,
}, },
], ],
[], [],

View File

@@ -0,0 +1,29 @@
.root {
:global .table{
.thead {
.th {
background: #fff;
text-transform: uppercase;
letter-spacing: 1px;
font-size: 13px;
font-weight: 500;
}
}
.tbody-inner .tr.no-results {
.td {
padding: 2rem 0;
font-size: 14px;
color: #888;
font-weight: 400;
border-bottom: 0;
}
}
.tbody-inner {
.tr .td{
border-bottom: 1px solid #ececec;
}
}
}
}

View File

@@ -0,0 +1,19 @@
import clsx from 'classnames';
import { DataTable } from '@/components';
import styles from './BankAccountDataTable.module.scss';
interface BankAccountDataTableProps {
className?: string;
}
export function BankAccountDataTable({
className,
...props
}: BankAccountDataTableProps) {
return (
<DataTable
{...props}
className={clsx('table-constrant', styles.root, className)}
/>
);
}

View File

@@ -1,6 +1,8 @@
// @ts-nocheck // @ts-nocheck
import { Suspense } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import * as R from 'ramda'; import * as R from 'ramda';
import { Spinner } from '@blueprintjs/core';
import { CategorizeTransactionBoot } from './CategorizeTransactionBoot'; import { CategorizeTransactionBoot } from './CategorizeTransactionBoot';
import { CategorizeTransactionForm } from './CategorizeTransactionForm'; import { CategorizeTransactionForm } from './CategorizeTransactionForm';
import { withBanking } from '@/containers/CashFlow/withBanking'; import { withBanking } from '@/containers/CashFlow/withBanking';
@@ -13,7 +15,9 @@ function CategorizeTransactionContentRoot({
uncategorizedTransactionsIds={transactionsToCategorizeIdsSelected} uncategorizedTransactionsIds={transactionsToCategorizeIdsSelected}
> >
<CategorizeTransactionDrawerBody> <CategorizeTransactionDrawerBody>
<CategorizeTransactionForm /> <Suspense fallback={<Spinner size={40} />}>
<CategorizeTransactionForm />
</Suspense>
</CategorizeTransactionDrawerBody> </CategorizeTransactionDrawerBody>
</CategorizeTransactionBoot> </CategorizeTransactionBoot>
); );

View File

@@ -2,6 +2,7 @@ import React, { createContext } from 'react';
import { defaultTo } from 'lodash'; import { defaultTo } from 'lodash';
import * as R from 'ramda'; import * as R from 'ramda';
import { useGetBankTransactionsMatches } from '@/hooks/query/bank-rules'; import { useGetBankTransactionsMatches } from '@/hooks/query/bank-rules';
import { Spinner } from '@blueprintjs/core';
interface MatchingTransactionBootValues { interface MatchingTransactionBootValues {
isMatchingTransactionsLoading: boolean; isMatchingTransactionsLoading: boolean;
@@ -52,6 +53,11 @@ function MatchingTransactionBoot({
matches, matches,
} as MatchingTransactionBootValues; } as MatchingTransactionBootValues;
const isLoading = isMatchingTransactionsLoading;
if (isLoading) {
return <Spinner size={40} />;
}
return <RuleFormBootContext.Provider value={provider} {...props} />; return <RuleFormBootContext.Provider value={provider} {...props} />;
} }

View File

@@ -1,6 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import clsx from 'classnames';
import { import {
Menu, Menu,
MenuItem, MenuItem,
@@ -14,6 +15,7 @@ import {
import { Can, Icon, Money, If, AvatarCell } from '@/components'; import { Can, Icon, Money, If, AvatarCell } from '@/components';
import { CustomerAction, AbilitySubject } from '@/constants/abilityOption'; import { CustomerAction, AbilitySubject } from '@/constants/abilityOption';
import { safeCallback } from '@/utils'; import { safeCallback } from '@/utils';
import { CLASSES } from '@/constants';
/** /**
* Actions menu. * Actions menu.
@@ -140,7 +142,7 @@ export function useCustomersTableColumns() {
id: 'company_name', id: 'company_name',
Header: intl.get('company_name'), Header: intl.get('company_name'),
accessor: 'company_name', accessor: 'company_name',
className: 'company_name', className: clsx('company_name', CLASSES.TEXT_MUTED),
width: 150, width: 150,
clickable: true, clickable: true,
}, },
@@ -148,9 +150,9 @@ export function useCustomersTableColumns() {
id: 'work_phone', id: 'work_phone',
Header: intl.get('phone_number'), Header: intl.get('phone_number'),
accessor: PhoneNumberAccessor, accessor: PhoneNumberAccessor,
className: 'phone_number',
width: 100, width: 100,
clickable: true, clickable: true,
className: clsx('phone_number', CLASSES.TEXT_MUTED)
}, },
{ {
id: 'note', id: 'note',
@@ -159,6 +161,7 @@ export function useCustomersTableColumns() {
disableSortBy: true, disableSortBy: true,
width: 85, width: 85,
clickable: true, clickable: true,
className: clsx(CLASSES.TEXT_MUTED)
}, },
{ {
id: 'balance', id: 'balance',
@@ -167,6 +170,7 @@ export function useCustomersTableColumns() {
align: 'right', align: 'right',
width: 100, width: 100,
clickable: true, clickable: true,
money: true,
}, },
], ],
[], [],

View File

@@ -1,9 +1,9 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { InputGroup, FormGroup, Position, Classes } from '@blueprintjs/core'; import { FormGroup, Position, Classes } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage } from 'formik'; import { FastField, ErrorMessage } from 'formik';
import { CustomersSelect, FormattedMessage as T } from '@/components'; import { CustomersSelect, FInputGroup, FormattedMessage as T } from '@/components';
import classNames from 'classnames'; import classNames from 'classnames';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { import {
@@ -15,15 +15,14 @@ import {
import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils'; import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils';
import { import {
CurrencySelectList, CurrencySelectList,
CustomerSelectField,
FFormGroup, FFormGroup,
AccountsSelect, AccountsSelect,
FieldRequiredHint, FieldRequiredHint,
Hint, Hint,
} from '@/components'; } from '@/components';
import { ExpensesExchangeRateInputField } from './components'; import { ExpensesExchangeRateInputField } from './components';
import { ACCOUNT_PARENT_TYPE } from '@/constants/accountTypes';
import { useExpenseFormContext } from './ExpenseFormPageProvider'; import { useExpenseFormContext } from './ExpenseFormPageProvider';
import { SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES } from './constants';
/** /**
* Expense form header. * Expense form header.
@@ -68,7 +67,7 @@ export default function ExpenseFormHeader() {
name={'payment_account_id'} name={'payment_account_id'}
items={accounts} items={accounts}
placeholder={<T id={'select_payment_account'} />} placeholder={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]} filterByTypes={SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES}
allowCreate={true} allowCreate={true}
fastField={true} fastField={true}
shouldUpdate={accountsFieldShouldUpdate} shouldUpdate={accountsFieldShouldUpdate}
@@ -107,19 +106,15 @@ export default function ExpenseFormHeader() {
formGroupProps={{ label: ' ', inline: true }} formGroupProps={{ label: ' ', inline: true }}
/> />
<FastField name={'reference_no'}> {/* ----------- Reference No. ----------- */}
{({ form, field, meta: { error, touched } }) => ( <FFormGroup
<FormGroup name={'reference_no'}
label={<T id={'reference_no'} />} label={<T id={'reference_no'} />}
className={classNames('form-group--ref_no', Classes.FILL)} inline={true}
intent={inputIntent({ error, touched })} fastField
helperText={<ErrorMessage name="reference_no" />} >
inline={true} <FInputGroup minimal={true} name={'reference_no'} fastField />
> </FFormGroup>
<InputGroup minimal={true} {...field} />
</FormGroup>
)}
</FastField>
{/* ----------- Customer ----------- */} {/* ----------- Customer ----------- */}
<ExpenseFormCustomerSelect /> <ExpenseFormCustomerSelect />

View File

@@ -0,0 +1,10 @@
import { ACCOUNT_TYPE } from "@/constants";
export const SUPPORTED_EXPENSE_PAYMENT_ACCOUNT_TYPES = [
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CREDIT_CARD,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
ACCOUNT_TYPE.NON_CURRENT_ASSET,
ACCOUNT_TYPE.FIXED_ASSET,
];

View File

@@ -140,26 +140,25 @@ export function useExpensesTableColumns() {
id: 'amount', id: 'amount',
Header: intl.get('full_amount'), Header: intl.get('full_amount'),
accessor: 'formatted_amount', accessor: 'formatted_amount',
className: 'amount',
align: 'right', align: 'right',
width: 150, width: 150,
clickable: true, clickable: true,
money: true,
className: clsx(CLASSES.FONT_BOLD), className: clsx(CLASSES.FONT_BOLD),
}, },
{ {
id: 'payment_account', id: 'payment_account',
Header: intl.get('payment_account'), Header: intl.get('payment_account'),
accessor: 'payment_account.name', accessor: 'payment_account.name',
className: 'payment_account',
width: 150, width: 150,
clickable: true, clickable: true,
className: clsx(CLASSES.TEXT_MUTED),
}, },
{ {
id: 'expense_account', id: 'expense_account',
Header: intl.get('expense_account'), Header: intl.get('expense_account'),
accessor: ExpenseAccountAccessor, accessor: ExpenseAccountAccessor,
width: 160, width: 160,
className: 'expense_account',
disableSortBy: true, disableSortBy: true,
clickable: true, clickable: true,
}, },

View File

@@ -1,5 +1,4 @@
// @ts-nocheck // @ts-nocheck
import React, { useMemo } from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { getColumnWidth } from '@/utils'; import { getColumnWidth } from '@/utils';
import { Align } from '@/constants'; import { Align } from '@/constants';
@@ -25,6 +24,7 @@ const currentAccessor = R.curry((data, column) => {
className: column.id, className: column.id,
width: getColumnWidth(data, accessor, { minWidth: 120 }), width: getColumnWidth(data, accessor, { minWidth: 120 }),
align: Align.Right, align: Align.Right,
money: true,
}; };
}); });
@@ -38,6 +38,7 @@ const totalAccessor = R.curry((data, column) => {
className: column.key, className: column.key,
width: getColumnWidth(data, accessor, { minWidth: 120 }), width: getColumnWidth(data, accessor, { minWidth: 120 }),
align: Align.Right, align: Align.Right,
money: true,
}; };
}); });
@@ -51,6 +52,7 @@ const agingPeriodAccessor = R.curry((data, column) => {
className: column.key, className: column.key,
width: getColumnWidth(data, accessor, { minWidth: 120 }), width: getColumnWidth(data, accessor, { minWidth: 120 }),
align: Align.Right, align: Align.Right,
money: true,
}; };
}); });

View File

@@ -3,7 +3,6 @@ import * as R from 'ramda';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { Align } from '@/constants'; import { Align } from '@/constants';
import { CellTextSpan } from '@/components/Datatable/Cells';
import { getColumnWidth } from '@/utils'; import { getColumnWidth } from '@/utils';
const getTableCellValueAccessor = (index) => `cells[${index}].value`; const getTableCellValueAccessor = (index) => `cells[${index}].value`;
@@ -12,12 +11,11 @@ const getReportColWidth = (data, accessor, headerText) => {
return getColumnWidth( return getColumnWidth(
data, data,
accessor, accessor,
{ magicSpacing: 10, minWidth: 100 }, { magicSpacing: 12, minWidth: 100 },
headerText, headerText,
); );
}; };
/** /**
* Account name column mapper. * Account name column mapper.
*/ */
@@ -77,6 +75,7 @@ const dateRangeMapper = R.curry((data, column) => {
key: column.key, key: column.key,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
align: isDateColumnHasColumns ? Align.Center : Align.Right, align: isDateColumnHasColumns ? Align.Center : Align.Right,
}; };
return R.compose( return R.compose(
@@ -104,9 +103,9 @@ const totalMapper = R.curry((data, column) => {
Header: column.label, Header: column.label,
accessor, accessor,
textOverview: true, textOverview: true,
Cell: CellTextSpan,
width, width,
disableSortBy: true, disableSortBy: true,
money: true,
align: hasChildren ? Align.Center : Align.Right, align: hasChildren ? Align.Center : Align.Right,
}; };
return R.compose( return R.compose(
@@ -129,6 +128,7 @@ const percentageOfColumnAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });
@@ -147,6 +147,7 @@ const percentageOfRowAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });
@@ -165,6 +166,7 @@ const previousYearAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });
@@ -183,6 +185,7 @@ const previousYearChangeAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });
@@ -201,6 +204,7 @@ const previousYearPercentageAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });
@@ -219,6 +223,7 @@ const previousPeriodAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });
@@ -237,6 +242,7 @@ const previousPeriodChangeAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });
@@ -255,6 +261,7 @@ const previousPeriodPercentageAccessor = R.curry((data, column) => {
align: Align.Right, align: Align.Right,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
money: true,
}; };
}); });

View File

@@ -30,13 +30,14 @@ const dateRangeMapper = (data, index, column) => ({
key: column.key, key: column.key,
accessor: `cells[${index}].value`, accessor: `cells[${index}].value`,
width: getColumnWidth(data, `cells.${index}.value`, { width: getColumnWidth(data, `cells.${index}.value`, {
magicSpacing: 10, magicSpacing: 12,
minWidth: 100, minWidth: 100,
}), }),
className: `date-period ${column.key}`, className: `date-period ${column.key}`,
disableSortBy: true, disableSortBy: true,
textOverview: true, textOverview: true,
align: Align.Right, align: Align.Right,
money: true
}); });
/** /**
@@ -50,11 +51,12 @@ const totalMapper = (data, index, column) => ({
textOverview: true, textOverview: true,
Cell: CellTextSpan, Cell: CellTextSpan,
width: getColumnWidth(data, `cells[${index}].value`, { width: getColumnWidth(data, `cells[${index}].value`, {
magicSpacing: 10, magicSpacing: 12,
minWidth: 100, minWidth: 100,
}), }),
disableSortBy: true, disableSortBy: true,
align: Align.Right, align: Align.Right,
money: true
}); });
/** /**

View File

@@ -61,9 +61,10 @@ export const useCustomersTransactionsColumns = () => {
textOverview: true, textOverview: true,
width: getColumnWidth(tableRows, 'cells[5].value', { width: getColumnWidth(tableRows, 'cells[5].value', {
minWidth: 100, minWidth: 100,
magicSpacing: 10, magicSpacing: 12,
}), }),
align: Align.Right, align: Align.Right,
money: true,
}, },
{ {
Header: intl.get('debit'), Header: intl.get('debit'),
@@ -72,9 +73,10 @@ export const useCustomersTransactionsColumns = () => {
textOverview: true, textOverview: true,
width: getColumnWidth(tableRows, 'cells[6].value', { width: getColumnWidth(tableRows, 'cells[6].value', {
minWidth: 100, minWidth: 100,
magicSpacing: 10, magicSpacing: 12,
}), }),
align: Align.Right, align: Align.Right,
money: true,
}, },
{ {
Header: intl.get('running_balance'), Header: intl.get('running_balance'),
@@ -83,9 +85,10 @@ export const useCustomersTransactionsColumns = () => {
textOverview: true, textOverview: true,
width: getColumnWidth(tableRows, 'cells[7].value', { width: getColumnWidth(tableRows, 'cells[7].value', {
minWidth: 120, minWidth: 120,
magicSpacing: 10, magicSpacing: 12,
}), }),
align: Align.Right, align: Align.Right,
money: true,
}, },
], ],
[tableRows], [tableRows],

Some files were not shown because too many files have changed in this diff Show More