From 67ae7ad037669b528dc0d117e9d22f1f4cf4d592 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 11 Mar 2025 22:12:08 +0200 Subject: [PATCH] refactor: inventory cost to nestjs --- packages/server-nest/package.json | 1 + .../src/common/config/inventory.ts | 5 + .../commands/BillInventoryTransactions.ts | 2 +- .../src/modules/Branches/Branches.module.ts | 32 +- .../Cashflow/CashflowActivateBranches.ts | 3 +- .../Expense/ExpensesActivateBranches.ts | 3 +- .../CreditNotesInventoryTransactions.ts | 2 +- ...nventoryAdjustmentInventoryTransactions.ts | 2 +- .../src/modules/InventoryCost/Inventory.ts | 214 ------------- .../InventoryCost/InventoryAverageCost.ts | 254 --------------- .../InventoryCost/InventoryCost.module.ts | 10 +- .../InventoryCost/InventoryCostApplication.ts | 35 +- .../InventoryCost/InventoryCostLotTracker.ts | 302 ------------------ .../InventoryCost/InventoryCosts.service.ts | 149 --------- .../InventoryAverageCostMethod.service.ts | 97 ++++++ .../commands/InventoryAverageCostMethod.ts | 120 +++++++ .../commands/InventoryComputeCost.service.ts | 159 +++++++++ .../InventoryCostGLStorage.service.ts | 13 +- .../commands/InventoryCosts.service.ts | 135 ++++++++ .../InventoryItemOpeningAvgCost.service.ts | 80 +++++ .../InventoryItemsQuantitySync.service.ts | 8 +- .../InventoryTransactions.service.ts | 15 +- .../StoreInventortyLotsCost.service.ts} | 31 +- .../processors/ComputeItemCost.processor.ts | 40 +++ .../InventoryCostGLBeforeWriteSubscriber.ts | 6 +- .../src/modules/Items/ItemsEntries.service.ts | 31 +- .../src/modules/Items/models/Item.ts | 2 + .../modules/SaleInvoices/SalesInvoicesCost.ts | 290 ++++++++--------- .../commands/CreateSaleInvoice.service.ts | 11 + .../inventory/InvoiceInventoryTransactions.ts | 2 +- .../SaleReceiptInventoryTransactions.ts | 2 +- .../TaxRates/SyncItemTaxRateOnEditTaxRate.ts | 99 +++--- .../SyncItemTaxRateOnEditTaxSubscriber.ts | 45 --- .../src/modules/TaxRates/TaxRate.module.ts | 12 +- .../WriteTaxTransactionsItemEntries.ts | 188 +++++------ .../CommandTaxRatesValidator.service.ts | 57 ++-- .../models/TaxRateTransaction.model.ts | 7 + .../BillTaxRateValidateSubscriber.ts | 140 ++++---- .../SaleInvoiceTaxRateValidateSubscriber.ts | 149 ++++----- .../SyncItemTaxRateOnEditTaxSubscriber.ts | 35 ++ .../WriteBillTaxTransactionsSubscriber.ts | 144 ++++----- .../WriteInvoiceTaxTransactionsSubscriber.ts | 140 ++++---- .../VendorCreditInventoryTransactions.ts | 2 +- pnpm-lock.yaml | 125 ++++---- 44 files changed, 1436 insertions(+), 1763 deletions(-) create mode 100644 packages/server-nest/src/common/config/inventory.ts delete mode 100644 packages/server-nest/src/modules/InventoryCost/Inventory.ts delete mode 100644 packages/server-nest/src/modules/InventoryCost/InventoryAverageCost.ts delete mode 100644 packages/server-nest/src/modules/InventoryCost/InventoryCostLotTracker.ts delete mode 100644 packages/server-nest/src/modules/InventoryCost/InventoryCosts.service.ts create mode 100644 packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.service.ts create mode 100644 packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.ts create mode 100644 packages/server-nest/src/modules/InventoryCost/commands/InventoryComputeCost.service.ts rename packages/server-nest/src/modules/InventoryCost/{ => commands}/InventoryCostGLStorage.service.ts (70%) create mode 100644 packages/server-nest/src/modules/InventoryCost/commands/InventoryCosts.service.ts create mode 100644 packages/server-nest/src/modules/InventoryCost/commands/InventoryItemOpeningAvgCost.service.ts rename packages/server-nest/src/modules/InventoryCost/{ => commands}/InventoryItemsQuantitySync.service.ts (91%) rename packages/server-nest/src/modules/InventoryCost/{ => commands}/InventoryTransactions.service.ts (91%) rename packages/server-nest/src/modules/InventoryCost/{InventoryCostMethod.ts => commands/StoreInventortyLotsCost.service.ts} (60%) create mode 100644 packages/server-nest/src/modules/InventoryCost/processors/ComputeItemCost.processor.ts delete mode 100644 packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts create mode 100644 packages/server-nest/src/modules/TaxRates/subscribers/SyncItemTaxRateOnEditTaxSubscriber.ts diff --git a/packages/server-nest/package.json b/packages/server-nest/package.json index 0dd77ccbe..3341ff4f6 100644 --- a/packages/server-nest/package.json +++ b/packages/server-nest/package.json @@ -41,6 +41,7 @@ "@types/ramda": "^0.30.2", "accounting": "^0.4.1", "async": "^3.2.0", + "async-mutex": "^0.5.0", "axios": "^1.6.0", "bluebird": "^3.7.2", "bull": "^4.16.3", diff --git a/packages/server-nest/src/common/config/inventory.ts b/packages/server-nest/src/common/config/inventory.ts new file mode 100644 index 000000000..1a5785521 --- /dev/null +++ b/packages/server-nest/src/common/config/inventory.ts @@ -0,0 +1,5 @@ +import { registerAs } from "@nestjs/config"; + +export default registerAs('inventory', () => ({ + scheduleComputeItemCost: process.env.INVENTORY_SCHEDULE_COMPUTE_ITEM_COST, +})); diff --git a/packages/server-nest/src/modules/Bills/commands/BillInventoryTransactions.ts b/packages/server-nest/src/modules/Bills/commands/BillInventoryTransactions.ts index c7af1b739..a22719e4a 100644 --- a/packages/server-nest/src/modules/Bills/commands/BillInventoryTransactions.ts +++ b/packages/server-nest/src/modules/Bills/commands/BillInventoryTransactions.ts @@ -3,7 +3,7 @@ import { Knex } from 'knex'; import { Bill } from '../models/Bill'; import { Inject, Injectable } from '@nestjs/common'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; -import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service'; +import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; @Injectable() diff --git a/packages/server-nest/src/modules/Branches/Branches.module.ts b/packages/server-nest/src/modules/Branches/Branches.module.ts index 10b1d0d6e..545a0f69f 100644 --- a/packages/server-nest/src/modules/Branches/Branches.module.ts +++ b/packages/server-nest/src/modules/Branches/Branches.module.ts @@ -51,22 +51,22 @@ import { FeaturesModule } from '../Features/Features.module'; BranchCommandValidator, BranchTransactionDTOTransformer, ManualJournalBranchesDTOTransformer, - // BillBranchValidateSubscriber, - // CreditNoteBranchValidateSubscriber, - // CreditNoteRefundBranchValidateSubscriber, - // ContactBranchValidateSubscriber, - // ExpenseBranchValidateSubscriber, - // InventoryAdjustmentBranchValidateSubscriber, - // ManualJournalBranchValidateSubscriber, - // PaymentMadeBranchValidateSubscriber, - // PaymentReceiveBranchValidateSubscriber, - // SaleEstimateBranchValidateSubscriber, - // SaleReceiptBranchValidateSubscriber, - // VendorCreditBranchValidateSubscriber, - // ValidateBranchExistance, - // ManualJournalBranchesValidator, - // CashflowTransactionsActivateBranches, - // ExpensesActivateBranches + BillBranchValidateSubscriber, + CreditNoteBranchValidateSubscriber, + CreditNoteRefundBranchValidateSubscriber, + ContactBranchValidateSubscriber, + ExpenseBranchValidateSubscriber, + InventoryAdjustmentBranchValidateSubscriber, + ManualJournalBranchValidateSubscriber, + PaymentMadeBranchValidateSubscriber, + PaymentReceiveBranchValidateSubscriber, + SaleEstimateBranchValidateSubscriber, + SaleReceiptBranchValidateSubscriber, + VendorCreditBranchValidateSubscriber, + ValidateBranchExistance, + ManualJournalBranchesValidator, + CashflowTransactionsActivateBranches, + ExpensesActivateBranches ], exports: [ BranchesSettingsService, diff --git a/packages/server-nest/src/modules/Branches/integrations/Cashflow/CashflowActivateBranches.ts b/packages/server-nest/src/modules/Branches/integrations/Cashflow/CashflowActivateBranches.ts index f1cd772ea..040cdd3ee 100644 --- a/packages/server-nest/src/modules/Branches/integrations/Cashflow/CashflowActivateBranches.ts +++ b/packages/server-nest/src/modules/Branches/integrations/Cashflow/CashflowActivateBranches.ts @@ -1,11 +1,12 @@ import { Knex } from 'knex'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { BankTransaction } from '@/modules/BankingTransactions/models/BankTransaction'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; @Injectable() export class CashflowTransactionsActivateBranches { constructor( + @Inject(BankTransaction.name) private readonly bankTransaction: TenantModelProxy, ) {} diff --git a/packages/server-nest/src/modules/Branches/integrations/Expense/ExpensesActivateBranches.ts b/packages/server-nest/src/modules/Branches/integrations/Expense/ExpensesActivateBranches.ts index 3c26123da..a39049fc5 100644 --- a/packages/server-nest/src/modules/Branches/integrations/Expense/ExpensesActivateBranches.ts +++ b/packages/server-nest/src/modules/Branches/integrations/Expense/ExpensesActivateBranches.ts @@ -1,11 +1,12 @@ import { Knex } from 'knex'; -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { Expense } from '@/modules/Expenses/models/Expense.model'; @Injectable() export class ExpensesActivateBranches { constructor( + @Inject(Expense.name) private readonly expenseModel: TenantModelProxy, ) {} diff --git a/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts index 95a1fc758..ff5ac4928 100644 --- a/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts +++ b/packages/server-nest/src/modules/CreditNotes/commands/CreditNotesInventoryTransactions.ts @@ -1,6 +1,6 @@ // @ts-nocheck import { Injectable } from '@nestjs/common'; -import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service'; +import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; import { CreditNote } from '../models/CreditNote'; import { Knex } from 'knex'; diff --git a/packages/server-nest/src/modules/InventoryAdjutments/inventory/InventoryAdjustmentInventoryTransactions.ts b/packages/server-nest/src/modules/InventoryAdjutments/inventory/InventoryAdjustmentInventoryTransactions.ts index c6c82d49a..3c6305b7f 100644 --- a/packages/server-nest/src/modules/InventoryAdjutments/inventory/InventoryAdjustmentInventoryTransactions.ts +++ b/packages/server-nest/src/modules/InventoryAdjutments/inventory/InventoryAdjustmentInventoryTransactions.ts @@ -2,7 +2,7 @@ import { Injectable } from "@nestjs/common"; import { Knex } from "knex"; import { InventoryAdjustment } from "../models/InventoryAdjustment"; import { InventoryTransaction } from "@/modules/InventoryCost/models/InventoryTransaction"; -import { InventoryTransactionsService } from "@/modules/InventoryCost/InventoryTransactions.service"; +import { InventoryTransactionsService } from "@/modules/InventoryCost/commands/InventoryTransactions.service"; @Injectable() export class InventoryAdjustmentInventoryTransactions { diff --git a/packages/server-nest/src/modules/InventoryCost/Inventory.ts b/packages/server-nest/src/modules/InventoryCost/Inventory.ts deleted file mode 100644 index d1e2a26fa..000000000 --- a/packages/server-nest/src/modules/InventoryCost/Inventory.ts +++ /dev/null @@ -1,214 +0,0 @@ -// import { pick } from 'lodash'; -// import { Inject, Injectable } from '@nestjs/common'; -// import { EventEmitter2 } from '@nestjs/event-emitter'; -// import { Knex } from 'knex'; -// import { -// IInventoryLotCost, -// IInventoryTransaction, -// TInventoryTransactionDirection, -// IItemEntry, -// IItemEntryTransactionType, -// IInventoryTransactionsCreatedPayload, -// IInventoryTransactionsDeletedPayload, -// IInventoryItemCostScheduledPayload, -// } from '@/interfaces'; -// import { InventoryAverageCostMethod } from './InventoryAverageCost'; -// import { InventoryCostLotTracker } from './InventoryCostLotTracker'; -// import { ItemsEntriesService } from '../Items/ItemsEntries.service'; -// import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; -// import { Item } from '../Items/models/Item'; -// import { SETTINGS_PROVIDER } from '../Settings/Settings.types'; -// import { SettingsStore } from '../Settings/SettingsStore'; -// import { events } from '@/common/events/events'; -// import { InventoryTransaction } from './models/InventoryTransaction'; -// import InventoryCostMethod from './InventoryCostMethod'; - -// @Injectable() -// export class InventoryService { -// constructor( -// private readonly eventEmitter: EventEmitter2, -// private readonly uow: UnitOfWork, - -// @Inject(InventoryTransaction.name) -// private readonly inventoryTransactionModel: typeof InventoryTransaction, - -// @Inject(InventoryCostLotTracker.name) -// private readonly inventoryCostLotTracker: typeof InventoryCostLotTracker, - -// @Inject(SETTINGS_PROVIDER) -// private readonly settings: SettingsStore, -// ) {} - -// /** -// * Transforms the items entries to inventory transactions. -// */ -// transformItemEntriesToInventory(transaction: { -// transactionId: number; -// transactionType: IItemEntryTransactionType; -// transactionNumber?: string; - -// exchangeRate?: number; - -// warehouseId: number | null; - -// date: Date | string; -// direction: TInventoryTransactionDirection; -// entries: IItemEntry[]; -// createdAt: Date; -// }): IInventoryTransaction[] { -// const exchangeRate = transaction.exchangeRate || 1; - -// return transaction.entries.map((entry: IItemEntry) => ({ -// ...pick(entry, ['itemId', 'quantity']), -// rate: entry.rate * exchangeRate, -// transactionType: transaction.transactionType, -// transactionId: transaction.transactionId, -// direction: transaction.direction, -// date: transaction.date, -// entryId: entry.id, -// createdAt: transaction.createdAt, -// costAccountId: entry.costAccountId, - -// warehouseId: entry.warehouseId || transaction.warehouseId, -// meta: { -// transactionNumber: transaction.transactionNumber, -// description: entry.description, -// }, -// })); -// } - -// async computeItemCost(fromDate: Date, itemId: number) { -// return this.uow.withTransaction((trx: Knex.Transaction) => { -// return this.computeInventoryItemCost(fromDate, itemId); -// }); -// } - -// /** -// * Computes the given item cost and records the inventory lots transactions -// * and journal entries based on the cost method FIFO, LIFO or average cost rate. -// * @param {Date} fromDate - From date. -// * @param {number} itemId - Item id. -// */ -// async computeInventoryItemCost( -// fromDate: Date, -// itemId: number, -// trx?: Knex.Transaction, -// ) { -// // Fetches the item with associated item category. -// const item = await Item.query().findById(itemId); - -// // Cannot continue if the given item was not inventory item. -// if (item.type !== 'inventory') { -// throw new Error('You could not compute item cost has no inventory type.'); -// } -// let costMethodComputer: InventoryCostMethod; - -// // Switch between methods based on the item cost method. -// switch ('AVG') { -// case 'FIFO': -// case 'LIFO': -// costMethodComputer = new InventoryCostLotTracker( -// tenantId, -// fromDate, -// itemId, -// ); -// break; -// case 'AVG': -// costMethodComputer = new InventoryAverageCostMethod( -// fromDate, -// itemId, -// trx, -// ); -// break; -// } -// return costMethodComputer.computeItemCost(); -// } - -// /** -// * Schedule item cost compute job. -// * @param {number} tenantId -// * @param {number} itemId -// * @param {Date} startingDate -// */ -// async scheduleComputeItemCost( -// tenantId: number, -// itemId: number, -// startingDate: Date | string, -// ) { -// const agenda = Container.get('agenda'); - -// const commonJobsQuery = { -// name: 'compute-item-cost', -// lastRunAt: { $exists: false }, -// 'data.tenantId': tenantId, -// 'data.itemId': itemId, -// }; -// // Cancel any `compute-item-cost` in the queue has upper starting date -// // with the same given item. -// await agenda.cancel({ -// ...commonJobsQuery, -// 'data.startingDate': { $lte: startingDate }, -// }); -// // Retrieve any `compute-item-cost` in the queue has lower starting date -// // with the same given item. -// const dependsJobs = await agenda.jobs({ -// ...commonJobsQuery, -// 'data.startingDate': { $gte: startingDate }, -// }); -// // If the depends jobs cleared. -// if (dependsJobs.length === 0) { -// await agenda.schedule( -// config.scheduleComputeItemCost, -// 'compute-item-cost', -// { -// startingDate, -// itemId, -// tenantId, -// }, -// ); -// // Triggers `onComputeItemCostJobScheduled` event. -// await this.eventPublisher.emitAsync( -// events.inventory.onComputeItemCostJobScheduled, -// { -// startingDate, -// itemId, -// tenantId, -// } as IInventoryItemCostScheduledPayload, -// ); -// } else { -// // Re-schedule the jobs that have higher date from current moment. -// await Promise.all( -// dependsJobs.map((job) => -// job.schedule(config.scheduleComputeItemCost).save(), -// ), -// ); -// } -// } - - -// /** -// * Mark item cost computing is running. -// * @param {boolean} isRunning - -// */ -// async markItemsCostComputeRunning(isRunning: boolean = true) { -// this.settings.set({ -// key: 'cost_compute_running', -// group: 'inventory', -// value: isRunning, -// }); -// await this.settings.save(); -// } - -// /** -// * Checks if the items cost compute is running. -// * @returns {boolean} -// */ -// isItemsCostComputeRunning() { -// return ( -// this.settings.get({ -// key: 'cost_compute_running', -// group: 'inventory', -// }) ?? false -// ); -// } -// } diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryAverageCost.ts b/packages/server-nest/src/modules/InventoryCost/InventoryAverageCost.ts deleted file mode 100644 index 2386bdb7d..000000000 --- a/packages/server-nest/src/modules/InventoryCost/InventoryAverageCost.ts +++ /dev/null @@ -1,254 +0,0 @@ -// import { pick } from 'lodash'; -// import { Knex } from 'knex'; -// import InventoryCostMethod from './InventoryCostMethod'; -// import { InventoryTransaction } from './models/InventoryTransaction'; - -// export class InventoryAverageCostMethod extends InventoryCostMethod { -// startingDate: Date; -// itemId: number; -// costTransactions: any[]; -// trx: Knex.Transaction; - -// /** -// * Constructor method. -// * @param {number} tenantId - The given tenant id. -// * @param {Date} startingDate - -// * @param {number} itemId - The given inventory item id. -// */ -// constructor( -// tenantId: number, -// startingDate: Date, -// itemId: number, -// trx?: Knex.Transaction, -// ) { -// super(tenantId, startingDate, itemId); - -// this.trx = trx; -// this.startingDate = startingDate; -// this.itemId = itemId; -// this.costTransactions = []; -// } - -// /** -// * Computes items costs from the given date using average cost method. -// * ---------- -// * - Calculate the items average cost in the given date. -// * - Remove the journal entries that associated to the inventory transacions -// * after the given date. -// * - Re-compute the inventory transactions and re-write the journal entries -// * after the given date. -// * ---------- -// * @async -// * @param {Date} startingDate -// * @param {number} referenceId -// * @param {string} referenceType -// */ -// public async computeItemCost() { -// const { InventoryTransaction } = this.tenantModels; -// const { averageCost, openingQuantity, openingCost } = -// await this.getOpeningAverageCost(this.startingDate, this.itemId); - -// const afterInvTransactions = -// await InventoryTransaction.query() -// .modify('filterDateRange', this.startingDate) -// .orderBy('date', 'ASC') -// .orderByRaw("FIELD(direction, 'IN', 'OUT')") -// .orderBy('createdAt', 'ASC') -// .where('item_id', this.itemId) -// .withGraphFetched('item'); - -// // Tracking inventroy transactions and retrieve cost transactions based on -// // average rate cost method. -// const costTransactions = this.trackingCostTransactions( -// afterInvTransactions, -// openingQuantity, -// openingCost, -// ); -// // Revert the inveout out lots transactions -// await this.revertTheInventoryOutLotTrans(); - -// // Store inventory lots cost transactions. -// await this.storeInventoryLotsCost(costTransactions); -// } - -// /** -// * Get items Average cost from specific date from inventory transactions. -// * @async -// * @param {Date} closingDate -// * @return {number} -// */ -// public async getOpeningAverageCost(closingDate: Date, itemId: number) { -// const { InventoryCostLotTracker } = this.tenantModels; - -// const commonBuilder = (builder: any) => { -// if (closingDate) { -// builder.where('date', '<', closingDate); -// } -// builder.where('item_id', itemId); -// builder.sum('rate as rate'); -// builder.sum('quantity as quantity'); -// builder.sum('cost as cost'); -// builder.first(); -// }; -// // Calculates the total inventory total quantity and rate `IN` transactions. -// const inInvSumationOper: Promise = InventoryCostLotTracker.query() -// .onBuild(commonBuilder) -// .where('direction', 'IN'); - -// // Calculates the total inventory total quantity and rate `OUT` transactions. -// const outInvSumationOper: Promise = InventoryCostLotTracker.query() -// .onBuild(commonBuilder) -// .where('direction', 'OUT'); - -// const [inInvSumation, outInvSumation] = await Promise.all([ -// inInvSumationOper, -// outInvSumationOper, -// ]); -// return this.computeItemAverageCost( -// inInvSumation?.cost || 0, -// inInvSumation?.quantity || 0, -// outInvSumation?.cost || 0, -// outInvSumation?.quantity || 0, -// ); -// } - -// /** -// * Computes the item average cost. -// * @static -// * @param {number} quantityIn -// * @param {number} rateIn -// * @param {number} quantityOut -// * @param {number} rateOut -// */ -// public computeItemAverageCost( -// totalCostIn: number, -// totalQuantityIn: number, - -// totalCostOut: number, -// totalQuantityOut: number, -// ) { -// const openingCost = totalCostIn - totalCostOut; -// const openingQuantity = totalQuantityIn - totalQuantityOut; - -// const averageCost = openingQuantity ? openingCost / openingQuantity : 0; - -// return { averageCost, openingCost, openingQuantity }; -// } - -// private getCost(rate: number, quantity: number) { -// return quantity ? rate * quantity : rate; -// } - -// /** -// * Records the journal entries from specific item inventory transactions. -// * @param {IInventoryTransaction[]} invTransactions -// * @param {number} openingAverageCost -// * @param {string} referenceType -// * @param {number} referenceId -// * @param {JournalCommand} journalCommands -// */ -// public trackingCostTransactions( -// invTransactions: InventoryTransaction[], -// openingQuantity: number = 0, -// openingCost: number = 0, -// ) { -// const costTransactions: any[] = []; - -// // Cumulative item quantity and cost. This will decrement after -// // each out transactions depends on its quantity and cost. -// let accQuantity: number = openingQuantity; -// let accCost: number = openingCost; - -// invTransactions.forEach((invTransaction: InventoryTransaction) => { -// const commonEntry = { -// invTransId: invTransaction.id, -// ...pick(invTransaction, [ -// 'date', -// 'direction', -// 'itemId', -// 'quantity', -// 'rate', -// 'entryId', -// 'transactionId', -// 'transactionType', -// 'createdAt', -// 'costAccountId', -// 'branchId', -// 'warehouseId', -// ]), -// inventoryTransactionId: invTransaction.id, -// }; -// switch (invTransaction.direction) { -// case 'IN': -// const inCost = this.getCost( -// invTransaction.rate, -// invTransaction.quantity, -// ); -// // Increases the quantity and cost in `IN` inventory transactions. -// accQuantity += invTransaction.quantity; -// accCost += inCost; - -// costTransactions.push({ -// ...commonEntry, -// cost: inCost, -// }); -// break; -// case 'OUT': -// // Average cost = Total cost / Total quantity -// const averageCost = accQuantity ? accCost / accQuantity : 0; - -// const quantity = -// accQuantity > 0 -// ? Math.min(invTransaction.quantity, accQuantity) -// : invTransaction.quantity; - -// // Cost = the transaction quantity * Average cost. -// const cost = this.getCost(averageCost, quantity); - -// // Revenue = transaction quanity * rate. -// // const revenue = quantity * invTransaction.rate; -// costTransactions.push({ -// ...commonEntry, -// quantity, -// cost, -// }); -// accQuantity = Math.max(accQuantity - quantity, 0); -// accCost = Math.max(accCost - cost, 0); - -// if (invTransaction.quantity > quantity) { -// const remainingQuantity = Math.max( -// invTransaction.quantity - quantity, -// 0, -// ); -// const remainingIncome = remainingQuantity * invTransaction.rate; - -// costTransactions.push({ -// ...commonEntry, -// quantity: remainingQuantity, -// cost: 0, -// }); -// accQuantity = Math.max(accQuantity - remainingQuantity, 0); -// accCost = Math.max(accCost - remainingIncome, 0); -// } -// break; -// } -// }); -// return costTransactions; -// } - -// /** -// * Reverts the inventory lots `OUT` transactions. -// * @param {Date} openingDate - Opening date. -// * @param {number} itemId - Item id. -// * @returns {Promise} -// */ -// async revertTheInventoryOutLotTrans(): Promise { -// const { InventoryCostLotTracker } = this.tenantModels; - -// await InventoryCostLotTracker.query(this.trx) -// .modify('filterDateRange', this.startingDate) -// .orderBy('date', 'DESC') -// .where('item_id', this.itemId) -// .delete(); -// } -// } diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryCost.module.ts b/packages/server-nest/src/modules/InventoryCost/InventoryCost.module.ts index 6f91ab040..0560c8a8a 100644 --- a/packages/server-nest/src/modules/InventoryCost/InventoryCost.module.ts +++ b/packages/server-nest/src/modules/InventoryCost/InventoryCost.module.ts @@ -1,13 +1,13 @@ import { Module } from '@nestjs/common'; -import { InventoryCostGLStorage } from './InventoryCostGLStorage.service'; +import { InventoryCostGLStorage } from './commands/InventoryCostGLStorage.service'; import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module'; import { InventoryCostLotTracker } from './models/InventoryCostLotTracker'; import { InventoryTransaction } from './models/InventoryTransaction'; import { InventoryCostGLBeforeWriteSubscriber } from './subscribers/InventoryCostGLBeforeWriteSubscriber'; -import { InventoryItemsQuantitySyncService } from './InventoryItemsQuantitySync.service'; -import { InventoryCostMethod } from './InventoryCostMethod'; -import { InventoryTransactionsService } from './InventoryTransactions.service'; +import { InventoryItemsQuantitySyncService } from './commands/InventoryItemsQuantitySync.service'; +import { InventoryTransactionsService } from './commands/InventoryTransactions.service'; import { LedgerModule } from '../Ledger/Ledger.module'; +import { InventoryComputeCostService } from './commands/InventoryComputeCost.service'; const models = [ RegisterTenancyModel(InventoryCostLotTracker), @@ -20,8 +20,8 @@ const models = [ InventoryCostGLBeforeWriteSubscriber, InventoryCostGLStorage, InventoryItemsQuantitySyncService, - InventoryCostMethod, InventoryTransactionsService, + InventoryComputeCostService, ], exports: [...models, InventoryTransactionsService], }) diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryCostApplication.ts b/packages/server-nest/src/modules/InventoryCost/InventoryCostApplication.ts index bc7cc74f9..b3aa6244d 100644 --- a/packages/server-nest/src/modules/InventoryCost/InventoryCostApplication.ts +++ b/packages/server-nest/src/modules/InventoryCost/InventoryCostApplication.ts @@ -1,12 +1,19 @@ import { Injectable } from '@nestjs/common'; -// import { InventoryItemCostService } from './InventoryCosts.service'; -import { IInventoryItemCostMeta } from './types/InventoryCost.types'; +import { InventoryItemCostService } from './commands/InventoryCosts.service'; @Injectable() export class InventoryCostApplication { - constructor( - // private readonly inventoryCost: InventoryItemCostService, - ) {} + constructor(private readonly inventoryCost: InventoryItemCostService) {} + + /** + * Computes the item cost. + * @param {Date} fromDate - From date. + * @param {number} itemId - Item id. + * @returns {Promise>} + */ + computeItemCost(fromDate: Date, itemId: number) { + return this.inventoryCost.getItemsInventoryValuation([itemId], fromDate); + } /** * Retrieves the items inventory valuation list. @@ -14,14 +21,14 @@ export class InventoryCostApplication { * @param {Date} date * @returns {Promise} */ - public getItemsInventoryValuationList = async ( + async getItemsInventoryValuation( itemsId: number[], - date: Date - ): Promise => { - // const itemsMap = await this.inventoryCost.getItemsInventoryValuation( - // itemsId, - // date - // ); - // return [...itemsMap.values()]; - }; + date: Date, + ): Promise { + const itemsMap = await this.inventoryCost.getItemsInventoryValuation( + itemsId, + date, + ); + return [...itemsMap.values()]; + } } diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryCostLotTracker.ts b/packages/server-nest/src/modules/InventoryCost/InventoryCostLotTracker.ts deleted file mode 100644 index 5df269782..000000000 --- a/packages/server-nest/src/modules/InventoryCost/InventoryCostLotTracker.ts +++ /dev/null @@ -1,302 +0,0 @@ -// import { pick, chain } from 'lodash'; -// import moment from 'moment'; -// import { IInventoryLotCost, IInventoryTransaction } from "interfaces"; -// import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod'; - -// type TCostMethod = 'FIFO' | 'LIFO'; - -// export class InventoryCostLotTracker extends InventoryCostMethod { -// startingDate: Date; -// itemId: number; -// costMethod: TCostMethod; -// itemsById: Map; -// inventoryINTrans: any; -// inventoryByItem: any; -// costLotsTransactions: IInventoryLotCost[]; -// inTransactions: any[]; -// outTransactions: IInventoryTransaction[]; -// revertJEntriesTransactions: IInventoryTransaction[]; - -// /** -// * Constructor method. -// * @param {Date} startingDate - -// * @param {number} itemId - -// * @param {string} costMethod - -// */ -// constructor( -// tenantId: number, -// startingDate: Date, -// itemId: number, -// costMethod: TCostMethod = 'FIFO' -// ) { -// super(tenantId, startingDate, itemId); - -// this.startingDate = startingDate; -// this.itemId = itemId; -// this.costMethod = costMethod; - -// // Collect cost lots transactions to insert them to the storage in bulk. -// this.costLotsTransactions= []; -// // Collect inventory transactions by item id. -// this.inventoryByItem = {}; -// // Collection `IN` inventory tranaction by transaction id. -// this.inventoryINTrans = {}; -// // Collects `IN` transactions. -// this.inTransactions = []; -// // Collects `OUT` transactions. -// this.outTransactions = []; -// } - -// /** -// * Computes items costs from the given date using FIFO or LIFO cost method. -// * -------- -// * - Revert the inventory lots after the given date. -// * - Remove all the journal entries from the inventory transactions -// * after the given date. -// * - Re-tracking the inventory lots from inventory transactions. -// * - Re-write the journal entries from the given inventory transactions. -// * @async -// * @return {void} -// */ -// public async computeItemCost(): Promise { -// await this.revertInventoryLots(this.startingDate); -// await this.fetchInvINTransactions(); -// await this.fetchInvOUTTransactions(); -// await this.fetchRevertInvJReferenceIds(); -// await this.fetchItemsMapped(); - -// this.trackingInventoryINLots(this.inTransactions); -// this.trackingInventoryOUTLots(this.outTransactions); - -// // Re-tracking the inventory `IN` and `OUT` lots costs. -// const storedTrackedInvLotsOper = this.storeInventoryLotsCost( -// this.costLotsTransactions, -// ); -// return Promise.all([ -// storedTrackedInvLotsOper, -// ]); -// } - -// /** -// * Fetched inventory transactions that has date from the starting date and -// * fetches available IN LOTs transactions that has remaining bigger than zero. -// * @private -// */ -// private async fetchInvINTransactions() { -// const { InventoryTransaction, InventoryLotCostTracker } = this.tenantModels; - -// const commonBuilder = (builder: any) => { -// builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC'); -// builder.where('item_id', this.itemId); -// }; -// const afterInvTransactions: IInventoryTransaction[] = -// await InventoryTransaction.query() -// .modify('filterDateRange', this.startingDate) -// .orderByRaw("FIELD(direction, 'IN', 'OUT')") -// .onBuild(commonBuilder) -// .orderBy('lot_number', (this.costMethod === 'LIFO') ? 'DESC' : 'ASC') -// .withGraphFetched('item'); - -// const availableINLots: IInventoryLotCost[] = -// await InventoryLotCostTracker.query() -// .modify('filterDateRange', null, this.startingDate) -// .orderBy('date', 'ASC') -// .where('direction', 'IN') -// .orderBy('lot_number', 'ASC') -// .onBuild(commonBuilder) -// .whereNot('remaining', 0); - -// this.inTransactions = [ -// ...availableINLots.map((trans) => ({ lotTransId: trans.id, ...trans })), -// ...afterInvTransactions.map((trans) => ({ invTransId: trans.id, ...trans })), -// ]; -// } - -// /** -// * Fetches inventory OUT transactions that has date from the starting date. -// * @private -// */ -// private async fetchInvOUTTransactions() { -// const { InventoryTransaction } = this.tenantModels; - -// const afterOUTTransactions: IInventoryTransaction[] = -// await InventoryTransaction.query() -// .modify('filterDateRange', this.startingDate) -// .orderBy('date', 'ASC') -// .orderBy('lot_number', 'ASC') -// .where('item_id', this.itemId) -// .where('direction', 'OUT') -// .withGraphFetched('item'); - -// this.outTransactions = [ ...afterOUTTransactions ]; -// } - -// private async fetchItemsMapped() { -// const itemsIds = chain(this.inTransactions).map((e) => e.itemId).uniq().value(); -// const { Item } = this.tenantModels; -// const storedItems = await Item.query() -// .where('type', 'inventory') -// .whereIn('id', itemsIds); - -// this.itemsById = new Map(storedItems.map((item: any) => [item.id, item])); -// } - -// /** -// * Fetch the inventory transactions that should revert its journal entries. -// * @private -// */ -// private async fetchRevertInvJReferenceIds() { -// const { InventoryTransaction } = this.tenantModels; -// const revertJEntriesTransactions: IInventoryTransaction[] = -// await InventoryTransaction.query() -// .select(['transactionId', 'transactionType']) -// .modify('filterDateRange', this.startingDate) -// .where('direction', 'OUT') -// .where('item_id', this.itemId); - -// this.revertJEntriesTransactions = revertJEntriesTransactions; -// } - -// /** -// * Revert the inventory lots to the given date by removing the inventory lots -// * transactions after the given date and increment the remaining that -// * associate to lot number. -// * @async -// * @return {Promise} -// */ -// public async revertInventoryLots(startingDate: Date) { -// const { InventoryLotCostTracker } = this.tenantModels; -// const asyncOpers: any[] = []; -// const inventoryLotsTrans = await InventoryLotCostTracker.query() -// .modify('filterDateRange', this.startingDate) -// .orderBy('date', 'DESC') -// .where('item_id', this.itemId) -// .where('direction', 'OUT'); - -// const deleteInvLotsTrans = InventoryLotCostTracker.query() -// .modify('filterDateRange', this.startingDate) -// .where('item_id', this.itemId) -// .delete(); - -// inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => { -// if (!inventoryLot.lotNumber) { return; } - -// const incrementOper = InventoryLotCostTracker.query() -// .where('lot_number', inventoryLot.lotNumber) -// .where('direction', 'IN') -// .increment('remaining', inventoryLot.quantity); - -// asyncOpers.push(incrementOper); -// }); -// return Promise.all([deleteInvLotsTrans, ...asyncOpers]); -// } - -// /** -// * Tracking inventory `IN` lots transactions. -// * @public -// * @param {IInventoryTransaction[]} inventoryTransactions - -// * @return {void} -// */ -// public trackingInventoryINLots( -// inventoryTransactions: IInventoryTransaction[], -// ) { -// inventoryTransactions.forEach((transaction: IInventoryTransaction) => { -// const { itemId, id } = transaction; -// (this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = [])); - -// const commonLotTransaction: IInventoryLotCost = { -// ...pick(transaction, [ -// 'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId', -// 'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining' -// ]), -// }; -// this.inventoryByItem[itemId].push(id); -// this.inventoryINTrans[id] = { -// ...commonLotTransaction, -// decrement: 0, -// remaining: commonLotTransaction.remaining || commonLotTransaction.quantity, -// }; -// this.costLotsTransactions.push(this.inventoryINTrans[id]); -// }); -// } - -// /** -// * Tracking inventory `OUT` lots transactions. -// * @public -// * @param {IInventoryTransaction[]} inventoryTransactions - -// * @return {void} -// */ -// public trackingInventoryOUTLots( -// inventoryTransactions: IInventoryTransaction[], -// ) { -// inventoryTransactions.forEach((transaction: IInventoryTransaction) => { -// const { itemId, id } = transaction; -// (this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = [])); - -// const commonLotTransaction: IInventoryLotCost = { -// ...pick(transaction, [ -// 'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId', 'entryId', -// 'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining' -// ]), -// }; -// let invRemaining = transaction.quantity; -// const idsShouldDel: number[] = []; - -// this.inventoryByItem?.[itemId]?.some((_invTransactionId: number) => { -// const _invINTransaction = this.inventoryINTrans[_invTransactionId]; - -// // Can't continue if the IN transaction remaining equals zero. -// if (invRemaining <= 0) { return true; } - -// // Can't continue if the IN transaction date is after the current transaction date. -// if (moment(_invINTransaction.date).isAfter(transaction.date)) { -// return true; -// } -// // Detarmines the 'OUT' lot tranasctions whether bigger than 'IN' remaining transaction. -// const biggerThanRemaining = (_invINTransaction.remaining - transaction.quantity) > 0; -// const decrement = (biggerThanRemaining) ? transaction.quantity : _invINTransaction.remaining; -// const maxDecrement = Math.min(decrement, invRemaining); -// const cost = maxDecrement * _invINTransaction.rate; - -// _invINTransaction.decrement += maxDecrement; -// _invINTransaction.remaining = Math.max( -// _invINTransaction.remaining - maxDecrement, -// 0, -// ); -// invRemaining = Math.max(invRemaining - maxDecrement, 0); - -// this.costLotsTransactions.push({ -// ...commonLotTransaction, -// cost, -// quantity: maxDecrement, -// lotNumber: _invINTransaction.lotNumber, -// }); -// // Pop the 'IN' lots that has zero remaining. -// if (_invINTransaction.remaining === 0) { -// idsShouldDel.push(_invTransactionId); -// } -// return false; -// }); -// if (invRemaining > 0) { -// this.costLotsTransactions.push({ -// ...commonLotTransaction, -// quantity: invRemaining, -// }); -// } -// this.removeInventoryItems(itemId, idsShouldDel); -// }); -// } - -// /** -// * Remove inventory transactions for specific item id. -// * @private -// * @param {number} itemId -// * @param {number[]} idsShouldDel -// * @return {void} -// */ -// private removeInventoryItems(itemId: number, idsShouldDel: number[]) { -// // Remove the IN transactions that has zero remaining amount. -// this.inventoryByItem[itemId] = this.inventoryByItem?.[itemId] -// ?.filter((transId: number) => idsShouldDel.indexOf(transId) === -1); -// } -// } \ No newline at end of file diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryCosts.service.ts b/packages/server-nest/src/modules/InventoryCost/InventoryCosts.service.ts deleted file mode 100644 index c83a8f1cb..000000000 --- a/packages/server-nest/src/modules/InventoryCost/InventoryCosts.service.ts +++ /dev/null @@ -1,149 +0,0 @@ -// import { keyBy, get } from 'lodash'; -// import { Knex } from 'knex'; -// import * as R from 'ramda'; -// import { IInventoryItemCostMeta } from './types/InventoryCost.types'; -// import { Inject, Injectable } from '@nestjs/common'; -// import { InventoryTransaction } from './models/InventoryTransaction'; -// import { InventoryCostLotTracker } from './models/InventoryCostLotTracker'; -// import { Item } from '../Items/models/Item'; - -// @Injectable() -// export class InventoryItemCostService { -// constructor( -// @Inject(InventoryTransaction.name) -// private readonly inventoryTransactionModel: typeof InventoryTransaction, - -// @Inject(InventoryCostLotTracker.name) -// private readonly inventoryCostLotTrackerModel: typeof InventoryCostLotTracker, - -// @Inject(Item.name) -// private readonly itemModel: typeof Item, -// ) {} - -// /** -// * Common query of items inventory valuation. -// * @param {number[]} itemsIds - -// * @param {Date} date - -// * @param {Knex.QueryBuilder} builder - -// */ -// private itemsInventoryValuationCommonQuery = R.curry( -// (itemsIds: number[], date: Date, builder: Knex.QueryBuilder) => { -// if (date) { -// builder.where('date', '<', date); -// } -// builder.whereIn('item_id', itemsIds); -// builder.sum('rate as rate'); -// builder.sum('quantity as quantity'); -// builder.sum('cost as cost'); - -// builder.groupBy('item_id'); -// builder.select(['item_id']); -// } -// ); - -// /** -// * -// * @param {} INValuationMap - -// * @param {} OUTValuationMap - -// * @param {number} itemId -// */ -// private getItemInventoryMeta = R.curry( -// ( -// INValuationMap, -// OUTValuationMap, -// itemId: number -// ): IInventoryItemCostMeta => { -// const INCost = get(INValuationMap, `[${itemId}].cost`, 0); -// const INQuantity = get(INValuationMap, `[${itemId}].quantity`, 0); - -// const OUTCost = get(OUTValuationMap, `[${itemId}].cost`, 0); -// const OUTQuantity = get(OUTValuationMap, `[${itemId}].quantity`, 0); - -// const valuation = INCost - OUTCost; -// const quantity = INQuantity - OUTQuantity; -// const average = quantity ? valuation / quantity : 0; - -// return { itemId, valuation, quantity, average }; -// } -// ); - -// /** -// * -// * @param {number} tenantId -// * @param {number} itemsId -// * @param {Date} date -// * @returns -// */ -// private getItemsInventoryINAndOutAggregated = ( -// itemsId: number[], -// date: Date -// ): Promise => { - -// const commonBuilder = this.itemsInventoryValuationCommonQuery( -// itemsId, -// date -// ); -// const INValuationOper = this.inventoryCostLotTrackerModel.query() -// .onBuild(commonBuilder) -// .where('direction', 'IN'); - -// const OUTValuationOper = this.inventoryCostLotTrackerModel.query() -// .onBuild(commonBuilder) -// .where('direction', 'OUT'); - -// return Promise.all([OUTValuationOper, INValuationOper]); -// }; - -// /** -// * -// * @param {number} tenantId - -// * @param {number[]} itemsIds - -// * @param {Date} date - -// */ -// private getItemsInventoryInOutMap = async ( -// itemsId: number[], -// date: Date -// ) => { -// const [OUTValuation, INValuation] = -// await this.getItemsInventoryINAndOutAggregated(itemsId, date); - -// const OUTValuationMap = keyBy(OUTValuation, 'itemId'); -// const INValuationMap = keyBy(INValuation, 'itemId'); - -// return [OUTValuationMap, INValuationMap]; -// }; - -// /** -// * -// * @param {number} tenantId -// * @param {number} itemId -// * @param {Date} date -// * @returns {Promise>} -// */ -// public getItemsInventoryValuation = async ( -// itemsId: number[], -// date: Date -// ): Promise> => { -// // Retrieves the inventory items. -// const items = await this.itemModel.query() -// .whereIn('id', itemsId) -// .where('type', 'inventory'); - -// // Retrieves the inventory items ids. -// const inventoryItemsIds: number[] = items.map((item) => item.id); - -// // Retreives the items inventory IN/OUT map. -// const [OUTValuationMap, INValuationMap] = -// await this.getItemsInventoryInOutMap(itemsId, date); - -// const getItemValuation = this.getItemInventoryMeta( -// INValuationMap, -// OUTValuationMap -// ); -// const itemsValuations = inventoryItemsIds.map(getItemValuation); -// const itemsValuationsMap = new Map( -// itemsValuations.map((i) => [i.itemId, i]) -// ); -// return itemsValuationsMap; -// }; -// } diff --git a/packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.service.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.service.ts new file mode 100644 index 000000000..e64761cec --- /dev/null +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.service.ts @@ -0,0 +1,97 @@ +import { pick } from 'lodash'; +import { Knex } from 'knex'; +import { InventoryTransaction } from '../models/InventoryTransaction'; +import { InventoryItemOpeningAvgCostService } from './InventoryItemOpeningAvgCost.service'; +import { Inject, Injectable } from '@nestjs/common'; +import { InventoryAverageCostMethod } from './InventoryAverageCostMethod'; +import { StoreInventoryLotsCostService } from './StoreInventortyLotsCost.service'; +import { TenantModelProxy } from '../../System/models/TenantBaseModel'; + +@Injectable() +export class InventoryAverageCostMethodService { + constructor( + private readonly itemOpeningAvgCostService: InventoryItemOpeningAvgCostService, + private readonly storeInventoryLotsCostService: StoreInventoryLotsCostService, + + @Inject(InventoryTransaction.name) + private readonly inventoryTransactionModel: TenantModelProxy< + typeof InventoryTransaction + >, + ) {} + + /** + * Retrieves the inventory cost lots. + * @param {Date} startingDate + * @param {number} itemId + * @param {number} openingQuantity + * @param {number} openingCost + * @returns {Promise} + */ + async getInventoryCostLots( + startingDate: Date, + itemId: number, + openingQuantity: number, + openingCost: number, + ) { + const afterInvTransactions = await this.inventoryTransactionModel() + .query() + .modify('filterDateRange', startingDate) + .orderBy('date', 'ASC') + .orderByRaw("FIELD(direction, 'IN', 'OUT')") + .orderBy('createdAt', 'ASC') + .where('item_id', itemId) + .withGraphFetched('item'); + + const avgCostTracker = new InventoryAverageCostMethod(); + + // Tracking inventroy transactions and retrieve cost transactions based on + // average rate cost method. + return avgCostTracker.trackingCostTransactions( + afterInvTransactions, + openingQuantity, + openingCost, + ); + } + /** + * Computes items costs from the given date using average cost method. + * ---------- + * - Calculate the items average cost in the given date. + * - Remove the journal entries that associated to the inventory transacions + * after the given date. + * - Re-compute the inventory transactions and re-write the journal entries + * after the given date. + * ---------- + * @param {Date} startingDate + * @param {number} itemId + * @param {Knex.Transaction} trx + */ + public async computeItemCost( + startingDate: Date, + itemId: number, + trx?: Knex.Transaction, + ) { + const { openingQuantity, openingCost } = + await this.itemOpeningAvgCostService.getOpeningAverageCost( + startingDate, + itemId, + ); + // Retrieves the new calculated inventory cost lots. + const inventoryCostLots = await this.getInventoryCostLots( + startingDate, + itemId, + openingQuantity, + openingCost, + ); + // Revert the inveout out lots transactions + await this.storeInventoryLotsCostService.revertInventoryCostLotTransactions( + startingDate, + itemId, + trx, + ); + // Store inventory lots cost transactions. + await this.storeInventoryLotsCostService.storeInventoryLotsCost( + inventoryCostLots, + trx, + ); + } +} diff --git a/packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.ts new file mode 100644 index 000000000..0e8d548ce --- /dev/null +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryAverageCostMethod.ts @@ -0,0 +1,120 @@ +import { pick } from 'lodash'; +import { Knex } from 'knex'; +import { InventoryTransaction } from '../models/InventoryTransaction'; + +export class InventoryAverageCostMethod { + /** + * Constructor method. + * @param {number} tenantId - The given tenant id. + * @param {Date} startingDate - + * @param {number} itemId - The given inventory item id. + */ + constructor() {} + + /** + * Computes the cost of the given rate and quantity. + * @param {number} rate - The given rate. + * @param {number} quantity - The given quantity. + * @returns {number} + */ + private getCost(rate: number, quantity: number) { + return quantity ? rate * quantity : rate; + } + + /** + * Records the journal entries from specific item inventory transactions. + * @param {IInventoryTransaction[]} invTransactions + * @param {number} openingAverageCost + * @param {string} referenceType + * @param {number} referenceId + * @param {JournalCommand} journalCommands + */ + public trackingCostTransactions( + invTransactions: InventoryTransaction[], + openingQuantity: number = 0, + openingCost: number = 0, + ) { + const costTransactions: any[] = []; + + // Cumulative item quantity and cost. This will decrement after + // each out transactions depends on its quantity and cost. + let accQuantity: number = openingQuantity; + let accCost: number = openingCost; + + invTransactions.forEach((invTransaction: InventoryTransaction) => { + const commonEntry = { + invTransId: invTransaction.id, + ...pick(invTransaction, [ + 'date', + 'direction', + 'itemId', + 'quantity', + 'rate', + 'entryId', + 'transactionId', + 'transactionType', + 'createdAt', + 'costAccountId', + 'branchId', + 'warehouseId', + ]), + inventoryTransactionId: invTransaction.id, + }; + switch (invTransaction.direction) { + case 'IN': + const inCost = this.getCost( + invTransaction.rate, + invTransaction.quantity, + ); + // Increases the quantity and cost in `IN` inventory transactions. + accQuantity += invTransaction.quantity; + accCost += inCost; + + costTransactions.push({ + ...commonEntry, + cost: inCost, + }); + break; + case 'OUT': + // Average cost = Total cost / Total quantity + const averageCost = accQuantity ? accCost / accQuantity : 0; + + const quantity = + accQuantity > 0 + ? Math.min(invTransaction.quantity, accQuantity) + : invTransaction.quantity; + + // Cost = the transaction quantity * Average cost. + const cost = this.getCost(averageCost, quantity); + + // Revenue = transaction quanity * rate. + // const revenue = quantity * invTransaction.rate; + costTransactions.push({ + ...commonEntry, + quantity, + cost, + }); + accQuantity = Math.max(accQuantity - quantity, 0); + accCost = Math.max(accCost - cost, 0); + + if (invTransaction.quantity > quantity) { + const remainingQuantity = Math.max( + invTransaction.quantity - quantity, + 0, + ); + const remainingIncome = remainingQuantity * invTransaction.rate; + + costTransactions.push({ + ...commonEntry, + quantity: remainingQuantity, + cost: 0, + }); + accQuantity = Math.max(accQuantity - remainingQuantity, 0); + accCost = Math.max(accCost - remainingIncome, 0); + } + break; + } + }); + return costTransactions; + } +} diff --git a/packages/server-nest/src/modules/InventoryCost/commands/InventoryComputeCost.service.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryComputeCost.service.ts new file mode 100644 index 000000000..c0a81e6b1 --- /dev/null +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryComputeCost.service.ts @@ -0,0 +1,159 @@ +import { pick } from 'lodash'; +import { Inject, Injectable } from '@nestjs/common'; +import { Knex } from 'knex'; +import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service'; +import { Item } from '../../Items/models/Item'; +import { SETTINGS_PROVIDER } from '../../Settings/Settings.types'; +import { SettingsStore } from '../../Settings/SettingsStore'; +import { InventoryTransaction } from '../models/InventoryTransaction'; +import { IItemEntryTransactionType } from '../../TransactionItemEntry/ItemEntry.types'; +import { ModelObject } from 'objection'; +import { ItemEntry } from '../../TransactionItemEntry/models/ItemEntry'; +import { TInventoryTransactionDirection } from '../types/InventoryCost.types'; +import { InventoryAverageCostMethodService } from './InventoryAverageCostMethod.service'; +import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; + +@Injectable() +export class InventoryComputeCostService { + constructor( + private readonly uow: UnitOfWork, + private readonly inventoryAverageCostMethod: InventoryAverageCostMethodService, + + @Inject(Item.name) + private readonly itemModel: TenantModelProxy, + + @Inject(SETTINGS_PROVIDER) + private readonly settingsStore: () => SettingsStore, + ) {} + + /** + * Compute item cost. + * @param {Date} fromDate - From date. + * @param {number} itemId - Item id. + * @returns {Promise} + */ + async computeItemCost(fromDate: Date, itemId: number) { + return this.uow.withTransaction((trx: Knex.Transaction) => { + return this.computeInventoryItemCost(fromDate, itemId, trx); + }); + } + + /** + * Computes the given item cost and records the inventory lots transactions + * and journal entries based on the cost method FIFO, LIFO or average cost rate. + * @param {Date} fromDate - From date. + * @param {number} itemId - Item id. + * @param {Knex.Transaction} trx - Knex transaction. + * @returns {Promise} + */ + async computeInventoryItemCost( + fromDate: Date, + itemId: number, + trx?: Knex.Transaction, + ) { + // Fetches the item with associated item category. + const item = await this.itemModel().query().findById(itemId); + + // Cannot continue if the given item was not inventory item. + if (item.type !== 'inventory') { + throw new Error('You could not compute item cost has no inventory type.'); + } + return this.inventoryAverageCostMethod.computeItemCost( + fromDate, + itemId, + trx, + ); + } + + /** + * Schedule item cost compute job. + * @param {number} tenantId + * @param {number} itemId + * @param {Date} startingDate + */ + async scheduleComputeItemCost( + tenantId: number, + itemId: number, + startingDate: Date | string, + ) { + // const agenda = Container.get('agenda'); + // const commonJobsQuery = { + // name: 'compute-item-cost', + // lastRunAt: { $exists: false }, + // 'data.tenantId': tenantId, + // 'data.itemId': itemId, + // }; + // // Cancel any `compute-item-cost` in the queue has upper starting date + // // with the same given item. + // await agenda.cancel({ + // ...commonJobsQuery, + // 'data.startingDate': { $lte: startingDate }, + // }); + // // Retrieve any `compute-item-cost` in the queue has lower starting date + // // with the same given item. + // const dependsJobs = await agenda.jobs({ + // ...commonJobsQuery, + // 'data.startingDate': { $gte: startingDate }, + // }); + // // If the depends jobs cleared. + // if (dependsJobs.length === 0) { + // await agenda.schedule( + // this.config.get('inventory.scheduleComputeItemCost'), + // 'compute-item-cost', + // { + // startingDate, + // itemId, + // tenantId, + // }, + // ); + // // Triggers `onComputeItemCostJobScheduled` event. + // await this.eventEmitter.emitAsync( + // events.inventory.onComputeItemCostJobScheduled, + // { + // startingDate, + // itemId, + // tenantId, + // } as IInventoryItemCostScheduledPayload, + // ); + // } else { + // // Re-schedule the jobs that have higher date from current moment. + // await Promise.all( + // dependsJobs.map((job) => + // job + // .schedule(this.config.get('inventory.scheduleComputeItemCost')) + // .save(), + // ), + // ); + // } + } + + /** + * Mark item cost computing is running. + * @param {boolean} isRunning - + */ + async markItemsCostComputeRunning(isRunning: boolean = true) { + const settings = await this.settingsStore(); + + settings.set({ + key: 'cost_compute_running', + group: 'inventory', + value: isRunning, + }); + await settings.save(); + } + + /** + * Checks if the items cost compute is running. + * @returns {boolean} + */ + async isItemsCostComputeRunning() { + const settings = await this.settingsStore(); + + return ( + settings.get({ + key: 'cost_compute_running', + group: 'inventory', + }) ?? false + ); + } +} diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryCostGLStorage.service.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryCostGLStorage.service.ts similarity index 70% rename from packages/server-nest/src/modules/InventoryCost/InventoryCostGLStorage.service.ts rename to packages/server-nest/src/modules/InventoryCost/commands/InventoryCostGLStorage.service.ts index 6e52b1157..a5fca2911 100644 --- a/packages/server-nest/src/modules/InventoryCost/InventoryCostGLStorage.service.ts +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryCostGLStorage.service.ts @@ -1,8 +1,9 @@ import { Knex } from 'knex'; import { Inject, Injectable } from '@nestjs/common'; -import { LedgerStorageService } from '../Ledger/LedgerStorage.service'; -import { Ledger } from '../Ledger/Ledger'; -import { AccountTransaction } from '../Accounts/models/AccountTransaction.model'; +import { LedgerStorageService } from '../../Ledger/LedgerStorage.service'; +import { Ledger } from '../../Ledger/Ledger'; +import { AccountTransaction } from '../../Accounts/models/AccountTransaction.model'; +import { TenantModelProxy } from '../../System/models/TenantBaseModel'; @Injectable() export class InventoryCostGLStorage { @@ -10,7 +11,9 @@ export class InventoryCostGLStorage { private readonly ledgerStorage: LedgerStorageService, @Inject(AccountTransaction.name) - private readonly accountTransactionModel: typeof AccountTransaction, + private readonly accountTransactionModel: TenantModelProxy< + typeof AccountTransaction + >, ) {} /** @@ -23,7 +26,7 @@ export class InventoryCostGLStorage { trx?: Knex.Transaction, ): Promise { // Retrieve transactions from specific date range and costable transactions only. - const transactions = await this.accountTransactionModel + const transactions = await this.accountTransactionModel() .query() .where('costable', true) .modify('filterDateRange', startingDate) diff --git a/packages/server-nest/src/modules/InventoryCost/commands/InventoryCosts.service.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryCosts.service.ts new file mode 100644 index 000000000..431fed8f9 --- /dev/null +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryCosts.service.ts @@ -0,0 +1,135 @@ +import { keyBy, get } from 'lodash'; +import { Knex } from 'knex'; +import * as R from 'ramda'; +import { IInventoryItemCostMeta } from '../types/InventoryCost.types'; +import { Inject, Injectable } from '@nestjs/common'; +import { InventoryTransaction } from '../models/InventoryTransaction'; +import { InventoryCostLotTracker } from '../models/InventoryCostLotTracker'; +import { Item } from '../../Items/models/Item'; + +@Injectable() +export class InventoryItemCostService { + constructor( + @Inject(InventoryTransaction.name) + private readonly inventoryTransactionModel: typeof InventoryTransaction, + + @Inject(InventoryCostLotTracker.name) + private readonly inventoryCostLotTrackerModel: typeof InventoryCostLotTracker, + + @Inject(Item.name) + private readonly itemModel: typeof Item, + ) {} + + /** + * + * @param {} INValuationMap - + * @param {} OUTValuationMap - + * @param {number} itemId + */ + private getItemInventoryMeta = R.curry( + ( + INValuationMap, + OUTValuationMap, + itemId: number, + ): IInventoryItemCostMeta => { + const INCost = get(INValuationMap, `[${itemId}].cost`, 0); + const INQuantity = get(INValuationMap, `[${itemId}].quantity`, 0); + + const OUTCost = get(OUTValuationMap, `[${itemId}].cost`, 0); + const OUTQuantity = get(OUTValuationMap, `[${itemId}].quantity`, 0); + + const valuation = INCost - OUTCost; + const quantity = INQuantity - OUTQuantity; + const average = quantity ? valuation / quantity : 0; + + return { itemId, valuation, quantity, average }; + }, + ); + + /** + * + * @param {number} tenantId + * @param {number} itemsId + * @param {Date} date + * @returns + */ + private getItemsInventoryINAndOutAggregated = ( + itemsId: number[], + date: Date, + ): Promise => { + const commonBuilder = (builder: Knex.QueryBuilder) => { + if (date) { + builder.where('date', '<', date); + } + builder.whereIn('item_id', itemsId); + builder.sum('rate as rate'); + builder.sum('quantity as quantity'); + builder.sum('cost as cost'); + + builder.groupBy('item_id'); + builder.select(['item_id']); + }; + const INValuationOper = this.inventoryCostLotTrackerModel + .query() + .onBuild(commonBuilder) + .where('direction', 'IN'); + + const OUTValuationOper = this.inventoryCostLotTrackerModel + .query() + .onBuild(commonBuilder) + .where('direction', 'OUT'); + + return Promise.all([OUTValuationOper, INValuationOper]); + }; + + /** + * + * @param {number} tenantId - + * @param {number[]} itemsIds - + * @param {Date} date - + */ + private getItemsInventoryInOutMap = async (itemsId: number[], date: Date) => { + const [OUTValuation, INValuation] = + await this.getItemsInventoryINAndOutAggregated(itemsId, date); + + const OUTValuationMap = keyBy(OUTValuation, 'itemId'); + const INValuationMap = keyBy(INValuation, 'itemId'); + + return [OUTValuationMap, INValuationMap]; + }; + + /** + * + * @param {number} tenantId + * @param {number} itemId + * @param {Date} date + * @returns {Promise>} + */ + public getItemsInventoryValuation = async ( + itemsId: number[], + date: Date, + ): Promise> => { + // Retrieves the inventory items. + const items = await this.itemModel + .query() + .whereIn('id', itemsId) + .where('type', 'inventory'); + + // Retrieves the inventory items ids. + const inventoryItemsIds: number[] = items.map((item) => item.id); + + // Retreives the items inventory IN/OUT map. + const [OUTValuationMap, INValuationMap] = + await this.getItemsInventoryInOutMap(itemsId, date); + + const getItemValuation = this.getItemInventoryMeta( + INValuationMap, + OUTValuationMap, + ); + const itemsValuations = inventoryItemsIds.map(getItemValuation); + const itemsValuationsMap = new Map( + itemsValuations.map((i) => [i.itemId, i]), + ); + return itemsValuationsMap; + }; +} diff --git a/packages/server-nest/src/modules/InventoryCost/commands/InventoryItemOpeningAvgCost.service.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryItemOpeningAvgCost.service.ts new file mode 100644 index 000000000..6cf6834e4 --- /dev/null +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryItemOpeningAvgCost.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@nestjs/common'; +import { TenantModelProxy } from '../../System/models/TenantBaseModel'; +import { InventoryCostLotTracker } from '../models/InventoryCostLotTracker'; + +@Injectable() +export class InventoryItemOpeningAvgCostService { + constructor( + private readonly inventoryCostLotTrackerModel: TenantModelProxy< + typeof InventoryCostLotTracker + >, + ) {} + + /** + * Get items Average cost from specific date from inventory transactions. + * @param {Date} closingDate - Closing date. + * @param {number} itemId - Item id. + * @return {Promise<{ + * averageCost: number, + * openingCost: number, + * openingQuantity: number, + * }>} + */ + public async getOpeningAverageCost(closingDate: Date, itemId: number) { + const commonBuilder = (builder: any) => { + if (closingDate) { + builder.where('date', '<', closingDate); + } + builder.where('item_id', itemId); + builder.sum('rate as rate'); + builder.sum('quantity as quantity'); + builder.sum('cost as cost'); + builder.first(); + }; + // Calculates the total inventory total quantity and rate `IN` transactions. + const inInvSumationOper = this.inventoryCostLotTrackerModel() + .query() + .onBuild(commonBuilder) + .where('direction', 'IN'); + + // Calculates the total inventory total quantity and rate `OUT` transactions. + const outInvSumationOper = this.inventoryCostLotTrackerModel() + .query() + .onBuild(commonBuilder) + .where('direction', 'OUT'); + + const [inInvSumation, outInvSumation] = await Promise.all([ + inInvSumationOper, + outInvSumationOper, + ]); + return this.computeItemAverageCost( + inInvSumation?.cost || 0, + inInvSumation?.quantity || 0, + outInvSumation?.cost || 0, + outInvSumation?.quantity || 0, + ); + } + + /** + * Computes the item average cost. + * @static + * @param {number} quantityIn + * @param {number} rateIn + * @param {number} quantityOut + * @param {number} rateOut + */ + public computeItemAverageCost( + totalCostIn: number, + totalQuantityIn: number, + + totalCostOut: number, + totalQuantityOut: number, + ) { + const openingCost = totalCostIn - totalCostOut; + const openingQuantity = totalQuantityIn - totalQuantityOut; + + const averageCost = openingQuantity ? openingCost / openingQuantity : 0; + + return { averageCost, openingCost, openingQuantity }; + } +} diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryItemsQuantitySync.service.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryItemsQuantitySync.service.ts similarity index 91% rename from packages/server-nest/src/modules/InventoryCost/InventoryItemsQuantitySync.service.ts rename to packages/server-nest/src/modules/InventoryCost/commands/InventoryItemsQuantitySync.service.ts index 6a87da8d3..3790b8a98 100644 --- a/packages/server-nest/src/modules/InventoryCost/InventoryItemsQuantitySync.service.ts +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryItemsQuantitySync.service.ts @@ -1,11 +1,11 @@ import { toSafeInteger } from 'lodash'; -import { IItemsQuantityChanges } from './types/InventoryCost.types'; +import { IItemsQuantityChanges } from '../types/InventoryCost.types'; import { Knex } from 'knex'; import { Inject } from '@nestjs/common'; -import { Item } from '../Items/models/Item'; +import { Item } from '../../Items/models/Item'; import { Injectable } from '@nestjs/common'; -import { InventoryTransaction } from './models/InventoryTransaction'; -import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { InventoryTransaction } from '../models/InventoryTransaction'; +import { TenantModelProxy } from '../../System/models/TenantBaseModel'; /** * Syncs the inventory transactions with inventory items quantity. diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryTransactions.service.ts b/packages/server-nest/src/modules/InventoryCost/commands/InventoryTransactions.service.ts similarity index 91% rename from packages/server-nest/src/modules/InventoryCost/InventoryTransactions.service.ts rename to packages/server-nest/src/modules/InventoryCost/commands/InventoryTransactions.service.ts index 82357b66c..689701415 100644 --- a/packages/server-nest/src/modules/InventoryCost/InventoryTransactions.service.ts +++ b/packages/server-nest/src/modules/InventoryCost/commands/InventoryTransactions.service.ts @@ -5,14 +5,14 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { IInventoryTransactionsDeletedPayload, TInventoryTransactionDirection, -} from './types/InventoryCost.types'; -import { InventoryCostLotTracker } from './models/InventoryCostLotTracker'; -import { InventoryTransaction } from './models/InventoryTransaction'; +} from '../types/InventoryCost.types'; +import { InventoryCostLotTracker } from '../models/InventoryCostLotTracker'; +import { InventoryTransaction } from '../models/InventoryTransaction'; import { events } from '@/common/events/events'; -import { IInventoryTransactionsCreatedPayload } from './types/InventoryCost.types'; -import { transformItemEntriesToInventory } from './utils'; -import { IItemEntryTransactionType } from '../TransactionItemEntry/ItemEntry.types'; -import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry'; +import { IInventoryTransactionsCreatedPayload } from '../types/InventoryCost.types'; +import { transformItemEntriesToInventory } from '../utils'; +import { IItemEntryTransactionType } from '../../TransactionItemEntry/ItemEntry.types'; +import { ItemEntry } from '../../TransactionItemEntry/models/ItemEntry'; export class InventoryTransactionsService { constructor( @@ -82,7 +82,6 @@ export class InventoryTransactionsService { /** * Records the inventory transactions from items entries that have (inventory) type. - * * @param {number} tenantId * @param {number} transactionId * @param {string} transactionType diff --git a/packages/server-nest/src/modules/InventoryCost/InventoryCostMethod.ts b/packages/server-nest/src/modules/InventoryCost/commands/StoreInventortyLotsCost.service.ts similarity index 60% rename from packages/server-nest/src/modules/InventoryCost/InventoryCostMethod.ts rename to packages/server-nest/src/modules/InventoryCost/commands/StoreInventortyLotsCost.service.ts index fc11d383f..cb60193a0 100644 --- a/packages/server-nest/src/modules/InventoryCost/InventoryCostMethod.ts +++ b/packages/server-nest/src/modules/InventoryCost/commands/StoreInventortyLotsCost.service.ts @@ -1,10 +1,11 @@ import { Knex } from 'knex'; -import { Inject } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { omit } from 'lodash'; -import { InventoryCostLotTracker } from './models/InventoryCostLotTracker'; -import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { InventoryCostLotTracker } from '../models/InventoryCostLotTracker'; +import { TenantModelProxy } from '../../System/models/TenantBaseModel'; -export class InventoryCostMethod { +@Injectable() +export class StoreInventoryLotsCostService { constructor( @Inject(InventoryCostLotTracker.name) private readonly inventoryCostLotTracker: TenantModelProxy< @@ -20,7 +21,7 @@ export class InventoryCostMethod { */ public storeInventoryLotsCost( costLotsTransactions: InventoryCostLotTracker[], - trx: Knex.Transaction, + trx?: Knex.Transaction, ): Promise { const opers: any = []; @@ -43,4 +44,24 @@ export class InventoryCostMethod { }); return Promise.all(opers); } + + /** + * Reverts the inventory lots `OUT` transactions. + * @param {Date} startingDate - Starting date. + * @param {number} itemId - Item id. + * @param {Knex.Transaction} trx - Knex transaction. + * @returns {Promise} + */ + async revertInventoryCostLotTransactions( + startingDate: Date, + itemId: number, + trx?: Knex.Transaction, + ): Promise { + await this.inventoryCostLotTracker() + .query(trx) + .modify('filterDateRange', startingDate) + .orderBy('date', 'DESC') + .where('item_id', itemId) + .delete(); + } } diff --git a/packages/server-nest/src/modules/InventoryCost/processors/ComputeItemCost.processor.ts b/packages/server-nest/src/modules/InventoryCost/processors/ComputeItemCost.processor.ts new file mode 100644 index 000000000..148c13aab --- /dev/null +++ b/packages/server-nest/src/modules/InventoryCost/processors/ComputeItemCost.processor.ts @@ -0,0 +1,40 @@ +import { JOB_REF, Processor } from '@nestjs/bullmq'; +import { Inject, Scope } from '@nestjs/common'; +import { Job } from 'bullmq'; +import { ClsService } from 'nestjs-cls'; +import { TenantJobPayload } from '@/interfaces/Tenant'; +import { InventoryComputeCostService } from '../commands/InventoryComputeCost.service'; + +interface ComputeItemCostJobPayload extends TenantJobPayload { + itemId: number; + startingDate: Date; +} + +@Processor({ + name: 'compute-item-cost', + scope: Scope.REQUEST, +}) +export class ComputeItemCostProcessor { + constructor( + private readonly inventoryComputeCostService: InventoryComputeCostService, + private readonly clsService: ClsService, + + @Inject(JOB_REF) + private readonly jobRef: Job, + ) {} + + /** + * Handle compute item cost job. + */ + async handleComputeItemCost() { + const { itemId, startingDate, organizationId, userId } = this.jobRef.data; + + this.clsService.set('organizationId', organizationId); + this.clsService.set('userId', userId); + + await this.inventoryComputeCostService.computeItemCost( + startingDate, + itemId, + ); + } +} diff --git a/packages/server-nest/src/modules/InventoryCost/subscribers/InventoryCostGLBeforeWriteSubscriber.ts b/packages/server-nest/src/modules/InventoryCost/subscribers/InventoryCostGLBeforeWriteSubscriber.ts index e695a5fa1..c72248e18 100644 --- a/packages/server-nest/src/modules/InventoryCost/subscribers/InventoryCostGLBeforeWriteSubscriber.ts +++ b/packages/server-nest/src/modules/InventoryCost/subscribers/InventoryCostGLBeforeWriteSubscriber.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { events } from '@/common/events/events'; import { IInventoryCostLotsGLEntriesWriteEvent } from '../types/InventoryCost.types'; -import { InventoryCostGLStorage } from '../InventoryCostGLStorage.service'; +import { InventoryCostGLStorage } from '../commands/InventoryCostGLStorage.service'; @Injectable() export class InventoryCostGLBeforeWriteSubscriber { @@ -21,7 +21,7 @@ export class InventoryCostGLBeforeWriteSubscriber { }: IInventoryCostLotsGLEntriesWriteEvent) { await this.inventoryCostGLStorage.revertInventoryCostGLEntries( startingDate, - trx + trx, ); - }; + } } diff --git a/packages/server-nest/src/modules/Items/ItemsEntries.service.ts b/packages/server-nest/src/modules/Items/ItemsEntries.service.ts index 2997df8f7..12d14ba8b 100644 --- a/packages/server-nest/src/modules/Items/ItemsEntries.service.ts +++ b/packages/server-nest/src/modules/Items/ItemsEntries.service.ts @@ -6,6 +6,7 @@ import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry'; import { ServiceError } from './ServiceError'; import { IItemEntryDTO } from '../TransactionItemEntry/ItemEntry.types'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { entriesAmountDiff } from '@/utils/entries-amount-diff'; const ERRORS = { ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND', @@ -176,21 +177,21 @@ export class ItemsEntriesService { ): Promise { const opers = []; - // const diffEntries = entriesAmountDiff( - // entries, - // oldEntries, - // 'quantity', - // 'itemId', - // ); - // diffEntries.forEach((entry: ItemEntry) => { - // const changeQuantityOper = this.itemRepository.changeNumber( - // { id: entry.itemId, type: 'inventory' }, - // 'quantityOnHand', - // entry.quantity, - // ); - // opers.push(changeQuantityOper); - // }); - // await Promise.all(opers); + const diffEntries = entriesAmountDiff( + entries, + oldEntries, + 'quantity', + 'itemId', + ); + diffEntries.forEach((entry: ItemEntry) => { + const changeQuantityOper = this.itemModel() + .query() + .where({ id: entry.itemId, type: 'inventory' }) + .modify('quantityOnHand', entry.quantity); + + opers.push(changeQuantityOper); + }); + await Promise.all(opers); } /** diff --git a/packages/server-nest/src/modules/Items/models/Item.ts b/packages/server-nest/src/modules/Items/models/Item.ts index 2bd448c71..4137da231 100644 --- a/packages/server-nest/src/modules/Items/models/Item.ts +++ b/packages/server-nest/src/modules/Items/models/Item.ts @@ -22,6 +22,8 @@ export class Item extends TenantBaseModel{ public readonly landedCost: boolean; public readonly note: string; public readonly userId: number; + public readonly sellTaxRateId: number; + public readonly purchaseTaxRateId: number; public readonly warehouse!: Warehouse; diff --git a/packages/server-nest/src/modules/SaleInvoices/SalesInvoicesCost.ts b/packages/server-nest/src/modules/SaleInvoices/SalesInvoicesCost.ts index a36c88e08..f6d2dde39 100644 --- a/packages/server-nest/src/modules/SaleInvoices/SalesInvoicesCost.ts +++ b/packages/server-nest/src/modules/SaleInvoices/SalesInvoicesCost.ts @@ -1,161 +1,149 @@ -// import { Mutex } from 'async-mutex'; -// import { Container, Service, Inject } from 'typedi'; -// import { chain } from 'lodash'; -// import moment from 'moment'; -// import { Knex } from 'knex'; -// import InventoryService from '@/services/Inventory/Inventory'; -// import { -// IInventoryCostLotsGLEntriesWriteEvent, -// IInventoryTransaction, -// } from '@/interfaces'; -// import UnitOfWork from '@/services/UnitOfWork'; -// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; -// import events from '@/subscribers/events'; +import { Mutex } from 'async-mutex'; +import { chain } from 'lodash'; +import moment from 'moment'; +import { Knex } from 'knex'; +import { Injectable } from '@nestjs/common'; +import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { ModelObject } from 'objection'; +import { InventoryTransaction } from '../InventoryCost/models/InventoryTransaction'; +import { IInventoryCostLotsGLEntriesWriteEvent } from '../InventoryCost/types/InventoryCost.types'; -// @Service() -// export class SaleInvoicesCost { -// @Inject() -// private inventoryService: InventoryService; +@Injectable() +export class SaleInvoicesCost { + constructor( + private readonly inventoryService: InventoryService, + private readonly uow: UnitOfWork, + private readonly eventPublisher: EventEmitter2, + ) {} -// @Inject() -// private uow: UnitOfWork; + /** + * Schedule sale invoice re-compute based on the item + * cost method and starting date. + * @param {number[]} itemIds - Inventory items ids. + * @param {Date} startingDate - Starting compute cost date. + * @return {Promise} + */ + async scheduleComputeCostByItemsIds( + inventoryItemsIds: number[], + startingDate: Date, + ): Promise { + const mutex = new Mutex(); + const asyncOpers = inventoryItemsIds.map( + async (inventoryItemId: number) => { + // @todo refactor the lock acquire to be distrbuted using Redis + // and run the cost schedule job after running invoice transaction. + const release = await mutex.acquire(); -// @Inject() -// private eventPublisher: EventPublisher; + try { + await this.inventoryService.scheduleComputeItemCost( + inventoryItemId, + startingDate, + ); + } finally { + release(); + } + }, + ); + await Promise.all(asyncOpers); + } -// /** -// * Schedule sale invoice re-compute based on the item -// * cost method and starting date. -// * @param {number[]} itemIds - Inventory items ids. -// * @param {Date} startingDate - Starting compute cost date. -// * @return {Promise} -// */ -// async scheduleComputeCostByItemsIds( -// tenantId: number, -// inventoryItemsIds: number[], -// startingDate: Date -// ): Promise { -// const mutex = new Mutex(); + /** + * Retrieve the max dated inventory transactions in the transactions that + * have the same item id. + * @param {ModelObject[]} inventoryTransactions + * @return {ModelObject[]} + */ + getMaxDateInventoryTransactions( + inventoryTransactions: ModelObject[], + ): ModelObject[] { + return chain(inventoryTransactions) + .reduce((acc: any, transaction) => { + const compatatorDate = acc[transaction.itemId]; -// const asyncOpers = inventoryItemsIds.map( -// async (inventoryItemId: number) => { -// // @todo refactor the lock acquire to be distrbuted using Redis -// // and run the cost schedule job after running invoice transaction. -// const release = await mutex.acquire(); + if ( + !compatatorDate || + moment(compatatorDate.date).isBefore(transaction.date) + ) { + return { + ...acc, + [transaction.itemId]: { + ...transaction, + }, + }; + } + return acc; + }, {}) + .values() + .value(); + } -// try { -// await this.inventoryService.scheduleComputeItemCost( -// tenantId, -// inventoryItemId, -// startingDate -// ); -// } finally { -// release(); -// } -// } -// ); -// await Promise.all(asyncOpers); -// } + /** + * Computes items costs by the given inventory transaction. + * @param {number} tenantId + * @param {IInventoryTransaction[]} inventoryTransactions + */ + async computeItemsCostByInventoryTransactions( + inventoryTransactions: ModelObject[], + ) { + const mutex = new Mutex(); + const reducedTransactions = this.getMaxDateInventoryTransactions( + inventoryTransactions, + ); + const asyncOpers = reducedTransactions.map(async (transaction) => { + const release = await mutex.acquire(); -// /** -// * Retrieve the max dated inventory transactions in the transactions that -// * have the same item id. -// * @param {IInventoryTransaction[]} inventoryTransactions -// * @return {IInventoryTransaction[]} -// */ -// getMaxDateInventoryTransactions( -// inventoryTransactions: IInventoryTransaction[] -// ): IInventoryTransaction[] { -// return chain(inventoryTransactions) -// .reduce((acc: any, transaction) => { -// const compatatorDate = acc[transaction.itemId]; + try { + await this.inventoryService.scheduleComputeItemCost( + transaction.itemId, + transaction.date, + ); + } finally { + release(); + } + }); + await Promise.all([...asyncOpers]); + } -// if ( -// !compatatorDate || -// moment(compatatorDate.date).isBefore(transaction.date) -// ) { -// return { -// ...acc, -// [transaction.itemId]: { -// ...transaction, -// }, -// }; -// } -// return acc; -// }, {}) -// .values() -// .value(); -// } + /** + * Schedule writing journal entries. + * @param {Date} startingDate - Starting date. + * @return {Promise} + */ + scheduleWriteJournalEntries(startingDate?: Date) { + const agenda = Container.get('agenda'); -// /** -// * Computes items costs by the given inventory transaction. -// * @param {number} tenantId -// * @param {IInventoryTransaction[]} inventoryTransactions -// */ -// async computeItemsCostByInventoryTransactions( -// tenantId: number, -// inventoryTransactions: IInventoryTransaction[] -// ) { -// const mutex = new Mutex(); -// const reducedTransactions = this.getMaxDateInventoryTransactions( -// inventoryTransactions -// ); -// const asyncOpers = reducedTransactions.map(async (transaction) => { -// const release = await mutex.acquire(); + return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', { + startingDate, + tenantId, + }); + } -// try { -// await this.inventoryService.scheduleComputeItemCost( -// tenantId, -// transaction.itemId, -// transaction.date -// ); -// } finally { -// release(); -// } -// }); -// await Promise.all([...asyncOpers]); -// } - -// /** -// * Schedule writing journal entries. -// * @param {Date} startingDate -// * @return {Promise} -// */ -// scheduleWriteJournalEntries(tenantId: number, startingDate?: Date) { -// const agenda = Container.get('agenda'); - -// return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', { -// startingDate, -// tenantId, -// }); -// } - -// /** -// * Writes cost GL entries from the inventory cost lots. -// * @param {number} tenantId - -// * @param {Date} startingDate - -// * @returns {Promise} -// */ -// public writeCostLotsGLEntries = (tenantId: number, startingDate: Date) => { -// return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { -// // Triggers event `onInventoryCostLotsGLEntriesBeforeWrite`. -// await this.eventPublisher.emitAsync( -// events.inventory.onCostLotsGLEntriesBeforeWrite, -// { -// tenantId, -// startingDate, -// trx, -// } as IInventoryCostLotsGLEntriesWriteEvent -// ); -// // Triggers event `onInventoryCostLotsGLEntriesWrite`. -// await this.eventPublisher.emitAsync( -// events.inventory.onCostLotsGLEntriesWrite, -// { -// tenantId, -// startingDate, -// trx, -// } as IInventoryCostLotsGLEntriesWriteEvent -// ); -// }); -// }; -// } + /** + * Writes cost GL entries from the inventory cost lots. + * @param {number} tenantId - + * @param {Date} startingDate - + * @returns {Promise} + */ + public writeCostLotsGLEntries = (startingDate: Date) => { + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers event `onInventoryCostLotsGLEntriesBeforeWrite`. + await this.eventPublisher.emitAsync( + events.inventory.onCostLotsGLEntriesBeforeWrite, + { + startingDate, + trx, + } as IInventoryCostLotsGLEntriesWriteEvent, + ); + // Triggers event `onInventoryCostLotsGLEntriesWrite`. + await this.eventPublisher.emitAsync( + events.inventory.onCostLotsGLEntriesWrite, + { + startingDate, + trx, + } as IInventoryCostLotsGLEntriesWriteEvent, + ); + }); + }; +} diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/CreateSaleInvoice.service.ts b/packages/server-nest/src/modules/SaleInvoices/commands/CreateSaleInvoice.service.ts index a016f71a8..ae505183f 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/CreateSaleInvoice.service.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/CreateSaleInvoice.service.ts @@ -19,6 +19,17 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; @Injectable() export class CreateSaleInvoice { + /** + * @param {ItemsEntriesService} itemsEntriesService - Items entries service. + * @param {CommandSaleInvoiceValidators} validators - Command sale invoice validators. + * @param {CommandSaleInvoiceDTOTransformer} transformerDTO - Command sale invoice DTO transformer. + * @param {EventEmitter2} eventPublisher - Event emitter. + * @param {SaleEstimateValidators} commandEstimateValidators - Command sale estimate validators. + * @param {UnitOfWork} uow - Unit of work. + * @param {TenantModelProxy} saleInvoiceModel - Sale invoice model. + * @param {TenantModelProxy} saleEstimateModel - Sale estimate model. + * @param {TenantModelProxy} customerModel - Customer model. + */ constructor( private readonly itemsEntriesService: ItemsEntriesService, private readonly validators: CommandSaleInvoiceValidators, diff --git a/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts b/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts index 05b71669e..13bd88419 100644 --- a/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts +++ b/packages/server-nest/src/modules/SaleInvoices/commands/inventory/InvoiceInventoryTransactions.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service'; +import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; import { Injectable } from '@nestjs/common'; import { Knex } from 'knex'; diff --git a/packages/server-nest/src/modules/SaleReceipts/inventory/SaleReceiptInventoryTransactions.ts b/packages/server-nest/src/modules/SaleReceipts/inventory/SaleReceiptInventoryTransactions.ts index c7fe01871..23851a329 100644 --- a/packages/server-nest/src/modules/SaleReceipts/inventory/SaleReceiptInventoryTransactions.ts +++ b/packages/server-nest/src/modules/SaleReceipts/inventory/SaleReceiptInventoryTransactions.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { Knex } from 'knex'; import { SaleReceipt } from '../models/SaleReceipt'; -import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service'; +import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; @Injectable() diff --git a/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxRate.ts b/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxRate.ts index f3ba72384..67878255e 100644 --- a/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxRate.ts +++ b/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxRate.ts @@ -1,55 +1,54 @@ -// import { Knex } from 'knex'; -// import { Inject, Service } from 'typedi'; -// import HasTenancyService from '../Tenancy/TenancyService'; +import { Knex } from 'knex'; +import { Inject, Injectable } from '@nestjs/common'; +import { Item } from '../Items/models/Item'; +import { TenantModelProxy } from '../System/models/TenantBaseModel'; -// @Service() -// export class SyncItemTaxRateOnEditTaxRate { -// @Inject() -// private tenancy: HasTenancyService; +@Injectable() +export class SyncItemTaxRateOnEditTaxRate { + constructor( + @Inject(Item.name) + private readonly itemModel: TenantModelProxy, + ) {} -// /** -// * Syncs the new tax rate created to item default sell tax rate. -// * @param {number} tenantId -// * @param {number} itemId -// * @param {number} sellTaxRateId -// */ -// public updateItemSellTaxRate = async ( -// tenantId: number, -// oldSellTaxRateId: number, -// sellTaxRateId: number, -// trx?: Knex.Transaction -// ) => { -// const { Item } = this.tenancy.models(tenantId); + /** + * Syncs the new tax rate created to item default sell tax rate. + * @param {number} itemId - Item id. + * @param {number} sellTaxRateId - Sell tax rate id. + */ + public updateItemSellTaxRate = async ( + oldSellTaxRateId: number, + sellTaxRateId: number, + trx?: Knex.Transaction, + ) => { + // Can't continue if the old and new sell tax rate id are equal. + if (oldSellTaxRateId === sellTaxRateId) return; -// // Can't continue if the old and new sell tax rate id are equal. -// if (oldSellTaxRateId === sellTaxRateId) return; + await this.itemModel() + .query() + .where('sellTaxRateId', oldSellTaxRateId) + .update({ + sellTaxRateId, + }); + }; -// await Item.query().where('sellTaxRateId', oldSellTaxRateId).update({ -// sellTaxRateId, -// }); -// }; + /** + * Syncs the new tax rate created to item default purchase tax rate. + * @param {number} itemId + * @param {number} purchaseTaxRateId + */ + public updateItemPurchaseTaxRate = async ( + oldPurchaseTaxRateId: number, + purchaseTaxRateId: number, + trx?: Knex.Transaction, + ) => { + // Can't continue if the old and new sell tax rate id are equal. + if (oldPurchaseTaxRateId === purchaseTaxRateId) return; -// /** -// * Syncs the new tax rate created to item default purchase tax rate. -// * @param {number} tenantId -// * @param {number} itemId -// * @param {number} purchaseTaxRateId -// */ -// public updateItemPurchaseTaxRate = async ( -// tenantId: number, -// oldPurchaseTaxRateId: number, -// purchaseTaxRateId: number, -// trx?: Knex.Transaction -// ) => { -// const { Item } = this.tenancy.models(tenantId); - -// // Can't continue if the old and new sell tax rate id are equal. -// if (oldPurchaseTaxRateId === purchaseTaxRateId) return; - -// await Item.query(trx) -// .where('purchaseTaxRateId', oldPurchaseTaxRateId) -// .update({ -// purchaseTaxRateId, -// }); -// }; -// } + await this.itemModel() + .query(trx) + .where('purchaseTaxRateId', oldPurchaseTaxRateId) + .update({ + purchaseTaxRateId, + }); + }; +} diff --git a/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts b/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts deleted file mode 100644 index e98b4fb65..000000000 --- a/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts +++ /dev/null @@ -1,45 +0,0 @@ -// import { Inject, Service } from 'typedi'; -// import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate'; -// import events from '@/subscribers/events'; -// import { ITaxRateEditedPayload } from '@/interfaces'; -// import { runAfterTransaction } from '../UnitOfWork/TransactionsHooks'; - -// @Service() -// export class SyncItemTaxRateOnEditTaxSubscriber { -// @Inject() -// private syncItemRateOnEdit: SyncItemTaxRateOnEditTaxRate; - -// /** -// * Attaches events with handles. -// */ -// public attach(bus) { -// bus.subscribe( -// events.taxRates.onEdited, -// this.handleSyncNewTaxRateToItemTaxRate -// ); -// } - -// /** -// * Syncs the new tax rate created to default item tax rates. -// * @param {ITaxRateEditedPayload} payload - -// */ -// private handleSyncNewTaxRateToItemTaxRate = async ({ -// taxRate, -// tenantId, -// oldTaxRate, -// trx, -// }: ITaxRateEditedPayload) => { -// runAfterTransaction(trx, async () => { -// await this.syncItemRateOnEdit.updateItemPurchaseTaxRate( -// tenantId, -// oldTaxRate.id, -// taxRate.id -// ); -// await this.syncItemRateOnEdit.updateItemSellTaxRate( -// tenantId, -// oldTaxRate.id, -// taxRate.id -// ); -// }); -// }; -// } diff --git a/packages/server-nest/src/modules/TaxRates/TaxRate.module.ts b/packages/server-nest/src/modules/TaxRates/TaxRate.module.ts index 94a72c3a1..b7006dbd9 100644 --- a/packages/server-nest/src/modules/TaxRates/TaxRate.module.ts +++ b/packages/server-nest/src/modules/TaxRates/TaxRate.module.ts @@ -12,6 +12,11 @@ import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { TaxRatesApplication } from './TaxRate.application'; import { ItemEntriesTaxTransactions } from './ItemEntriesTaxTransactions.service'; import { GetTaxRatesService } from './queries/GetTaxRates.service'; +import { WriteBillTaxTransactionsSubscriber } from './subscribers/WriteBillTaxTransactionsSubscriber'; +import { WriteInvoiceTaxTransactionsSubscriber } from './subscribers/WriteInvoiceTaxTransactionsSubscriber'; +import { BillTaxRateValidateSubscriber } from './subscribers/BillTaxRateValidateSubscriber'; +import { SaleInvoiceTaxRateValidateSubscriber } from './subscribers/SaleInvoiceTaxRateValidateSubscriber'; +import { SyncItemTaxRateOnEditTaxSubscriber } from './subscribers/SyncItemTaxRateOnEditTaxSubscriber'; @Module({ imports: [], @@ -28,7 +33,12 @@ import { GetTaxRatesService } from './queries/GetTaxRates.service'; TransformerInjectable, TenancyContext, TaxRatesApplication, - ItemEntriesTaxTransactions + ItemEntriesTaxTransactions, + WriteBillTaxTransactionsSubscriber, + WriteInvoiceTaxTransactionsSubscriber, + BillTaxRateValidateSubscriber, + SaleInvoiceTaxRateValidateSubscriber, + SyncItemTaxRateOnEditTaxSubscriber, ], exports: [ItemEntriesTaxTransactions], }) diff --git a/packages/server-nest/src/modules/TaxRates/WriteTaxTransactionsItemEntries.ts b/packages/server-nest/src/modules/TaxRates/WriteTaxTransactionsItemEntries.ts index d61d5ae11..a9813df1c 100644 --- a/packages/server-nest/src/modules/TaxRates/WriteTaxTransactionsItemEntries.ts +++ b/packages/server-nest/src/modules/TaxRates/WriteTaxTransactionsItemEntries.ts @@ -1,99 +1,105 @@ -// import { sumBy, chain, keyBy } from 'lodash'; -// import { IItemEntry, ITaxTransaction } from '@/interfaces'; -// import HasTenancyService from '../Tenancy/TenancyService'; -// import { Inject, Service } from 'typedi'; -// import { Knex } from 'knex'; +import { sumBy, chain, keyBy } from 'lodash'; +import { Knex } from 'knex'; +import { SaleInvoice } from '../SaleInvoices/models/SaleInvoice'; +import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { TaxRateModel } from './models/TaxRate.model'; +import { Inject, Injectable } from '@nestjs/common'; +import { ModelObject } from 'objection'; +import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry'; -// @Service() -// export class WriteTaxTransactionsItemEntries { -// @Inject() -// private tenancy: HasTenancyService; +@Injectable() +export class WriteTaxTransactionsItemEntries { + constructor( + @Inject(SaleInvoice.name) + private readonly taxRateTransactionModel: TenantModelProxy< + typeof SaleInvoice + >, -// /** -// * Writes the tax transactions from the given item entries. -// * @param {number} tenantId -// * @param {IItemEntry[]} itemEntries -// */ -// public async writeTaxTransactionsFromItemEntries( -// tenantId: number, -// itemEntries: IItemEntry[], -// trx?: Knex.Transaction -// ) { -// const { TaxRateTransaction, TaxRate } = this.tenancy.models(tenantId); -// const aggregatedEntries = this.aggregateItemEntriesByTaxCode(itemEntries); + @Inject(TaxRateModel.name) + private readonly taxRateModel: TenantModelProxy, + ) {} -// const entriesTaxRateIds = aggregatedEntries.map((entry) => entry.taxRateId); + /** + * Writes the tax transactions from the given item entries. + * @param {number} tenantId + * @param {IItemEntry[]} itemEntries + */ + public async writeTaxTransactionsFromItemEntries( + itemEntries: ModelObject[], + trx?: Knex.Transaction, + ) { + const aggregatedEntries = this.aggregateItemEntriesByTaxCode(itemEntries); + const entriesTaxRateIds = aggregatedEntries.map((entry) => entry.taxRateId); -// const taxRates = await TaxRate.query(trx).whereIn('id', entriesTaxRateIds); -// const taxRatesById = keyBy(taxRates, 'id'); + const taxRates = await this.taxRateModel() + .query(trx) + .whereIn('id', entriesTaxRateIds); + const taxRatesById = keyBy(taxRates, 'id'); -// const taxTransactions = aggregatedEntries.map((entry) => ({ -// taxRateId: entry.taxRateId, -// referenceType: entry.referenceType, -// referenceId: entry.referenceId, -// rate: entry.taxRate || taxRatesById[entry.taxRateId]?.rate, -// })) as ITaxTransaction[]; + const taxTransactions = aggregatedEntries.map((entry) => ({ + taxRateId: entry.taxRateId, + referenceType: entry.referenceType, + referenceId: entry.referenceId, + rate: entry.taxRate || taxRatesById[entry.taxRateId]?.rate, + })); + await this.taxRateTransactionModel() + .query(trx) + .upsertGraph(taxTransactions); + } -// await TaxRateTransaction.query(trx).upsertGraph(taxTransactions); -// } + /** + * Rewrites the tax rate transactions from the given item entries. + * @param {number} tenantId + * @param {IItemEntry[]} itemEntries + * @param {string} referenceType + * @param {number} referenceId + * @param {Knex.Transaction} trx + */ + public async rewriteTaxRateTransactionsFromItemEntries( + itemEntries: ModelObject[], + referenceType: string, + referenceId: number, + trx?: Knex.Transaction, + ) { + await Promise.all([ + this.removeTaxTransactionsFromItemEntries( + referenceId, + referenceType, + trx, + ), + this.writeTaxTransactionsFromItemEntries(itemEntries, trx), + ]); + } -// /** -// * Rewrites the tax rate transactions from the given item entries. -// * @param {number} tenantId -// * @param {IItemEntry[]} itemEntries -// * @param {string} referenceType -// * @param {number} referenceId -// * @param {Knex.Transaction} trx -// */ -// public async rewriteTaxRateTransactionsFromItemEntries( -// tenantId: number, -// itemEntries: IItemEntry[], -// referenceType: string, -// referenceId: number, -// trx?: Knex.Transaction -// ) { -// await Promise.all([ -// this.removeTaxTransactionsFromItemEntries( -// tenantId, -// referenceId, -// referenceType, -// trx -// ), -// this.writeTaxTransactionsFromItemEntries(tenantId, itemEntries, trx), -// ]); -// } + /** + * Aggregates by tax code id and sums the amount. + * @param {IItemEntry[]} itemEntries + * @returns {IItemEntry[]} + */ + private aggregateItemEntriesByTaxCode = ( + itemEntries: ModelObject[], + ): ModelObject[] => { + return chain(itemEntries.filter((item) => item.taxRateId)) + .groupBy((item) => item.taxRateId) + .values() + .map((group) => ({ ...group[0], amount: sumBy(group, 'amount') })) + .value(); + }; -// /** -// * Aggregates by tax code id and sums the amount. -// * @param {IItemEntry[]} itemEntries -// * @returns {IItemEntry[]} -// */ -// private aggregateItemEntriesByTaxCode = ( -// itemEntries: IItemEntry[] -// ): IItemEntry[] => { -// return chain(itemEntries.filter((item) => item.taxRateId)) -// .groupBy((item) => item.taxRateId) -// .values() -// .map((group) => ({ ...group[0], amount: sumBy(group, 'amount') })) -// .value(); -// }; - -// /** -// * Removes the tax transactions from the given item entries. -// * @param {number} tenantId - Tenant id. -// * @param {string} referenceType - Reference type. -// * @param {number} referenceId - Reference id. -// */ -// public async removeTaxTransactionsFromItemEntries( -// tenantId: number, -// referenceId: number, -// referenceType: string, -// trx?: Knex.Transaction -// ) { -// const { TaxRateTransaction } = this.tenancy.models(tenantId); - -// await TaxRateTransaction.query(trx) -// .where({ referenceType, referenceId }) -// .delete(); -// } -// } + /** + * Removes the tax transactions from the given item entries. + * @param {number} tenantId - Tenant id. + * @param {string} referenceType - Reference type. + * @param {number} referenceId - Reference id. + */ + public async removeTaxTransactionsFromItemEntries( + referenceId: number, + referenceType: string, + trx?: Knex.Transaction, + ) { + await this.taxRateTransactionModel() + .query(trx) + .where({ referenceType, referenceId }) + .delete(); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/commands/CommandTaxRatesValidator.service.ts b/packages/server-nest/src/modules/TaxRates/commands/CommandTaxRatesValidator.service.ts index 3982728c9..19c6473f3 100644 --- a/packages/server-nest/src/modules/TaxRates/commands/CommandTaxRatesValidator.service.ts +++ b/packages/server-nest/src/modules/TaxRates/commands/CommandTaxRatesValidator.service.ts @@ -7,6 +7,7 @@ import { Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { ServiceError } from '@/modules/Items/ServiceError'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types'; @Injectable() export class CommandTaxRatesValidators { @@ -70,46 +71,46 @@ export class CommandTaxRatesValidators { * @param {IItemEntryDTO[]} itemEntriesDTO * @throws {ServiceError} */ - // public async validateItemEntriesTaxCode(itemEntriesDTO: IItemEntryDTO[]) { - // const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxCode); - // const taxCodes = filteredTaxEntries.map((e) => e.taxCode); + public async validateItemEntriesTaxCode(itemEntriesDTO: IItemEntryDTO[]) { + const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxCode); + const taxCodes = filteredTaxEntries.map((e) => e.taxCode); - // // Can't validate if there is no tax codes. - // if (taxCodes.length === 0) return; + // Can't validate if there is no tax codes. + if (taxCodes.length === 0) return; - // const foundTaxCodes = await this.taxRateModel - // .query() - // .whereIn('code', taxCodes); - // const foundCodes = foundTaxCodes.map((tax) => tax.code); + const foundTaxCodes = await this.taxRateModel() + .query() + .whereIn('code', taxCodes); + const foundCodes = foundTaxCodes.map((tax) => tax.code); - // const notFoundTaxCodes = difference(taxCodes, foundCodes); + const notFoundTaxCodes = difference(taxCodes, foundCodes); - // if (notFoundTaxCodes.length > 0) { - // throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND); - // } - // } + if (notFoundTaxCodes.length > 0) { + throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND); + } + } /** * Validates the tax rate id of the given item entries DTO. * @param {IItemEntryDTO[]} itemEntriesDTO * @throws {ServiceError} */ - // public async validateItemEntriesTaxCodeId(itemEntriesDTO: IItemEntryDTO[]) { - // const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxRateId); - // const taxRatesIds = filteredTaxEntries.map((e) => e.taxRateId); + public async validateItemEntriesTaxCodeId(itemEntriesDTO: IItemEntryDTO[]) { + const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxRateId); + const taxRatesIds = filteredTaxEntries.map((e) => e.taxRateId); - // // Can't validate if there is no tax codes. - // if (taxRatesIds.length === 0) return; + // Can't validate if there is no tax codes. + if (taxRatesIds.length === 0) return; - // const foundTaxCodes = await this.taxRateModel - // .query() - // .whereIn('id', taxRatesIds); - // const foundTaxRatesIds = foundTaxCodes.map((tax) => tax.id); + const foundTaxCodes = await this.taxRateModel() + .query() + .whereIn('id', taxRatesIds); + const foundTaxRatesIds = foundTaxCodes.map((tax) => tax.id); - // const notFoundTaxCodes = difference(taxRatesIds, foundTaxRatesIds); + const notFoundTaxCodes = difference(taxRatesIds, foundTaxRatesIds); - // if (notFoundTaxCodes.length > 0) { - // throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND); - // } - // } + if (notFoundTaxCodes.length > 0) { + throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND); + } + } } diff --git a/packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts b/packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts index 19c162cb5..238dd345a 100644 --- a/packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts +++ b/packages/server-nest/src/modules/TaxRates/models/TaxRateTransaction.model.ts @@ -4,6 +4,13 @@ import { mixin, Model, raw } from 'objection'; import { BaseModel } from '@/models/Model'; export class TaxRateTransaction extends BaseModel { + id: number; + taxRateId: number; + referenceType: string; + referenceId: number; + rate: number; + taxAccountId: number; + /** * Table name */ diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/BillTaxRateValidateSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/BillTaxRateValidateSubscriber.ts index 3a5e54532..37e9962df 100644 --- a/packages/server-nest/src/modules/TaxRates/subscribers/BillTaxRateValidateSubscriber.ts +++ b/packages/server-nest/src/modules/TaxRates/subscribers/BillTaxRateValidateSubscriber.ts @@ -1,89 +1,61 @@ -// import { Inject, Service } from 'typedi'; -// import { IBillCreatingPayload, IBillEditingPayload } from '@/interfaces'; -// import events from '@/subscribers/events'; -// import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service'; +import { Injectable } from '@nestjs/common'; +import { IBillCreatingPayload } from '@/modules/Bills/Bills.types'; +import { IBillEditingPayload } from '@/modules/Bills/Bills.types'; -// @Service() -// export class BillTaxRateValidateSubscriber { -// @Inject() -// private taxRateDTOValidator: CommandTaxRatesValidators; +@Injectable() +export class BillTaxRateValidateSubscriber { + constructor( + private readonly taxRateDTOValidator: CommandTaxRatesValidators, + ) {} -// /** -// * Attaches events with handlers. -// */ -// public attach(bus) { -// bus.subscribe( -// events.bill.onCreating, -// this.validateBillEntriesTaxCodeExistanceOnCreating -// ); -// bus.subscribe( -// events.bill.onCreating, -// this.validateBillEntriesTaxIdExistanceOnCreating -// ); -// bus.subscribe( -// events.bill.onEditing, -// this.validateBillEntriesTaxCodeExistanceOnEditing -// ); -// bus.subscribe( -// events.bill.onEditing, -// this.validateBillEntriesTaxIdExistanceOnEditing -// ); -// return bus; -// } + /** + * Validate bill entries tax rate code existance when creating. + * @param {IBillCreatingPayload} + */ + @OnEvent(events.bill.onCreating) + async validateBillEntriesTaxCodeExistanceOnCreating({ + billDTO, + }: IBillCreatingPayload) { + await this.taxRateDTOValidator.validateItemEntriesTaxCode(billDTO.entries); + } -// /** -// * Validate bill entries tax rate code existance when creating. -// * @param {IBillCreatingPayload} -// */ -// private validateBillEntriesTaxCodeExistanceOnCreating = async ({ -// billDTO, -// tenantId, -// }: IBillCreatingPayload) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCode( -// tenantId, -// billDTO.entries -// ); -// }; + /** + * Validate the tax rate id existance when creating. + * @param {IBillCreatingPayload} + */ + @OnEvent(events.bill.onCreating) + async validateBillEntriesTaxIdExistanceOnCreating({ + billDTO, + }: IBillCreatingPayload) { + await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( + billDTO.entries, + ); + } -// /** -// * Validate the tax rate id existance when creating. -// * @param {IBillCreatingPayload} -// */ -// private validateBillEntriesTaxIdExistanceOnCreating = async ({ -// billDTO, -// tenantId, -// }: IBillCreatingPayload) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( -// tenantId, -// billDTO.entries -// ); -// }; + /** + * Validate bill entries tax rate code existance when editing. + * @param {IBillEditingPayload} + */ + @OnEvent(events.bill.onEditing) + async validateBillEntriesTaxCodeExistanceOnEditing({ + billDTO, + }: IBillEditingPayload) { + await this.taxRateDTOValidator.validateItemEntriesTaxCode(billDTO.entries); + } -// /** -// * Validate bill entries tax rate code existance when editing. -// * @param {IBillEditingPayload} -// */ -// private validateBillEntriesTaxCodeExistanceOnEditing = async ({ -// tenantId, -// billDTO, -// }: IBillEditingPayload) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCode( -// tenantId, -// billDTO.entries -// ); -// }; - -// /** -// * Validates the bill entries tax rate id existance when editing. -// * @param {ISaleInvoiceEditingPayload} payload - -// */ -// private validateBillEntriesTaxIdExistanceOnEditing = async ({ -// tenantId, -// billDTO, -// }: IBillEditingPayload) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( -// tenantId, -// billDTO.entries -// ); -// }; -// } + /** + * Validates the bill entries tax rate id existance when editing. + * @param {ISaleInvoiceEditingPayload} payload - + */ + @OnEvent(events.bill.onEditing) + async validateBillEntriesTaxIdExistanceOnEditing({ + billDTO, + }: IBillEditingPayload) { + await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( + billDTO.entries, + ); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber.ts index 52db3ec0c..10e95a713 100644 --- a/packages/server-nest/src/modules/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber.ts +++ b/packages/server-nest/src/modules/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber.ts @@ -1,92 +1,67 @@ -// import { Inject, Service } from 'typedi'; -// import { -// ISaleInvoiceCreatingPaylaod, -// ISaleInvoiceEditingPayload, -// } from '@/interfaces'; -// import events from '@/subscribers/events'; -// import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service'; +import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Injectable } from '@nestjs/common'; +import { + ISaleInvoiceCreatingPaylaod, + ISaleInvoiceEditingPayload, +} from '@/modules/SaleInvoices/SaleInvoice.types'; +import { events } from '@/common/events/events'; -// @Service() -// export class SaleInvoiceTaxRateValidateSubscriber { -// @Inject() -// private taxRateDTOValidator: CommandTaxRatesValidators; +@Injectable() +export class SaleInvoiceTaxRateValidateSubscriber { + constructor( + private readonly taxRateDTOValidator: CommandTaxRatesValidators, + ) {} -// /** -// * Attaches events with handlers. -// */ -// public attach(bus) { -// bus.subscribe( -// events.saleInvoice.onCreating, -// this.validateSaleInvoiceEntriesTaxCodeExistanceOnCreating -// ); -// bus.subscribe( -// events.saleInvoice.onCreating, -// this.validateSaleInvoiceEntriesTaxIdExistanceOnCreating -// ); -// bus.subscribe( -// events.saleInvoice.onEditing, -// this.validateSaleInvoiceEntriesTaxCodeExistanceOnEditing -// ); -// bus.subscribe( -// events.saleInvoice.onEditing, -// this.validateSaleInvoiceEntriesTaxIdExistanceOnEditing -// ); -// return bus; -// } + /** + * Validate invoice entries tax rate code existance when creating. + * @param {ISaleInvoiceCreatingPaylaod} + */ + @OnEvent(events.saleInvoice.onCreating) + async validateSaleInvoiceEntriesTaxCodeExistanceOnCreating({ + saleInvoiceDTO, + }: ISaleInvoiceCreatingPaylaod) { + await this.taxRateDTOValidator.validateItemEntriesTaxCode( + saleInvoiceDTO.entries, + ); + } -// /** -// * Validate invoice entries tax rate code existance when creating. -// * @param {ISaleInvoiceCreatingPaylaod} -// */ -// private validateSaleInvoiceEntriesTaxCodeExistanceOnCreating = async ({ -// saleInvoiceDTO, -// tenantId, -// }: ISaleInvoiceCreatingPaylaod) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCode( -// tenantId, -// saleInvoiceDTO.entries -// ); -// }; + /** + * Validate the tax rate id existance when creating. + * @param {ISaleInvoiceCreatingPaylaod} + */ + @OnEvent(events.saleInvoice.onCreating) + async validateSaleInvoiceEntriesTaxIdExistanceOnCreating({ + saleInvoiceDTO, + }: ISaleInvoiceCreatingPaylaod) { + await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( + saleInvoiceDTO.entries, + ); + } -// /** -// * Validate the tax rate id existance when creating. -// * @param {ISaleInvoiceCreatingPaylaod} -// */ -// private validateSaleInvoiceEntriesTaxIdExistanceOnCreating = async ({ -// saleInvoiceDTO, -// tenantId, -// }: ISaleInvoiceCreatingPaylaod) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( -// tenantId, -// saleInvoiceDTO.entries -// ); -// }; + /** + * Validate invoice entries tax rate code existance when editing. + * @param {ISaleInvoiceEditingPayload} + */ + @OnEvent(events.saleInvoice.onEditing) + async validateSaleInvoiceEntriesTaxCodeExistanceOnEditing({ + saleInvoiceDTO, + }: ISaleInvoiceEditingPayload) { + await this.taxRateDTOValidator.validateItemEntriesTaxCode( + saleInvoiceDTO.entries, + ); + } -// /** -// * Validate invoice entries tax rate code existance when editing. -// * @param {ISaleInvoiceEditingPayload} -// */ -// private validateSaleInvoiceEntriesTaxCodeExistanceOnEditing = async ({ -// tenantId, -// saleInvoiceDTO, -// }: ISaleInvoiceEditingPayload) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCode( -// tenantId, -// saleInvoiceDTO.entries -// ); -// }; - -// /** -// * Validates the invoice entries tax rate id existance when editing. -// * @param {ISaleInvoiceEditingPayload} payload - -// */ -// private validateSaleInvoiceEntriesTaxIdExistanceOnEditing = async ({ -// tenantId, -// saleInvoiceDTO, -// }: ISaleInvoiceEditingPayload) => { -// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( -// tenantId, -// saleInvoiceDTO.entries -// ); -// }; -// } + /** + * Validates the invoice entries tax rate id existance when editing. + * @param {ISaleInvoiceEditingPayload} payload - + */ + @OnEvent(events.saleInvoice.onEditing) + async validateSaleInvoiceEntriesTaxIdExistanceOnEditing({ + saleInvoiceDTO, + }: ISaleInvoiceEditingPayload) { + await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( + saleInvoiceDTO.entries, + ); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/SyncItemTaxRateOnEditTaxSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/SyncItemTaxRateOnEditTaxSubscriber.ts new file mode 100644 index 000000000..cd1aa20fb --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/subscribers/SyncItemTaxRateOnEditTaxSubscriber.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@nestjs/common'; +import { ITaxRateEditedPayload } from '../TaxRates.types'; +import { runAfterTransaction } from '@/modules/Tenancy/TenancyDB/TransactionsHooks'; +import { events } from '@/common/events/events'; +import { SyncItemTaxRateOnEditTaxRate } from '../SyncItemTaxRateOnEditTaxRate'; +import { OnEvent } from '@nestjs/event-emitter'; + +@Injectable() +export class SyncItemTaxRateOnEditTaxSubscriber { + constructor( + private readonly syncItemRateOnEdit: SyncItemTaxRateOnEditTaxRate, + ) {} + + /** + * Syncs the new tax rate created to default item tax rates. + * @param {ITaxRateEditedPayload} payload - + */ + @OnEvent(events.taxRates.onEdited) + async handleSyncNewTaxRateToItemTaxRate({ + taxRate, + oldTaxRate, + trx, + }: ITaxRateEditedPayload) { + runAfterTransaction(trx, async () => { + await this.syncItemRateOnEdit.updateItemPurchaseTaxRate( + oldTaxRate.id, + taxRate.id, + ); + await this.syncItemRateOnEdit.updateItemSellTaxRate( + oldTaxRate.id, + taxRate.id, + ); + }); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber.ts index 2968aa413..ac3e2f6e9 100644 --- a/packages/server-nest/src/modules/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber.ts +++ b/packages/server-nest/src/modules/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber.ts @@ -1,87 +1,65 @@ -// import { Inject, Service } from 'typedi'; -// import { -// IBIllEventDeletedPayload, -// IBillCreatedPayload, -// IBillEditedPayload, -// ISaleInvoiceCreatedPayload, -// ISaleInvoiceDeletedPayload, -// ISaleInvoiceEditedPayload, -// } from '@/interfaces'; -// import events from '@/subscribers/events'; -// import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries'; +import { + IBIllEventDeletedPayload, + IBillCreatedPayload, + IBillEditedPayload, +} from '@/modules/Bills/Bills.types'; +import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { Inject, Injectable } from '@nestjs/common'; -// @Service() -// export class WriteBillTaxTransactionsSubscriber { -// @Inject() -// private writeTaxTransactions: WriteTaxTransactionsItemEntries; +@Injectable() +export class WriteBillTaxTransactionsSubscriber { + constructor( + @Inject() + private readonly writeTaxTransactions: WriteTaxTransactionsItemEntries, + ) {} -// /** -// * Attaches events with handlers. -// */ -// public attach(bus) { -// bus.subscribe( -// events.bill.onCreated, -// this.writeInvoiceTaxTransactionsOnCreated -// ); -// bus.subscribe( -// events.bill.onEdited, -// this.rewriteInvoiceTaxTransactionsOnEdited -// ); -// bus.subscribe( -// events.bill.onDeleted, -// this.removeInvoiceTaxTransactionsOnDeleted -// ); -// return bus; -// } + /** + * Writes the bill tax transactions on invoice created. + * @param {ISaleInvoiceCreatingPaylaod} + */ + @OnEvent(events.bill.onCreated) + async writeInvoiceTaxTransactionsOnCreated({ + bill, + trx, + }: IBillCreatedPayload) { + await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries( + bill.entries, + trx, + ); + } -// /** -// * Writes the bill tax transactions on invoice created. -// * @param {ISaleInvoiceCreatingPaylaod} -// */ -// private writeInvoiceTaxTransactionsOnCreated = async ({ -// tenantId, -// bill, -// trx, -// }: IBillCreatedPayload) => { -// await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries( -// tenantId, -// bill.entries, -// trx -// ); -// }; + /** + * Rewrites the bill tax transactions on invoice edited. + * @param {IBillEditedPayload} payload - + */ + @OnEvent(events.bill.onEdited) + async rewriteInvoiceTaxTransactionsOnEdited({ + bill, + trx, + }: IBillEditedPayload) { + await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries( + bill.entries, + 'Bill', + bill.id, + trx, + ); + } -// /** -// * Rewrites the bill tax transactions on invoice edited. -// * @param {IBillEditedPayload} payload - -// */ -// private rewriteInvoiceTaxTransactionsOnEdited = async ({ -// tenantId, -// bill, -// trx, -// }: IBillEditedPayload) => { -// await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries( -// tenantId, -// bill.entries, -// 'Bill', -// bill.id, -// trx -// ); -// }; - -// /** -// * Removes the invoice tax transactions on invoice deleted. -// * @param {IBIllEventDeletedPayload} -// */ -// private removeInvoiceTaxTransactionsOnDeleted = async ({ -// tenantId, -// oldBill, -// trx, -// }: IBIllEventDeletedPayload) => { -// await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries( -// tenantId, -// oldBill.id, -// 'Bill', -// trx -// ); -// }; -// } + /** + * Removes the invoice tax transactions on invoice deleted. + * @param {IBIllEventDeletedPayload} + */ + @OnEvent(events.bill.onDeleted) + async removeInvoiceTaxTransactionsOnDeleted({ + oldBill, + trx, + }: IBIllEventDeletedPayload) { + await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries( + oldBill.id, + 'Bill', + trx, + ); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber.ts index c1adf3a4c..cd054bbcd 100644 --- a/packages/server-nest/src/modules/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber.ts +++ b/packages/server-nest/src/modules/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber.ts @@ -1,84 +1,64 @@ -// import { Inject, Service } from 'typedi'; -// import { -// ISaleInvoiceCreatedPayload, -// ISaleInvoiceDeletedPayload, -// ISaleInvoiceEditedPayload, -// } from '@/interfaces'; -// import events from '@/subscribers/events'; -// import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries'; +import { + ISaleInvoiceDeletedPayload, + ISaleInvoiceEditedPayload, +} from '@/modules/SaleInvoices/SaleInvoice.types'; +import { OnEvent } from '@nestjs/event-emitter'; +import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries'; +import { events } from '@/common/events/events'; +import { ISaleInvoiceCreatedPayload } from '@/modules/SaleInvoices/SaleInvoice.types'; +import { Injectable } from '@nestjs/common'; -// @Service() -// export class WriteInvoiceTaxTransactionsSubscriber { -// @Inject() -// private writeTaxTransactions: WriteTaxTransactionsItemEntries; +@Injectable() +export class WriteInvoiceTaxTransactionsSubscriber { + constructor( + private readonly writeTaxTransactions: WriteTaxTransactionsItemEntries, + ) {} -// /** -// * Attaches events with handlers. -// */ -// public attach(bus) { -// bus.subscribe( -// events.saleInvoice.onCreated, -// this.writeInvoiceTaxTransactionsOnCreated -// ); -// bus.subscribe( -// events.saleInvoice.onEdited, -// this.rewriteInvoiceTaxTransactionsOnEdited -// ); -// bus.subscribe( -// events.saleInvoice.onDelete, -// this.removeInvoiceTaxTransactionsOnDeleted -// ); -// return bus; -// } + /** + * Writes the invoice tax transactions on invoice created. + * @param {ISaleInvoiceCreatingPaylaod} + */ + @OnEvent(events.saleInvoice.onCreated) + async writeInvoiceTaxTransactionsOnCreated({ + saleInvoice, + trx, + }: ISaleInvoiceCreatedPayload) { + await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries( + saleInvoice.entries, + trx, + ); + } -// /** -// * Writes the invoice tax transactions on invoice created. -// * @param {ISaleInvoiceCreatingPaylaod} -// */ -// private writeInvoiceTaxTransactionsOnCreated = async ({ -// tenantId, -// saleInvoice, -// trx -// }: ISaleInvoiceCreatedPayload) => { -// await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries( -// tenantId, -// saleInvoice.entries, -// trx -// ); -// }; + /** + * Rewrites the invoice tax transactions on invoice edited. + * @param {ISaleInvoiceEditedPayload} payload - + */ + @OnEvent(events.saleInvoice.onEdited) + async rewriteInvoiceTaxTransactionsOnEdited({ + saleInvoice, + trx, + }: ISaleInvoiceEditedPayload) { + await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries( + saleInvoice.entries, + 'SaleInvoice', + saleInvoice.id, + trx, + ); + } -// /** -// * Rewrites the invoice tax transactions on invoice edited. -// * @param {ISaleInvoiceEditedPayload} payload - -// */ -// private rewriteInvoiceTaxTransactionsOnEdited = async ({ -// tenantId, -// saleInvoice, -// trx, -// }: ISaleInvoiceEditedPayload) => { -// await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries( -// tenantId, -// saleInvoice.entries, -// 'SaleInvoice', -// saleInvoice.id, -// trx -// ); -// }; - -// /** -// * Removes the invoice tax transactions on invoice deleted. -// * @param {ISaleInvoiceEditingPayload} -// */ -// private removeInvoiceTaxTransactionsOnDeleted = async ({ -// tenantId, -// oldSaleInvoice, -// trx -// }: ISaleInvoiceDeletedPayload) => { -// await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries( -// tenantId, -// oldSaleInvoice.id, -// 'SaleInvoice', -// trx -// ); -// }; -// } + /** + * Removes the invoice tax transactions on invoice deleted. + * @param {ISaleInvoiceEditingPayload} + */ + @OnEvent(events.saleInvoice.onDelete) + async removeInvoiceTaxTransactionsOnDeleted({ + oldSaleInvoice, + trx, + }: ISaleInvoiceDeletedPayload) { + await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries( + oldSaleInvoice.id, + 'SaleInvoice', + trx, + ); + } +} diff --git a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts index 9b26e6395..d35cec5b3 100644 --- a/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts +++ b/packages/server-nest/src/modules/VendorCredit/commands/VendorCreditInventoryTransactions.ts @@ -2,7 +2,7 @@ import { Knex } from 'knex'; import { Injectable } from '@nestjs/common'; import { VendorCredit } from '../models/VendorCredit'; -import { InventoryTransactionsService } from '@/modules/InventoryCost/InventoryTransactions.service'; +import { InventoryTransactionsService } from '@/modules/InventoryCost/commands/InventoryTransactions.service'; import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service'; @Injectable() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 964c2b8ec..c9f5c413c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -547,6 +547,9 @@ importers: async: specifier: ^3.2.0 version: 3.2.5 + async-mutex: + specifier: ^0.5.0 + version: 0.5.0 axios: specifier: ^1.6.0 version: 1.7.7 @@ -1650,7 +1653,7 @@ packages: '@smithy/util-middleware': 3.0.0 '@smithy/util-retry': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 transitivePeerDependencies: - '@aws-sdk/client-sts' - aws-crt @@ -1745,7 +1748,7 @@ packages: '@smithy/util-middleware': 3.0.0 '@smithy/util-retry': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 transitivePeerDependencies: - aws-crt dev: false @@ -1760,7 +1763,7 @@ packages: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 fast-xml-parser: 4.2.5 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/credential-provider-cognito-identity@3.583.0: @@ -1840,7 +1843,7 @@ packages: '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -1927,7 +1930,7 @@ packages: buffer: 5.6.0 events: 3.3.0 stream-browserify: 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-bucket-endpoint@3.577.0: @@ -1940,7 +1943,7 @@ packages: '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 '@smithy/util-config-provider': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-expect-continue@3.577.0: @@ -1950,7 +1953,7 @@ packages: '@aws-sdk/types': 3.577.0 '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-flexible-checksums@3.577.0: @@ -1964,7 +1967,7 @@ packages: '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-host-header@3.577.0: @@ -1974,7 +1977,7 @@ packages: '@aws-sdk/types': 3.577.0 '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-location-constraint@3.577.0: @@ -1983,7 +1986,7 @@ packages: dependencies: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-logger@3.577.0: @@ -1992,7 +1995,7 @@ packages: dependencies: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-recursion-detection@3.577.0: @@ -2002,7 +2005,7 @@ packages: '@aws-sdk/types': 3.577.0 '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-sdk-s3@3.582.0: @@ -2017,7 +2020,7 @@ packages: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 '@smithy/util-config-provider': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-signing@3.577.0: @@ -2030,7 +2033,7 @@ packages: '@smithy/signature-v4': 3.0.0 '@smithy/types': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-ssec@3.577.0: @@ -2039,7 +2042,7 @@ packages: dependencies: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/middleware-user-agent@3.583.0: @@ -2050,7 +2053,7 @@ packages: '@aws-sdk/util-endpoints': 3.583.0 '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/region-config-resolver@3.577.0: @@ -2062,7 +2065,7 @@ packages: '@smithy/types': 3.0.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/s3-request-presigner@3.583.0: @@ -2088,7 +2091,7 @@ packages: '@smithy/protocol-http': 4.0.0 '@smithy/signature-v4': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.583.0): @@ -2110,7 +2113,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/util-arn-parser@3.568.0: @@ -2127,7 +2130,7 @@ packages: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 '@smithy/util-endpoints': 2.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/util-format-url@3.577.0: @@ -2137,7 +2140,7 @@ packages: '@aws-sdk/types': 3.577.0 '@smithy/querystring-builder': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/util-locate-window@3.568.0: @@ -2153,7 +2156,7 @@ packages: '@aws-sdk/types': 3.577.0 '@smithy/types': 3.0.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/util-user-agent-node@3.577.0: @@ -2168,7 +2171,7 @@ packages: '@aws-sdk/types': 3.577.0 '@smithy/node-config-provider': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@aws-sdk/util-utf8-browser@3.259.0: @@ -2182,7 +2185,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@babel/code-frame@7.10.4: @@ -7799,7 +7802,7 @@ packages: hasBin: true dependencies: nx: 19.0.7 - tslib: 2.6.2 + tslib: 2.8.0 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' @@ -9571,7 +9574,7 @@ packages: '@smithy/types': 3.0.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/core@2.0.1: @@ -9585,7 +9588,7 @@ packages: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/credential-provider-imds@3.0.0: @@ -9614,7 +9617,7 @@ packages: dependencies: '@smithy/eventstream-serde-universal': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/eventstream-serde-config-resolver@3.0.0: @@ -9622,7 +9625,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/eventstream-serde-node@3.0.0: @@ -9631,7 +9634,7 @@ packages: dependencies: '@smithy/eventstream-serde-universal': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/eventstream-serde-universal@3.0.0: @@ -9650,7 +9653,7 @@ packages: '@smithy/querystring-builder': 3.0.0 '@smithy/types': 3.0.0 '@smithy/util-base64': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/hash-blob-browser@3.0.0: @@ -9659,7 +9662,7 @@ packages: '@smithy/chunked-blob-reader': 3.0.0 '@smithy/chunked-blob-reader-native': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/hash-node@3.0.0: @@ -9669,7 +9672,7 @@ packages: '@smithy/types': 3.0.0 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/hash-stream-node@3.0.0: @@ -9678,14 +9681,14 @@ packages: dependencies: '@smithy/types': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/invalid-dependency@3.0.0: resolution: {integrity: sha512-F6wBBaEFgJzj0s4KUlliIGPmqXemwP6EavgvDqYwCH40O5Xr2iMHvS8todmGVZtuJCorBkXsYLyTu4PuizVq5g==} dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/is-array-buffer@3.0.0: @@ -9700,7 +9703,7 @@ packages: dependencies: '@smithy/types': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/middleware-content-length@3.0.0: @@ -9709,7 +9712,7 @@ packages: dependencies: '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/middleware-endpoint@3.0.0: @@ -9722,7 +9725,7 @@ packages: '@smithy/types': 3.0.0 '@smithy/url-parser': 3.0.0 '@smithy/util-middleware': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/middleware-retry@3.0.1: @@ -9736,7 +9739,7 @@ packages: '@smithy/types': 3.0.0 '@smithy/util-middleware': 3.0.0 '@smithy/util-retry': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 uuid: 9.0.1 dev: false @@ -9745,7 +9748,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/middleware-stack@3.0.0: @@ -9753,7 +9756,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/node-config-provider@3.0.0: @@ -9763,7 +9766,7 @@ packages: '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/node-http-handler@3.0.0: @@ -9774,7 +9777,7 @@ packages: '@smithy/protocol-http': 4.0.0 '@smithy/querystring-builder': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/property-provider@3.0.0: @@ -9790,7 +9793,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/querystring-builder@3.0.0: @@ -9847,14 +9850,14 @@ packages: '@smithy/protocol-http': 4.0.0 '@smithy/types': 3.0.0 '@smithy/util-stream': 3.0.1 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/types@3.0.0: resolution: {integrity: sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw==} engines: {node: '>=16.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/url-parser@3.0.0: @@ -9862,7 +9865,7 @@ packages: dependencies: '@smithy/querystring-parser': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-base64@3.0.0: @@ -9871,20 +9874,20 @@ packages: dependencies: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-body-length-browser@3.0.0: resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} dependencies: - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-body-length-node@3.0.0: resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} engines: {node: '>=16.0.0'} dependencies: - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-buffer-from@3.0.0: @@ -9910,7 +9913,7 @@ packages: '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 bowser: 2.11.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-defaults-mode-node@3.0.1: @@ -9923,7 +9926,7 @@ packages: '@smithy/property-provider': 3.0.0 '@smithy/smithy-client': 3.0.1 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-endpoints@2.0.0: @@ -9932,7 +9935,7 @@ packages: dependencies: '@smithy/node-config-provider': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-hex-encoding@3.0.0: @@ -9956,7 +9959,7 @@ packages: dependencies: '@smithy/service-error-classification': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-stream@3.0.1: @@ -9970,7 +9973,7 @@ packages: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-uri-escape@3.0.0: @@ -9985,7 +9988,7 @@ packages: engines: {node: '>=16.0.0'} dependencies: '@smithy/util-buffer-from': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@smithy/util-waiter@3.0.0: @@ -9994,7 +9997,7 @@ packages: dependencies: '@smithy/abort-controller': 3.0.0 '@smithy/types': 3.0.0 - tslib: 2.6.2 + tslib: 2.8.0 dev: false /@socket.io/component-emitter@3.1.2: @@ -13600,7 +13603,7 @@ packages: engines: {node: '>=14.15.0'} dependencies: js-yaml: 3.14.1 - tslib: 2.6.2 + tslib: 2.8.0 dev: true /@zkochan/js-yaml@0.0.7: @@ -14382,7 +14385,7 @@ packages: resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==} engines: {node: '>=4'} dependencies: - tslib: 2.6.2 + tslib: 2.8.0 dev: true /ast-types@0.15.2: @@ -19738,7 +19741,7 @@ packages: resolution: {integrity: sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==} engines: {node: '>= 10'} dependencies: - tslib: 2.6.2 + tslib: 2.8.0 dev: false /file-system-cache@2.3.0: @@ -32615,7 +32618,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/core': 0.1.1 - tslib: 2.6.2 + tslib: 2.8.0 dev: true /tailwindcss@3.4.14(ts-node@10.9.2):