mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
draft: AR and AP aging summary report.
This commit is contained in:
@@ -1,75 +1,158 @@
|
||||
import moment from 'moment';
|
||||
import { omit, reverse } from 'lodash';
|
||||
import { IAgingPeriod, IAgingPeriodClosingBalance, IAgingPeriodTotal } from 'interfaces';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
import { defaultTo } from 'lodash';
|
||||
import {
|
||||
IAgingPeriod,
|
||||
ISaleInvoice,
|
||||
IBill,
|
||||
IAgingPeriodTotal,
|
||||
IContact,
|
||||
} from 'interfaces';
|
||||
import AgingReport from './AgingReport';
|
||||
import { Dictionary } from 'tsyringe/dist/typings/types';
|
||||
|
||||
export default class AgingSummaryReport extends FinancialSheet{
|
||||
export default abstract class AgingSummaryReport extends AgingReport {
|
||||
protected readonly contacts: IContact[];
|
||||
protected readonly agingPeriods: IAgingPeriod[] = [];
|
||||
protected readonly baseCurrency: string;
|
||||
protected readonly unpaidInvoices: (ISaleInvoice | IBill)[];
|
||||
readonly unpaidInvoicesByContactId: Dictionary<
|
||||
(ISaleInvoice | IBill)[]
|
||||
>;
|
||||
protected periodsByContactId: {
|
||||
[key: number]: (IAgingPeriod & IAgingPeriodTotal)[];
|
||||
} = {};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array} agingPeriods
|
||||
* @param {Numeric} customerBalance
|
||||
* Setes initial aging periods to the given customer id.
|
||||
* @param {number} customerId - Customer id.
|
||||
*/
|
||||
contactAgingBalance(
|
||||
agingPeriods: IAgingPeriodClosingBalance[],
|
||||
receivableTotalCredit: number,
|
||||
): IAgingPeriodTotal[] {
|
||||
let prevAging = 0;
|
||||
let receivableCredit = receivableTotalCredit;
|
||||
let diff = receivableCredit;
|
||||
|
||||
const periods = reverse(agingPeriods).map((agingPeriod) => {
|
||||
const agingAmount = (agingPeriod.closingBalance - prevAging);
|
||||
const subtract = Math.min(diff, agingAmount);
|
||||
diff -= Math.min(agingAmount, diff);
|
||||
|
||||
const total = Math.max(agingAmount - subtract, 0);
|
||||
|
||||
const output = {
|
||||
...omit(agingPeriod, ['closingBalance']),
|
||||
total,
|
||||
};
|
||||
prevAging = agingPeriod.closingBalance;
|
||||
return output;
|
||||
});
|
||||
return reverse(periods);
|
||||
protected setInitialAgingPeriods(contactId: number): void {
|
||||
this.periodsByContactId[contactId] = this.agingPeriods.map(
|
||||
(agingPeriod) => ({
|
||||
...agingPeriod,
|
||||
...this.formatTotalAmount(0),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} asDay
|
||||
* @param {*} agingDaysBefore
|
||||
* @param {*} agingPeriodsFreq
|
||||
* Calculates the given contact aging periods.
|
||||
* @param {ICustomer} customer
|
||||
* @return {(IAgingPeriod & IAgingPeriodTotal)[]}
|
||||
*/
|
||||
agingRangePeriods(asDay, agingDaysBefore, agingPeriodsFreq): IAgingPeriod[] {
|
||||
const totalAgingDays = agingDaysBefore * agingPeriodsFreq;
|
||||
const startAging = moment(asDay).startOf('day');
|
||||
const endAging = startAging.clone().subtract('days', totalAgingDays).endOf('day');
|
||||
protected getContactAgingPeriods(
|
||||
contactId: number
|
||||
): (IAgingPeriod & IAgingPeriodTotal)[] {
|
||||
return defaultTo(this.periodsByContactId[contactId], []);
|
||||
}
|
||||
|
||||
const agingPeriods: IAgingPeriod[] = [];
|
||||
const startingAging = startAging.clone();
|
||||
|
||||
let beforeDays = 1;
|
||||
let toDays = 0;
|
||||
|
||||
while (startingAging > endAging) {
|
||||
const currentAging = startingAging.clone();
|
||||
startingAging.subtract('days', agingDaysBefore).endOf('day');
|
||||
toDays += agingDaysBefore;
|
||||
|
||||
agingPeriods.push({
|
||||
fromPeriod: moment(currentAging).toDate(),
|
||||
toPeriod: moment(startingAging).toDate(),
|
||||
beforeDays: beforeDays === 1 ? 0 : beforeDays,
|
||||
toDays: toDays,
|
||||
...(startingAging.valueOf() === endAging.valueOf()) ? {
|
||||
toPeriod: null,
|
||||
toDays: null,
|
||||
} : {},
|
||||
});
|
||||
beforeDays += agingDaysBefore;
|
||||
/**
|
||||
* Sets the customer aging due amount to the table.
|
||||
* @param {number} customerId - Customer id.
|
||||
* @param {number} dueAmount - Due amount.
|
||||
* @param {number} overdueDays - Overdue days.
|
||||
*/
|
||||
protected setContactAgingDueAmount(
|
||||
customerId: number,
|
||||
dueAmount: number,
|
||||
overdueDays: number
|
||||
): void {
|
||||
if (!this.periodsByContactId[customerId]) {
|
||||
this.setInitialAgingPeriods(customerId);
|
||||
}
|
||||
return agingPeriods;
|
||||
const agingPeriods = this.periodsByContactId[customerId];
|
||||
|
||||
const newAgingPeriods = agingPeriods.map((agingPeriod) => {
|
||||
const isInAgingPeriod =
|
||||
agingPeriod.beforeDays < overdueDays &&
|
||||
agingPeriod.toDays > overdueDays;
|
||||
|
||||
return {
|
||||
...agingPeriod,
|
||||
total: isInAgingPeriod
|
||||
? agingPeriod.total + dueAmount
|
||||
: agingPeriod.total,
|
||||
};
|
||||
});
|
||||
this.periodsByContactId[customerId] = newAgingPeriods;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Retrieve the aging period total object.
|
||||
* @param {number} amount
|
||||
* @return {IAgingPeriodTotal}
|
||||
*/
|
||||
protected formatTotalAmount(amount: number): IAgingPeriodTotal {
|
||||
return {
|
||||
total: amount,
|
||||
formattedTotal: this.formatNumber(amount),
|
||||
currencyCode: this.baseCurrency,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total of the aging period by the given index.
|
||||
* @param {number} index
|
||||
* @return {number}
|
||||
*/
|
||||
protected getTotalAgingPeriodByIndex(index: number): number {
|
||||
return this.contacts.reduce((acc, customer) => {
|
||||
const periods = this.getContactAgingPeriods(customer.id);
|
||||
const totalPeriod = periods[index] ? periods[index].total : 0;
|
||||
|
||||
return acc + totalPeriod;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial aging periods to the all customers.
|
||||
*/
|
||||
protected initContactsAgingPeriods(): void {
|
||||
this.contacts.forEach((contact) => {
|
||||
this.setInitialAgingPeriods(contact.id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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], []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve total aging periods of the report.
|
||||
* @return {(IAgingPeriodTotal & IAgingPeriod)[]}
|
||||
*/
|
||||
protected getTotalAgingPeriods(): (IAgingPeriodTotal & IAgingPeriod)[] {
|
||||
return this.agingPeriods.map((agingPeriod, index) => {
|
||||
const total = this.getTotalAgingPeriodByIndex(index);
|
||||
|
||||
return {
|
||||
...agingPeriod,
|
||||
...this.formatTotalAmount(total),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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