diff --git a/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts b/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts index d61352b77..3ea6d6d22 100644 --- a/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts +++ b/server/src/api/controllers/FinancialStatements/ARAgingSummary.ts @@ -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(), ]; } diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index 29c8cc91d..121cc1110 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -321,7 +321,6 @@ export default class SaleInvoicesController extends BaseController { tenantId, customerId ); - return res.status(200).send({ sales_invoices: this.transfromToResponse(salesInvoices), }); diff --git a/server/src/interfaces/APAgingSummaryReport.ts b/server/src/interfaces/APAgingSummaryReport.ts index 1becb1d22..490403338 100644 --- a/server/src/interfaces/APAgingSummaryReport.ts +++ b/server/src/interfaces/APAgingSummaryReport.ts @@ -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[]; \ No newline at end of file diff --git a/server/src/interfaces/ARAgingSummaryReport.ts b/server/src/interfaces/ARAgingSummaryReport.ts index 4648391c9..a7b3f45dd 100644 --- a/server/src/interfaces/ARAgingSummaryReport.ts +++ b/server/src/interfaces/ARAgingSummaryReport.ts @@ -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[]; \ No newline at end of file diff --git a/server/src/interfaces/Bill.ts b/server/src/interfaces/Bill.ts index dfe2b3acd..7c8c20ffa 100644 --- a/server/src/interfaces/Bill.ts +++ b/server/src/interfaces/Bill.ts @@ -42,6 +42,9 @@ export interface IBill { amount: number, paymentAmount: number, + dueAmount: number, + overdueDays: number, + invLotNumber: string, openedAt: Date | string, diff --git a/server/src/interfaces/SaleInvoice.ts b/server/src/interfaces/SaleInvoice.ts index 554f2cb34..3fd3e5507 100644 --- a/server/src/interfaces/SaleInvoice.ts +++ b/server/src/interfaces/SaleInvoice.ts @@ -7,6 +7,7 @@ export interface ISaleInvoice { invoiceDate: Date, dueDate: Date, dueAmount: number, + overdueDays: number, customerId: number, entries: IItemEntry[], deliveredAt: string | Date, diff --git a/server/src/interfaces/index.ts b/server/src/interfaces/index.ts index 48aecded5..0912db23b 100644 --- a/server/src/interfaces/index.ts +++ b/server/src/interfaces/index.ts @@ -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'; \ No newline at end of file diff --git a/server/src/models/Bill.js b/server/src/models/Bill.js index 40074de3c..44164227e 100644 --- a/server/src/models/Bill.js +++ b/server/src/models/Bill.js @@ -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', }, - } + }; } } diff --git a/server/src/models/SaleInvoice.js b/server/src/models/SaleInvoice.js index cf9fb4b3b..1cbea60e2 100644 --- a/server/src/models/SaleInvoice.js +++ b/server/src/models/SaleInvoice.js @@ -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) } }; } diff --git a/server/src/repositories/BillRepository.ts b/server/src/repositories/BillRepository.ts index fb3b2514d..d8b96fc2e 100644 --- a/server/src/repositories/BillRepository.ts +++ b/server/src/repositories/BillRepository.ts @@ -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); + }) + } } \ No newline at end of file diff --git a/server/src/repositories/SaleInvoiceRepository.ts b/server/src/repositories/SaleInvoiceRepository.ts index f1a0806b2..c5e962d39 100644 --- a/server/src/repositories/SaleInvoiceRepository.ts +++ b/server/src/repositories/SaleInvoiceRepository.ts @@ -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); } -} \ No newline at end of file + + 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); + }); + } +} diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index 1fb128516..8b5d6e1b8 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -291,8 +291,6 @@ export default class JournalCommands { referenceType: ['SaleInvoice'], index: [3, 4], }); - console.log(transactions); - this.journal.fromTransactions(transactions); this.journal.removeEntries(); } diff --git a/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts b/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts index 44852d1cc..cdb00ddbb 100644 --- a/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts +++ b/server/src/services/FinancialStatements/AgingSummary/APAgingSummaryService.ts @@ -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. diff --git a/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts b/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts index 397c5261e..769be797c 100644 --- a/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts +++ b/server/src/services/FinancialStatements/AgingSummary/APAgingSummarySheet.ts @@ -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; + readonly overdueInvoicesByContactId: Dictionary; + readonly currentInvoicesByContactId: Dictionary; + 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; } } \ No newline at end of file diff --git a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts index 3bdc3c231..9be5a8e22 100644 --- a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts +++ b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummaryService.ts @@ -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. diff --git a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts index 28ee6569c..39f0d18fa 100644 --- a/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts +++ b/server/src/services/FinancialStatements/AgingSummary/ARAgingSummarySheet.ts @@ -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; + + readonly overdueInvoicesByContactId: Dictionary; + readonly currentInvoicesByContactId: Dictionary; /** * 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, + } }; } diff --git a/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts b/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts index 91bcb5b77..b13ecaaa2 100644 --- a/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts +++ b/server/src/services/FinancialStatements/AgingSummary/AgingSummary.ts @@ -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'); + } } diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index 94289cef1..beeef28f4 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -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 { - 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 { - 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]);