mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
fix: AR/AP aging summary report.
This commit is contained in:
@@ -83,7 +83,7 @@ export const ARAgingSummaryTableRowsMapper = (sheet, total) => {
|
|||||||
|
|
||||||
const mapAging = (agingPeriods) => {
|
const mapAging = (agingPeriods) => {
|
||||||
return agingPeriods.reduce((acc, aging, index) => {
|
return agingPeriods.reduce((acc, aging, index) => {
|
||||||
acc[`aging-${index}`] = aging.formatted_total;
|
acc[`aging-${index}`] = aging.total.formatted_amount;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
@@ -94,18 +94,21 @@ export const ARAgingSummaryTableRowsMapper = (sheet, total) => {
|
|||||||
rowType: 'customer',
|
rowType: 'customer',
|
||||||
name: customer.customer_name,
|
name: customer.customer_name,
|
||||||
...agingRow,
|
...agingRow,
|
||||||
current: customer.current.formatted_total,
|
current: customer.current.formatted_amount,
|
||||||
total: customer.total.formatted_total,
|
total: customer.total.formatted_amount,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (rows.length <= 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
...rows,
|
...rows,
|
||||||
{
|
{
|
||||||
name: 'TOTAL',
|
name: 'TOTAL',
|
||||||
rowType: 'total',
|
rowType: 'total',
|
||||||
current: sheet.total.current.formatted_total,
|
current: sheet.total.current.formatted_amount,
|
||||||
...mapAging(sheet.total.aging),
|
...mapAging(sheet.total.aging),
|
||||||
total: sheet.total.total.formatted_total,
|
total: sheet.total.total.formatted_amount,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
IAgingPeriod,
|
IAgingPeriod,
|
||||||
IAgingPeriodTotal
|
IAgingPeriodTotal,
|
||||||
|
IAgingAmount
|
||||||
} from './AgingReport';
|
} from './AgingReport';
|
||||||
import {
|
import {
|
||||||
INumberFormatQuery
|
INumberFormatQuery
|
||||||
@@ -17,14 +18,15 @@ export interface IAPAgingSummaryQuery {
|
|||||||
|
|
||||||
export interface IAPAgingSummaryVendor {
|
export interface IAPAgingSummaryVendor {
|
||||||
vendorName: string,
|
vendorName: string,
|
||||||
current: IAgingPeriodTotal,
|
current: IAgingAmount,
|
||||||
aging: (IAgingPeriod & IAgingPeriodTotal)[],
|
aging: IAgingPeriodTotal[],
|
||||||
total: IAgingPeriodTotal,
|
total: IAgingAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IAPAgingSummaryTotal {
|
export interface IAPAgingSummaryTotal {
|
||||||
current: IAgingPeriodTotal,
|
current: IAgingAmount,
|
||||||
aging: (IAgingPeriodTotal & IAgingPeriod)[],
|
aging: IAgingPeriodTotal[],
|
||||||
|
total: IAgingAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IAPAgingSummaryData {
|
export interface IAPAgingSummaryData {
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import {
|
import { IAgingPeriod, IAgingPeriodTotal, IAgingAmount } from './AgingReport';
|
||||||
IAgingPeriod,
|
import { INumberFormatQuery } from './FinancialStatements';
|
||||||
IAgingPeriodTotal
|
|
||||||
} from './AgingReport';
|
|
||||||
import {
|
|
||||||
INumberFormatQuery
|
|
||||||
} from './FinancialStatements';
|
|
||||||
|
|
||||||
export interface IARAgingSummaryQuery {
|
export interface IARAgingSummaryQuery {
|
||||||
asDate: Date | string;
|
asDate: Date | string;
|
||||||
@@ -17,20 +12,20 @@ export interface IARAgingSummaryQuery {
|
|||||||
|
|
||||||
export interface IARAgingSummaryCustomer {
|
export interface IARAgingSummaryCustomer {
|
||||||
customerName: string;
|
customerName: string;
|
||||||
current: IAgingPeriodTotal,
|
current: IAgingAmount;
|
||||||
aging: (IAgingPeriodTotal & IAgingPeriod)[];
|
aging: IAgingPeriodTotal[];
|
||||||
total: IAgingPeriodTotal;
|
total: IAgingAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IARAgingSummaryTotal {
|
export interface IARAgingSummaryTotal {
|
||||||
current: IAgingPeriodTotal,
|
current: IAgingAmount;
|
||||||
aging: (IAgingPeriodTotal & IAgingPeriod)[],
|
aging: IAgingPeriodTotal[];
|
||||||
total: IAgingPeriodTotal,
|
total: IAgingAmount;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IARAgingSummaryData {
|
export interface IARAgingSummaryData {
|
||||||
customers: IARAgingSummaryCustomer[],
|
customers: IARAgingSummaryCustomer[];
|
||||||
total: IARAgingSummaryTotal,
|
total: IARAgingSummaryTotal;
|
||||||
};
|
}
|
||||||
|
|
||||||
export type IARAgingSummaryColumns = IAgingPeriod[];
|
export type IARAgingSummaryColumns = IAgingPeriod[];
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
export interface IAgingPeriodTotal {
|
export interface IAgingPeriodTotal extends IAgingPeriod {
|
||||||
total: number;
|
total: IAgingAmount;
|
||||||
formattedTotal: string;
|
};
|
||||||
|
|
||||||
|
export interface IAgingAmount {
|
||||||
|
amount: number;
|
||||||
|
formattedAmount: string;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAgingPeriod {
|
export interface IAgingPeriod {
|
||||||
fromPeriod: Date|string;
|
fromPeriod: Date | string;
|
||||||
toPeriod: Date|string;
|
toPeriod: Date | string;
|
||||||
beforeDays: number;
|
beforeDays: number;
|
||||||
toDays: number;
|
toDays: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAgingSummaryContact {
|
||||||
|
current: IAgingAmount;
|
||||||
|
aging: IAgingPeriodTotal[];
|
||||||
|
total: IAgingAmount;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default class CustomerRepository extends TenantRepository {
|
|||||||
*/
|
*/
|
||||||
constructor(knex, cache) {
|
constructor(knex, cache) {
|
||||||
super(knex, cache);
|
super(knex, cache);
|
||||||
this.repositoryName = 'ContactRepository';
|
this.repositoryName = 'CustomerRepository';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default class VendorRepository extends TenantRepository {
|
|||||||
*/
|
*/
|
||||||
constructor(knex, cache) {
|
constructor(knex, cache) {
|
||||||
super(knex, cache);
|
super(knex, cache);
|
||||||
this.repositoryName = 'ContactRepository';
|
this.repositoryName = 'VendorRepository';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +17,10 @@ export default class VendorRepository extends TenantRepository {
|
|||||||
return Vendor.bindKnex(this.knex);
|
return Vendor.bindKnex(this.knex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unpaid() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
changeBalance(vendorId: number, amount: number) {
|
changeBalance(vendorId: number, amount: number) {
|
||||||
return super.changeNumber({ id: vendorId }, 'balance', amount);
|
return super.changeNumber({ id: vendorId }, 'balance', amount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import {
|
|||||||
IVendor,
|
IVendor,
|
||||||
IAPAgingSummaryData,
|
IAPAgingSummaryData,
|
||||||
IAPAgingSummaryVendor,
|
IAPAgingSummaryVendor,
|
||||||
IAPAgingSummaryColumns
|
IAPAgingSummaryColumns,
|
||||||
|
IAPAgingSummaryTotal
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import { Dictionary } from 'tsyringe/dist/typings/types';
|
import { Dictionary } from 'tsyringe/dist/typings/types';
|
||||||
|
|
||||||
export default class APAgingSummarySheet extends AgingSummaryReport {
|
export default class APAgingSummarySheet extends AgingSummaryReport {
|
||||||
readonly tenantId: number;
|
readonly tenantId: number;
|
||||||
readonly query: IAPAgingSummaryQuery;
|
readonly query: IAPAgingSummaryQuery;
|
||||||
@@ -56,6 +58,23 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the vendors aging and current total.
|
||||||
|
* @param {IAPAgingSummaryTotal} vendorsAgingPeriods
|
||||||
|
* @return {IAPAgingSummaryTotal}
|
||||||
|
*/
|
||||||
|
getVendorsTotal(vendorsAgingPeriods): IAPAgingSummaryTotal {
|
||||||
|
const totalAgingPeriods = this.getTotalAgingPeriods(vendorsAgingPeriods);
|
||||||
|
const totalCurrent = this.getTotalCurrent(vendorsAgingPeriods);
|
||||||
|
const totalVendorsTotal = this.getTotalContactsTotals(vendorsAgingPeriods);
|
||||||
|
|
||||||
|
return {
|
||||||
|
current: this.formatTotalAmount(totalCurrent),
|
||||||
|
aging: totalAgingPeriods,
|
||||||
|
total: this.formatTotalAmount(totalVendorsTotal),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the vendor section data.
|
* Retrieve the vendor section data.
|
||||||
* @param {IVendor} vendor
|
* @param {IVendor} vendor
|
||||||
@@ -85,7 +104,7 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
.map((vendor) => this.vendorData(vendor))
|
.map((vendor) => this.vendorData(vendor))
|
||||||
.filter(
|
.filter(
|
||||||
(vendor: IAPAgingSummaryVendor) =>
|
(vendor: IAPAgingSummaryVendor) =>
|
||||||
!(vendor.total.total === 0 && this.query.noneZero)
|
!(vendor.total.amount === 0 && this.query.noneZero)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,16 +114,12 @@ export default class APAgingSummarySheet extends AgingSummaryReport {
|
|||||||
*/
|
*/
|
||||||
public reportData(): IAPAgingSummaryData {
|
public reportData(): IAPAgingSummaryData {
|
||||||
const vendorsAgingPeriods = this.vendorsWalker(this.contacts);
|
const vendorsAgingPeriods = this.vendorsWalker(this.contacts);
|
||||||
const totalAgingPeriods = this.getTotalAgingPeriods(vendorsAgingPeriods);
|
const vendorsTotal = this.getVendorsTotal(vendorsAgingPeriods);
|
||||||
const totalCurrent = this.getTotalCurrent(vendorsAgingPeriods);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vendors: vendorsAgingPeriods,
|
vendors: vendorsAgingPeriods,
|
||||||
total: {
|
total: vendorsTotal,
|
||||||
current: this.formatTotalAmount(totalCurrent),
|
};
|
||||||
aging: totalAgingPeriods,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ISaleInvoice,
|
ISaleInvoice,
|
||||||
IARAgingSummaryData,
|
IARAgingSummaryData,
|
||||||
IARAgingSummaryColumns,
|
IARAgingSummaryColumns,
|
||||||
|
IARAgingSummaryTotal,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import AgingSummaryReport from './AgingSummary';
|
import AgingSummaryReport from './AgingSummary';
|
||||||
import { Dictionary } from 'tsyringe/dist/typings/types';
|
import { Dictionary } from 'tsyringe/dist/typings/types';
|
||||||
@@ -44,8 +45,14 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
|
|||||||
this.baseCurrency = baseCurrency;
|
this.baseCurrency = baseCurrency;
|
||||||
this.numberFormat = this.query.numberFormat;
|
this.numberFormat = this.query.numberFormat;
|
||||||
|
|
||||||
this.overdueInvoicesByContactId = groupBy(overdueSaleInvoices, 'customerId');
|
this.overdueInvoicesByContactId = groupBy(
|
||||||
this.currentInvoicesByContactId = groupBy(currentSaleInvoices, 'customerId');
|
overdueSaleInvoices,
|
||||||
|
'customerId'
|
||||||
|
);
|
||||||
|
this.currentInvoicesByContactId = groupBy(
|
||||||
|
currentSaleInvoices,
|
||||||
|
'customerId'
|
||||||
|
);
|
||||||
|
|
||||||
// Initializes the aging periods.
|
// Initializes the aging periods.
|
||||||
this.agingPeriods = this.agingRangePeriods(
|
this.agingPeriods = this.agingRangePeriods(
|
||||||
@@ -84,27 +91,41 @@ export default class ARAgingSummarySheet extends AgingSummaryReport {
|
|||||||
.map((customer) => this.customerData(customer))
|
.map((customer) => this.customerData(customer))
|
||||||
.filter(
|
.filter(
|
||||||
(customer: IARAgingSummaryCustomer) =>
|
(customer: IARAgingSummaryCustomer) =>
|
||||||
!(customer.total.total === 0 && this.query.noneZero)
|
!(customer.total.amount === 0 && this.query.noneZero)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the customers aging and current total.
|
||||||
|
* @param {IARAgingSummaryCustomer} customersAgingPeriods
|
||||||
|
*/
|
||||||
|
private getCustomersTotal(
|
||||||
|
customersAgingPeriods: IARAgingSummaryCustomer[]
|
||||||
|
): IARAgingSummaryTotal {
|
||||||
|
const totalAgingPeriods = this.getTotalAgingPeriods(customersAgingPeriods);
|
||||||
|
const totalCurrent = this.getTotalCurrent(customersAgingPeriods);
|
||||||
|
const totalCustomersTotal = this.getTotalContactsTotals(
|
||||||
|
customersAgingPeriods
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
current: this.formatTotalAmount(totalCurrent),
|
||||||
|
aging: totalAgingPeriods,
|
||||||
|
total: this.formatTotalAmount(totalCustomersTotal),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve A/R aging summary report data.
|
* Retrieve A/R aging summary report data.
|
||||||
* @return {IARAgingSummaryData}
|
* @return {IARAgingSummaryData}
|
||||||
*/
|
*/
|
||||||
public reportData(): IARAgingSummaryData {
|
public reportData(): IARAgingSummaryData {
|
||||||
const customersAgingPeriods = this.customersWalker(this.contacts);
|
const customersAgingPeriods = this.customersWalker(this.contacts);
|
||||||
const totalAgingPeriods = this.getTotalAgingPeriods(customersAgingPeriods);
|
const customersTotal = this.getCustomersTotal(customersAgingPeriods);
|
||||||
const totalCurrent = this.getTotalCurrent(customersAgingPeriods);
|
|
||||||
const totalCustomersTotal = this.getTotalContactsTotals(customersAgingPeriods);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customers: customersAgingPeriods,
|
customers: customersAgingPeriods,
|
||||||
total: {
|
total: customersTotal,
|
||||||
current: this.formatTotalAmount(totalCurrent),
|
|
||||||
aging: totalAgingPeriods,
|
|
||||||
total: this.formatTotalAmount(totalCustomersTotal),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
IContact,
|
IContact,
|
||||||
IARAgingSummaryQuery,
|
IARAgingSummaryQuery,
|
||||||
IFormatNumberSettings,
|
IFormatNumberSettings,
|
||||||
|
IAgingAmount,
|
||||||
|
IAgingSummaryContact
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import AgingReport from './AgingReport';
|
import AgingReport from './AgingReport';
|
||||||
import { Dictionary } from 'tsyringe/dist/typings/types';
|
import { Dictionary } from 'tsyringe/dist/typings/types';
|
||||||
@@ -25,60 +27,61 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setes initial aging periods to the given customer id.
|
* Setes initial aging periods to the contact.
|
||||||
* @param {number} customerId - Customer id.
|
|
||||||
*/
|
*/
|
||||||
protected getInitialAgingPeriodsTotal() {
|
protected getInitialAgingPeriodsTotal(): IAgingPeriodTotal[] {
|
||||||
return this.agingPeriods.map((agingPeriod) => ({
|
return this.agingPeriods.map((agingPeriod) => ({
|
||||||
...agingPeriod,
|
...agingPeriod,
|
||||||
...this.formatAmount(0),
|
total: this.formatAmount(0),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the given contact aging periods.
|
* Calculates the given contact aging periods.
|
||||||
* @param {ICustomer} customer
|
* @param {number} contactId - Contact id.
|
||||||
* @return {(IAgingPeriod & IAgingPeriodTotal)[]}
|
* @return {IAgingPeriodTotal[]}
|
||||||
*/
|
*/
|
||||||
protected getContactAgingPeriods(
|
protected getContactAgingPeriods(contactId: number): IAgingPeriodTotal[] {
|
||||||
contactId: number
|
|
||||||
): (IAgingPeriod & IAgingPeriodTotal)[] {
|
|
||||||
const unpaidInvoices = this.getUnpaidInvoicesByContactId(contactId);
|
const unpaidInvoices = this.getUnpaidInvoicesByContactId(contactId);
|
||||||
const initialAgingPeriods = this.getInitialAgingPeriodsTotal();
|
const initialAgingPeriods = this.getInitialAgingPeriodsTotal();
|
||||||
|
|
||||||
return unpaidInvoices.reduce((agingPeriods, unpaidInvoice) => {
|
return unpaidInvoices.reduce(
|
||||||
const newAgingPeriods = this.getContactAgingDueAmount(
|
(agingPeriods: IAgingPeriodTotal[], unpaidInvoice) => {
|
||||||
agingPeriods,
|
const newAgingPeriods = this.getContactAgingDueAmount(
|
||||||
unpaidInvoice.dueAmount,
|
agingPeriods,
|
||||||
unpaidInvoice.getOverdueDays(this.query.asDate)
|
unpaidInvoice.dueAmount,
|
||||||
);
|
unpaidInvoice.getOverdueDays(this.query.asDate)
|
||||||
return newAgingPeriods;
|
);
|
||||||
}, initialAgingPeriods);
|
return newAgingPeriods;
|
||||||
|
},
|
||||||
|
initialAgingPeriods
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the customer aging due amount to the table. (Xx)
|
* Sets the contact aging due amount to the table.
|
||||||
* @param {number} customerId - Customer id.
|
* @param {IAgingPeriodTotal} agingPeriods - Aging periods.
|
||||||
* @param {number} dueAmount - Due amount.
|
* @param {number} dueAmount - Due amount.
|
||||||
* @param {number} overdueDays - Overdue days.
|
* @param {number} overdueDays - Overdue days.
|
||||||
|
* @return {IAgingPeriodTotal[]}
|
||||||
*/
|
*/
|
||||||
protected getContactAgingDueAmount(
|
protected getContactAgingDueAmount(
|
||||||
agingPeriods: any,
|
agingPeriods: IAgingPeriodTotal[],
|
||||||
dueAmount: number,
|
dueAmount: number,
|
||||||
overdueDays: number
|
overdueDays: number
|
||||||
): (IAgingPeriod & IAgingPeriodTotal)[] {
|
): IAgingPeriodTotal[] {
|
||||||
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);
|
(agingPeriod.toDays > overdueDays || !agingPeriod.toDays);
|
||||||
|
|
||||||
const total = isInAgingPeriod
|
const total: number = isInAgingPeriod
|
||||||
? agingPeriod.total + dueAmount
|
? agingPeriod.total.amount + dueAmount
|
||||||
: agingPeriod.total;
|
: agingPeriod.total.amount;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...agingPeriod,
|
...agingPeriod,
|
||||||
total,
|
total: this.formatAmount(total),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return newAgingPeriods;
|
return newAgingPeriods;
|
||||||
@@ -87,27 +90,34 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
/**
|
/**
|
||||||
* Retrieve the aging period total object.
|
* Retrieve the aging period total object.
|
||||||
* @param {number} amount
|
* @param {number} amount
|
||||||
* @return {IAgingPeriodTotal}
|
* @param {IFormatNumberSettings} settings - Override the format number settings.
|
||||||
|
* @return {IAgingAmount}
|
||||||
*/
|
*/
|
||||||
protected formatAmount(
|
protected formatAmount(
|
||||||
amount: number,
|
amount: number,
|
||||||
settings: IFormatNumberSettings = {}
|
settings: IFormatNumberSettings = {}
|
||||||
): IAgingPeriodTotal {
|
): IAgingAmount {
|
||||||
return {
|
return {
|
||||||
total: amount,
|
amount,
|
||||||
formattedTotal: this.formatNumber(amount, settings),
|
formattedAmount: this.formatNumber(amount, settings),
|
||||||
currencyCode: this.baseCurrency,
|
currencyCode: this.baseCurrency,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the aging period total object.
|
||||||
|
* @param {number} amount
|
||||||
|
* @param {IFormatNumberSettings} settings - Override the format number settings.
|
||||||
|
* @return {IAgingPeriodTotal}
|
||||||
|
*/
|
||||||
protected formatTotalAmount(
|
protected formatTotalAmount(
|
||||||
amount: number,
|
amount: number,
|
||||||
settings: IFormatNumberSettings = {}
|
settings: IFormatNumberSettings = {}
|
||||||
): IAgingPeriodTotal {
|
): IAgingAmount {
|
||||||
return this.formatAmount(amount, {
|
return this.formatAmount(amount, {
|
||||||
money: true,
|
money: true,
|
||||||
excerptZero: false,
|
excerptZero: false,
|
||||||
...settings
|
...settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +128,9 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
*/
|
*/
|
||||||
protected getTotalAgingPeriodByIndex(
|
protected getTotalAgingPeriodByIndex(
|
||||||
contactsAgingPeriods: any,
|
contactsAgingPeriods: any,
|
||||||
index: number
|
index: number,
|
||||||
): number {
|
): number {
|
||||||
return this.contacts.reduce((acc, customer) => {
|
return this.contacts.reduce((acc, contact) => {
|
||||||
const totalPeriod = contactsAgingPeriods[index]
|
const totalPeriod = contactsAgingPeriods[index]
|
||||||
? contactsAgingPeriods[index].total
|
? contactsAgingPeriods[index].total
|
||||||
: 0;
|
: 0;
|
||||||
@@ -130,9 +140,9 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the due invoices by the given customer id.
|
* Retrieve the due invoices by the given contact id.
|
||||||
* @param {number} customerId -
|
* @param {number} contactId -
|
||||||
* @return {ISaleInvoice[]}
|
* @return {(ISaleInvoice | IBill)[]}
|
||||||
*/
|
*/
|
||||||
protected getUnpaidInvoicesByContactId(
|
protected getUnpaidInvoicesByContactId(
|
||||||
contactId: number
|
contactId: number
|
||||||
@@ -146,13 +156,23 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
*/
|
*/
|
||||||
protected getTotalAgingPeriods(
|
protected getTotalAgingPeriods(
|
||||||
contactsAgingPeriods: IARAgingSummaryCustomer[]
|
contactsAgingPeriods: IARAgingSummaryCustomer[]
|
||||||
): (IAgingPeriodTotal & IAgingPeriod)[] {
|
): IAgingPeriodTotal[] {
|
||||||
return this.agingPeriods.map((agingPeriod, index) => {
|
return this.agingPeriods.map((agingPeriod, index) => {
|
||||||
const total = sumBy(contactsAgingPeriods, `aging[${index}].total`);
|
const total = sumBy(
|
||||||
|
contactsAgingPeriods,
|
||||||
|
(summary: IARAgingSummaryCustomer) => {
|
||||||
|
const aging = summary.aging[index];
|
||||||
|
|
||||||
|
if (!aging) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return aging.total.amount;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...agingPeriod,
|
...agingPeriod,
|
||||||
...this.formatTotalAmount(total),
|
total: this.formatTotalAmount(total),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -179,14 +199,14 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve to total sumation of the given customers sections.
|
* Retrieve to total sumation of the given contacts summeries sections.
|
||||||
* @param {IARAgingSummaryCustomer[]} contactsSections -
|
* @param {IARAgingSummaryCustomer[]} contactsSections -
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
protected getTotalCurrent(
|
protected getTotalCurrent(
|
||||||
customersSummary: IARAgingSummaryCustomer[]
|
contactsSummaries: IAgingSummaryContact[]
|
||||||
): number {
|
): number {
|
||||||
return sumBy(customersSummary, (summary) => summary.current.total);
|
return sumBy(contactsSummaries, (summary) => summary.current.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -195,13 +215,16 @@ export default abstract class AgingSummaryReport extends AgingReport {
|
|||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
protected getAgingPeriodsTotal(agingPeriods: IAgingPeriodTotal[]): number {
|
protected getAgingPeriodsTotal(agingPeriods: IAgingPeriodTotal[]): number {
|
||||||
return sumBy(agingPeriods, 'total');
|
return sumBy(agingPeriods, (period) => period.total.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve total of contacts totals.
|
||||||
|
* @param {IAgingSummaryContact[]} contactsSummaries
|
||||||
|
*/
|
||||||
protected getTotalContactsTotals(
|
protected getTotalContactsTotals(
|
||||||
customersSummary: IARAgingSummaryCustomer[]
|
contactsSummaries: IAgingSummaryContact[]
|
||||||
): number {
|
): number {
|
||||||
return sumBy(customersSummary, (summary) => summary.total.total);
|
return sumBy(contactsSummaries, (summary) => summary.total.amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,56 @@
|
|||||||
import {
|
import { IFormatNumberSettings, INumberFormatQuery } from 'interfaces';
|
||||||
formatNumber
|
import { formatNumber } from 'utils';
|
||||||
} from 'utils';
|
|
||||||
|
|
||||||
export default class FinancialSheet {
|
export default class FinancialSheet {
|
||||||
numberFormat: { noCents: boolean, divideOn1000: boolean };
|
numberFormat: INumberFormatQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes the number format query to settings
|
||||||
|
*/
|
||||||
|
protected transfromFormatQueryToSettings(): IFormatNumberSettings {
|
||||||
|
const { numberFormat } = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
precision: numberFormat.precision,
|
||||||
|
divideOn1000: numberFormat.divideOn1000,
|
||||||
|
excerptZero: !numberFormat.showZero,
|
||||||
|
negativeFormat: numberFormat.negativeFormat,
|
||||||
|
money: numberFormat.formatMoney === 'always',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formating amount based on the given report query.
|
* Formating amount based on the given report query.
|
||||||
* @param {number} number
|
* @param {number} number -
|
||||||
|
* @param {IFormatNumberSettings} overrideSettings -
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
protected formatNumber(number): string {
|
protected formatNumber(
|
||||||
return formatNumber(number, this.numberFormat);
|
number,
|
||||||
|
overrideSettings: IFormatNumberSettings = {}
|
||||||
|
): string {
|
||||||
|
const settings = {
|
||||||
|
...this.transfromFormatQueryToSettings(),
|
||||||
|
...overrideSettings,
|
||||||
|
};
|
||||||
|
return formatNumber(number, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatting full amount with different format settings.
|
||||||
|
* @param {number} amount -
|
||||||
|
* @param {IFormatNumberSettings} settings -
|
||||||
|
*/
|
||||||
|
protected formatTotalNumber(
|
||||||
|
amount: number,
|
||||||
|
settings: IFormatNumberSettings = {}
|
||||||
|
): string {
|
||||||
|
const { numberFormat } = this;
|
||||||
|
|
||||||
|
return this.formatNumber(amount, {
|
||||||
|
money: numberFormat.formatMoney === 'none' ? false : true,
|
||||||
|
excerptZero: false,
|
||||||
|
...settings
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
IAccount,
|
IAccount,
|
||||||
IJournalPoster,
|
IJournalPoster,
|
||||||
IAccountType,
|
IAccountType,
|
||||||
IJournalEntry
|
IJournalEntry,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import FinancialSheet from "../FinancialSheet";
|
import FinancialSheet from '../FinancialSheet';
|
||||||
|
|
||||||
export default class GeneralLedgerSheet extends FinancialSheet {
|
export default class GeneralLedgerSheet extends FinancialSheet {
|
||||||
tenantId: number;
|
tenantId: number;
|
||||||
@@ -35,7 +35,7 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
transactions: IJournalPoster,
|
transactions: IJournalPoster,
|
||||||
openingBalancesJournal: IJournalPoster,
|
openingBalancesJournal: IJournalPoster,
|
||||||
closingBalancesJournal: IJournalPoster,
|
closingBalancesJournal: IJournalPoster,
|
||||||
baseCurrency: string,
|
baseCurrency: string
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -59,24 +59,33 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
): IGeneralLedgerSheetAccountTransaction[] {
|
): IGeneralLedgerSheetAccountTransaction[] {
|
||||||
const entries = this.transactions.getAccountEntries(account.id);
|
const entries = this.transactions.getAccountEntries(account.id);
|
||||||
|
|
||||||
return entries.map((transaction: IJournalEntry): IGeneralLedgerSheetAccountTransaction => {
|
return entries.map(
|
||||||
let amount = 0;
|
(transaction: IJournalEntry): IGeneralLedgerSheetAccountTransaction => {
|
||||||
|
let amount = 0;
|
||||||
|
|
||||||
if (account.type.normal === 'credit') {
|
if (account.type.normal === 'credit') {
|
||||||
amount += transaction.credit - transaction.debit;
|
amount += transaction.credit - transaction.debit;
|
||||||
} else if (account.type.normal === 'debit') {
|
} else if (account.type.normal === 'debit') {
|
||||||
amount += transaction.debit - transaction.credit;
|
amount += transaction.debit - transaction.credit;
|
||||||
|
}
|
||||||
|
const formattedAmount = this.formatNumber(amount);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pick(transaction, [
|
||||||
|
'id',
|
||||||
|
'note',
|
||||||
|
'transactionType',
|
||||||
|
'referenceType',
|
||||||
|
'referenceId',
|
||||||
|
'referenceTypeFormatted',
|
||||||
|
'date',
|
||||||
|
]),
|
||||||
|
amount,
|
||||||
|
formattedAmount,
|
||||||
|
currencyCode: this.baseCurrency,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const formattedAmount = this.formatNumber(amount);
|
);
|
||||||
|
|
||||||
return {
|
|
||||||
...pick(transaction, ['id', 'note', 'transactionType', 'referenceType',
|
|
||||||
'referenceId', 'date']),
|
|
||||||
amount,
|
|
||||||
formattedAmount,
|
|
||||||
currencyCode: this.baseCurrency,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,9 +93,11 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccountBalance}
|
* @return {IGeneralLedgerSheetAccountBalance}
|
||||||
*/
|
*/
|
||||||
private accountOpeningBalance(account: IAccount): IGeneralLedgerSheetAccountBalance {
|
private accountOpeningBalance(
|
||||||
|
account: IAccount
|
||||||
|
): IGeneralLedgerSheetAccountBalance {
|
||||||
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
|
const amount = this.openingBalancesJournal.getAccountBalance(account.id);
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
const date = this.query.fromDate;
|
const date = this.query.fromDate;
|
||||||
|
|
||||||
@@ -98,9 +109,11 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
* @param {IAccount} account
|
* @param {IAccount} account
|
||||||
* @return {IGeneralLedgerSheetAccountBalance}
|
* @return {IGeneralLedgerSheetAccountBalance}
|
||||||
*/
|
*/
|
||||||
private accountClosingBalance(account: IAccount): IGeneralLedgerSheetAccountBalance {
|
private accountClosingBalance(
|
||||||
|
account: IAccount
|
||||||
|
): IGeneralLedgerSheetAccountBalance {
|
||||||
const amount = this.closingBalancesJournal.getAccountBalance(account.id);
|
const amount = this.closingBalancesJournal.getAccountBalance(account.id);
|
||||||
const formattedAmount = this.formatNumber(amount);
|
const formattedAmount = this.formatTotalNumber(amount);
|
||||||
const currencyCode = this.baseCurrency;
|
const currencyCode = this.baseCurrency;
|
||||||
const date = this.query.toDate;
|
const date = this.query.toDate;
|
||||||
|
|
||||||
@@ -113,14 +126,14 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
* @return {IGeneralLedgerSheetAccount}
|
* @return {IGeneralLedgerSheetAccount}
|
||||||
*/
|
*/
|
||||||
private accountMapper(
|
private accountMapper(
|
||||||
account: IAccount & { type: IAccountType },
|
account: IAccount & { type: IAccountType }
|
||||||
): IGeneralLedgerSheetAccount {
|
): IGeneralLedgerSheetAccount {
|
||||||
return {
|
return {
|
||||||
...pick(account, ['id', 'name', 'code', 'index', 'parentAccountId']),
|
...pick(account, ['id', 'name', 'code', 'index', 'parentAccountId']),
|
||||||
opening: this.accountOpeningBalance(account),
|
opening: this.accountOpeningBalance(account),
|
||||||
transactions: this.accountTransactionsMapper(account),
|
transactions: this.accountTransactionsMapper(account),
|
||||||
closing: this.accountClosingBalance(account),
|
closing: this.accountClosingBalance(account),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,13 +144,20 @@ export default class GeneralLedgerSheet extends FinancialSheet {
|
|||||||
private accountsWalker(
|
private accountsWalker(
|
||||||
accounts: IAccount & { type: IAccountType }[]
|
accounts: IAccount & { type: IAccountType }[]
|
||||||
): IGeneralLedgerSheetAccount[] {
|
): IGeneralLedgerSheetAccount[] {
|
||||||
return accounts
|
return (
|
||||||
.map((account: IAccount & { type: IAccountType }) => this.accountMapper(account))
|
accounts
|
||||||
|
.map((account: IAccount & { type: IAccountType }) =>
|
||||||
// Filter general ledger accounts that have no transactions when `noneTransactions` is on.
|
this.accountMapper(account)
|
||||||
.filter((generalLedgerAccount: IGeneralLedgerSheetAccount) => (
|
)
|
||||||
!(generalLedgerAccount.transactions.length === 0 && this.query.noneTransactions)
|
// Filter general ledger accounts that have no transactions when `noneTransactions` is on.
|
||||||
));
|
.filter(
|
||||||
|
(generalLedgerAccount: IGeneralLedgerSheetAccount) =>
|
||||||
|
!(
|
||||||
|
generalLedgerAccount.transactions.length === 0 &&
|
||||||
|
this.query.noneTransactions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -85,8 +85,7 @@ export default class GeneralLedgerService {
|
|||||||
group: 'organization',
|
group: 'organization',
|
||||||
key: 'base_currency',
|
key: 'base_currency',
|
||||||
});
|
});
|
||||||
|
// Retrieve all accounts with associated type from the storage.
|
||||||
// Retrieve all accounts from the storage.
|
|
||||||
const accounts = await accountRepository.all('type');
|
const accounts = await accountRepository.all('type');
|
||||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
@@ -111,11 +110,13 @@ export default class GeneralLedgerService {
|
|||||||
tenantId,
|
tenantId,
|
||||||
accountsGraph
|
accountsGraph
|
||||||
);
|
);
|
||||||
|
// Accounts opening transactions.
|
||||||
const openingTransJournal = Journal.fromTransactions(
|
const openingTransJournal = Journal.fromTransactions(
|
||||||
openingBalanceTrans,
|
openingBalanceTrans,
|
||||||
tenantId,
|
tenantId,
|
||||||
accountsGraph
|
accountsGraph
|
||||||
);
|
);
|
||||||
|
// Accounts closing transactions.
|
||||||
const closingTransJournal = Journal.fromTransactions(
|
const closingTransJournal = Journal.fromTransactions(
|
||||||
closingBalanceTrans,
|
closingBalanceTrans,
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export default class JournalSheetService {
|
|||||||
group: 'organization',
|
group: 'organization',
|
||||||
key: 'base_currency',
|
key: 'base_currency',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Retrieve all accounts on the storage.
|
// Retrieve all accounts on the storage.
|
||||||
const accountsGraph = await accountRepository.getDependencyGraph();
|
const accountsGraph = await accountRepository.getDependencyGraph();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user