diff --git a/packages/server/src/modules/SaleReceipts/SaleReceiptCostGLEntries.ts b/packages/server/src/modules/SaleReceipts/SaleReceiptCostGLEntries.ts new file mode 100644 index 000000000..1743288f2 --- /dev/null +++ b/packages/server/src/modules/SaleReceipts/SaleReceiptCostGLEntries.ts @@ -0,0 +1,143 @@ +import * as R from 'ramda'; +import { Knex } from 'knex'; +import { Inject, Injectable } from '@nestjs/common'; +import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { InventoryCostLotTracker } from '../InventoryCost/models/InventoryCostLotTracker'; +import { LedgerStorageService } from '../Ledger/LedgerStorage.service'; +import { groupInventoryTransactionsByTypeId } from '../InventoryCost/utils'; +import { Ledger } from '../Ledger/Ledger'; +import { AccountNormal } from '@/interfaces/Account'; +import { ILedgerEntry } from '../Ledger/types/Ledger.types'; +import { increment } from '@/utils/increment'; + +@Injectable() +export class SaleReceiptCostGLEntries { + constructor( + private readonly ledgerStorage: LedgerStorageService, + + @Inject(InventoryCostLotTracker.name) + private readonly inventoryCostLotTracker: TenantModelProxy< + typeof InventoryCostLotTracker + >, + ) {} + + /** + * Writes journal entries from sales receipts. + * @param {Date} startingDate - Starting date. + * @param {Knex.Transaction} trx - Transaction. + */ + public writeInventoryCostJournalEntries = async ( + startingDate: Date, + trx?: Knex.Transaction, + ): Promise => { + const inventoryCostLotTrans = await this.inventoryCostLotTracker() + .query() + .where('direction', 'OUT') + .where('transaction_type', 'SaleReceipt') + .where('cost', '>', 0) + .modify('filterDateRange', startingDate) + .orderBy('date', 'ASC') + .withGraphFetched('receipt') + .withGraphFetched('item') + .withGraphFetched('itemEntry'); + + const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans); + + await this.ledgerStorage.commit(ledger, trx); + }; + + /** + * Retrieves the inventory cost lots ledger. + */ + private getInventoryCostLotsLedger = ( + inventoryCostLots: InventoryCostLotTracker[], + ) => { + const inventoryTransactions = + groupInventoryTransactionsByTypeId(inventoryCostLots); + + const entries = inventoryTransactions + .map(this.getSaleReceiptCostGLEntries) + .flat(); + return new Ledger(entries); + }; + + /** + * Builds the common GL entry fields for a sale receipt cost. + */ + private getReceiptCostGLCommonEntry = ( + inventoryCostLot: InventoryCostLotTracker, + ) => { + return { + currencyCode: inventoryCostLot.receipt.currencyCode, + exchangeRate: inventoryCostLot.receipt.exchangeRate, + + transactionType: inventoryCostLot.transactionType, + transactionId: inventoryCostLot.transactionId, + + transactionNumber: inventoryCostLot.receipt.receiptNumber, + referenceNumber: inventoryCostLot.receipt.referenceNo, + + date: inventoryCostLot.date, + indexGroup: 20, + costable: true, + createdAt: inventoryCostLot.createdAt, + + debit: 0, + credit: 0, + + branchId: inventoryCostLot.receipt.branchId, + }; + }; + + /** + * Retrieves the inventory cost GL entry for a single lot. + */ + private getInventoryCostGLEntry = R.curry( + ( + getIndexIncrement: () => number, + inventoryCostLot: InventoryCostLotTracker, + ): ILedgerEntry[] => { + const commonEntry = this.getReceiptCostGLCommonEntry(inventoryCostLot); + const costAccountId = + inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId; + + const description = inventoryCostLot.itemEntry?.description || null; + + const costEntry = { + ...commonEntry, + debit: inventoryCostLot.cost, + accountId: costAccountId, + accountNormal: AccountNormal.DEBIT, + itemId: inventoryCostLot.itemId, + note: description, + index: getIndexIncrement(), + }; + + const inventoryEntry = { + ...commonEntry, + credit: inventoryCostLot.cost, + accountId: inventoryCostLot.item.inventoryAccountId, + accountNormal: AccountNormal.DEBIT, + itemId: inventoryCostLot.itemId, + note: description, + index: getIndexIncrement(), + }; + return [costEntry, inventoryEntry]; + }, + ); + + /** + * Builds GL entries for a group of sale receipt cost lots. + * - Cost of goods sold -> Debit + * - Inventory assets -> Credit + */ + public getSaleReceiptCostGLEntries = ( + inventoryCostLots: InventoryCostLotTracker[], + ): ILedgerEntry[] => { + const getIndexIncrement = increment(0); + const getInventoryLotEntry = + this.getInventoryCostGLEntry(getIndexIncrement); + + return inventoryCostLots.map((t) => getInventoryLotEntry(t)).flat(); + }; +} diff --git a/packages/server/src/modules/SaleReceipts/SaleReceipts.module.ts b/packages/server/src/modules/SaleReceipts/SaleReceipts.module.ts index faf83b892..faf266e13 100644 --- a/packages/server/src/modules/SaleReceipts/SaleReceipts.module.ts +++ b/packages/server/src/modules/SaleReceipts/SaleReceipts.module.ts @@ -40,6 +40,8 @@ import { SaleReceiptsImportable } from './commands/SaleReceiptsImportable'; import { GetSaleReceiptMailStateService } from './queries/GetSaleReceiptMailState.service'; import { GetSaleReceiptMailTemplateService } from './queries/GetSaleReceiptMailTemplate.service'; import { SaleReceiptAutoIncrementSubscriber } from './subscribers/SaleReceiptAutoIncrementSubscriber'; +import { SaleReceiptCostGLEntriesSubscriber } from './subscribers/SaleReceiptCostGLEntriesSubscriber'; +import { SaleReceiptCostGLEntries } from './SaleReceiptCostGLEntries'; import { BulkDeleteSaleReceiptsService } from './BulkDeleteSaleReceipts.service'; import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleReceipts.service'; @@ -87,6 +89,8 @@ import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleR GetSaleReceiptMailStateService, GetSaleReceiptMailTemplateService, SaleReceiptAutoIncrementSubscriber, + SaleReceiptCostGLEntries, + SaleReceiptCostGLEntriesSubscriber, BulkDeleteSaleReceiptsService, ValidateBulkDeleteSaleReceiptsService, ], diff --git a/packages/server/src/modules/SaleReceipts/commands/SaleReceiptCostGLEntries.ts b/packages/server/src/modules/SaleReceipts/commands/SaleReceiptCostGLEntries.ts deleted file mode 100644 index f94474bbd..000000000 --- a/packages/server/src/modules/SaleReceipts/commands/SaleReceiptCostGLEntries.ts +++ /dev/null @@ -1,148 +0,0 @@ -// import { Service, Inject } from 'typedi'; -// import * as R from 'ramda'; -// import { Knex } from 'knex'; -// import { AccountNormal, IInventoryLotCost, ILedgerEntry } from '@/interfaces'; -// import { increment } from 'utils'; -// import HasTenancyService from '@/services/Tenancy/TenancyService'; -// import Ledger from '@/services/Accounting/Ledger'; -// import LedgerStorageService from '@/services/Accounting/LedgerStorageService'; -// import { groupInventoryTransactionsByTypeId } from '../../Inventory/utils'; - -// @Service() -// export class SaleReceiptCostGLEntries { -// @Inject() -// private tenancy: HasTenancyService; - -// @Inject() -// private ledgerStorage: LedgerStorageService; - -// /** -// * Writes journal entries from sales invoices. -// * @param {number} tenantId - The tenant id. -// * @param {Date} startingDate - Starting date. -// * @param {boolean} override -// */ -// public writeInventoryCostJournalEntries = async ( -// tenantId: number, -// startingDate: Date, -// trx?: Knex.Transaction -// ): Promise => { -// const { InventoryCostLotTracker } = this.tenancy.models(tenantId); - -// const inventoryCostLotTrans = await InventoryCostLotTracker.query() -// .where('direction', 'OUT') -// .where('transaction_type', 'SaleReceipt') -// .where('cost', '>', 0) -// .modify('filterDateRange', startingDate) -// .orderBy('date', 'ASC') -// .withGraphFetched('receipt') -// .withGraphFetched('item'); - -// const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans); - -// // Commit the ledger to the storage. -// await this.ledgerStorage.commit(tenantId, ledger, trx); -// }; - -// /** -// * Retrieves the inventory cost lots ledger. -// * @param {} inventoryCostLots -// * @returns {Ledger} -// */ -// private getInventoryCostLotsLedger = ( -// inventoryCostLots: IInventoryLotCost[] -// ) => { -// // Groups the inventory cost lots transactions. -// const inventoryTransactions = -// groupInventoryTransactionsByTypeId(inventoryCostLots); - -// // -// const entries = inventoryTransactions -// .map(this.getSaleInvoiceCostGLEntries) -// .flat(); - -// return new Ledger(entries); -// }; - -// /** -// * -// * @param {IInventoryLotCost} inventoryCostLot -// * @returns {} -// */ -// private getInvoiceCostGLCommonEntry = ( -// inventoryCostLot: IInventoryLotCost -// ) => { -// return { -// currencyCode: inventoryCostLot.receipt.currencyCode, -// exchangeRate: inventoryCostLot.receipt.exchangeRate, - -// transactionType: inventoryCostLot.transactionType, -// transactionId: inventoryCostLot.transactionId, - -// date: inventoryCostLot.date, -// indexGroup: 20, -// costable: true, -// createdAt: inventoryCostLot.createdAt, - -// debit: 0, -// credit: 0, - -// branchId: inventoryCostLot.receipt.branchId, -// }; -// }; - -// /** -// * Retrieves the inventory cost GL entry. -// * @param {IInventoryLotCost} inventoryLotCost -// * @returns {ILedgerEntry[]} -// */ -// private getInventoryCostGLEntry = R.curry( -// ( -// getIndexIncrement, -// inventoryCostLot: IInventoryLotCost -// ): ILedgerEntry[] => { -// const commonEntry = this.getInvoiceCostGLCommonEntry(inventoryCostLot); -// const costAccountId = -// inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId; - -// // XXX Debit - Cost account. -// const costEntry = { -// ...commonEntry, -// debit: inventoryCostLot.cost, -// accountId: costAccountId, -// accountNormal: AccountNormal.DEBIT, -// itemId: inventoryCostLot.itemId, -// index: getIndexIncrement(), -// }; -// // XXX Credit - Inventory account. -// const inventoryEntry = { -// ...commonEntry, -// credit: inventoryCostLot.cost, -// accountId: inventoryCostLot.item.inventoryAccountId, -// accountNormal: AccountNormal.DEBIT, -// itemId: inventoryCostLot.itemId, -// index: getIndexIncrement(), -// }; -// return [costEntry, inventoryEntry]; -// } -// ); - -// /** -// * Writes journal entries for given sale invoice. -// * ------- -// * - Cost of goods sold -> Debit -> YYYY -// * - Inventory assets -> Credit -> YYYY -// * -------- -// * @param {ISaleInvoice} saleInvoice -// * @param {JournalPoster} journal -// */ -// public getSaleInvoiceCostGLEntries = ( -// inventoryCostLots: IInventoryLotCost[] -// ): ILedgerEntry[] => { -// const getIndexIncrement = increment(0); -// const getInventoryLotEntry = -// this.getInventoryCostGLEntry(getIndexIncrement); - -// return inventoryCostLots.map(getInventoryLotEntry).flat(); -// }; -// } diff --git a/packages/server/src/modules/SaleReceipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts b/packages/server/src/modules/SaleReceipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts index 57209ea12..e3985540c 100644 --- a/packages/server/src/modules/SaleReceipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts +++ b/packages/server/src/modules/SaleReceipts/subscribers/SaleReceiptCostGLEntriesSubscriber.ts @@ -1,36 +1,26 @@ -// import { Inject, Service } from 'typedi'; -// import events from '@/subscribers/events'; -// import { IInventoryCostLotsGLEntriesWriteEvent } from '@/interfaces'; -// import { SaleReceiptCostGLEntries } from '../SaleReceiptCostGLEntries'; +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { events } from '@/common/events/events'; +import { IInventoryCostLotsGLEntriesWriteEvent } from '@/modules/InventoryCost/types/InventoryCost.types'; +import { SaleReceiptCostGLEntries } from '../SaleReceiptCostGLEntries'; -// @Service() -// export class SaleReceiptCostGLEntriesSubscriber { -// @Inject() -// private saleReceiptCostEntries: SaleReceiptCostGLEntries; +@Injectable() +export class SaleReceiptCostGLEntriesSubscriber { + constructor( + private readonly saleReceiptCostEntries: SaleReceiptCostGLEntries, + ) {} -// /** -// * Attaches events. -// */ -// public attach(bus) { -// bus.subscribe( -// events.inventory.onCostLotsGLEntriesWrite, -// this.writeJournalEntriesOnceWriteoffCreate -// ); -// } - -// /** -// * Writes the receipts cost GL entries once the inventory cost lots be written. -// * @param {IInventoryCostLotsGLEntriesWriteEvent} -// */ -// private writeJournalEntriesOnceWriteoffCreate = async ({ -// trx, -// startingDate, -// tenantId, -// }: IInventoryCostLotsGLEntriesWriteEvent) => { -// await this.saleReceiptCostEntries.writeInventoryCostJournalEntries( -// tenantId, -// startingDate, -// trx -// ); -// }; -// } + /** + * Writes the receipts cost GL entries once the inventory cost lots are written. + */ + @OnEvent(events.inventory.onCostLotsGLEntriesWrite) + async writeReceiptsCostEntriesOnCostLotsWritten({ + trx, + startingDate, + }: IInventoryCostLotsGLEntriesWriteEvent) { + await this.saleReceiptCostEntries.writeInventoryCostJournalEntries( + startingDate, + trx, + ); + } +}