mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat: AR/AP aging summary report.
This commit is contained in:
@@ -20,6 +20,13 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
readonly unpaidInvoicesByContactId: Dictionary<IBill[]>;
|
readonly unpaidInvoicesByContactId: Dictionary<IBill[]>;
|
||||||
readonly agingPeriods: IAgingPeriod[];
|
readonly agingPeriods: IAgingPeriod[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor method.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {IAPAgingSummaryQuery} query - Report query.
|
||||||
|
* @param {IVendor[]} vendors - Unpaid bills.
|
||||||
|
* @param {string} baseCurrency - Base currency of the organization.
|
||||||
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
query: IAPAgingSummaryQuery,
|
query: IAPAgingSummaryQuery,
|
||||||
@@ -44,16 +51,14 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
this.query.agingDaysBefore,
|
this.query.agingDaysBefore,
|
||||||
this.query.agingPeriods
|
this.query.agingPeriods
|
||||||
);
|
);
|
||||||
this.initContactsAgingPeriods();
|
|
||||||
this.calcUnpaidInvoicesAgingPeriods();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the vendor section data.
|
* Retrieve the vendor section data.
|
||||||
* @param {IVendor} vendor
|
* @param {IVendor} vendor
|
||||||
* @return {IAPAgingSummaryData}
|
* @return {IAPAgingSummaryVendor}
|
||||||
*/
|
*/
|
||||||
protected vendorData(vendor: IVendor): IAPAgingSummaryVendor {
|
private vendorData(vendor: IVendor): IAPAgingSummaryVendor {
|
||||||
const agingPeriods = this.getContactAgingPeriods(vendor.id);
|
const agingPeriods = this.getContactAgingPeriods(vendor.id);
|
||||||
const amount = sumBy(agingPeriods, 'total');
|
const amount = sumBy(agingPeriods, 'total');
|
||||||
|
|
||||||
@@ -68,8 +73,8 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
* Retrieve vendors aging periods.
|
* Retrieve vendors aging periods.
|
||||||
* @return {IAPAgingSummaryVendor[]}
|
* @return {IAPAgingSummaryVendor[]}
|
||||||
*/
|
*/
|
||||||
private vendorsWalker(): IAPAgingSummaryVendor[] {
|
private vendorsWalker(vendors: IVendor[]): IAPAgingSummaryVendor[] {
|
||||||
return this.contacts
|
return vendors
|
||||||
.map((vendor) => this.vendorData(vendor))
|
.map((vendor) => this.vendorData(vendor))
|
||||||
.filter(
|
.filter(
|
||||||
(vendor: IAPAgingSummaryVendor) =>
|
(vendor: IAPAgingSummaryVendor) =>
|
||||||
@@ -82,9 +87,12 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
* @return {IAPAgingSummaryData}
|
* @return {IAPAgingSummaryData}
|
||||||
*/
|
*/
|
||||||
public reportData(): IAPAgingSummaryData {
|
public reportData(): IAPAgingSummaryData {
|
||||||
|
const vendorsAgingPeriods = this.vendorsWalker(this.contacts);
|
||||||
|
const totalAgingPeriods = this.getTotalAgingPeriods(vendorsAgingPeriods);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vendors: this.vendorsWalker(),
|
vendors: vendorsAgingPeriods,
|
||||||
total: this.getTotalAgingPeriods(),
|
total: totalAgingPeriods,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
|
|||||||
this.query.agingDaysBefore,
|
this.query.agingDaysBefore,
|
||||||
this.query.agingPeriods
|
this.query.agingPeriods
|
||||||
);
|
);
|
||||||
this.initContactsAgingPeriods();
|
|
||||||
this.calcUnpaidInvoicesAgingPeriods();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,8 +75,8 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
|
|||||||
* @param {ICustomer[]} customers
|
* @param {ICustomer[]} customers
|
||||||
* @return {IARAgingSummaryCustomer[]}
|
* @return {IARAgingSummaryCustomer[]}
|
||||||
*/
|
*/
|
||||||
private customersWalker(): IARAgingSummaryCustomer[] {
|
private customersWalker(customers: ICustomer[]): IARAgingSummaryCustomer[] {
|
||||||
return this.contacts
|
return customers
|
||||||
.map((customer) => this.customerData(customer))
|
.map((customer) => this.customerData(customer))
|
||||||
.filter(
|
.filter(
|
||||||
(customer: IARAgingSummaryCustomer) =>
|
(customer: IARAgingSummaryCustomer) =>
|
||||||
@@ -91,9 +89,12 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
|
|||||||
* @return {IARAgingSummaryData}
|
* @return {IARAgingSummaryData}
|
||||||
*/
|
*/
|
||||||
public reportData(): IARAgingSummaryData {
|
public reportData(): IARAgingSummaryData {
|
||||||
|
const customersAgingPeriods = this.customersWalker(this.contacts);
|
||||||
|
const totalAgingPeriods = this.getTotalAgingPeriods(customersAgingPeriods);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customers: this.customersWalker(),
|
customers: customersAgingPeriods,
|
||||||
total: this.getTotalAgingPeriods(),
|
total: totalAgingPeriods,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default abstract class AgingReport extends FinancialSheet{
|
|||||||
* @param {number} agingPeriodsFreq
|
* @param {number} agingPeriodsFreq
|
||||||
*/
|
*/
|
||||||
agingRangePeriods(
|
agingRangePeriods(
|
||||||
asDay: string,
|
asDay: Date|string,
|
||||||
agingDaysBefore: number,
|
agingDaysBefore: number,
|
||||||
agingPeriodsFreq: number
|
agingPeriodsFreq: number
|
||||||
): IAgingPeriod[] {
|
): IAgingPeriod[] {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import moment from 'moment';
|
import { defaultTo, sumBy } from 'lodash';
|
||||||
import { defaultTo } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
IAgingPeriod,
|
IAgingPeriod,
|
||||||
ISaleInvoice,
|
ISaleInvoice,
|
||||||
IBill,
|
IBill,
|
||||||
IAgingPeriodTotal,
|
IAgingPeriodTotal,
|
||||||
|
IARAgingSummaryCustomer,
|
||||||
IContact,
|
IContact,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import AgingReport from './AgingReport';
|
import AgingReport from './AgingReport';
|
||||||
@@ -15,7 +15,7 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
protected readonly agingPeriods: IAgingPeriod[] = [];
|
protected readonly agingPeriods: IAgingPeriod[] = [];
|
||||||
protected readonly baseCurrency: string;
|
protected readonly baseCurrency: string;
|
||||||
protected readonly unpaidInvoices: (ISaleInvoice | IBill)[];
|
protected readonly unpaidInvoices: (ISaleInvoice | IBill)[];
|
||||||
readonly unpaidInvoicesByContactId: Dictionary<
|
protected readonly unpaidInvoicesByContactId: Dictionary<
|
||||||
(ISaleInvoice | IBill)[]
|
(ISaleInvoice | IBill)[]
|
||||||
>;
|
>;
|
||||||
protected periodsByContactId: {
|
protected periodsByContactId: {
|
||||||
@@ -26,13 +26,11 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
* Setes initial aging periods to the given customer id.
|
* Setes initial aging periods to the given customer id.
|
||||||
* @param {number} customerId - Customer id.
|
* @param {number} customerId - Customer id.
|
||||||
*/
|
*/
|
||||||
protected setInitialAgingPeriods(contactId: number): void {
|
protected getInitialAgingPeriodsTotal() {
|
||||||
this.periodsByContactId[contactId] = this.agingPeriods.map(
|
return this.agingPeriods.map((agingPeriod) => ({
|
||||||
(agingPeriod) => ({
|
|
||||||
...agingPeriod,
|
...agingPeriod,
|
||||||
...this.formatTotalAmount(0),
|
...this.formatTotalAmount(0),
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,29 +41,34 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
protected getContactAgingPeriods(
|
protected getContactAgingPeriods(
|
||||||
contactId: number
|
contactId: number
|
||||||
): (IAgingPeriod & IAgingPeriodTotal)[] {
|
): (IAgingPeriod & IAgingPeriodTotal)[] {
|
||||||
return defaultTo(this.periodsByContactId[contactId], []);
|
const unpaidInvoices = this.getUnpaidInvoicesByContactId(contactId);
|
||||||
|
const initialAgingPeriods = this.getInitialAgingPeriodsTotal();
|
||||||
|
|
||||||
|
return unpaidInvoices.reduce((agingPeriods, unpaidInvoice) => {
|
||||||
|
const newAgingPeriods = this.getContactAgingDueAmount(
|
||||||
|
agingPeriods,
|
||||||
|
unpaidInvoice.dueAmount,
|
||||||
|
unpaidInvoice.overdueDays
|
||||||
|
);
|
||||||
|
return newAgingPeriods;
|
||||||
|
}, initialAgingPeriods);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the customer aging due amount to the table.
|
* Sets the customer aging due amount to the table. (Xx)
|
||||||
* @param {number} customerId - Customer id.
|
* @param {number} customerId - Customer id.
|
||||||
* @param {number} dueAmount - Due amount.
|
* @param {number} dueAmount - Due amount.
|
||||||
* @param {number} overdueDays - Overdue days.
|
* @param {number} overdueDays - Overdue days.
|
||||||
*/
|
*/
|
||||||
protected setContactAgingDueAmount(
|
protected getContactAgingDueAmount(
|
||||||
customerId: number,
|
agingPeriods: any,
|
||||||
dueAmount: number,
|
dueAmount: number,
|
||||||
overdueDays: number
|
overdueDays: number
|
||||||
): void {
|
): (IAgingPeriod & IAgingPeriodTotal)[] {
|
||||||
if (!this.periodsByContactId[customerId]) {
|
|
||||||
this.setInitialAgingPeriods(customerId);
|
|
||||||
}
|
|
||||||
const agingPeriods = this.periodsByContactId[customerId];
|
|
||||||
|
|
||||||
const newAgingPeriods = agingPeriods.map((agingPeriod) => {
|
const newAgingPeriods = agingPeriods.map((agingPeriod) => {
|
||||||
const isInAgingPeriod =
|
const isInAgingPeriod =
|
||||||
agingPeriod.beforeDays < overdueDays &&
|
agingPeriod.beforeDays <= overdueDays &&
|
||||||
agingPeriod.toDays > overdueDays;
|
(agingPeriod.toDays > overdueDays || !agingPeriod.toDays);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...agingPeriod,
|
...agingPeriod,
|
||||||
@@ -74,11 +77,11 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
: agingPeriod.total,
|
: agingPeriod.total,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this.periodsByContactId[customerId] = newAgingPeriods;
|
return newAgingPeriods;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the aging period total object.
|
* Retrieve the aging period total object. (xx)
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
* @return {IAgingPeriodTotal}
|
* @return {IAgingPeriodTotal}
|
||||||
*/
|
*/
|
||||||
@@ -95,26 +98,21 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
* @param {number} index
|
* @param {number} index
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
protected getTotalAgingPeriodByIndex(index: number): number {
|
protected getTotalAgingPeriodByIndex(
|
||||||
|
contactsAgingPeriods: any,
|
||||||
|
index: number
|
||||||
|
): number {
|
||||||
return this.contacts.reduce((acc, customer) => {
|
return this.contacts.reduce((acc, customer) => {
|
||||||
const periods = this.getContactAgingPeriods(customer.id);
|
const totalPeriod = contactsAgingPeriods[index]
|
||||||
const totalPeriod = periods[index] ? periods[index].total : 0;
|
? contactsAgingPeriods[index].total
|
||||||
|
: 0;
|
||||||
|
|
||||||
return acc + totalPeriod;
|
return acc + totalPeriod;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the initial aging periods to the all customers.
|
* Retrieve the due invoices by the given customer id. (XX)
|
||||||
*/
|
|
||||||
protected initContactsAgingPeriods(): void {
|
|
||||||
this.contacts.forEach((contact) => {
|
|
||||||
this.setInitialAgingPeriods(contact.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the due invoices by the given customer id.
|
|
||||||
* @param {number} customerId -
|
* @param {number} customerId -
|
||||||
* @return {ISaleInvoice[]}
|
* @return {ISaleInvoice[]}
|
||||||
*/
|
*/
|
||||||
@@ -128,9 +126,11 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
* Retrieve total aging periods of the report.
|
* Retrieve total aging periods of the report.
|
||||||
* @return {(IAgingPeriodTotal & IAgingPeriod)[]}
|
* @return {(IAgingPeriodTotal & IAgingPeriod)[]}
|
||||||
*/
|
*/
|
||||||
protected getTotalAgingPeriods(): (IAgingPeriodTotal & IAgingPeriod)[] {
|
protected getTotalAgingPeriods(
|
||||||
|
contactsAgingPeriods: IARAgingSummaryCustomer[]
|
||||||
|
): (IAgingPeriodTotal & IAgingPeriod)[] {
|
||||||
return this.agingPeriods.map((agingPeriod, index) => {
|
return this.agingPeriods.map((agingPeriod, index) => {
|
||||||
const total = this.getTotalAgingPeriodByIndex(index);
|
const total = sumBy(contactsAgingPeriods, `aging[${index}].total`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...agingPeriod,
|
...agingPeriod,
|
||||||
@@ -138,21 +138,4 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets customers invoices to aging periods.
|
|
||||||
*/
|
|
||||||
protected calcUnpaidInvoicesAgingPeriods(): void {
|
|
||||||
this.contacts.forEach((contact) => {
|
|
||||||
const unpaidInvoices = this.getUnpaidInvoicesByContactId(contact.id);
|
|
||||||
|
|
||||||
unpaidInvoices.forEach((unpaidInvoice) => {
|
|
||||||
this.setContactAgingDueAmount(
|
|
||||||
contact.id,
|
|
||||||
unpaidInvoice.dueAmount,
|
|
||||||
unpaidInvoice.overdueDays
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user