From 401b3dc111c89e5cf7e4ea0ac60488692c57eb4a Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 4 May 2025 19:42:35 +0200 Subject: [PATCH] refactor(nestjs): add sale receipts retrieval and metadata configuration --- .../SaleReceipts/SaleReceipts.controller.ts | 8 + .../SaleReceipts/models/SaleReceipt.meta.ts | 287 ++++++++++++++++++ .../SaleReceipts/models/SaleReceipt.ts | 5 + .../queries/GetSaleReceipts.service.ts | 11 +- .../queries/GetVendorCredits.service.ts | 12 +- 5 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 packages/server/src/modules/SaleReceipts/models/SaleReceipt.meta.ts diff --git a/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts b/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts index b122ff558..a7e1b9ff6 100644 --- a/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts +++ b/packages/server/src/modules/SaleReceipts/SaleReceipts.controller.ts @@ -8,6 +8,7 @@ import { ParseIntPipe, Post, Put, + Query, } from '@nestjs/common'; import { SaleReceiptApplication } from './SaleReceiptApplication.service'; import { ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; @@ -15,6 +16,7 @@ import { CreateSaleReceiptDto, EditSaleReceiptDto, } from './dtos/SaleReceipt.dto'; +import { ISalesReceiptsFilter } from './types/SaleReceipts.types'; @Controller('sale-receipts') @ApiTags('sale-receipts') @@ -80,6 +82,12 @@ export class SaleReceiptsController { return this.saleReceiptApplication.getSaleReceipt(id); } + @Get() + @ApiOperation({ summary: 'Retrieves the sale receipts paginated list' }) + getSaleReceipts(@Query() filterDTO: Partial) { + return this.saleReceiptApplication.getSaleReceipts(filterDTO); + } + @Delete(':id') @ApiOperation({ summary: 'Delete the given sale receipt.' }) @ApiParam({ diff --git a/packages/server/src/modules/SaleReceipts/models/SaleReceipt.meta.ts b/packages/server/src/modules/SaleReceipts/models/SaleReceipt.meta.ts new file mode 100644 index 000000000..afb0798ff --- /dev/null +++ b/packages/server/src/modules/SaleReceipts/models/SaleReceipt.meta.ts @@ -0,0 +1,287 @@ +import { Features } from '@/common/types/Features'; + +export const SaleReceiptMeta = { + defaultFilterField: 'receipt_date', + defaultSort: { + sortOrder: 'DESC', + sortField: 'created_at', + }, + exportable: true, + exportFlattenOn: 'entries', + + importable: true, + importAggregator: 'group', + importAggregateOn: 'entries', + importAggregateBy: 'receiptNumber', + + print: { + pageTitle: 'Sale Receipts', + }, + fields: { + amount: { + name: 'receipt.field.amount', + column: 'amount', + fieldType: 'number', + }, + deposit_account: { + column: 'deposit_account_id', + name: 'receipt.field.deposit_account', + fieldType: 'relation', + + relationType: 'enumeration', + relationKey: 'depositAccount', + + relationEntityLabel: 'name', + relationEntityKey: 'slug', + }, + customer: { + name: 'receipt.field.customer', + column: 'customer_id', + fieldType: 'relation', + + relationType: 'enumeration', + relationKey: 'customer', + + relationEntityLabel: 'display_name', + relationEntityKey: 'id', + }, + receipt_date: { + name: 'receipt.field.receipt_date', + column: 'receipt_date', + fieldType: 'date', + }, + receipt_number: { + name: 'receipt.field.receipt_number', + column: 'receipt_number', + fieldType: 'text', + }, + reference_no: { + name: 'receipt.field.reference_no', + column: 'reference_no', + fieldType: 'text', + }, + receipt_message: { + name: 'receipt.field.receipt_message', + column: 'receipt_message', + fieldType: 'text', + }, + statement: { + name: 'receipt.field.statement', + column: 'statement', + fieldType: 'text', + }, + created_at: { + name: 'receipt.field.created_at', + column: 'created_at', + fieldType: 'date', + }, + status: { + name: 'receipt.field.status', + fieldType: 'enumeration', + options: [ + { key: 'draft', label: 'receipt.field.status.draft' }, + { key: 'closed', label: 'receipt.field.status.closed' }, + ], + filterCustomQuery: StatusFieldFilterQuery, + sortCustomQuery: StatusFieldSortQuery, + }, + }, + columns: { + depositAccount: { + name: 'receipt.field.deposit_account', + type: 'text', + accessor: 'depositAccount.name', + }, + customer: { + name: 'receipt.field.customer', + type: 'text', + accessor: 'customer.displayName', + }, + receiptDate: { + name: 'receipt.field.receipt_date', + accessor: 'formattedReceiptDate', + type: 'date', + }, + receiptNumber: { + name: 'receipt.field.receipt_number', + type: 'text', + }, + referenceNo: { + name: 'receipt.field.reference_no', + column: 'reference_no', + type: 'text', + exportable: true, + }, + receiptMessage: { + name: 'receipt.field.receipt_message', + column: 'receipt_message', + type: 'text', + printable: false, + }, + amount: { + name: 'receipt.field.amount', + accessor: 'formattedAmount', + type: 'number', + }, + statement: { + name: 'receipt.field.statement', + type: 'text', + printable: false, + }, + status: { + name: 'receipt.field.status', + type: 'enumeration', + options: [ + { key: 'draft', label: 'receipt.field.status.draft' }, + { key: 'closed', label: 'receipt.field.status.closed' }, + ], + exportable: true, + printable: false, + }, + entries: { + name: 'Entries', + accessor: 'entries', + type: 'collection', + collectionOf: 'object', + columns: { + itemName: { + name: 'Item Name', + accessor: 'item.name', + }, + rate: { + name: 'Item Rate', + accessor: 'rateFormatted', + }, + quantity: { + name: 'Item Quantity', + accessor: 'quantityFormatted', + }, + description: { + name: 'Item Description', + printable: false, + }, + amount: { + name: 'Item Amount', + accessor: 'totalFormatted', + }, + }, + }, + createdAt: { + name: 'receipt.field.created_at', + type: 'date', + 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: { + receiptDate: { + name: 'Receipt Date', + fieldType: 'date', + required: true, + }, + customerId: { + name: 'Customer', + fieldType: 'relation', + relationModel: 'Contact', + relationImportMatch: 'displayName', + required: true, + }, + depositAccountId: { + name: 'Deposit Account', + fieldType: 'relation', + relationModel: 'Account', + relationImportMatch: ['name', 'code'], + required: true, + }, + exchangeRate: { + name: 'Exchange Rate', + fieldType: 'number', + }, + receiptNumber: { + name: 'Receipt Number', + fieldType: 'text', + }, + referenceNo: { + name: 'Reference No.', + fieldType: 'text', + }, + closed: { + name: 'Closed', + fieldType: 'boolean', + }, + entries: { + name: 'Entries', + fieldType: 'collection', + collectionOf: 'object', + collectionMinLength: 1, + required: true, + fields: { + itemId: { + name: 'invoice.field.item_name', + fieldType: 'relation', + relationModel: 'Item', + relationImportMatch: ['name', 'code'], + required: true, + importHint: 'Matches the item name or code.', + }, + rate: { + name: 'invoice.field.rate', + fieldType: 'number', + required: true, + }, + quantity: { + name: 'invoice.field.quantity', + fieldType: 'number', + required: true, + }, + description: { + name: 'invoice.field.description', + fieldType: 'text', + }, + }, + }, + statement: { + name: 'Statement', + fieldType: 'text', + }, + receiptMessage: { + name: 'Receipt Message', + 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, + }, + }, +}; + +function StatusFieldFilterQuery(query, role) { + query.modify('filterByStatus', role.value); +} + +function StatusFieldSortQuery(query, role) { + query.modify('sortByStatus', role.order); +} diff --git a/packages/server/src/modules/SaleReceipts/models/SaleReceipt.ts b/packages/server/src/modules/SaleReceipts/models/SaleReceipt.ts index 80b880398..34eaa2c39 100644 --- a/packages/server/src/modules/SaleReceipts/models/SaleReceipt.ts +++ b/packages/server/src/modules/SaleReceipts/models/SaleReceipt.ts @@ -13,6 +13,9 @@ import { ResourceableModelMixin } from '@/modules/Resource/models/ResourcableMod import { CustomViewBaseModelMixin } from '@/modules/CustomViews/CustomViewBaseModel'; import { SearchableBaseModelMixin } from '@/modules/DynamicListing/models/SearchableBaseModel'; import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator'; +import { ImportableModel } from '@/modules/Import/decorators/Import.decorator'; +import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator'; +import { SaleReceiptMeta } from './SaleReceipt.meta'; const ExtendedModel = R.pipe( CustomViewBaseModelMixin, @@ -22,6 +25,8 @@ const ExtendedModel = R.pipe( )(BaseModel); @ExportableModel() +@ImportableModel() +@InjectModelMeta(SaleReceiptMeta) export class SaleReceipt extends ExtendedModel { public amount!: number; public exchangeRate!: number; diff --git a/packages/server/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts b/packages/server/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts index 4217413f4..628d7d83e 100644 --- a/packages/server/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts +++ b/packages/server/src/modules/SaleReceipts/queries/GetSaleReceipts.service.ts @@ -30,8 +30,15 @@ export class GetSaleReceiptsService { pagination: IPaginationMeta; filterMeta: IFilterMeta; }> { + const _filterDto = { + sortOrder: 'desc', + columnSortBy: 'created_at', + page: 1, + pageSize: 12, + ...filterDTO, + }; // Parses the stringified filter roles. - const filter = this.parseListFilterDTO(filterDTO); + const filter = this.parseListFilterDTO(_filterDto); // Dynamic list service. const dynamicFilter = await this.dynamicListService.dynamicList( @@ -46,7 +53,7 @@ export class GetSaleReceiptsService { builder.withGraphFetched('entries.item'); dynamicFilter.buildQuery()(builder); - filterDTO?.filterQuery && filterDTO?.filterQuery(builder); + _filterDto?.filterQuery && _filterDto?.filterQuery(builder); }) .pagination(filter.page - 1, filter.pageSize); diff --git a/packages/server/src/modules/VendorCredit/queries/GetVendorCredits.service.ts b/packages/server/src/modules/VendorCredit/queries/GetVendorCredits.service.ts index cabae58a8..b0f1a75bd 100644 --- a/packages/server/src/modules/VendorCredit/queries/GetVendorCredits.service.ts +++ b/packages/server/src/modules/VendorCredit/queries/GetVendorCredits.service.ts @@ -33,8 +33,15 @@ export class GetVendorCreditsService { public getVendorCredits = async ( vendorCreditQuery: IVendorCreditsQueryDTO, ) => { + const filterDto = { + sortOrder: 'desc', + columnSortBy: 'created_at', + page: 1, + pageSize: 12, + ...vendorCreditQuery, + }; // Parses stringified filter roles. - const filter = this.parseListFilterDTO(vendorCreditQuery); + const filter = this.parseListFilterDTO(filterDto); // Dynamic list service. const dynamicFilter = await this.dynamicListService.dynamicList( @@ -49,8 +56,7 @@ export class GetVendorCreditsService { dynamicFilter.buildQuery()(builder); // Gives ability to inject custom query to filter results. - vendorCreditQuery?.filterQuery && - vendorCreditQuery?.filterQuery(builder); + filterDto?.filterQuery && filterDto?.filterQuery(builder); }) .pagination(filter.page - 1, filter.pageSize);