From b46f2a91c36fa7d94ea7fd72381e3d5ea9ee07e3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 21 Jan 2025 11:38:07 +0200 Subject: [PATCH] refactor: financial statements to nestjs --- .../APAgingSummaryRepository.ts | 145 ++++++++++++-- .../APAgingSummary/APAgingSummaryService.ts | 16 +- .../APAgingSummary/APAgingSummarySheet.ts | 28 +-- .../ARAgingSummaryRepository.ts | 166 +++++++++++++--- .../ARAgingSummary/ARAgingSummaryService.ts | 15 +- .../ARAgingSummary/ARAgingSummarySheet.ts | 33 +--- .../InventoryItemDetails.controller.ts | 4 +- .../InventoryItemDetails.module.ts | 2 +- .../InventoryItemDetails.service.ts | 46 +++++ .../InventoryItemDetails.ts | 180 +++++++++--------- .../InventoryItemDetailsApplication.ts | 4 +- .../InventoryItemDetailsRepository.ts | 130 ++++++++++++- .../InventoryItemDetailsService.ts | 94 --------- .../InventoryItemDetailsTableInjectable.ts | 11 +- .../modules/InventoryItemDetails/constant.ts | 31 +++ .../InventoryValuationSheet.ts | 48 ++--- .../InventoryValuationSheetRepository.ts | 128 +++++++++++-- .../InventoryValuationSheetService.ts | 22 +-- .../models/InventoryTransaction.ts | 3 + .../models/InventoryTransactionMeta.ts | 4 + .../src/utils/transform-to-map-key-value.ts | 6 + 21 files changed, 743 insertions(+), 373 deletions(-) create mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.service.ts delete mode 100644 packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsService.ts create mode 100644 packages/server-nest/src/utils/transform-to-map-key-value.ts diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts index 69b03a2d0..9b4c28643 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryRepository.ts @@ -1,35 +1,138 @@ - - +import { isEmpty } from 'lodash'; +import { Bill } from '@/modules/Bills/models/Bill'; +import { Vendor } from '@/modules/Vendors/models/Vendor'; +import { IAPAgingSummaryQuery } from './APAgingSummary.types'; +import { Inject } from '@nestjs/common'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { groupBy } from 'ramda'; export class APAgingSummaryRepository { + @Inject(Vendor.name) + private readonly vendorModel: typeof Vendor; + @Inject(Bill.name) + private readonly billModel: typeof Bill; - asyncInit() { - // Settings tenant service. - const tenant = await Tenant.query() - .findById(tenantId) - .withGraphFetched('metadata'); + @Inject(TenancyContext) + private readonly tenancyContext: TenancyContext; + /** + * Filter. + * @param {IAPAgingSummaryQuery} filter + */ + filter: IAPAgingSummaryQuery; + + /** + * Due bills. + * @param {Bill[]} dueBills + */ + dueBills: Bill[]; + + /** + * Due bills by vendor id. + * @param {Record} dueBillsByVendorId + */ + dueBillsByVendorId: Record; + + /** + * Overdue bills. + * @param {Bill[]} overdueBills + */ + overdueBills: Bill[]; + + /** + * Overdue bills by vendor id. + * @param {Record} overdueBillsByVendorId + */ + overdueBillsByVendorId: Record; + + /** + * Vendors. + * @param {Vendor[]} vendors + */ + vendors: Vendor[]; + + /** + * Base currency. + * @param {string} baseCurrency + */ + baseCurrency: string; + + /** + * Set the filter. + * @param {IAPAgingSummaryQuery} filter + */ + setFilter(filter: IAPAgingSummaryQuery) { + this.filter = filter; + } + + /** + * Load the data. + */ + async load() { + await this.asyncBaseCurrency(); + await this.asyncVendors(); + await this.asyncDueBills(); + await this.asyncOverdueBills(); + } + + /** + * Retrieve the base currency. + * @returns {Promise} + */ + async asyncBaseCurrency() { + const metadata = await this.tenancyContext.getTenantMetadata(); + + this.baseCurrency = metadata.baseCurrency; + } + + /** + * Retrieve all vendors from the storage. + */ + async asyncVendors() { // Retrieve all vendors from the storage. const vendors = - filter.vendorsIds.length > 0 - ? await vendorRepository.findWhereIn('id', filter.vendorsIds) - : await vendorRepository.all(); + this.filter.vendorsIds.length > 0 + ? await this.vendorModel.query().whereIn('id', this.filter.vendorsIds) + : await this.vendorModel.query(); - // Common query. + this.vendors = vendors; + } + + /** + * Retrieve all overdue bills from the storage. + */ + async asyncOverdueBills() { const commonQuery = (query) => { - if (!isEmpty(filter.branchesIds)) { - query.modify('filterByBranches', filter.branchesIds); + if (!isEmpty(this.filter.branchesIds)) { + query.modify('filterByBranches', this.filter.branchesIds); } }; - // Retrieve all overdue vendors bills. - const overdueBills = await Bill.query() - .modify('overdueBillsFromDate', filter.asDate) + const overdueBills = await this.billModel + .query() + .modify('overdueBillsFromDate', this.filter.asDate) .onBuild(commonQuery); - // Retrieve all due vendors bills. - const dueBills = await Bill.query() - .modify('dueBillsFromDate', filter.asDate) - .onBuild(commonQuery); + this.overdueBills = overdueBills; + this.overdueBillsByVendorId = groupBy(overdueBills, 'vendorId'); } -} \ No newline at end of file + + /** + * Retrieve all due bills from the storage. + */ + async asyncDueBills() { + const commonQuery = (query) => { + if (!isEmpty(this.filter.branchesIds)) { + query.modify('filterByBranches', this.filter.branchesIds); + } + }; + // Retrieve all due vendors bills. + const dueBills = await this.billModel + .query() + .modify('dueBillsFromDate', this.filter.asDate) + .onBuild(commonQuery); + + this.dueBills = dueBills; + this.dueBillsByVendorId = groupBy(dueBills, 'vendorId'); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts index a1e121572..dfa46de0f 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummaryService.ts @@ -1,5 +1,6 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { Injectable } from '@nestjs/common'; +import { events } from '@/common/events/events'; import { IAPAgingSummaryQuery, IAPAgingSummarySheet, @@ -7,19 +8,19 @@ import { import { APAgingSummarySheet } from './APAgingSummarySheet'; import { APAgingSummaryMeta } from './APAgingSummaryMeta'; import { getAPAgingSummaryDefaultQuery } from './utils'; -import { events } from '@/common/events/events'; +import { APAgingSummaryRepository } from './APAgingSummaryRepository'; @Injectable() export class APAgingSummaryService { constructor( private readonly APAgingSummaryMeta: APAgingSummaryMeta, private readonly eventPublisher: EventEmitter2, + private readonly APAgingSummaryRepository: APAgingSummaryRepository, ) {} /** * Retrieve A/P aging summary report. - * @param {number} tenantId - - * @param {IAPAgingSummaryQuery} query - + * @param {IAPAgingSummaryQuery} query - A/P aging summary query. * @returns {Promise} */ public async APAgingSummary( @@ -30,13 +31,14 @@ export class APAgingSummaryService { ...getAPAgingSummaryDefaultQuery(), ...query, }; + // Load the data. + this.APAgingSummaryRepository.setFilter(filter); + await this.APAgingSummaryRepository.load(); + // A/P aging summary report instance. const APAgingSummaryReport = new APAgingSummarySheet( filter, - vendors, - overdueBills, - dueBills, - tenant.metadata.baseCurrency, + this.APAgingSummaryRepository, ); // A/P aging summary report data and columns. const data = APAgingSummaryReport.reportData(); diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts b/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts index 38a4ef6fb..a8df5cb61 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/APAgingSummary/APAgingSummarySheet.ts @@ -1,4 +1,4 @@ -import { groupBy, sum, isEmpty } from 'lodash'; +import { sum, isEmpty } from 'lodash'; import * as R from 'ramda'; import { IAPAgingSummaryQuery, @@ -10,20 +10,13 @@ import { import { AgingSummaryReport } from '../AgingSummary/AgingSummary'; import { IAgingPeriod } from '../AgingSummary/AgingSummary.types'; import { ModelObject } from 'objection'; -import { Bill } from '@/modules/Bills/models/Bill'; import { Vendor } from '@/modules/Vendors/models/Vendor'; import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; +import { APAgingSummaryRepository } from './APAgingSummaryRepository'; export class APAgingSummarySheet extends AgingSummaryReport { - readonly tenantId: number; + readonly repository: APAgingSummaryRepository; readonly query: IAPAgingSummaryQuery; - readonly contacts: ModelObject[]; - readonly unpaidBills: ModelObject[]; - readonly baseCurrency: string; - - readonly overdueInvoicesByContactId: Record>>; - readonly currentInvoicesByContactId: Record>>; - readonly agingPeriods: IAgingPeriod[]; /** @@ -34,23 +27,14 @@ export class APAgingSummarySheet extends AgingSummaryReport { * @param {string} baseCurrency - Base currency of the organization. */ constructor( - tenantId: number, query: IAPAgingSummaryQuery, - vendors: ModelObject[], - overdueBills: ModelObject[], - unpaidBills: ModelObject[], - baseCurrency: string, + repository: APAgingSummaryRepository, ) { super(); - this.tenantId = tenantId; this.query = query; + this.repository = repository; this.numberFormat = this.query.numberFormat; - this.contacts = vendors; - this.baseCurrency = baseCurrency; - - this.overdueInvoicesByContactId = groupBy(overdueBills, 'vendorId'); - this.currentInvoicesByContactId = groupBy(unpaidBills, 'vendorId'); // Initializes the aging periods. this.agingPeriods = this.agingRangePeriods( @@ -170,7 +154,7 @@ export class APAgingSummarySheet extends AgingSummaryReport { * @return {IAPAgingSummaryData} */ public reportData = (): IAPAgingSummaryData => { - const vendorsAgingPeriods = this.vendorsSection(this.contacts); + const vendorsAgingPeriods = this.vendorsSection(this.repository.vendors); const vendorsTotal = this.getVendorsTotal(vendorsAgingPeriods); return { diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts index 81181e9ed..5fc175cb9 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryRepository.ts @@ -1,38 +1,146 @@ - - - - - +import { isEmpty, groupBy } from 'lodash'; +import { Customer } from '@/modules/Customers/models/Customer'; +import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; +import { ModelObject } from 'objection'; +import { IARAgingSummaryQuery } from './ARAgingSummary.types'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { Inject } from '@nestjs/common'; export class ARAgingSummaryRepository { + @Inject(TenancyContext) + private tenancyContext: TenancyContext; + @Inject(Customer.name) + private customerModel: typeof Customer; - init(){ - const tenant = await Tenant.query() - .findById(tenantId) - .withGraphFetched('metadata'); + @Inject(SaleInvoice.name) + private saleInvoiceModel: typeof SaleInvoice; - // Retrieve all customers from the storage. - const customers = - filter.customersIds.length > 0 - ? await customerRepository.findWhereIn('id', filter.customersIds) - : await customerRepository.all(); + /** + * Filter. + * @param {IARAgingSummaryQuery} filter + */ + filter: IARAgingSummaryQuery; - // Common query. - const commonQuery = (query) => { - if (!isEmpty(filter.branchesIds)) { - query.modify('filterByBranches', filter.branchesIds); - } - }; - // Retrieve all overdue sale invoices. - const overdueSaleInvoices = await SaleInvoice.query() - .modify('overdueInvoicesFromDate', filter.asDate) - .onBuild(commonQuery); + /** + * Base currency. + * @param {string} baseCurrency + */ + baseCurrency: string; - // Retrieve all due sale invoices. - const currentInvoices = await SaleInvoice.query() - .modify('dueInvoicesFromDate', filter.asDate) - .onBuild(commonQuery); + /** + * Customers. + * @param {ModelObject[]} customers + */ + customers: ModelObject[]; + /** + * Overdue sale invoices. + * @param {ModelObject[]} overdueSaleInvoices + */ + overdueSaleInvoices: ModelObject[]; + + /** + * Current sale invoices. + * @param {ModelObject[]} currentInvoices + */ + currentInvoices: ModelObject[]; + + /** + * Current sale invoices by contact id. + * @param {Record[]>} currentInvoicesByContactId + */ + currentInvoicesByContactId: Record[]>; + + /** + * Overdue sale invoices by contact id. + * @param {Record[]>} overdueInvoicesByContactId + */ + overdueInvoicesByContactId: Record[]>; + + /** + * Set the filter. + * @param {IARAgingSummaryQuery} filter + */ + setFilter(filter: IARAgingSummaryQuery) { + this.filter = filter; } -} \ No newline at end of file + + /** + * Initialize the repository. + */ + async load() { + await this.initBaseCurrency(); + await this.initCustomers(); + await this.initOverdueSaleInvoices(); + await this.initCurrentInvoices(); + } + + /** + * Initialize the base currency. + */ + async initBaseCurrency() { + const tenantMetadata = await this.tenancyContext.getTenantMetadata(); + + this.baseCurrency = tenantMetadata.baseCurrency; + } + + /** + * Initialize the customers. + */ + async initCustomers() { + // Retrieve all customers from the storage. + const customers = + this.filter.customersIds.length > 0 + ? await this.customerModel + .query() + .whereIn('id', this.filter.customersIds) + : await this.customerModel.query(); + + this.customers = customers; + } + + /** + * Initialize the overdue sale invoices. + */ + async initOverdueSaleInvoices() { + const commonQuery = (query) => { + if (!isEmpty(this.filter.branchesIds)) { + query.modify('filterByBranches', this.filter.branchesIds); + } + }; + // Retrieve all overdue sale invoices. + const overdueSaleInvoices = await this.saleInvoiceModel + .query() + .modify('overdueInvoicesFromDate', this.filter.asDate) + .onBuild(commonQuery); + + this.overdueSaleInvoices = overdueSaleInvoices; + this.overdueInvoicesByContactId = groupBy( + overdueSaleInvoices, + 'customerId', + ); + } + + /** + * Initialize the current sale invoices. + */ + async initCurrentInvoices() { + const commonQuery = (query) => { + if (!isEmpty(this.filter.branchesIds)) { + query.modify('filterByBranches', this.filter.branchesIds); + } + }; + // Retrieve all due sale invoices. + const currentInvoices = await this.saleInvoiceModel + .query() + .modify('dueInvoicesFromDate', this.filter.asDate) + .onBuild(commonQuery); + + this.currentInvoices = currentInvoices; + this.currentInvoicesByContactId = groupBy( + currentInvoices, + 'customerId', + ); + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts index aed7cc30a..1fb8cd6af 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummaryService.ts @@ -5,11 +5,13 @@ import { Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { IARAgingSummaryQuery } from './ARAgingSummary.types'; import { events } from '@/common/events/events'; +import { ARAgingSummaryRepository } from './ARAgingSummaryRepository'; @Injectable() export class ARAgingSummaryService { constructor( private readonly ARAgingSummaryMeta: ARAgingSummaryMeta, + private readonly ARAgingSummaryRepository: ARAgingSummaryRepository, private readonly eventPublisher: EventEmitter2, ) {} @@ -22,13 +24,14 @@ export class ARAgingSummaryService { ...getARAgingSummaryDefaultQuery(), ...query, }; + // Load the A/R aging summary repository. + this.ARAgingSummaryRepository.setFilter(filter); + await this.ARAgingSummaryRepository.load(); + // AR aging summary report instance. const ARAgingSummaryReport = new ARAgingSummarySheet( filter, - customers, - overdueSaleInvoices, - currentInvoices, - tenant.metadata.baseCurrency, + this.ARAgingSummaryRepository, ); // AR aging summary report data and columns. const data = ARAgingSummaryReport.reportData(); @@ -40,9 +43,7 @@ export class ARAgingSummaryService { // Triggers `onReceivableAgingViewed` event. await this.eventPublisher.emitAsync( events.reports.onReceivableAgingViewed, - { - query, - }, + { query }, ); return { diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts b/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts index 9cef473ce..0012e8326 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/ARAgingSummary/ARAgingSummarySheet.ts @@ -1,5 +1,5 @@ import * as R from 'ramda'; -import { Dictionary, groupBy, isEmpty, sum } from 'lodash'; +import { isEmpty, sum } from 'lodash'; import { IAgingPeriod } from '../AgingSummary/AgingSummary.types'; import { IARAgingSummaryQuery, @@ -12,17 +12,12 @@ import { AgingSummaryReport } from '../AgingSummary/AgingSummary'; import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; import { ModelObject } from 'objection'; import { Customer } from '@/modules/Customers/models/Customer'; -import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; +import { ARAgingSummaryRepository } from './ARAgingSummaryRepository'; export class ARAgingSummarySheet extends AgingSummaryReport { - readonly tenantId: number; readonly query: IARAgingSummaryQuery; - readonly contacts: ModelObject[]; readonly agingPeriods: IAgingPeriod[]; - readonly baseCurrency: string; - - readonly overdueInvoicesByContactId: Dictionary[]>; - readonly currentInvoicesByContactId: Dictionary[]>; + readonly repository: ARAgingSummaryRepository; /** * Constructor method. @@ -32,29 +27,15 @@ export class ARAgingSummarySheet extends AgingSummaryReport { * @param {IJournalPoster} journal */ constructor( - tenantId: number, query: IARAgingSummaryQuery, - customers: ModelObject[], - overdueSaleInvoices: ModelObject[], - currentSaleInvoices: ModelObject[], - baseCurrency: string, + repository: ARAgingSummaryRepository, ) { super(); - this.tenantId = tenantId; - this.contacts = customers; this.query = query; - this.baseCurrency = baseCurrency; + this.repository = repository; this.numberFormat = this.query.numberFormat; - this.overdueInvoicesByContactId = groupBy( - overdueSaleInvoices, - 'customerId', - ); - this.currentInvoicesByContactId = groupBy( - currentSaleInvoices, - 'customerId', - ); // Initializes the aging periods. this.agingPeriods = this.agingRangePeriods( this.query.asDate, @@ -179,7 +160,9 @@ export class ARAgingSummarySheet extends AgingSummaryReport { * @return {IARAgingSummaryData} */ public reportData = (): IARAgingSummaryData => { - const customersAgingPeriods = this.customersWalker(this.contacts); + const customersAgingPeriods = this.customersWalker( + this.repository.customers, + ); const customersTotal = this.getCustomersTotal(customersAgingPeriods); return { diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.controller.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.controller.ts index 8b802df96..2ad1f9063 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.controller.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.controller.ts @@ -1,5 +1,5 @@ import { Controller, Headers, Query, Res } from '@nestjs/common'; -import { InventortyDetailsApplication } from './InventoryItemDetailsApplication'; +import { InventoryItemDetailsApplication } from './InventoryItemDetailsApplication'; import { IInventoryDetailsQuery } from './InventoryItemDetails.types'; import { AcceptType } from '@/constants/accept-type'; import { Response } from 'express'; @@ -7,7 +7,7 @@ import { Response } from 'express'; @Controller('reports/inventory-item-details') export class InventoryItemDetailsController { constructor( - private readonly inventoryItemDetailsApp: InventortyDetailsApplication, + private readonly inventoryItemDetailsApp: InventoryItemDetailsApplication, ) {} async inventoryItemDetails( diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.module.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.module.ts index 1d792d4f7..beba367e1 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.module.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { InventoryItemDetailsController } from './InventoryItemDetails.controller'; import { InventoryDetailsTablePdf } from './InventoryItemDetailsTablePdf'; -import { InventoryDetailsService } from './InventoryItemDetailsService'; +import { InventoryDetailsService } from './InventoryItemDetails.service'; import { InventoryDetailsTableInjectable } from './InventoryItemDetailsTableInjectable'; import { InventoryItemDetailsExportInjectable } from './InventoryItemDetailsExportInjectable'; import { InventoryItemDetailsApplication } from './InventoryItemDetailsApplication'; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.service.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.service.ts new file mode 100644 index 000000000..56c0653ac --- /dev/null +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.service.ts @@ -0,0 +1,46 @@ +import { I18nService } from 'nestjs-i18n'; +import { Injectable } from '@nestjs/common'; +import { + IInventoryDetailsQuery, + IInvetoryItemDetailDOO, +} from './InventoryItemDetails.types'; +import { InventoryDetails } from './InventoryItemDetails'; +import { InventoryItemDetailsRepository } from './InventoryItemDetailsRepository'; +import { InventoryDetailsMetaInjectable } from './InventoryItemDetailsMeta'; +import { getInventoryItemDetailsDefaultQuery } from './constant'; + +@Injectable() +export class InventoryDetailsService { + constructor( + private readonly inventoryItemDetailsRepository: InventoryItemDetailsRepository, + private readonly inventoryDetailsMeta: InventoryDetailsMetaInjectable, + private readonly i18n: I18nService, + ) {} + + /** + * Retrieve the inventory details report data. + * @param {IInventoryDetailsQuery} query - Inventory details query. + * @return {Promise} + */ + public async inventoryDetails( + query: IInventoryDetailsQuery, + ): Promise { + const filter = { + ...getInventoryItemDetailsDefaultQuery(), + ...query, + }; + // Inventory details report mapper. + const inventoryDetailsInstance = new InventoryDetails( + filter, + this.inventoryItemDetailsRepository, + this.i18n, + ); + const meta = await this.inventoryDetailsMeta.meta(query); + + return { + data: inventoryDetailsInstance.reportData(), + query: filter, + meta, + }; + } +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.ts index f796f23da..18172f7ca 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetails.ts @@ -1,6 +1,7 @@ import * as R from 'ramda'; +import * as moment from 'moment'; import { defaultTo, sumBy, get } from 'lodash'; -import moment from 'moment'; +import { I18nService } from 'nestjs-i18n'; import { IInventoryDetailsQuery, IInventoryDetailsNumber, @@ -11,56 +12,42 @@ import { IInventoryDetailsOpening, IInventoryDetailsItemTransaction, } from './InventoryItemDetails.types'; -import FinancialSheet from '../FinancialSheet'; -import { transformToMapBy, transformToMapKeyValue } from 'utils'; -import { filterDeep } from 'utils/deepdash'; - -const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; - -enum INodeTypes { - ITEM = 'item', - TRANSACTION = 'transaction', - OPENING_ENTRY = 'OPENING_ENTRY', - CLOSING_ENTRY = 'CLOSING_ENTRY', -} +import { ModelObject } from 'objection'; +import { Item } from '@/modules/Items/models/Item'; +import { + IFormatNumberSettings, + INumberFormatQuery, +} from '../../types/Report.types'; +import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; +import { InventoryItemDetailsRepository } from './InventoryItemDetailsRepository'; +import { TInventoryTransactionDirection } from '@/modules/InventoryCost/types/InventoryCost.types'; +import { FinancialSheet } from '../../common/FinancialSheet'; +import { filterDeep } from '@/utils/deepdash'; +import { INodeTypes, MAP_CONFIG } from './constant'; export class InventoryDetails extends FinancialSheet { - readonly inventoryTransactionsByItemId: Map; - readonly openingBalanceTransactions: Map; + readonly repository: InventoryItemDetailsRepository; readonly query: IInventoryDetailsQuery; readonly numberFormat: INumberFormatQuery; - readonly baseCurrency: string; - readonly items: IItem[]; + readonly items: ModelObject[]; + readonly i18n: I18nService; /** * Constructor method. - * @param {IItem[]} items - Items. - * @param {IInventoryTransaction[]} inventoryTransactions - Inventory transactions. - * @param {IInventoryDetailsQuery} query - Report query. - * @param {string} baseCurrency - The base currency. + * @param {InventoryItemDetailsRepository} repository - The repository. + * @param {I18nService} i18n - The i18n service. */ constructor( - items: IItem[], - openingBalanceTransactions: IInventoryTransaction[], - inventoryTransactions: IInventoryTransaction[], - query: IInventoryDetailsQuery, - baseCurrency: string, - i18n: any + filter: IInventoryDetailsQuery, + repository: InventoryItemDetailsRepository, + i18n: I18nService, ) { super(); - this.inventoryTransactionsByItemId = transformToMapBy( - inventoryTransactions, - 'itemId' - ); - this.openingBalanceTransactions = transformToMapKeyValue( - openingBalanceTransactions, - 'itemId' - ); - this.query = query; + this.repository = repository; + + this.query = filter; this.numberFormat = this.query.numberFormat; - this.items = items; - this.baseCurrency = baseCurrency; this.i18n = i18n; } @@ -69,9 +56,9 @@ export class InventoryDetails extends FinancialSheet { * @param {number} number * @returns */ - private getNumberMeta( + public getNumberMeta( number: number, - settings?: IFormatNumberSettings + settings?: IFormatNumberSettings, ): IInventoryDetailsNumber { return { formattedNumber: this.formatNumber(number, { @@ -89,9 +76,9 @@ export class InventoryDetails extends FinancialSheet { * @param {IFormatNumberSettings} settings - * @retrun {IInventoryDetailsNumber} */ - private getTotalNumberMeta( + public getTotalNumberMeta( number: number, - settings?: IFormatNumberSettings + settings?: IFormatNumberSettings, ): IInventoryDetailsNumber { return this.getNumberMeta(number, { excerptZero: false, ...settings }); } @@ -101,7 +88,7 @@ export class InventoryDetails extends FinancialSheet { * @param {Date|string} date * @returns {IInventoryDetailsDate} */ - private getDateMeta(date: Date | string): IInventoryDetailsDate { + public getDateMeta(date: Date | string): IInventoryDetailsDate { return { formattedDate: moment(date).format('YYYY-MM-DD'), date: moment(date).toDate(), @@ -110,14 +97,14 @@ export class InventoryDetails extends FinancialSheet { /** * Adjusts the movement amount. - * @param {number} amount - * @param {TInventoryTransactionDirection} direction + * @param {number} amount - The amount. + * @param {TInventoryTransactionDirection} direction - The transaction direction. * @returns {number} */ - private adjustAmountMovement = R.curry( + public adjustAmountMovement = R.curry( (direction: TInventoryTransactionDirection, amount: number): number => { return direction === 'OUT' ? amount * -1 : amount; - } + }, ); /** @@ -125,8 +112,8 @@ export class InventoryDetails extends FinancialSheet { * @param {IInventoryDetailsItemTransaction[]} transactions * @returns {IInventoryDetailsItemTransaction[]} */ - private mapAccumTransactionsRunningQuantity( - transactions: IInventoryDetailsItemTransaction[] + public mapAccumTransactionsRunningQuantity( + transactions: IInventoryDetailsItemTransaction[], ): IInventoryDetailsItemTransaction[] { const initial = this.getNumberMeta(0); @@ -140,7 +127,7 @@ export class InventoryDetails extends FinancialSheet { return R.mapAccum( mapAccumAppender, { runningQuantity: initial }, - transactions + transactions, )[1]; } @@ -149,8 +136,8 @@ export class InventoryDetails extends FinancialSheet { * @param {IInventoryDetailsItemTransaction[]} transactions * @returns {IInventoryDetailsItemTransaction} */ - private mapAccumTransactionsRunningValuation( - transactions: IInventoryDetailsItemTransaction[] + public mapAccumTransactionsRunningValuation( + transactions: IInventoryDetailsItemTransaction[], ): IInventoryDetailsItemTransaction[] { const initial = this.getNumberMeta(0); @@ -165,16 +152,18 @@ export class InventoryDetails extends FinancialSheet { return R.mapAccum( mapAccumAppender, { runningValuation: initial }, - transactions + transactions, )[1]; } /** * Retrieve the inventory transaction total. - * @param {IInventoryTransaction} transaction + * @param {ModelObject} transaction * @returns {number} */ - private getTransactionTotal = (transaction: IInventoryTransaction) => { + public getTransactionTotal = ( + transaction: ModelObject, + ) => { return transaction.quantity ? transaction.quantity * transaction.rate : transaction.rate; @@ -186,9 +175,9 @@ export class InventoryDetails extends FinancialSheet { * @param {IInvetoryTransaction} transaction * @returns {IInventoryDetailsItemTransaction} */ - private itemTransactionMapper( - item: IItem, - transaction: IInventoryTransaction + public itemTransactionMapper( + item: ModelObject, + transaction: ModelObject, ): IInventoryDetailsItemTransaction { const total = this.getTransactionTotal(transaction); const amountMovement = this.adjustAmountMovement(transaction.direction); @@ -209,7 +198,7 @@ export class InventoryDetails extends FinancialSheet { return { nodeType: INodeTypes.TRANSACTION, date: this.getDateMeta(transaction.date), - transactionType: this.i18n.__(transaction.transcationTypeFormatted), + transactionType: this.i18n.t(transaction.transcationTypeFormatted), transactionNumber: transaction?.meta?.transactionNumber, direction: transaction.direction, @@ -232,13 +221,16 @@ export class InventoryDetails extends FinancialSheet { /** * Retrieve the inventory transcations by item id. - * @param {number} itemId - * @returns {IInventoryTransaction[]} + * @param {number} itemId - The item id. + * @returns {ModelObject[]} */ - private getInventoryTransactionsByItemId( - itemId: number - ): IInventoryTransaction[] { - return defaultTo(this.inventoryTransactionsByItemId.get(itemId + ''), []); + public getInventoryTransactionsByItemId( + itemId: number, + ): ModelObject[] { + return defaultTo( + this.repository.inventoryTransactionsByItemId.get(itemId), + [], + ); } /** @@ -246,13 +238,15 @@ export class InventoryDetails extends FinancialSheet { * @param {IItem} item * @returns {IInventoryDetailsItemTransaction[]} */ - private getItemTransactions(item: IItem): IInventoryDetailsItemTransaction[] { + public getItemTransactions( + item: ModelObject, + ): ModelObject[] { const transactions = this.getInventoryTransactionsByItemId(item.id); return R.compose( this.mapAccumTransactionsRunningQuantity.bind(this), this.mapAccumTransactionsRunningValuation.bind(this), - R.map(R.curry(this.itemTransactionMapper.bind(this))(item)) + R.map(R.curry(this.itemTransactionMapper.bind(this))(item)), )(transactions); } @@ -265,8 +259,8 @@ export class InventoryDetails extends FinancialSheet { * | IInventoryDetailsClosing * )[]} */ - private itemTransactionsMapper( - item: IItem + public itemTransactionsMapper( + item: ModelObject, ): ( | IInventoryDetailsItemTransaction | IInventoryDetailsOpening @@ -277,7 +271,7 @@ export class InventoryDetails extends FinancialSheet { const closingValuation = this.getItemClosingValuation( item, transactions, - openingValuation + openingValuation, ); const hasTransactions = transactions.length > 0; const isItemHasOpeningBalance = this.isItemHasOpeningBalance(item.id); @@ -285,7 +279,7 @@ export class InventoryDetails extends FinancialSheet { return R.pipe( R.concat(transactions), R.when(R.always(isItemHasOpeningBalance), R.prepend(openingValuation)), - R.when(R.always(hasTransactions), R.append(closingValuation)) + R.when(R.always(hasTransactions), R.append(closingValuation)), )([]); } @@ -294,8 +288,8 @@ export class InventoryDetails extends FinancialSheet { * @param {number} itemId - Item id. * @return {boolean} */ - private isItemHasOpeningBalance(itemId: number): boolean { - return !!this.openingBalanceTransactions.get(itemId); + public isItemHasOpeningBalance(itemId: number): boolean { + return !!this.repository.openingBalanceTransactionsByItemId.get(itemId); } /** @@ -303,8 +297,12 @@ export class InventoryDetails extends FinancialSheet { * @param {IItem} item - * @returns {IInventoryDetailsOpening} */ - private getItemOpeingValuation(item: IItem): IInventoryDetailsOpening { - const openingBalance = this.openingBalanceTransactions.get(item.id); + public getItemOpeingValuation( + item: ModelObject, + ): IInventoryDetailsOpening { + const openingBalance = this.repository.openingBalanceTransactionsByItemId.get( + item.id, + ); const quantity = defaultTo(get(openingBalance, 'quantity'), 0); const value = defaultTo(get(openingBalance, 'value'), 0); @@ -321,10 +319,10 @@ export class InventoryDetails extends FinancialSheet { * @param {IItem} item - * @returns {IInventoryDetailsOpening} */ - private getItemClosingValuation( - item: IItem, - transactions: IInventoryDetailsItemTransaction[], - openingValuation: IInventoryDetailsOpening + public getItemClosingValuation( + item: ModelObject, + transactions: ModelObject[], + openingValuation: IInventoryDetailsOpening, ): IInventoryDetailsOpening { const value = sumBy(transactions, 'valueMovement.number'); const quantity = sumBy(transactions, 'quantityMovement.number'); @@ -347,7 +345,7 @@ export class InventoryDetails extends FinancialSheet { * @param {IItem} item * @returns {IInventoryDetailsItem} */ - private itemsNodeMapper(item: IItem): IInventoryDetailsItem { + public itemsNodeMapper(item: ModelObject): IInventoryDetailsItem { return { id: item.id, name: item.name, @@ -363,9 +361,9 @@ export class InventoryDetails extends FinancialSheet { * @param {IItem} node * @returns {boolean} */ - private isNodeTypeEquals( + public isNodeTypeEquals( nodeType: string, - node: IInventoryDetailsItem + node: IInventoryDetailsItem, ): boolean { return nodeType === node.nodeType; } @@ -375,8 +373,8 @@ export class InventoryDetails extends FinancialSheet { * @param {IInventoryDetailsItem} item * @returns {boolean} */ - private isItemNodeHasTransactions(item: IInventoryDetailsItem) { - return !!this.inventoryTransactionsByItemId.get(item.id); + public isItemNodeHasTransactions(item: IInventoryDetailsItem) { + return !!this.repository.inventoryTransactionsByItemId.get(item.id); } /** @@ -384,11 +382,11 @@ export class InventoryDetails extends FinancialSheet { * @param {IInventoryDetailsItem} item * @return {boolean} */ - private isFilterNode(item: IInventoryDetailsItem): boolean { + public isFilterNode(item: IInventoryDetailsItem): boolean { return R.ifElse( R.curry(this.isNodeTypeEquals)(INodeTypes.ITEM), this.isItemNodeHasTransactions.bind(this), - R.always(true) + R.always(true), )(item); } @@ -397,24 +395,24 @@ export class InventoryDetails extends FinancialSheet { * @param {IInventoryDetailsItem[]} items - * @returns {IInventoryDetailsItem[]} */ - private filterItemsNodes(items: IInventoryDetailsItem[]) { + public filterItemsNodes(items: IInventoryDetailsItem[]) { const filtered = filterDeep( items, this.isFilterNode.bind(this), - MAP_CONFIG + MAP_CONFIG, ); return defaultTo(filtered, []); } /** * Retrieve the items nodes of the report. - * @param {IItem} items + * @param {ModelObject[]} items * @returns {IInventoryDetailsItem[]} */ - private itemsNodes(items: IItem[]): IInventoryDetailsItem[] { + public itemsNodes(items: ModelObject[]): IInventoryDetailsItem[] { return R.compose( this.filterItemsNodes.bind(this), - R.map(this.itemsNodeMapper.bind(this)) + R.map(this.itemsNodeMapper.bind(this)), )(items); } diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsApplication.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsApplication.ts index 95a02da2d..57ff5547b 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsApplication.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsApplication.ts @@ -1,10 +1,10 @@ import { IInventoryDetailsQuery, IInvetoryItemDetailsTable, -} from '@/interfaces'; +} from './InventoryItemDetails.types'; import { InventoryItemDetailsExportInjectable } from './InventoryItemDetailsExportInjectable'; import { InventoryDetailsTableInjectable } from './InventoryItemDetailsTableInjectable'; -import { InventoryDetailsService } from './InventoryItemDetailsService'; +import { InventoryDetailsService } from './InventoryItemDetails.service'; import { InventoryDetailsTablePdf } from './InventoryItemDetailsTablePdf'; import { Injectable } from '@nestjs/common'; diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsRepository.ts index 495ec3125..bcbf40c6a 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsRepository.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsRepository.ts @@ -4,19 +4,129 @@ import moment from 'moment'; import { IInventoryDetailsQuery } from './InventoryItemDetails.types'; import { Item } from '@/modules/Items/models/Item'; import { InventoryTransaction } from '@/modules/InventoryCost/models/InventoryTransaction'; -import { Injectable, Scope } from '@nestjs/common'; +import { Inject, Injectable, Scope } from '@nestjs/common'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; +import { transformToMapKeyValue } from '@/utils/transform-to-map-key-value'; +import { transformToMapBy } from '@/utils/transform-to-map-by'; @Injectable({ scope: Scope.TRANSIENT }) export class InventoryItemDetailsRepository { + @Inject(Item.name) + readonly itemModel: typeof Item; + + @Inject(InventoryTransaction.name) + readonly inventoryTransactionModel: typeof InventoryTransaction; + + @Inject(TenancyContext) + readonly tenancyContext: TenancyContext; + /** - * Constructor method. - * @param {typeof Item} itemModel - Item model. - * @param {typeof InventoryTransaction} inventoryTransactionModel - Inventory transaction model. + * The items. + * @param {ModelObject[]} items - The items. */ - constructor( - private readonly itemModel: typeof Item, - private readonly inventoryTransactionModel: typeof InventoryTransaction, - ) {} + items: ModelObject[]; + + /** + * The opening balance transactions. + * @param {ModelObject[]} openingBalanceTransactions - The opening balance transactions. + */ + openingBalanceTransactions: ModelObject[]; + + /** + * The opening balance transactions by item id. + * @param {Map>} openingBalanceTransactionsByItemId - The opening balance transactions by item id. + */ + openingBalanceTransactionsByItemId: Map>; + + /** + * The inventory transactions. + * @param {ModelObject[]} inventoryTransactions - The inventory transactions. + */ + inventoryTransactions: ModelObject[]; + + /** + * The inventory transactions by item id. + * @param {Map>} inventoryTransactionsByItemId - The inventory transactions by item id. + */ + inventoryTransactionsByItemId: Map[]>; + + /** + * The filter. + * @param {IInventoryDetailsQuery} filter - The filter. + */ + filter: IInventoryDetailsQuery; + + /** + * The base currency. + * @param {string} baseCurrency - The base currency. + */ + baseCurrency: string; + + /** + * Set the filter. + * @param {IInventoryDetailsQuery} filter - The filter. + */ + setFilter(filter: IInventoryDetailsQuery) { + this.filter = filter; + } + + /** + * Initialize the repository. + */ + async asyncInit() { + await this.initItems(); + await this.initOpeningBalanceTransactions(); + await this.initInventoryTransactions(); + await this.initBaseCurrency(); + } + + /** + * Initialize the items. + */ + async initItems() { + // Retrieves the items. + const items = await this.getInventoryItems(this.filter.itemsIds); + this.items = items; + } + + /** + * Initialize the opening balance transactions. + */ + async initOpeningBalanceTransactions() { + // Retrieves the opening balance transactions. + const openingBalanceTransactions = await this.getOpeningBalanceTransactions( + this.filter, + ); + this.openingBalanceTransactions = openingBalanceTransactions; + this.openingBalanceTransactionsByItemId = transformToMapKeyValue( + openingBalanceTransactions, + 'itemId' + ); + } + + /** + * Initialize the inventory transactions. + */ + async initInventoryTransactions() { + // Retrieves the inventory transactions. + const inventoryTransactions = await this.getItemInventoryTransactions( + this.filter, + ); + this.inventoryTransactions = inventoryTransactions; + this.inventoryTransactionsByItemId = transformToMapBy( + inventoryTransactions, + 'itemId' + ); + } + + /** + * Initialize the base currency. + */ + async initBaseCurrency() { + const tenantMetadata = await this.tenancyContext.getTenantMetadata(); + + this.baseCurrency = tenantMetadata.baseCurrency; + } /** * Retrieve inventory items. @@ -39,7 +149,7 @@ export class InventoryItemDetailsRepository { * @param {IInventoryDetailsQuery} * @return {Promise>} */ - public async openingBalanceTransactions( + public async getOpeningBalanceTransactions( filter: IInventoryDetailsQuery, ): Promise[]> { const openingBalanceDate = moment(filter.fromDate) @@ -95,7 +205,7 @@ export class InventoryItemDetailsRepository { * @param {IInventoryDetailsQuery} * @return {Promise} */ - public async itemInventoryTransactions( + public async getItemInventoryTransactions( filter: IInventoryDetailsQuery, ): Promise[]> { const inventoryTransactions = this.inventoryTransactionModel diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsService.ts deleted file mode 100644 index 1a4204c58..000000000 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsService.ts +++ /dev/null @@ -1,94 +0,0 @@ -import moment from 'moment'; -import { Service, Inject } from 'typedi'; -import { IInventoryDetailsQuery, IInvetoryItemDetailDOO } from '@/interfaces'; -import TenancyService from '@/services/Tenancy/TenancyService'; -import { InventoryDetails } from './InventoryItemDetails'; -import FinancialSheet from '../FinancialSheet'; -import InventoryDetailsRepository from './InventoryItemDetailsRepository'; -import { Tenant } from '@/system/models'; -import { InventoryDetailsMetaInjectable } from './InventoryItemDetailsMeta'; - -@Service() -export class InventoryDetailsService extends FinancialSheet { - @Inject() - private tenancy: TenancyService; - - @Inject() - private reportRepo: InventoryDetailsRepository; - - @Inject() - private inventoryDetailsMeta: InventoryDetailsMetaInjectable; - - /** - * Defaults balance sheet filter query. - * @return {IBalanceSheetQuery} - */ - private get defaultQuery(): IInventoryDetailsQuery { - return { - fromDate: moment().startOf('month').format('YYYY-MM-DD'), - toDate: moment().format('YYYY-MM-DD'), - itemsIds: [], - numberFormat: { - precision: 2, - divideOn1000: false, - showZero: false, - formatMoney: 'total', - negativeFormat: 'mines', - }, - noneTransactions: false, - branchesIds: [], - warehousesIds: [], - }; - } - - /** - * Retrieve the inventory details report data. - * @param {number} tenantId - - * @param {IInventoryDetailsQuery} query - - * @return {Promise} - */ - public async inventoryDetails( - tenantId: number, - query: IInventoryDetailsQuery - ): Promise { - const i18n = this.tenancy.i18n(tenantId); - - const tenant = await Tenant.query() - .findById(tenantId) - .withGraphFetched('metadata'); - - const filter = { - ...this.defaultQuery, - ...query, - }; - // Retrieves the items. - const items = await this.reportRepo.getInventoryItems( - tenantId, - filter.itemsIds - ); - // Opening balance transactions. - const openingBalanceTransactions = - await this.reportRepo.openingBalanceTransactions(tenantId, filter); - - // Retrieves the inventory transaction. - const inventoryTransactions = - await this.reportRepo.itemInventoryTransactions(tenantId, filter); - - // Inventory details report mapper. - const inventoryDetailsInstance = new InventoryDetails( - items, - openingBalanceTransactions, - inventoryTransactions, - filter, - tenant.metadata.baseCurrency, - i18n - ); - const meta = await this.inventoryDetailsMeta.meta(tenantId, query); - - return { - data: inventoryDetailsInstance.reportData(), - query: filter, - meta, - }; - } -} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsTableInjectable.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsTableInjectable.ts index 41580baac..845dbf618 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsTableInjectable.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/InventoryItemDetailsTableInjectable.ts @@ -1,11 +1,11 @@ -import { InventoryDetailsTable } from './InventoryItemDetailsTable'; +import { Injectable } from '@nestjs/common'; +import { I18nService } from 'nestjs-i18n'; import { IInventoryDetailsQuery, IInvetoryItemDetailsTable, } from './InventoryItemDetails.types'; -import { InventoryDetailsService } from './InventoryItemDetailsService'; -import { Injectable } from '@nestjs/common'; -import { I18nService } from 'nestjs-i18n'; +import { InventoryDetailsService } from './InventoryItemDetails.service'; +import { InventoryItemDetailsTable } from './InventoryItemDetailsTable'; @Injectable() export class InventoryDetailsTableInjectable { @@ -24,7 +24,8 @@ export class InventoryDetailsTableInjectable { ): Promise { const inventoryDetails = await this.inventoryDetails.inventoryDetails(query); - const table = new InventoryDetailsTable(inventoryDetails, this.i18n); + + const table = new InventoryItemDetailsTable(inventoryDetails.data, this.i18n); return { table: { diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/constant.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/constant.ts index eeb199236..318a3fb60 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/constant.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryItemDetails/constant.ts @@ -1,3 +1,5 @@ +import { IInventoryDetailsQuery } from "./InventoryItemDetails.types"; + export const HtmlTableCustomCss = ` table tr.row-type--item td, table tr.row-type--opening-entry td, @@ -5,3 +7,32 @@ table tr.row-type--closing-entry td{ font-weight: 500; } `; + +export const getInventoryItemDetailsDefaultQuery = + (): IInventoryDetailsQuery => { + return { + fromDate: moment().startOf('month').format('YYYY-MM-DD'), + toDate: moment().format('YYYY-MM-DD'), + itemsIds: [], + numberFormat: { + precision: 2, + divideOn1000: false, + showZero: false, + formatMoney: 'total', + negativeFormat: 'mines', + }, + noneTransactions: false, + branchesIds: [], + warehousesIds: [], + }; + }; + + +export const MAP_CONFIG = { childrenPath: 'children', pathFormat: 'array' }; + +export enum INodeTypes { + ITEM = 'item', + TRANSACTION = 'transaction', + OPENING_ENTRY = 'OPENING_ENTRY', + CLOSING_ENTRY = 'CLOSING_ENTRY', +} diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheet.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheet.ts index 98452d349..8eab7581d 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheet.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheet.ts @@ -1,3 +1,4 @@ +import { ModelObject } from 'objection'; import { sumBy, get, isEmpty } from 'lodash'; import * as R from 'ramda'; import { @@ -6,41 +7,29 @@ import { IInventoryValuationStatement, IInventoryValuationTotal, } from './InventoryValuationSheet.types'; -import { ModelObject } from 'objection'; import { Item } from '@/modules/Items/models/Item'; import { InventoryCostLotTracker } from '@/modules/InventoryCost/models/InventoryCostLotTracker'; import { FinancialSheet } from '../../common/FinancialSheet'; -import { transformToMap } from '@/utils/transform-to-key'; +import { InventoryValuationSheetRepository } from './InventoryValuationSheetRepository'; +import { allPassedConditionsPass } from '@/utils/all-conditions-passed'; export class InventoryValuationSheet extends FinancialSheet { readonly query: IInventoryValuationReportQuery; - readonly items: ModelObject[]; - readonly INInventoryCostLots: Map; - readonly OUTInventoryCostLots: Map; - readonly baseCurrency: string; + readonly repository: InventoryValuationSheetRepository; /** * Constructor method. - * @param {IInventoryValuationReportQuery} query - * @param {ModelObject[]} items - * @param {Map} INInventoryCostLots - * @param {Map} OUTInventoryCostLots - * @param {string} baseCurrency + * @param {IInventoryValuationReportQuery} query - Inventory valuation query. + * @param {InventoryValuationSheetRepository} repository - Inventory valuation sheet repository. */ constructor( query: IInventoryValuationReportQuery, - items: ModelObject[], - INInventoryCostLots: Map, - OUTInventoryCostLots: Map, - baseCurrency: string, + repository: InventoryValuationSheetRepository, ) { super(); this.query = query; - this.items = items; - this.INInventoryCostLots = transformToMap(INInventoryCostLots, 'itemId'); - this.OUTInventoryCostLots = transformToMap(OUTInventoryCostLots, 'itemId'); - this.baseCurrency = baseCurrency; + this.repository = repository; this.numberFormat = this.query.numberFormat; } @@ -70,7 +59,7 @@ export class InventoryValuationSheet extends FinancialSheet { cost: number; quantity: number; } { - return this.getItemTransaction(this.INInventoryCostLots, itemId); + return this.getItemTransaction(this.repository.INInventoryCostLots, itemId); } /** @@ -81,7 +70,7 @@ export class InventoryValuationSheet extends FinancialSheet { cost: number; quantity: number; } { - return this.getItemTransaction(this.OUTInventoryCostLots, itemId); + return this.getItemTransaction(this.repository.OUTInventoryCostLots, itemId); } /** @@ -148,10 +137,13 @@ export class InventoryValuationSheet extends FinancialSheet { private filterNoneTransactions = ( valuationItem: IInventoryValuationItem, ): boolean => { - const transactionIN = this.INInventoryCostLots.get(valuationItem.id); - const transactionOUT = this.OUTInventoryCostLots.get(valuationItem.id); - - return transactionOUT || transactionIN; + const transactionIN = this.repository.INInventoryCostLots.get( + valuationItem.id, + ); + const transactionOUT = this.repository.OUTInventoryCostLots.get( + valuationItem.id, + ); + return !isEmpty(transactionOUT) || !isEmpty(transactionIN); }; /** @@ -200,8 +192,8 @@ export class InventoryValuationSheet extends FinancialSheet { * @param {IItem[]} items * @returns {IInventoryValuationItem[]} */ - private itemsMapper = (items: IItem[]): IInventoryValuationItem[] => { - return this.items.map(this.itemMapper.bind(this)); + private itemsMapper = (items: ModelObject[]): IInventoryValuationItem[] => { + return this.repository.inventoryItems.map(this.itemMapper.bind(this)); }; /** @@ -230,7 +222,7 @@ export class InventoryValuationSheet extends FinancialSheet { return R.compose( R.when(this.isItemsPostFilter, this.itemsFilter), this.itemsMapper, - )(this.items); + )(this.repository.inventoryItems); } /** diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetRepository.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetRepository.ts index 4fe0c800a..0b95dec27 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetRepository.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetRepository.ts @@ -1,17 +1,109 @@ -import { Injectable } from '@nestjs/common'; +import { isEmpty } from 'lodash'; +import { Inject, Injectable, Scope } from '@nestjs/common'; +import { IInventoryValuationReportQuery } from './InventoryValuationSheet.types'; +import { InventoryCostLotTracker } from '@/modules/InventoryCost/models/InventoryCostLotTracker'; +import { ModelObject } from 'objection'; +import { Item } from '@/modules/Items/models/Item'; +import { transformToMap } from '@/utils/transform-to-key'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; - -@Injectable() +@Injectable({ scope: Scope.TRANSIENT }) export class InventoryValuationSheetRepository { - asyncInit() { - const inventoryItems = await Item.query().onBuild((q) => { + @Inject(TenancyContext) + private readonly tenancyContext: TenancyContext; + + @Inject(InventoryCostLotTracker.name) + private readonly inventoryCostLotTracker: typeof InventoryCostLotTracker; + + @Inject(Item.name) + private readonly itemModel: typeof Item; + + /** + * The filter. + * @param {IInventoryValuationReportQuery} value + */ + filter: IInventoryValuationReportQuery; + + /** + * The inventory items. + * @param {ModelObject[]} value + */ + inventoryItems: ModelObject[]; + + /** + * The inventory cost `IN` transactions. + * @param {ModelObject[]} value + */ + INTransactions: ModelObject[]; + + /** + * The inventory cost `IN` transactions map. + * @param {Map} value + */ + INInventoryCostLots: Map; + + /** + * The inventory cost `OUT` transactions. + * @param {ModelObject[]} value + */ + OUTTransactions: ModelObject[]; + + /** + * The inventory cost `OUT` transactions map. + * @param {Map} value + */ + OUTInventoryCostLots: Map; + + /** + * The base currency. + * @param {string} value + */ + baseCurrency: string; + + /** + * Set the filter. + * @param {IInventoryValuationReportQuery} filter + */ + setFilter(filter: IInventoryValuationReportQuery) { + this.filter = filter; + } + + /** + * Initialize the repository. + */ + async asyncInit() { + await this.asyncItems(); + await this.asyncCostLotsTransactions(); + await this.asyncBaseCurrency(); + } + + /** + * Retrieve the base currency. + */ + async asyncBaseCurrency() { + const tenantMetadata = await this.tenancyContext.getTenantMetadata(); + this.baseCurrency = tenantMetadata.baseCurrency; + } + + /** + * Retrieve the inventory items. + */ + async asyncItems() { + const inventoryItems = await this.itemModel.query().onBuild((q) => { q.where('type', 'inventory'); - if (filter.itemsIds.length > 0) { - q.whereIn('id', filter.itemsIds); + if (this.filter.itemsIds.length > 0) { + q.whereIn('id', this.filter.itemsIds); } }); - const inventoryItemsIds = inventoryItems.map((item) => item.id); + this.inventoryItems = inventoryItems; + } + + /** + * Retrieve the inventory cost `IN` and `OUT` transactions. + */ + async asyncCostLotsTransactions() { + const inventoryItemsIds = this.inventoryItems.map((item) => item.id); const commonQuery = (builder) => { builder.whereIn('item_id', inventoryItemsIds); @@ -21,23 +113,29 @@ export class InventoryValuationSheetRepository { builder.select('itemId'); builder.groupBy('itemId'); - if (!isEmpty(query.branchesIds)) { - builder.modify('filterByBranches', query.branchesIds); + if (!isEmpty(this.filter.branchesIds)) { + builder.modify('filterByBranches', this.filter.branchesIds); } - if (!isEmpty(query.warehousesIds)) { - builder.modify('filterByWarehouses', query.warehousesIds); + if (!isEmpty(this.filter.warehousesIds)) { + builder.modify('filterByWarehouses', this.filter.warehousesIds); } }; // Retrieve the inventory cost `IN` transactions. - const INTransactions = await InventoryCostLotTracker.query() + const INTransactions = await this.inventoryCostLotTracker + .query() .onBuild(commonQuery) .where('direction', 'IN'); // Retrieve the inventory cost `OUT` transactions. - const OUTTransactions = await InventoryCostLotTracker.query() + const OUTTransactions = await this.inventoryCostLotTracker + .query() .onBuild(commonQuery) .where('direction', 'OUT'); - + this.INTransactions = INTransactions; + this.OUTTransactions = OUTTransactions; + + this.INInventoryCostLots = transformToMap(INTransactions, 'itemId'); + this.OUTInventoryCostLots = transformToMap(OUTTransactions, 'itemId'); } } diff --git a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetService.ts b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetService.ts index 80c112370..059c6648d 100644 --- a/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetService.ts +++ b/packages/server-nest/src/modules/FinancialStatements/modules/InventoryValuationSheet/InventoryValuationSheetService.ts @@ -9,19 +9,15 @@ import { InventoryValuationMetaInjectable } from './InventoryValuationSheetMeta' import { getInventoryValuationDefaultQuery } from './_constants'; import { InventoryCostLotTracker } from '@/modules/InventoryCost/models/InventoryCostLotTracker'; import { Item } from '@/modules/Items/models/Item'; +import { InventoryValuationSheetRepository } from './InventoryValuationSheetRepository'; +import { InventoryValuationSheet } from './InventoryValuationSheet'; @Injectable() export class InventoryValuationSheetService { constructor( - private readonly inventoryService: InventoryService, private readonly inventoryValuationMeta: InventoryValuationMetaInjectable, private readonly eventPublisher: EventEmitter2, - - @Inject(Item.name) - private readonly itemModel: typeof Item, - - @Inject(InventoryCostLotTracker.name) - private readonly inventoryCostLotTracker: typeof InventoryCostLotTracker, + private readonly inventoryValuationSheetRepository: InventoryValuationSheetRepository, ) {} /** @@ -35,12 +31,12 @@ export class InventoryValuationSheetService { ...getInventoryValuationDefaultQuery(), ...query, }; + this.inventoryValuationSheetRepository.setFilter(filter); + await this.inventoryValuationSheetRepository.asyncInit(); + const inventoryValuationInstance = new InventoryValuationSheet( filter, - inventoryItems, - INTransactions, - OUTTransactions, - tenant.metadata.baseCurrency, + this.inventoryValuationSheetRepository, ); // Retrieve the inventory valuation report data. const inventoryValuationData = inventoryValuationInstance.reportData(); @@ -51,9 +47,7 @@ export class InventoryValuationSheetService { // Triggers `onInventoryValuationViewed` event. await this.eventPublisher.emitAsync( events.reports.onInventoryValuationViewed, - { - query, - }, + { query }, ); return { diff --git a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts index ddd592db1..4a8e75349 100644 --- a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts +++ b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransaction.ts @@ -4,6 +4,7 @@ import * as moment from 'moment'; import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils'; import { TInventoryTransactionDirection } from '../types/InventoryCost.types'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; +import { InventoryTransactionMeta } from './InventoryTransactionMeta'; export class InventoryTransaction extends TenantBaseModel { date: Date | string; @@ -21,6 +22,8 @@ export class InventoryTransaction extends TenantBaseModel { warehouseId?: number; + meta?: InventoryTransactionMeta; + /** * Table name */ diff --git a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransactionMeta.ts b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransactionMeta.ts index 1889e2bed..30041855f 100644 --- a/packages/server-nest/src/modules/InventoryCost/models/InventoryTransactionMeta.ts +++ b/packages/server-nest/src/modules/InventoryCost/models/InventoryTransactionMeta.ts @@ -2,6 +2,10 @@ import { BaseModel } from '@/models/Model'; import { Model, raw } from 'objection'; export class InventoryTransactionMeta extends BaseModel { + transactionNumber!: string; + description!: string; + inventoryTransactionId!: number; + /** * Table name */ diff --git a/packages/server-nest/src/utils/transform-to-map-key-value.ts b/packages/server-nest/src/utils/transform-to-map-key-value.ts new file mode 100644 index 000000000..f70d623a1 --- /dev/null +++ b/packages/server-nest/src/utils/transform-to-map-key-value.ts @@ -0,0 +1,6 @@ +export const transformToMapKeyValue = ( + collection: T[], + key: keyof T, +): Map => { + return new Map(collection.map((item) => [item[key], item])); +};