From 91976842a705d38ed679ad701d33161e030aed87 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 21 Jun 2025 20:15:42 +0200 Subject: [PATCH] fix: AR/AP aging report --- .../dtos/NumberFormatQuery.dto.ts | 35 ++++++++- .../APAgingSummary.controller.ts | 3 +- .../APAgingSummary/APAgingSummary.types.ts | 9 +-- .../APAgingSummaryApplication.ts | 24 +++--- .../APAgingSummaryExportInjectable.ts | 10 +-- .../APAgingSummaryPdfInjectable.ts | 8 +- .../APAgingSummary/APAgingSummaryQuery.dto.ts | 40 +++++++++- .../APAgingSummaryRepository.ts | 19 ++--- .../APAgingSummary/APAgingSummaryService.ts | 11 +-- .../APAgingSummary/APAgingSummarySheet.ts | 19 +++-- .../APAgingSummaryTableInjectable.ts | 12 ++- .../modules/APAgingSummary/utils.ts | 2 + .../ARAgingSummary.controller.ts | 4 +- .../ARAgingSummary/ARAgingSummary.types.ts | 11 +-- .../ARAgingSummaryApplication.ts | 24 +++--- .../ARAgingSummaryExportInjectable.ts | 14 ++-- .../ARAgingSummaryPdfInjectable.ts | 4 +- .../ARAgingSummary/ARAgingSummaryQuery.dto.ts | 74 +++++++++++++++++++ .../ARAgingSummaryRepository.ts | 18 ++--- .../ARAgingSummary/ARAgingSummaryService.ts | 10 +-- .../ARAgingSummary/ARAgingSummarySheet.ts | 28 +++++-- .../ARAgingSummaryTableInjectable.ts | 10 +-- .../CustomerBalanceSummaryPdf.ts | 2 +- 23 files changed, 265 insertions(+), 126 deletions(-) diff --git a/packages/server/src/modules/BankingTransactions/dtos/NumberFormatQuery.dto.ts b/packages/server/src/modules/BankingTransactions/dtos/NumberFormatQuery.dto.ts index 63c0c068a..97f7d6066 100644 --- a/packages/server/src/modules/BankingTransactions/dtos/NumberFormatQuery.dto.ts +++ b/packages/server/src/modules/BankingTransactions/dtos/NumberFormatQuery.dto.ts @@ -1,26 +1,55 @@ -import { Type } from "class-transformer"; -import { IsBoolean, IsEnum, IsNumber, IsOptional, IsPositive } from "class-validator"; +import { Type } from 'class-transformer'; +import { + IsBoolean, + IsEnum, + IsNumber, + IsOptional, + IsPositive, +} from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger'; export class NumberFormatQueryDto { + @ApiPropertyOptional({ + description: 'Number of decimal places to display', + example: 2, + }) @Type(() => Number) @IsNumber() @IsPositive() @IsOptional() readonly precision: number; + @ApiPropertyOptional({ + description: 'Whether to divide the number by 1000', + example: false, + }) @IsBoolean() @IsOptional() readonly divideOn1000: boolean; + @ApiPropertyOptional({ + description: 'Whether to show zero values', + example: true, + }) @IsBoolean() @IsOptional() readonly showZero: boolean; + @ApiPropertyOptional({ + description: 'How to format money values', + example: 'total', + enum: ['total', 'always', 'none'], + }) @IsEnum(['total', 'always', 'none']) @IsOptional() readonly formatMoney: 'total' | 'always' | 'none'; + @ApiPropertyOptional({ + description: 'How to format negative numbers', + example: 'parentheses', + enum: ['parentheses', 'mines'], + }) @IsEnum(['parentheses', 'mines']) @IsOptional() readonly negativeFormat: 'parentheses' | 'mines'; -} \ No newline at end of file +} diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts index c91f3e910..aadea90e5 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.controller.ts @@ -1,8 +1,7 @@ +import { Response } from 'express'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common'; import { APAgingSummaryApplication } from './APAgingSummaryApplication'; -import { IAPAgingSummaryQuery } from './APAgingSummary.types'; import { AcceptType } from '@/constants/accept-type'; -import { Response } from 'express'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.types.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.types.ts index 281594f55..74e840c9e 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.types.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummary.types.ts @@ -7,10 +7,7 @@ import { IAgingSummaryContact, IAgingSummaryData, } from '../AgingSummary/AgingSummary.types'; - -export interface IAPAgingSummaryQuery extends IAgingSummaryQuery { - vendorsIds: number[]; -} +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; export interface IAPAgingSummaryVendor extends IAgingSummaryContact { vendorName: string; @@ -33,13 +30,13 @@ export interface IAPAgingSummaryMeta extends IFinancialSheetCommonMeta { } export interface IAPAgingSummaryTable extends IFinancialTable { - query: IAPAgingSummaryQuery; + query: APAgingSummaryQueryDto; meta: IAPAgingSummaryMeta; } export interface IAPAgingSummarySheet { data: IAPAgingSummaryData; meta: IAPAgingSummaryMeta; - query: IAPAgingSummaryQuery; + query: APAgingSummaryQueryDto; columns: any; } diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryApplication.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryApplication.ts index 537e38560..74d2749c4 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryApplication.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryApplication.ts @@ -1,9 +1,9 @@ +import { Injectable } from '@nestjs/common'; import { APAgingSummaryExportInjectable } from './APAgingSummaryExportInjectable'; import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable'; -import { IAPAgingSummaryQuery } from './APAgingSummary.types'; import { APAgingSummaryService } from './APAgingSummaryService'; import { APAgingSummaryPdfInjectable } from './APAgingSummaryPdfInjectable'; -import { Injectable } from '@nestjs/common'; +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; @Injectable() export class APAgingSummaryApplication { @@ -16,42 +16,42 @@ export class APAgingSummaryApplication { /** * Retrieve the A/P aging summary in sheet format. - * @param {IAPAgingSummaryQuery} query + * @param {APAgingSummaryQueryDto} query */ - public sheet(query: IAPAgingSummaryQuery) { + public sheet(query: APAgingSummaryQueryDto) { return this.APAgingSummarySheet.APAgingSummary(query); } /** * Retrieve the A/P aging summary in table format. - * @param {IAPAgingSummaryQuery} query + * @param {APAgingSummaryQueryDto} query */ - public table(query: IAPAgingSummaryQuery) { + public table(query: APAgingSummaryQueryDto) { return this.APAgingSummaryTable.table(query); } /** * Retrieve the A/P aging summary in CSV format. - * @param {IAPAgingSummaryQuery} query + * @param {APAgingSummaryQueryDto} query */ - public csv(query: IAPAgingSummaryQuery) { + public csv(query: APAgingSummaryQueryDto) { return this.APAgingSummaryExport.csv(query); } /** * Retrieve the A/P aging summary in XLSX format. - * @param {IAPAgingSummaryQuery} query + * @param {APAgingSummaryQueryDto} query */ - public xlsx(query: IAPAgingSummaryQuery) { + public xlsx(query: APAgingSummaryQueryDto) { return this.APAgingSummaryExport.xlsx(query); } /** * Retrieves the A/P aging summary in pdf format. - * @param {IAPAgingSummaryQuery} query + * @param {APAgingSummaryQueryDto} query * @returns {Promise} */ - public pdf(query: IAPAgingSummaryQuery) { + public pdf(query: APAgingSummaryQueryDto) { return this.APAgingSumaryPdf.pdf(query); } } diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryExportInjectable.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryExportInjectable.ts index 9bf249fd5..6ed2ebea6 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryExportInjectable.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryExportInjectable.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { TableSheet } from '../../common/TableSheet'; -import { IAPAgingSummaryQuery } from './APAgingSummary.types'; import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable'; +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; @Injectable() export class APAgingSummaryExportInjectable { @@ -11,10 +11,10 @@ export class APAgingSummaryExportInjectable { /** * Retrieves the A/P aging summary sheet in XLSX format. - * @param {IAPAgingSummaryQuery} query + * @param {APAgingSummaryQueryDto} query * @returns {Promise} */ - public async xlsx(query: IAPAgingSummaryQuery) { + public async xlsx(query: APAgingSummaryQueryDto) { const table = await this.APAgingSummaryTable.table(query); const tableSheet = new TableSheet(table.table); @@ -25,10 +25,10 @@ export class APAgingSummaryExportInjectable { /** * Retrieves the A/P aging summary sheet in CSV format. - * @param {IAPAgingSummaryQuery} query + * @param {APAgingSummaryQueryDto} query * @returns {Promise} */ - public async csv(query: IAPAgingSummaryQuery): Promise { + public async csv(query: APAgingSummaryQueryDto): Promise { const table = await this.APAgingSummaryTable.table(query); const tableSheet = new TableSheet(table.table); diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryPdfInjectable.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryPdfInjectable.ts index f319442b4..b5b5955aa 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryPdfInjectable.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryPdfInjectable.ts @@ -1,8 +1,8 @@ +import { Injectable } from '@nestjs/common'; import { TableSheetPdf } from '../../common/TableSheetPdf'; import { HtmlTableCss } from '../AgingSummary/_constants'; -import { IAPAgingSummaryQuery } from './APAgingSummary.types'; +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; import { APAgingSummaryTableInjectable } from './APAgingSummaryTableInjectable'; -import { Injectable } from '@nestjs/common'; @Injectable() export class APAgingSummaryPdfInjectable { @@ -13,10 +13,10 @@ export class APAgingSummaryPdfInjectable { /** * Converts the given A/P aging summary sheet table to pdf. - * @param {IAPAgingSummaryQuery} query - Balance sheet query. + * @param {APAgingSummaryQueryDto} query - Balance sheet query. * @returns {Promise} */ - public async pdf(query: IAPAgingSummaryQuery): Promise { + public async pdf(query: APAgingSummaryQueryDto): Promise { const table = await this.APAgingSummaryTable.table(query); return this.tableSheetPdf.convertToPdf( diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryQuery.dto.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryQuery.dto.ts index 2e2fd1019..351844b36 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryQuery.dto.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryQuery.dto.ts @@ -1,36 +1,74 @@ import { Transform, Type } from 'class-transformer'; -import { IsNumber, IsOptional, IsDateString, IsBoolean, ValidateNested, IsArray } from 'class-validator'; +import { + IsNumber, + IsOptional, + IsDateString, + IsBoolean, + ValidateNested, + IsArray, +} from 'class-validator'; import { FinancialSheetBranchesQueryDto } from '../../dtos/FinancialSheetBranchesQuery.dto'; import { parseBoolean } from '@/utils/parse-boolean'; import { ToNumber } from '@/common/decorators/Validators'; import { NumberFormatQueryDto } from '@/modules/BankingTransactions/dtos/NumberFormatQuery.dto'; +import { ApiPropertyOptional } from '@nestjs/swagger'; export class APAgingSummaryQueryDto extends FinancialSheetBranchesQueryDto { @IsDateString() @IsOptional() + @ApiPropertyOptional({ + description: 'The date as of which the AP aging summary is calculated', + example: '2024-06-01', + }) asDate: Date | string; @ToNumber() @IsNumber() @IsOptional() + @ApiPropertyOptional({ + description: 'Number of days before the aging period starts', + example: 30, + }) agingDaysBefore: number; @ToNumber() @IsNumber() @IsOptional() + @ApiPropertyOptional({ + description: 'Number of aging periods to calculate', + example: 4, + }) agingPeriods: number; @IsOptional() @ValidateNested() @Type(() => NumberFormatQueryDto) + @ApiPropertyOptional({ + description: 'Number format configuration', + example: { + precision: 2, + divideOn1000: false, + showZero: true, + formatMoney: 'total', + negativeFormat: 'parentheses', + }, + }) numberFormat: NumberFormatQueryDto; @Transform(({ value }) => parseBoolean(value, false)) @IsBoolean() @IsOptional() + @ApiPropertyOptional({ + description: 'Whether to exclude zero values', + example: false, + }) noneZero: boolean; @IsArray() @IsOptional() + @ApiPropertyOptional({ + description: 'Array of vendor IDs to include', + example: [1, 2, 3], + }) vendorsIds: number[]; } diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts index 77c0d3b8a..a45b8a35c 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts @@ -1,12 +1,13 @@ -import { Inject } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { isEmpty, groupBy } from 'lodash'; +import { ModelObject } from 'objection'; import { Bill } from '@/modules/Bills/models/Bill'; import { Vendor } from '@/modules/Vendors/models/Vendor'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; -import { IAPAgingSummaryQuery } from './APAgingSummary.types'; -import { ModelObject } from 'objection'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; +@Injectable({ scope: Scope.REQUEST }) export class APAgingSummaryRepository { @Inject(Vendor.name) private readonly vendorModel: TenantModelProxy; @@ -18,10 +19,10 @@ export class APAgingSummaryRepository { private readonly tenancyContext: TenancyContext; /** - * Filter. - * @param {IAPAgingSummaryQuery} filter + * A/P aging filter. + * @param {APAgingSummaryQueryDto} filter */ - filter: IAPAgingSummaryQuery; + filter: APAgingSummaryQueryDto; /** * Due bills. @@ -45,7 +46,7 @@ export class APAgingSummaryRepository { * Overdue bills by vendor id. * @param {Record} overdueBillsByVendorId - Overdue bills by vendor id. */ - overdueBillsByVendorId: ModelObject[]; + overdueBillsByVendorId: Record>>; /** * Vendors. @@ -61,9 +62,9 @@ export class APAgingSummaryRepository { /** * Set the filter. - * @param {IAPAgingSummaryQuery} filter + * @param {APAgingSummaryQueryDto} filter */ - setFilter(filter: IAPAgingSummaryQuery) { + setFilter(filter: APAgingSummaryQueryDto) { this.filter = filter; } diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts index dfa46de0f..9e330b58a 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts @@ -1,14 +1,12 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { Injectable } from '@nestjs/common'; import { events } from '@/common/events/events'; -import { - IAPAgingSummaryQuery, - IAPAgingSummarySheet, -} from './APAgingSummary.types'; +import { IAPAgingSummarySheet } from './APAgingSummary.types'; import { APAgingSummarySheet } from './APAgingSummarySheet'; import { APAgingSummaryMeta } from './APAgingSummaryMeta'; import { getAPAgingSummaryDefaultQuery } from './utils'; import { APAgingSummaryRepository } from './APAgingSummaryRepository'; +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; @Injectable() export class APAgingSummaryService { @@ -20,13 +18,12 @@ export class APAgingSummaryService { /** * Retrieve A/P aging summary report. - * @param {IAPAgingSummaryQuery} query - A/P aging summary query. + * @param {APAgingSummaryQueryDto} query - A/P aging summary query. * @returns {Promise} */ public async APAgingSummary( - query: IAPAgingSummaryQuery, + query: APAgingSummaryQueryDto, ): Promise { - const filter = { ...getAPAgingSummaryDefaultQuery(), ...query, diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts index a8df5cb61..1e0089731 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts @@ -1,7 +1,6 @@ import { sum, isEmpty } from 'lodash'; import * as R from 'ramda'; import { - IAPAgingSummaryQuery, IAPAgingSummaryData, IAPAgingSummaryVendor, IAPAgingSummaryColumns, @@ -13,21 +12,24 @@ import { ModelObject } from 'objection'; import { Vendor } from '@/modules/Vendors/models/Vendor'; import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; import { APAgingSummaryRepository } from './APAgingSummaryRepository'; +import { Bill } from '@/modules/Bills/models/Bill'; +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; export class APAgingSummarySheet extends AgingSummaryReport { readonly repository: APAgingSummaryRepository; - readonly query: IAPAgingSummaryQuery; + readonly query: APAgingSummaryQueryDto; readonly agingPeriods: IAgingPeriod[]; + readonly overdueInvoicesByContactId: Record>>; + readonly currentInvoicesByContactId: Record>>; + /** * Constructor method. - * @param {number} tenantId - Tenant id. - * @param {IAPAgingSummaryQuery} query - Report query. - * @param {ModelObject[]} vendors - Unpaid bills. - * @param {string} baseCurrency - Base currency of the organization. + * @param {APAgingSummaryQueryDto} query - Report query. + * @param {APAgingSummaryRepository} repository - Repository */ constructor( - query: IAPAgingSummaryQuery, + query: APAgingSummaryQueryDto, repository: APAgingSummaryRepository, ) { super(); @@ -36,6 +38,9 @@ export class APAgingSummarySheet extends AgingSummaryReport { this.repository = repository; this.numberFormat = this.query.numberFormat; + this.overdueInvoicesByContactId = this.repository.overdueBillsByVendorId; + this.currentInvoicesByContactId = this.repository.dueBillsByVendorId; + // Initializes the aging periods. this.agingPeriods = this.agingRangePeriods( this.query.asDate, diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryTableInjectable.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryTableInjectable.ts index 0bec38699..d953f46db 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryTableInjectable.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryTableInjectable.ts @@ -1,11 +1,9 @@ +import { Injectable } from '@nestjs/common'; import { I18nService } from 'nestjs-i18n'; -import { - IAPAgingSummaryQuery, - IAPAgingSummaryTable, -} from './APAgingSummary.types'; +import { IAPAgingSummaryTable } from './APAgingSummary.types'; import { APAgingSummaryService } from './APAgingSummaryService'; import { APAgingSummaryTable } from './APAgingSummaryTable'; -import { Injectable } from '@nestjs/common'; +import { APAgingSummaryQueryDto } from './APAgingSummaryQuery.dto'; @Injectable() export class APAgingSummaryTableInjectable { @@ -16,11 +14,11 @@ export class APAgingSummaryTableInjectable { /** * Retrieves A/P aging summary in table format. - * @param {IAPAgingSummaryQuery} query - + * @param {APAgingSummaryQueryDto} query - * @returns {Promise} */ public async table( - query: IAPAgingSummaryQuery, + query: APAgingSummaryQueryDto, ): Promise { const report = await this.APAgingSummarySheet.APAgingSummary(query); const table = new APAgingSummaryTable(report.data, query, this.i18nService); diff --git a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/utils.ts b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/utils.ts index 2fe0a85a8..afae15bc8 100644 --- a/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/utils.ts +++ b/packages/server/src/modules/FinancialStatements/modules/APAgingSummary/utils.ts @@ -1,3 +1,5 @@ +import * as moment from 'moment'; + export const getAPAgingSummaryDefaultQuery = () => { return { asDate: moment().format('YYYY-MM-DD'), diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts index 168a117b8..97d61dee5 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.controller.ts @@ -1,10 +1,10 @@ -import { IARAgingSummaryQuery } from './ARAgingSummary.types'; import { Controller, Get, Headers } from '@nestjs/common'; import { Query, Res } from '@nestjs/common'; import { ARAgingSummaryApplication } from './ARAgingSummaryApplication'; import { AcceptType } from '@/constants/accept-type'; import { Response } from 'express'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; @Controller('reports/receivable-aging-summary') @ApiTags('Reports') @@ -14,7 +14,7 @@ export class ARAgingSummaryController { @Get() @ApiOperation({ summary: 'Get receivable aging summary' }) public async get( - @Query() filter: IARAgingSummaryQuery, + @Query() filter: ARAgingSummaryQueryDto, @Res({ passthrough: true }) res: Response, @Headers('accept') acceptHeader: string, ) { diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.types.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.types.ts index 638a285b7..749e3cbc0 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.types.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummary.types.ts @@ -1,16 +1,12 @@ import { IAgingPeriod, - IAgingSummaryQuery, IAgingSummaryTotal, IAgingSummaryContact, IAgingSummaryData, IAgingSummaryMeta, } from '../AgingSummary/AgingSummary.types'; import { IFinancialTable } from '../../types/Table.types'; - -export interface IARAgingSummaryQuery extends IAgingSummaryQuery { - customersIds: number[]; -} +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; export interface IARAgingSummaryCustomer extends IAgingSummaryContact { customerName: string; @@ -31,13 +27,12 @@ export interface IARAgingSummaryMeta extends IAgingSummaryMeta { export interface IARAgingSummaryTable extends IFinancialTable { meta: IARAgingSummaryMeta; - query: IARAgingSummaryQuery; + query: ARAgingSummaryQueryDto; } export interface IARAgingSummarySheet { data: IARAgingSummaryData; meta: IARAgingSummaryMeta; - query: IARAgingSummaryQuery; + query: ARAgingSummaryQueryDto; columns: IARAgingSummaryColumns; } - diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryApplication.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryApplication.ts index 382e70c3c..e988de5e3 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryApplication.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryApplication.ts @@ -1,9 +1,9 @@ +import { Injectable } from '@nestjs/common'; import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable'; import { ARAgingSummaryExportInjectable } from './ARAgingSummaryExportInjectable'; import { ARAgingSummaryService } from './ARAgingSummaryService'; import { ARAgingSummaryPdfInjectable } from './ARAgingSummaryPdfInjectable'; -import { IARAgingSummaryQuery } from './ARAgingSummary.types'; -import { Injectable } from '@nestjs/common'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; @Injectable() export class ARAgingSummaryApplication { @@ -16,42 +16,42 @@ export class ARAgingSummaryApplication { /** * Retrieve the A/R aging summary sheet. - * @param {IARAgingSummaryQuery} query + * @param {ARAgingSummaryQueryDto} query */ - public sheet(query: IARAgingSummaryQuery) { + public sheet(query: ARAgingSummaryQueryDto) { return this.ARAgingSummarySheet.ARAgingSummary(query); } /** * Retrieve the A/R aging summary in table format. - * @param {IAPAgingSummaryQuery} query + * @param {ARAgingSummaryQueryDto} query */ - public table(query: IARAgingSummaryQuery) { + public table(query: ARAgingSummaryQueryDto) { return this.ARAgingSummaryTable.table(query); } /** * Retrieve the A/R aging summary in XLSX format. - * @param {IAPAgingSummaryQuery} query + * @param {ARAgingSummaryQueryDto} query */ - public xlsx(query: IARAgingSummaryQuery) { + public xlsx(query: ARAgingSummaryQueryDto) { return this.ARAgingSummaryExport.xlsx(query); } /** * Retrieve the A/R aging summary in CSV format. - * @param {IAPAgingSummaryQuery} query + * @param {ARAgingSummaryQueryDto} query */ - public csv(query: IARAgingSummaryQuery) { + public csv(query: ARAgingSummaryQueryDto) { return this.ARAgingSummaryExport.csv(query); } /** * Retrieves the A/R aging summary in pdf format. - * @param {IARAgingSummaryQuery} query + * @param {ARAgingSummaryQueryDto} query * @returns {Promise} */ - public pdf(query: IARAgingSummaryQuery) { + public pdf(query: ARAgingSummaryQueryDto) { return this.ARAgingSummaryPdf.pdf(query); } } diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryExportInjectable.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryExportInjectable.ts index 3730c97d1..4afde602e 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryExportInjectable.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryExportInjectable.ts @@ -1,7 +1,7 @@ import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable'; -import { IARAgingSummaryQuery } from './ARAgingSummary.types'; import { TableSheet } from '../../common/TableSheet'; import { Injectable } from '@nestjs/common'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; @Injectable() export class ARAgingSummaryExportInjectable { @@ -11,12 +11,10 @@ export class ARAgingSummaryExportInjectable { /** * Retrieves the A/R aging summary sheet in XLSX format. - * @param {IARAgingSummaryQuery} query - A/R aging summary query. + * @param {ARAgingSummaryQueryDto} query - A/R aging summary query. * @returns {Promise} */ - public async xlsx( - query: IARAgingSummaryQuery - ): Promise { + public async xlsx(query: ARAgingSummaryQueryDto): Promise { const table = await this.ARAgingSummaryTable.table(query); const tableSheet = new TableSheet(table.table); @@ -27,12 +25,10 @@ export class ARAgingSummaryExportInjectable { /** * Retrieves the A/R aging summary sheet in CSV format. - * @param {IARAgingSummaryQuery} query - A/R aging summary query. + * @param {ARAgingSummaryQueryDto} query - A/R aging summary query. * @returns {Promise} */ - public async csv( - query: IARAgingSummaryQuery - ): Promise { + public async csv(query: ARAgingSummaryQueryDto): Promise { const table = await this.ARAgingSummaryTable.table(query); const tableSheet = new TableSheet(table.table); diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryPdfInjectable.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryPdfInjectable.ts index 29c118ee2..fd8901c23 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryPdfInjectable.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryPdfInjectable.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { ARAgingSummaryTableInjectable } from './ARAgingSummaryTableInjectable'; -import { IARAgingSummaryQuery } from './ARAgingSummary.types'; import { TableSheetPdf } from '../../common/TableSheetPdf'; import { HtmlTableCss } from '../AgingSummary/_constants'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; @Injectable() export class ARAgingSummaryPdfInjectable { @@ -16,7 +16,7 @@ export class ARAgingSummaryPdfInjectable { * @param {IBalanceSheetQuery} query - Balance sheet query. * @returns {Promise} */ - public async pdf(query: IARAgingSummaryQuery): Promise { + public async pdf(query: ARAgingSummaryQueryDto): Promise { const table = await this.ARAgingSummaryTable.table(query); return this.tableSheetPdf.convertToPdf( diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryQuery.dto.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryQuery.dto.ts index e69de29bb..666cf6a98 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryQuery.dto.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryQuery.dto.ts @@ -0,0 +1,74 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { + IsNumber, + IsOptional, + IsDateString, + IsBoolean, + ValidateNested, + IsArray, +} from 'class-validator'; +import { FinancialSheetBranchesQueryDto } from '../../dtos/FinancialSheetBranchesQuery.dto'; +import { ToNumber } from '@/common/decorators/Validators'; +import { NumberFormatQueryDto } from '@/modules/BankingTransactions/dtos/NumberFormatQuery.dto'; +import { parseBoolean } from '@/utils/parse-boolean'; + +export class ARAgingSummaryQueryDto extends FinancialSheetBranchesQueryDto { + @IsDateString() + @IsOptional() + @ApiPropertyOptional({ + description: 'The date as of which the A/R aging summary is calculated', + example: '2024-06-01', + }) + asDate: Date | string; + + @ToNumber() + @IsNumber() + @IsOptional() + @ApiPropertyOptional({ + description: 'Number of days before the aging period starts', + example: 30, + }) + agingDaysBefore: number; + + @ToNumber() + @IsNumber() + @IsOptional() + @ApiPropertyOptional({ + description: 'Number of aging periods to calculate', + example: 4, + }) + agingPeriods: number; + + @IsOptional() + @ValidateNested() + @Type(() => NumberFormatQueryDto) + @ApiPropertyOptional({ + description: 'Number format configuration', + example: { + precision: 2, + divideOn1000: false, + showZero: true, + formatMoney: 'total', + negativeFormat: 'parentheses', + }, + }) + numberFormat: NumberFormatQueryDto; + + @Transform(({ value }) => parseBoolean(value, false)) + @IsBoolean() + @IsOptional() + @ApiPropertyOptional({ + description: 'Whether to exclude zero values', + example: false, + }) + noneZero: boolean; + + @IsArray() + @IsOptional() + @ApiPropertyOptional({ + description: 'Array of customer IDs to include', + example: [1, 2, 3], + }) + customersIds: number[]; +} diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts index 488e0bb9f..cef4ef936 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts @@ -1,12 +1,13 @@ -import { Inject } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; import { isEmpty, groupBy } from 'lodash'; import { Customer } from '@/modules/Customers/models/Customer'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { ModelObject } from 'objection'; -import { IARAgingSummaryQuery } from './ARAgingSummary.types'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; +@Injectable({ scope: Scope.REQUEST }) export class ARAgingSummaryRepository { @Inject(TenancyContext) private tenancyContext: TenancyContext; @@ -19,9 +20,9 @@ export class ARAgingSummaryRepository { /** * Filter. - * @param {IARAgingSummaryQuery} filter + * @param {ARAgingSummaryQueryDto} filter */ - filter: IARAgingSummaryQuery; + filter: ARAgingSummaryQueryDto; /** * Base currency. @@ -61,9 +62,9 @@ export class ARAgingSummaryRepository { /** * Set the filter. - * @param {IARAgingSummaryQuery} filter + * @param {ARAgingSummaryQueryDto} filter */ - setFilter(filter: IARAgingSummaryQuery) { + setFilter(filter: ARAgingSummaryQueryDto) { this.filter = filter; } @@ -139,9 +140,6 @@ export class ARAgingSummaryRepository { .onBuild(commonQuery); this.currentInvoices = currentInvoices; - this.currentInvoicesByContactId = groupBy( - currentInvoices, - 'customerId', - ); + this.currentInvoicesByContactId = groupBy(currentInvoices, 'customerId'); } } diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts index 1fb8cd6af..3e3b6cd71 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts @@ -3,9 +3,9 @@ import { ARAgingSummaryMeta } from './ARAgingSummaryMeta'; import { getARAgingSummaryDefaultQuery } from './utils'; import { Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { IARAgingSummaryQuery } from './ARAgingSummary.types'; import { events } from '@/common/events/events'; import { ARAgingSummaryRepository } from './ARAgingSummaryRepository'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; @Injectable() export class ARAgingSummaryService { @@ -17,9 +17,9 @@ export class ARAgingSummaryService { /** * Retrieve A/R aging summary report. - * @param {IARAgingSummaryQuery} query - + * @param {ARAgingSummaryQueryDto} query - */ - async ARAgingSummary(query: IARAgingSummaryQuery) { + async ARAgingSummary(query: ARAgingSummaryQueryDto) { const filter = { ...getARAgingSummaryDefaultQuery(), ...query, @@ -28,12 +28,12 @@ export class ARAgingSummaryService { this.ARAgingSummaryRepository.setFilter(filter); await this.ARAgingSummaryRepository.load(); - // AR aging summary report instance. + // A/R aging summary report instance. const ARAgingSummaryReport = new ARAgingSummarySheet( filter, this.ARAgingSummaryRepository, ); - // AR aging summary report data and columns. + // A/R aging summary report data and columns. const data = ARAgingSummaryReport.reportData(); const columns = ARAgingSummaryReport.reportColumns(); diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts index 0012e8326..a6c52f313 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts @@ -2,7 +2,6 @@ import * as R from 'ramda'; import { isEmpty, sum } from 'lodash'; import { IAgingPeriod } from '../AgingSummary/AgingSummary.types'; import { - IARAgingSummaryQuery, IARAgingSummaryCustomer, IARAgingSummaryData, IARAgingSummaryColumns, @@ -11,23 +10,31 @@ import { import { AgingSummaryReport } from '../AgingSummary/AgingSummary'; import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; import { ModelObject } from 'objection'; -import { Customer } from '@/modules/Customers/models/Customer'; import { ARAgingSummaryRepository } from './ARAgingSummaryRepository'; +import { Customer } from '@/modules/Customers/models/Customer'; +import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; export class ARAgingSummarySheet extends AgingSummaryReport { - readonly query: IARAgingSummaryQuery; + readonly query: ARAgingSummaryQueryDto; readonly agingPeriods: IAgingPeriod[]; readonly repository: ARAgingSummaryRepository; + readonly overdueInvoicesByContactId: Record< + string, + ModelObject[] + >; + readonly currentInvoicesByContactId: Record< + number, + Array> + >; /** * Constructor method. - * @param {number} tenantId - * @param {IARAgingSummaryQuery} query - * @param {ICustomer[]} customers - * @param {IJournalPoster} journal + * @param {ARAgingSummaryQueryDto} query - Query + * @param {ARAgingSummaryRepository} repository - Repository. */ constructor( - query: IARAgingSummaryQuery, + query: ARAgingSummaryQueryDto, repository: ARAgingSummaryRepository, ) { super(); @@ -36,6 +43,11 @@ export class ARAgingSummarySheet extends AgingSummaryReport { this.repository = repository; this.numberFormat = this.query.numberFormat; + this.overdueInvoicesByContactId = + this.repository.overdueInvoicesByContactId; + this.currentInvoicesByContactId = + this.repository.currentInvoicesByContactId; + // Initializes the aging periods. this.agingPeriods = this.agingRangePeriods( this.query.asDate, diff --git a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryTableInjectable.ts b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryTableInjectable.ts index b0e977299..0ae905baa 100644 --- a/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryTableInjectable.ts +++ b/packages/server/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryTableInjectable.ts @@ -1,10 +1,8 @@ import { ARAgingSummaryTable } from './ARAgingSummaryTable'; import { ARAgingSummaryService } from './ARAgingSummaryService'; import { Injectable } from '@nestjs/common'; -import { - IARAgingSummaryQuery, - IARAgingSummaryTable, -} from './ARAgingSummary.types'; +import { IARAgingSummaryTable } from './ARAgingSummary.types'; +import { ARAgingSummaryQueryDto } from './ARAgingSummaryQuery.dto'; @Injectable() export class ARAgingSummaryTableInjectable { @@ -12,11 +10,11 @@ export class ARAgingSummaryTableInjectable { /** * Retrieves A/R aging summary in table format. - * @param {IARAgingSummaryQuery} query - Aging summary query. + * @param {ARAgingSummaryQueryDto} query - Aging summary query. * @returns {Promise} */ public async table( - query: IARAgingSummaryQuery, + query: ARAgingSummaryQueryDto, ): Promise { const report = await this.ARAgingSummarySheet.ARAgingSummary(query); const table = new ARAgingSummaryTable(report.data, query, {}); diff --git a/packages/server/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts b/packages/server/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts index 50a3fc8ac..711b821ab 100644 --- a/packages/server/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts +++ b/packages/server/src/modules/FinancialStatements/modules/CustomerBalanceSummary/CustomerBalanceSummaryPdf.ts @@ -13,7 +13,7 @@ export class CustomerBalanceSummaryPdf { /** * Converts the given customer balance summary sheet table to pdf. - * @param {IAPAgingSummaryQuery} query - Balance sheet query. + * @param {ICustomerBalanceSummaryQuery} query - Balance sheet query. * @returns {Promise} */ public async pdf(query: ICustomerBalanceSummaryQuery): Promise {