mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat: AR/AP aging summary report.
This commit is contained in:
@@ -41,7 +41,7 @@ export default class ARAgingSummaryReportController extends BaseController {
|
||||
],
|
||||
[query('customer_ids').optional().isNumeric().toInt()]
|
||||
),
|
||||
query('none_zero').optional().isBoolean().toBoolean(),
|
||||
query('none_zero').default(true).isBoolean().toBoolean(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -321,7 +321,6 @@ export default class SaleInvoicesController extends BaseController {
|
||||
tenantId,
|
||||
customerId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
sales_invoices: this.transfromToResponse(salesInvoices),
|
||||
});
|
||||
|
||||
@@ -17,13 +17,19 @@ export interface IAPAgingSummaryQuery {
|
||||
|
||||
export interface IAPAgingSummaryVendor {
|
||||
vendorName: string,
|
||||
current: IAgingPeriodTotal,
|
||||
aging: (IAgingPeriod & IAgingPeriodTotal)[],
|
||||
total: IAgingPeriodTotal,
|
||||
}
|
||||
};
|
||||
|
||||
export interface IAPAgingSummaryTotal {
|
||||
current: IAgingPeriodTotal,
|
||||
aging: (IAgingPeriodTotal & IAgingPeriod)[],
|
||||
};
|
||||
|
||||
export interface IAPAgingSummaryData {
|
||||
vendors: IAPAgingSummaryVendor[],
|
||||
total: (IAgingPeriod & IAgingPeriodTotal)[],
|
||||
}
|
||||
total: IAPAgingSummaryTotal,
|
||||
};
|
||||
|
||||
export type IAPAgingSummaryColumns = IAgingPeriod[];
|
||||
@@ -8,8 +8,8 @@ export interface IARAgingSummaryQuery {
|
||||
agingDaysBefore: number;
|
||||
agingPeriods: number;
|
||||
numberFormat: {
|
||||
noCents: number;
|
||||
divideOn1000: number;
|
||||
noCents: boolean;
|
||||
divideOn1000: boolean;
|
||||
};
|
||||
customersIds: number[];
|
||||
noneZero: boolean;
|
||||
@@ -17,13 +17,18 @@ export interface IARAgingSummaryQuery {
|
||||
|
||||
export interface IARAgingSummaryCustomer {
|
||||
customerName: string;
|
||||
current: IAgingPeriodTotal,
|
||||
aging: (IAgingPeriodTotal & IAgingPeriod)[];
|
||||
total: IAgingPeriodTotal;
|
||||
}
|
||||
|
||||
export interface IARAgingSummaryTotal {
|
||||
current: IAgingPeriodTotal,
|
||||
aging: (IAgingPeriodTotal & IAgingPeriod)[],
|
||||
};
|
||||
export interface IARAgingSummaryData {
|
||||
customers: IARAgingSummaryCustomer[],
|
||||
total: (IAgingPeriodTotal & IAgingPeriod)[]
|
||||
total: IARAgingSummaryTotal,
|
||||
}
|
||||
|
||||
export type IARAgingSummaryColumns = IAgingPeriod[];
|
||||
@@ -42,6 +42,9 @@ export interface IBill {
|
||||
amount: number,
|
||||
paymentAmount: number,
|
||||
|
||||
dueAmount: number,
|
||||
overdueDays: number,
|
||||
|
||||
invLotNumber: string,
|
||||
openedAt: Date | string,
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface ISaleInvoice {
|
||||
invoiceDate: Date,
|
||||
dueDate: Date,
|
||||
dueAmount: number,
|
||||
overdueDays: number,
|
||||
customerId: number,
|
||||
entries: IItemEntry[],
|
||||
deliveredAt: string | Date,
|
||||
|
||||
@@ -35,5 +35,7 @@ export * from './TrialBalanceSheet';
|
||||
export * from './GeneralLedgerSheet'
|
||||
export * from './ProfitLossSheet';
|
||||
export * from './JournalReport';
|
||||
export * from './AgingReport';
|
||||
export * from './ARAgingSummaryReport';
|
||||
export * from './APAgingSummaryReport';
|
||||
export * from './Mailable';
|
||||
@@ -48,6 +48,12 @@ export default class Bill extends TenantModel {
|
||||
overdue(query) {
|
||||
query.where('due_date', '<', moment().format('YYYY-MM-DD'));
|
||||
},
|
||||
/**
|
||||
* Filters the not overdue invoices.
|
||||
*/
|
||||
notOverdue(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.where('due_date', '>=', asDate);
|
||||
},
|
||||
/**
|
||||
* Filters the partially paid bills.
|
||||
*/
|
||||
@@ -61,7 +67,13 @@ export default class Bill extends TenantModel {
|
||||
paid(query) {
|
||||
query.where(raw('`PAYMENT_AMOUNT` = `AMOUNT`'));
|
||||
},
|
||||
}
|
||||
/**
|
||||
* Filters the bills from the given date.
|
||||
*/
|
||||
fromDate(query, fromDate) {
|
||||
query.where('bill_date', '<=', fromDate)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +83,7 @@ export default class Bill extends TenantModel {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
@@ -117,7 +129,7 @@ export default class Bill extends TenantModel {
|
||||
*/
|
||||
get isFullyPaid() {
|
||||
return this.dueAmount === 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the bill paid fully or partially.
|
||||
@@ -133,7 +145,9 @@ export default class Bill extends TenantModel {
|
||||
*/
|
||||
get remainingDays() {
|
||||
// Can't continue in case due date not defined.
|
||||
if (!this.dueDate) { return null; }
|
||||
if (!this.dueDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = moment();
|
||||
const dueDate = moment(this.dueDate);
|
||||
@@ -146,13 +160,7 @@ export default class Bill extends TenantModel {
|
||||
* @return {number|null}
|
||||
*/
|
||||
get overdueDays() {
|
||||
// Can't continue in case due date not defined.
|
||||
if (!this.dueDate) { return null; }
|
||||
|
||||
const date = moment();
|
||||
const dueDate = moment(this.dueDate);
|
||||
|
||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||
return this.getOverdueDays();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,6 +171,17 @@ export default class Bill extends TenantModel {
|
||||
return this.overdueDays > 0;
|
||||
}
|
||||
|
||||
getOverdueDays(asDate = moment().format('YYYY-MM-DD')) {
|
||||
// Can't continue in case due date not defined.
|
||||
if (!this.dueDate) {
|
||||
return null;
|
||||
}
|
||||
const date = moment(asDate);
|
||||
const dueDate = moment(this.dueDate);
|
||||
|
||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
@@ -180,7 +199,7 @@ export default class Bill extends TenantModel {
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'vendor');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
entries: {
|
||||
@@ -199,26 +218,22 @@ export default class Bill extends TenantModel {
|
||||
|
||||
/**
|
||||
* Retrieve the not found bills ids as array that associated to the given vendor.
|
||||
* @param {Array} billsIds
|
||||
* @param {number} vendorId -
|
||||
* @param {Array} billsIds
|
||||
* @param {number} vendorId -
|
||||
* @return {Array}
|
||||
*/
|
||||
static async getNotFoundBills(billsIds, vendorId) {
|
||||
const storedBills = await this.query()
|
||||
.onBuild((builder) => {
|
||||
builder.whereIn('id', billsIds);
|
||||
const storedBills = await this.query().onBuild((builder) => {
|
||||
builder.whereIn('id', billsIds);
|
||||
|
||||
if (vendorId) {
|
||||
builder.where('vendor_id', vendorId);
|
||||
}
|
||||
});
|
||||
|
||||
if (vendorId) {
|
||||
builder.where('vendor_id', vendorId);
|
||||
}
|
||||
});
|
||||
|
||||
const storedBillsIds = storedBills.map((t) => t.id);
|
||||
|
||||
const notFoundBillsIds = difference(
|
||||
billsIds,
|
||||
storedBillsIds,
|
||||
);
|
||||
const notFoundBillsIds = difference(billsIds, storedBillsIds);
|
||||
return notFoundBillsIds;
|
||||
}
|
||||
|
||||
@@ -263,19 +278,25 @@ export default class Bill extends TenantModel {
|
||||
label: 'Status',
|
||||
options: [],
|
||||
query: (query, role) => {
|
||||
switch(role.value) {
|
||||
switch (role.value) {
|
||||
case 'draft':
|
||||
query.modify('draft'); break;
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'opened':
|
||||
query.modify('opened'); break;
|
||||
query.modify('opened');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid'); break;
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue'); break;
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid'); break;
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid'); break;
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -295,14 +316,12 @@ export default class Bill extends TenantModel {
|
||||
label: 'Note',
|
||||
column: 'note',
|
||||
},
|
||||
user: {
|
||||
|
||||
},
|
||||
user: {},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,19 +103,27 @@ export default class SaleInvoice extends TenantModel {
|
||||
* @return {number|null}
|
||||
*/
|
||||
get overdueDays() {
|
||||
// Can't continue in case due date not defined.
|
||||
if (!this.dueDate) { return null; }
|
||||
|
||||
const date = moment();
|
||||
const dueDate = moment(this.dueDate);
|
||||
|
||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||
return this.getOverdueDays();
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} asDate
|
||||
*/
|
||||
getOverdueDays(asDate = moment().format('YYYY-MM-DD')) {
|
||||
// Can't continue in case due date not defined.
|
||||
if (!this.dueDate) { return null; }
|
||||
|
||||
const date = moment(asDate);
|
||||
const dueDate = moment(this.dueDate);
|
||||
|
||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
@@ -163,8 +171,14 @@ export default class SaleInvoice extends TenantModel {
|
||||
/**
|
||||
* Filters the overdue invoices.
|
||||
*/
|
||||
overdue(query) {
|
||||
query.where('due_date', '<', moment().format('YYYY-MM-DD'));
|
||||
overdue(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.where('due_date', '<', asDate);
|
||||
},
|
||||
/**
|
||||
* Filters the not overdue invoices.
|
||||
*/
|
||||
notOverdue(query, asDate = moment().format('YYYY-MM-DD')) {
|
||||
query.where('due_date', '>=', asDate);
|
||||
},
|
||||
/**
|
||||
* Filters the partially invoices.
|
||||
@@ -178,6 +192,12 @@ export default class SaleInvoice extends TenantModel {
|
||||
*/
|
||||
paid(query) {
|
||||
query.where(raw('PAYMENT_AMOUNT = BALANCE'));
|
||||
},
|
||||
/**
|
||||
* Filters the sale invoices from the given date.
|
||||
*/
|
||||
fromDate(query, fromDate) {
|
||||
query.where('invoice_date', '<=', fromDate)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import moment from 'moment';
|
||||
import { Bill } from 'models';
|
||||
import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
@@ -8,4 +9,30 @@ export default class BillRepository extends TenantRepository {
|
||||
get model() {
|
||||
return Bill.bindKnex(this.knex);
|
||||
}
|
||||
|
||||
dueBills(asDate = moment().format('YYYY-MM-DD'), withRelations) {
|
||||
const cacheKey = this.getCacheKey('dueInvoices', asDate, withRelations);
|
||||
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return this.model
|
||||
.query()
|
||||
.modify('dueBills')
|
||||
.modify('notOverdue')
|
||||
.modify('fromDate', asDate)
|
||||
.withGraphFetched(withRelations);
|
||||
});
|
||||
}
|
||||
|
||||
overdueBills(asDate = moment().format('YYYY-MM-DD'), withRelations) {
|
||||
const cacheKey = this.getCacheKey('overdueInvoices', asDate, withRelations);
|
||||
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return this.model
|
||||
.query()
|
||||
.modify('dueBills')
|
||||
.modify('overdue', asDate)
|
||||
.modify('fromDate', asDate)
|
||||
.withGraphFetched(withRelations);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import moment from 'moment';
|
||||
import { SaleInvoice } from 'models';
|
||||
import TenantRepository from 'repositories/TenantRepository';
|
||||
|
||||
@@ -8,4 +9,30 @@ export default class SaleInvoiceRepository extends TenantRepository {
|
||||
get model() {
|
||||
return SaleInvoice.bindKnex(this.knex);
|
||||
}
|
||||
}
|
||||
|
||||
dueInvoices(asDate = moment().format('YYYY-MM-DD'), withRelations) {
|
||||
const cacheKey = this.getCacheKey('dueInvoices', asDate, withRelations);
|
||||
|
||||
return this.cache.get(cacheKey, async () => {
|
||||
return this.model
|
||||
.query()
|
||||
.modify('dueInvoices')
|
||||
.modify('notOverdue', asDate)
|
||||
.modify('fromDate', asDate)
|
||||
.withGraphFetched(withRelations);
|
||||
});
|
||||
}
|
||||
|
||||
overdueInvoices(asDate = moment().format('YYYY-MM-DD'), withRelations) {
|
||||
const cacheKey = this.getCacheKey('overdueInvoices', asDate, withRelations);
|
||||
|
||||
return this.cache.get(cacheKey, () => {
|
||||
return this.model
|
||||
.query()
|
||||
.modify('dueInvoices')
|
||||
.modify('overdue', asDate)
|
||||
.modify('fromDate', asDate)
|
||||
.withGraphFetched(withRelations);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,8 +291,6 @@ export default class JournalCommands {
|
||||
referenceType: ['SaleInvoice'],
|
||||
index: [3, 4],
|
||||
});
|
||||
console.log(transactions);
|
||||
|
||||
this.journal.fromTransactions(transactions);
|
||||
this.journal.removeEntries();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface;
|
||||
|
||||
@@ -206,7 +206,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
billDTO: IBillDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[bill] trying to create a new bill', {
|
||||
tenantId,
|
||||
@@ -236,7 +236,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
billDTO.entries
|
||||
);
|
||||
// Inserts the bill graph object to the storage.
|
||||
const bill = await Bill.query().insertGraph({ ...billObj });
|
||||
const bill = await billRepository.upsertGraph({ ...billObj });
|
||||
|
||||
// Triggers `onBillCreated` event.
|
||||
await this.eventDispatcher.dispatch(events.bill.onCreated, {
|
||||
@@ -275,7 +275,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
billDTO: IBillEditDTO,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<IBill> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
this.logger.info('[bill] trying to edit bill.', { tenantId, billId });
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
@@ -314,7 +314,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
billDTO.entries
|
||||
);
|
||||
// Update the bill transaction.
|
||||
const bill = await Bill.query().upsertGraphAndFetch({
|
||||
const bill = await billRepository.upsertGraph({
|
||||
id: billId,
|
||||
...billObj,
|
||||
});
|
||||
@@ -339,7 +339,8 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
* @return {void}
|
||||
*/
|
||||
public async deleteBill(tenantId: number, billId: number) {
|
||||
const { Bill, ItemEntry } = this.tenancy.models(tenantId);
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
const { billRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the given bill or throw not found error.
|
||||
const oldBill = await this.getBillOrThrowError(tenantId, billId);
|
||||
@@ -351,7 +352,7 @@ export default class BillsService extends SalesInvoicesCost {
|
||||
.delete();
|
||||
|
||||
// Delete the bill transaction.
|
||||
const deleteBillOper = Bill.query().where('id', billId).delete();
|
||||
const deleteBillOper = billRepository.deleteById(billId);
|
||||
|
||||
await Promise.all([deleteBillEntriesOper, deleteBillOper]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user