fix: AR/AP aging report

This commit is contained in:
Ahmed Bouhuolia
2025-06-21 20:15:42 +02:00
parent 4d52059dba
commit 91976842a7
23 changed files with 265 additions and 126 deletions

View File

@@ -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';
}
}

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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<Buffer>}
*/
public pdf(query: IAPAgingSummaryQuery) {
public pdf(query: APAgingSummaryQueryDto) {
return this.APAgingSumaryPdf.pdf(query);
}
}

View File

@@ -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<Buffer>}
*/
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<Buffer>}
*/
public async csv(query: IAPAgingSummaryQuery): Promise<string> {
public async csv(query: APAgingSummaryQueryDto): Promise<string> {
const table = await this.APAgingSummaryTable.table(query);
const tableSheet = new TableSheet(table.table);

View File

@@ -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<Buffer>}
*/
public async pdf(query: IAPAgingSummaryQuery): Promise<Buffer> {
public async pdf(query: APAgingSummaryQueryDto): Promise<Buffer> {
const table = await this.APAgingSummaryTable.table(query);
return this.tableSheetPdf.convertToPdf(

View File

@@ -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[];
}

View File

@@ -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<typeof Vendor>;
@@ -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<string, Bill[]>} overdueBillsByVendorId - Overdue bills by vendor id.
*/
overdueBillsByVendorId: ModelObject<Bill>[];
overdueBillsByVendorId: Record<string, Array<ModelObject<Bill>>>;
/**
* 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;
}

View File

@@ -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<IAPAgingSummarySheet>}
*/
public async APAgingSummary(
query: IAPAgingSummaryQuery,
query: APAgingSummaryQueryDto,
): Promise<IAPAgingSummarySheet> {
const filter = {
...getAPAgingSummaryDefaultQuery(),
...query,

View File

@@ -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<string, Array<ModelObject<Bill>>>;
readonly currentInvoicesByContactId: Record<number, Array<ModelObject<Bill>>>;
/**
* Constructor method.
* @param {number} tenantId - Tenant id.
* @param {IAPAgingSummaryQuery} query - Report query.
* @param {ModelObject<Vendor>[]} 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,

View File

@@ -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<IAPAgingSummaryTable>}
*/
public async table(
query: IAPAgingSummaryQuery,
query: APAgingSummaryQueryDto,
): Promise<IAPAgingSummaryTable> {
const report = await this.APAgingSummarySheet.APAgingSummary(query);
const table = new APAgingSummaryTable(report.data, query, this.i18nService);

View File

@@ -1,3 +1,5 @@
import * as moment from 'moment';
export const getAPAgingSummaryDefaultQuery = () => {
return {
asDate: moment().format('YYYY-MM-DD'),

View File

@@ -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,
) {

View File

@@ -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;
}

View File

@@ -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<Buffer>}
*/
public pdf(query: IARAgingSummaryQuery) {
public pdf(query: ARAgingSummaryQueryDto) {
return this.ARAgingSummaryPdf.pdf(query);
}
}

View File

@@ -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<Buffer>}
*/
public async xlsx(
query: IARAgingSummaryQuery
): Promise<Buffer> {
public async xlsx(query: ARAgingSummaryQueryDto): Promise<Buffer> {
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<string>}
*/
public async csv(
query: IARAgingSummaryQuery
): Promise<string> {
public async csv(query: ARAgingSummaryQueryDto): Promise<string> {
const table = await this.ARAgingSummaryTable.table(query);
const tableSheet = new TableSheet(table.table);

View File

@@ -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<Buffer>}
*/
public async pdf(query: IARAgingSummaryQuery): Promise<Buffer> {
public async pdf(query: ARAgingSummaryQueryDto): Promise<Buffer> {
const table = await this.ARAgingSummaryTable.table(query);
return this.tableSheetPdf.convertToPdf(

View File

@@ -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[];
}

View File

@@ -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');
}
}

View File

@@ -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();

View File

@@ -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<SaleInvoice>[]
>;
readonly currentInvoicesByContactId: Record<
number,
Array<ModelObject<SaleInvoice>>
>;
/**
* 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,

View File

@@ -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<IARAgingSummaryTable>}
*/
public async table(
query: IARAgingSummaryQuery,
query: ARAgingSummaryQueryDto,
): Promise<IARAgingSummaryTable> {
const report = await this.ARAgingSummarySheet.ARAgingSummary(query);
const table = new ARAgingSummaryTable(report.data, query, {});

View File

@@ -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<Buffer>}
*/
public async pdf(query: ICustomerBalanceSummaryQuery): Promise<Buffer> {