feat: AR/AP aging summary report.

This commit is contained in:
a.bouhuolia
2021-01-09 13:37:53 +02:00
parent 09b2aa57a0
commit 40afb108e3
18 changed files with 283 additions and 100 deletions

View File

@@ -36,6 +36,7 @@ export default class PayableAgingSummaryService {
async APAgingSummary(tenantId: number, query) {
const {
vendorRepository,
billRepository
} = this.tenancy.repositories(tenantId);
const { Bill } = this.tenancy.models(tenantId);
@@ -55,15 +56,19 @@ export default class PayableAgingSummaryService {
// Retrieve all vendors from the storage.
const vendors = await vendorRepository.all();
// Retrieve all unpaid vendors bills.
const unpaidBills = await Bill.query().modify('unpaid');
// Retrieve all overdue vendors bills.
const overdueBills = await billRepository.overdueBills(
filter.asDate,
);
const dueBills = await billRepository.dueBills(filter.asDate);
// A/P aging summary report instance.
const APAgingSummaryReport = new APAgingSummarySheet(
tenantId,
filter,
vendors,
unpaidBills,
overdueBills,
dueBills,
baseCurrency,
);
// A/P aging summary report data and columns.

View File

@@ -1,4 +1,4 @@
import { groupBy, sumBy } from 'lodash';
import { groupBy, sum } from 'lodash';
import AgingSummaryReport from './AgingSummary';
import {
IAPAgingSummaryQuery,
@@ -17,7 +17,9 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
readonly unpaidBills: IBill[];
readonly baseCurrency: string;
readonly unpaidInvoicesByContactId: Dictionary<IBill[]>;
readonly overdueInvoicesByContactId: Dictionary<IBill[]>;
readonly currentInvoicesByContactId: Dictionary<IBill[]>;
readonly agingPeriods: IAgingPeriod[];
/**
@@ -31,6 +33,7 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
tenantId: number,
query: IAPAgingSummaryQuery,
vendors: IVendor[],
overdueBills: IBill[],
unpaidBills: IBill[],
baseCurrency: string
) {
@@ -40,10 +43,10 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
this.query = query;
this.numberFormat = this.query.numberFormat;
this.contacts = vendors;
this.unpaidBills = unpaidBills;
this.baseCurrency = baseCurrency;
this.unpaidInvoicesByContactId = groupBy(unpaidBills, 'vendorId');
this.overdueInvoicesByContactId = groupBy(overdueBills, 'vendorId');
this.currentInvoicesByContactId = groupBy(unpaidBills, 'vendorId');
// Initializes the aging periods.
this.agingPeriods = this.agingRangePeriods(
@@ -60,10 +63,14 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
*/
private vendorData(vendor: IVendor): IAPAgingSummaryVendor {
const agingPeriods = this.getContactAgingPeriods(vendor.id);
const amount = sumBy(agingPeriods, 'total');
const currentTotal = this.getContactCurrentTotal(vendor.id);
const agingPeriodsTotal = this.getAgingPeriodsTotal(agingPeriods);
const amount = sum([agingPeriodsTotal, currentTotal]);
return {
vendorName: vendor.displayName,
current: this.formatTotalAmount(currentTotal),
aging: agingPeriods,
total: this.formatTotalAmount(amount),
};
@@ -89,17 +96,21 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
public reportData(): IAPAgingSummaryData {
const vendorsAgingPeriods = this.vendorsWalker(this.contacts);
const totalAgingPeriods = this.getTotalAgingPeriods(vendorsAgingPeriods);
const totalCurrent = this.getTotalCurrent(vendorsAgingPeriods);
return {
vendors: vendorsAgingPeriods,
total: totalAgingPeriods,
total: {
current: this.formatTotalAmount(totalCurrent),
aging: totalAgingPeriods,
},
}
}
/**
* Retrieve the A/P aging summary report columns.
*/
reportColumns(): IAPAgingSummaryColumns {
public reportColumns(): IAPAgingSummaryColumns {
return this.agingPeriods;
}
}

View File

@@ -3,6 +3,7 @@ import { Inject, Service } from 'typedi';
import { IARAgingSummaryQuery } from 'interfaces';
import TenancyService from 'services/Tenancy/TenancyService';
import ARAgingSummarySheet from './ARAgingSummarySheet';
import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository';
@Service()
export default class ARAgingSummaryService {
@@ -30,14 +31,14 @@ export default class ARAgingSummaryService {
}
/**
*
*
* @param {number} tenantId
* @param query
*/
async ARAgingSummary(tenantId: number, query: IARAgingSummaryQuery) {
const {
customerRepository,
saleInvoiceRepository
saleInvoiceRepository,
} = this.tenancy.repositories(tenantId);
const filter = {
@@ -57,15 +58,21 @@ export default class ARAgingSummaryService {
// Retrieve all customers from the storage.
const customers = await customerRepository.all();
// Retrieve all overdue sale invoices.
const overdueSaleInvoices = await saleInvoiceRepository.overdueInvoices(
filter.asDate
);
// Retrieve all due sale invoices.
const dueSaleInvoices = await saleInvoiceRepository.dueInvoices();
const currentInvoices = await saleInvoiceRepository.dueInvoices(
filter.asDate
);
// AR aging summary report instance.
const ARAgingSummaryReport = new ARAgingSummarySheet(
tenantId,
filter,
customers,
dueSaleInvoices,
overdueSaleInvoices,
currentInvoices,
baseCurrency
);
// AR aging summary report data and columns.

View File

@@ -1,9 +1,8 @@
import { groupBy, sumBy, defaultTo } from 'lodash';
import { groupBy, sum } from 'lodash';
import {
ICustomer,
IARAgingSummaryQuery,
IARAgingSummaryCustomer,
IAgingPeriodTotal,
IAgingPeriod,
ISaleInvoice,
IARAgingSummaryData,
@@ -18,8 +17,9 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
readonly contacts: ICustomer[];
readonly agingPeriods: IAgingPeriod[];
readonly baseCurrency: string;
readonly dueInvoices: ISaleInvoice[];
readonly unpaidInvoicesByContactId: Dictionary<ISaleInvoice[]>;
readonly overdueInvoicesByContactId: Dictionary<ISaleInvoice[]>;
readonly currentInvoicesByContactId: Dictionary<ISaleInvoice[]>;
/**
* Constructor method.
@@ -32,7 +32,8 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
tenantId: number,
query: IARAgingSummaryQuery,
customers: ICustomer[],
unpaidSaleInvoices: ISaleInvoice[],
overdueSaleInvoices: ISaleInvoice[],
currentSaleInvoices: ISaleInvoice[],
baseCurrency: string
) {
super();
@@ -42,9 +43,9 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
this.query = query;
this.baseCurrency = baseCurrency;
this.numberFormat = this.query.numberFormat;
this.unpaidInvoicesByContactId = groupBy(unpaidSaleInvoices, 'customerId');
this.dueInvoices = unpaidSaleInvoices;
this.periodsByContactId = {};
this.overdueInvoicesByContactId = groupBy(overdueSaleInvoices, 'customerId');
this.currentInvoicesByContactId = groupBy(currentSaleInvoices, 'customerId');
// Initializes the aging periods.
this.agingPeriods = this.agingRangePeriods(
@@ -61,10 +62,13 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
*/
private customerData(customer: ICustomer): IARAgingSummaryCustomer {
const agingPeriods = this.getContactAgingPeriods(customer.id);
const amount = sumBy(agingPeriods, 'total');
const currentTotal = this.getContactCurrentTotal(customer.id);
const agingPeriodsTotal = this.getAgingPeriodsTotal(agingPeriods);
const amount = sum([agingPeriodsTotal, currentTotal]);
return {
customerName: customer.displayName,
current: this.formatTotalAmount(currentTotal),
aging: agingPeriods,
total: this.formatTotalAmount(amount),
};
@@ -91,10 +95,14 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
public reportData(): IARAgingSummaryData {
const customersAgingPeriods = this.customersWalker(this.contacts);
const totalAgingPeriods = this.getTotalAgingPeriods(customersAgingPeriods);
const totalCurrent = this.getTotalCurrent(customersAgingPeriods);
return {
customers: customersAgingPeriods,
total: totalAgingPeriods,
total: {
current: this.formatTotalAmount(totalCurrent),
aging: totalAgingPeriods,
}
};
}

View File

@@ -1,4 +1,4 @@
import { defaultTo, sumBy } from 'lodash';
import { defaultTo, sumBy, get } from 'lodash';
import {
IAgingPeriod,
ISaleInvoice,
@@ -6,6 +6,7 @@ import {
IAgingPeriodTotal,
IARAgingSummaryCustomer,
IContact,
IARAgingSummaryQuery,
} from 'interfaces';
import AgingReport from './AgingReport';
import { Dictionary } from 'tsyringe/dist/typings/types';
@@ -14,13 +15,13 @@ export default abstract class AgingSummaryReport extends AgingReport {
protected readonly contacts: IContact[];
protected readonly agingPeriods: IAgingPeriod[] = [];
protected readonly baseCurrency: string;
protected readonly unpaidInvoices: (ISaleInvoice | IBill)[];
protected readonly unpaidInvoicesByContactId: Dictionary<
protected readonly query: IARAgingSummaryQuery;
protected readonly overdueInvoicesByContactId: Dictionary<
(ISaleInvoice | IBill)[]
>;
protected readonly currentInvoicesByContactId: Dictionary<
(ISaleInvoice | IBill)[]
>;
protected periodsByContactId: {
[key: number]: (IAgingPeriod & IAgingPeriodTotal)[];
} = {};
/**
* Setes initial aging periods to the given customer id.
@@ -48,7 +49,7 @@ export default abstract class AgingSummaryReport extends AgingReport {
const newAgingPeriods = this.getContactAgingDueAmount(
agingPeriods,
unpaidInvoice.dueAmount,
unpaidInvoice.overdueDays
unpaidInvoice.getOverdueDays(this.query.asDate)
);
return newAgingPeriods;
}, initialAgingPeriods);
@@ -81,7 +82,7 @@ export default abstract class AgingSummaryReport extends AgingReport {
}
/**
* Retrieve the aging period total object. (xx)
* Retrieve the aging period total object.
* @param {number} amount
* @return {IAgingPeriodTotal}
*/
@@ -112,14 +113,14 @@ export default abstract class AgingSummaryReport extends AgingReport {
}
/**
* Retrieve the due invoices by the given customer id. (XX)
* @param {number} customerId -
* Retrieve the due invoices by the given customer id.
* @param {number} customerId -
* @return {ISaleInvoice[]}
*/
protected getUnpaidInvoicesByContactId(
contactId: number
): (ISaleInvoice | IBill)[] {
return defaultTo(this.unpaidInvoicesByContactId[contactId], []);
return defaultTo(this.overdueInvoicesByContactId[contactId], []);
}
/**
@@ -138,4 +139,47 @@ export default abstract class AgingSummaryReport extends AgingReport {
};
});
}
/**
* Retrieve the current invoices by the given contact id.
* @param {number} contactId
* @return {(ISaleInvoice | IBill)[]}
*/
protected getCurrentInvoicesByContactId(
contactId: number
): (ISaleInvoice | IBill)[] {
return get(this.currentInvoicesByContactId, contactId, []);
}
/**
* Retrieve the contact total due amount.
* @param {number} contactId
* @return {number}
*/
protected getContactCurrentTotal(contactId: number): number {
const currentInvoices = this.getCurrentInvoicesByContactId(contactId);
return sumBy(currentInvoices, invoice => invoice.dueAmount);
}
/**
* Retrieve to total sumation of the given customers sections.
* @param {IARAgingSummaryCustomer[]} contactsSections -
* @return {number}
*/
protected getTotalCurrent(
customersSummary: IARAgingSummaryCustomer[]
): number {
return sumBy(customersSummary, summary => summary.current.total);
}
/**
* Retrieve the total of the given aging periods.
* @param {IAgingPeriodTotal[]} agingPeriods
* @return {number}
*/
protected getAgingPeriodsTotal(
agingPeriods: IAgingPeriodTotal[],
): number {
return sumBy(agingPeriods, 'total');
}
}