From b07bb2df53fc708b8175e18808488c33efdca285 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 23 Dec 2020 18:57:17 +0200 Subject: [PATCH] feat: trigger compute items cost once the sale invoice and bill be edited or deleted. --- server/src/loaders/tenantRepositories.ts | 2 + .../InventoryTransactionRepository.ts | 11 +++ .../services/Accounting/JournalCommands.ts | 2 +- server/src/services/Purchases/Bills.ts | 16 +++- server/src/services/Sales/SalesInvoices.ts | 58 ++++-------- .../src/services/Sales/SalesInvoicesCost.ts | 86 ++++++++++++++++- server/src/subscribers/bills.ts | 23 ++++- server/src/subscribers/events.ts | 7 +- server/src/subscribers/saleInvoices.ts | 92 ++++++++++++++----- 9 files changed, 228 insertions(+), 69 deletions(-) create mode 100644 server/src/repositories/InventoryTransactionRepository.ts diff --git a/server/src/loaders/tenantRepositories.ts b/server/src/loaders/tenantRepositories.ts index 40447ac05..d0bf9e551 100644 --- a/server/src/loaders/tenantRepositories.ts +++ b/server/src/loaders/tenantRepositories.ts @@ -12,6 +12,7 @@ import ExpenseEntryRepository from 'repositories/ExpenseEntryRepository'; import BillRepository from 'repositories/BillRepository'; import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository'; import ItemRepository from 'repositories/ItemRepository'; +import InventoryTransactionRepository from 'repositories/InventoryTransactionRepository'; export default (knex, cache) => { return { @@ -29,5 +30,6 @@ export default (knex, cache) => { billRepository: new BillRepository(knex, cache), saleInvoiceRepository: new SaleInvoiceRepository(knex, cache), itemRepository: new ItemRepository(knex, cache), + inventoryTransactionRepository: new InventoryTransactionRepository(knex, cache), }; }; \ No newline at end of file diff --git a/server/src/repositories/InventoryTransactionRepository.ts b/server/src/repositories/InventoryTransactionRepository.ts new file mode 100644 index 000000000..b7cd23517 --- /dev/null +++ b/server/src/repositories/InventoryTransactionRepository.ts @@ -0,0 +1,11 @@ +import TenantRepository from 'repositories/TenantRepository'; +import { InventoryTransaction } from 'models'; + +export default class InventoryTransactionRepository extends TenantRepository { + /** + * Gets the repository's model. + */ + get model() { + return InventoryTransaction.bindKnex(this.knex); + } +} \ No newline at end of file diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index bee128e0a..50d21aca1 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -239,7 +239,7 @@ export default class JournalCommands{ .whereIn('reference_id', Array.isArray(referenceId) ? referenceId : [referenceId]) .withGraphFetched('account.type'); - this.journal.loadEntries(transactions); + this.journal.fromTransactions(transactions); this.journal.removeEntries(); } diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index 9471db469..5aa504dea 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -392,7 +392,7 @@ export default class BillsService extends SalesInvoicesCost { billId, 'IN', billDate, - lotNumber, + lotNumber ); // Records the inventory transactions. await this.inventoryService.recordInventoryTransactions( @@ -418,11 +418,25 @@ export default class BillsService extends SalesInvoicesCost { * @return {Promise} */ public async revertInventoryTransactions(tenantId: number, billId: number) { + const { inventoryTransactionRepository } = this.tenancy.repositories( + tenantId + ); + + // Retrieve the inventory transactions of the given sale invoice. + const oldInventoryTransactions = await inventoryTransactionRepository.find({ + transactionId: billId, + transactionType: 'Bill', + }); await this.inventoryService.deleteInventoryTransactions( tenantId, billId, 'Bill' ); + // Triggers 'onInventoryTransactionsDeleted' event. + this.eventDispatcher.dispatch( + events.bill.onInventoryTransactionsDeleted, + { tenantId, billId, oldInventoryTransactions } + ); } /** diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index bb7c59576..1323c6f35 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -1,5 +1,5 @@ import { Service, Inject } from 'typedi'; -import { omit, sumBy, map } from 'lodash'; +import { omit, sumBy } from 'lodash'; import moment from 'moment'; import { EventDispatcher, @@ -106,7 +106,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost { */ async getInvoiceOrThrowError(tenantId: number, saleInvoiceId: number) { const { SaleInvoice } = this.tenancy.models(tenantId); - const saleInvoice = await SaleInvoice.query().findById(saleInvoiceId) + const saleInvoice = await SaleInvoice.query() + .findById(saleInvoiceId) .withGraphFetched('entries'); if (!saleInvoice) { @@ -183,7 +184,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost { tenantId, saleInvoiceDTO.entries ); - // Validate items should be sellable items. await this.itemsEntriesService.validateNonSellableEntriesItems( tenantId, @@ -227,7 +227,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost { tenantId, saleInvoiceId ); - // Transform DTO object to model object. const saleInvoiceObj = this.transformDTOToModel( tenantId, @@ -325,10 +324,17 @@ export default class SaleInvoicesService extends SalesInvoicesCost { ): Promise { const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId); + // Retrieve the given sale invoice with associated entries or throw not found error. const oldSaleInvoice = await this.getInvoiceOrThrowError( tenantId, saleInvoiceId ); + // Triggers `onSaleInvoiceDelete` event. + await this.eventDispatcher.dispatch(events.saleInvoice.onDelete, { + tenantId, + saleInvoice: oldSaleInvoice, + saleInvoiceId, + }); // Unlink the converted sale estimates from the given sale invoice. await this.saleEstimatesService.unlinkConvertedEstimateFromInvoice( tenantId, @@ -448,6 +454,14 @@ export default class SaleInvoicesService extends SalesInvoicesCost { tenantId: number, saleInvoiceId: number ): Promise { + const { inventoryTransactionRepository } = this.tenancy.repositories(tenantId); + + // Retrieve the inventory transactions of the given sale invoice. + const oldInventoryTransactions = await inventoryTransactionRepository.find({ + transactionId: saleInvoiceId, + transactionType: 'SaleInvoice', + }); + // Delete the inventory transaction of the given sale invoice. await this.inventoryService.deleteInventoryTransactions( tenantId, saleInvoiceId, @@ -456,7 +470,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost { // Triggers 'onInventoryTransactionsDeleted' event. this.eventDispatcher.dispatch( events.saleInvoice.onInventoryTransactionsDeleted, - { tenantId, saleInvoiceId } + { tenantId, saleInvoiceId, oldInventoryTransactions } ); } @@ -482,40 +496,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost { return saleInvoice; } - /** - * Schedules compute sale invoice items cost based on each item - * cost method. - * @param {ISaleInvoice} saleInvoice - * @return {Promise} - */ - async scheduleComputeInvoiceItemsCost( - tenantId: number, - saleInvoiceId: number, - override?: boolean - ) { - const { SaleInvoice, Item } = this.tenancy.models(tenantId); - - // Retrieve the sale invoice with associated entries. - const saleInvoice: ISaleInvoice = await SaleInvoice.query() - .findById(saleInvoiceId) - .withGraphFetched('entries'); - - // Retrieve the inventory items that associated to the sale invoice entries. - const inventoryItems = await Item.query() - .whereIn('id', map(saleInvoice.entries, 'itemId')) - .where('type', 'inventory'); - - const inventoryItemsIds = map(inventoryItems, 'id'); - - if (inventoryItemsIds.length > 0) { - await this.scheduleComputeItemsCost( - tenantId, - inventoryItemsIds, - saleInvoice.invoiceDate - ); - } - } - /** * Retrieve sales invoices filterable and paginated list. * @param {Request} req diff --git a/server/src/services/Sales/SalesInvoicesCost.ts b/server/src/services/Sales/SalesInvoicesCost.ts index 0ae69464f..9dcc35cf9 100644 --- a/server/src/services/Sales/SalesInvoicesCost.ts +++ b/server/src/services/Sales/SalesInvoicesCost.ts @@ -1,6 +1,6 @@ import { Container, Service, Inject } from 'typedi'; +import { map } from 'lodash'; import JournalPoster from 'services/Accounting/JournalPoster'; -import JournalEntry from 'services/Accounting/JournalEntry'; import InventoryService from 'services/Inventory/Inventory'; import TenancyService from 'services/Tenancy/TenancyService'; import { ISaleInvoice, IItemEntry, IInventoryLotCost, IItem } from 'interfaces'; @@ -21,10 +21,10 @@ export default class SaleInvoicesCost { * @param {Date} startingDate - Starting compute cost date. * @return {Promise} */ - async scheduleComputeItemsCost( + async scheduleComputeCostByItemsIds( tenantId: number, inventoryItemsIds: number[], - startingDate: Date + startingDate: Date, ) { const asyncOpers: Promise<[]>[] = []; @@ -39,6 +39,86 @@ export default class SaleInvoicesCost { return Promise.all([...asyncOpers]); } + /** + * Schedules compute sale invoice items cost based on each item + * cost method. + * @param {number} tenantId - Tenant id. + * @param {ISaleInvoice} saleInvoiceId - Sale invoice id. + * @param {boolean} override - Allow to override old computes in edit mode. + * @return {Promise} + */ + async scheduleComputeCostByInvoiceId( + tenantId: number, + saleInvoiceId: number, + ) { + const { SaleInvoice } = this.tenancy.models(tenantId); + + // Retrieve the sale invoice with associated entries. + const saleInvoice: ISaleInvoice = await SaleInvoice.query() + .findById(saleInvoiceId) + .withGraphFetched('entries'); + + // Schedule compute inventory items cost by the given invoice model object. + return this.scheduleComputeCostByEntries( + tenantId, + saleInvoice.entries, + saleInvoice.invoiceDate, + ); + } + + /** + * Schedules the compute inventory items cost by the given bill id. + * @param {number} tenantId - Tenant id. + * @param {number} billId - Bill id. + * @return {Promise} + */ + async scheduleComputeCostByBillId( + tenantId: number, + billId: number + ): Promise { + const { Bill } = this.tenancy.models(tenantId); + + // Retrieve the bill with associated entries. + const bill = await Bill.query() + .findById(billId) + .withGraphFetched('entries'); + + return this.scheduleComputeCostByEntries( + tenantId, + bill.entries, + bill.billDate, + ); + } + + /** + * Schedules the compute inventory items by the given invoice. + * @param {number} tenantId + * @param {ISaleInvoice & { entries: IItemEntry[] }} saleInvoice + * @param {boolean} override + */ + async scheduleComputeCostByEntries( + tenantId: number, + entries: IItemEntry[], + startingDate: Date, + ) { + const { Item } = this.tenancy.models(tenantId); + + // Retrieve the inventory items that associated to the sale invoice entries. + const inventoryItems = await Item.query() + .whereIn('id', map(entries, 'itemId')) + .where('type', 'inventory'); + + const inventoryItemsIds = map(inventoryItems, 'id'); + + if (inventoryItemsIds.length > 0) { + await this.scheduleComputeCostByItemsIds( + tenantId, + inventoryItemsIds, + startingDate + ); + } + } + /** * Schedule writing journal entries. * @param {Date} startingDate diff --git a/server/src/subscribers/bills.ts b/server/src/subscribers/bills.ts index 22bbd63e5..bae90cafe 100644 --- a/server/src/subscribers/bills.ts +++ b/server/src/subscribers/bills.ts @@ -1,5 +1,6 @@ import { Container } from 'typedi'; import { EventSubscriber, On } from 'event-dispatch'; +import { map, head } from 'lodash'; import events from 'subscribers/events'; import TenancyService from 'services/Tenancy/TenancyService'; import BillsService from 'services/Purchases/Bills'; @@ -163,7 +164,27 @@ export default class BillSubscriber { tenantId, billId, }); - await this.billsService.scheduleComputeBillItemsCost(tenantId, billId); + await this.billsService.scheduleComputeCostByBillId(tenantId, billId); + } + + /** + * Handles computes items costs once the inventory transactions deleted. + */ + @On(events.bill.onInventoryTransactionsDeleted) + public async handleComputeCostsOnInventoryTransactionsDeleted({ + tenantId, + billId, + oldInventoryTransactions, + }) { + const inventoryItemsIds = map(oldInventoryTransactions, 'itemId'); + const startingDates = map(oldInventoryTransactions, 'date'); + const startingDate = head(startingDates); + + await this.billsService.scheduleComputeCostByItemsIds( + tenantId, + inventoryItemsIds, + startingDate + ); } /** diff --git a/server/src/subscribers/events.ts b/server/src/subscribers/events.ts index b332cb1ba..d8781d512 100644 --- a/server/src/subscribers/events.ts +++ b/server/src/subscribers/events.ts @@ -79,6 +79,7 @@ export default { saleInvoice: { onCreated: 'onSaleInvoiceCreated', onEdited: 'onSaleInvoiceEdited', + onDelete: 'onSaleInvoiceDelete', onDeleted: 'onSaleInvoiceDeleted', onBulkDelete: 'onSaleInvoiceBulkDeleted', onPublished: 'onSaleInvoicePublished', @@ -127,7 +128,8 @@ export default { onDeleted: 'onBillDeleted', onBulkDeleted: 'onBillBulkDeleted', onPublished: 'onBillPublished', - onInventoryTransactionsCreated: 'onBillInventoryTransactionsCreated' + onInventoryTransactionsCreated: 'onBillInventoryTransactionsCreated', + onInventoryTransactionsDeleted: 'onBillInventoryTransactionsDeleted' }, /** @@ -163,6 +165,9 @@ export default { onOpeningBalanceChanged: 'onOpeingBalanceChanged', }, + /** + * Items service. + */ items: { onCreated: 'onItemCreated', onEdited: 'onItemEdited', diff --git a/server/src/subscribers/saleInvoices.ts b/server/src/subscribers/saleInvoices.ts index 9bc51da96..936b8b0b1 100644 --- a/server/src/subscribers/saleInvoices.ts +++ b/server/src/subscribers/saleInvoices.ts @@ -1,4 +1,5 @@ import { Container } from 'typedi'; +import { head, map } from 'lodash'; import { On, EventSubscriber } from 'event-dispatch'; import events from 'subscribers/events'; import TenancyService from 'services/Tenancy/TenancyService'; @@ -89,7 +90,7 @@ export default class SaleInvoiceSubscriber { await this.saleInvoicesService.recordInventoryTranscactions( tenantId, saleInvoice.id, - saleInvoice.invoiceDate, + saleInvoice.invoiceDate ); } @@ -101,12 +102,12 @@ export default class SaleInvoiceSubscriber { public async handleWritingNonInventoryEntries({ tenantId, saleInvoice }) { await this.saleInvoicesService.recordNonInventoryJournalEntries( tenantId, - saleInvoice.id, + saleInvoice.id ); } /** - * + * Rewriting the inventory transactions once the sale invoice be edited. */ @On(events.saleInvoice.onEdited) public async handleRewritingInventoryTransactions({ tenantId, saleInvoice }) { @@ -117,7 +118,7 @@ export default class SaleInvoiceSubscriber { tenantId, saleInvoice.id, saleInvoice.invoiceDate, - true, + true ); } @@ -167,14 +168,22 @@ export default class SaleInvoiceSubscriber { /** * Handles deleting the inventory transactions once the invoice deleted. */ - @On(events.saleInvoice.onDeleted) - public async handleDeletingInventoryTransactions({ tenantId, saleInvoiceId }) { - this.logger.info('[sale_invoice] trying to revert inventory transactions.', { - tenantId, saleInvoiceId, - }); + @On(events.saleInvoice.onDelete) + public async handleDeletingInventoryTransactions({ + tenantId, + saleInvoiceId, + oldSaleInvoice, + }) { + this.logger.info( + '[sale_invoice] trying to revert inventory transactions.', + { + tenantId, + saleInvoiceId, + } + ); await this.saleInvoicesService.revertInventoryTransactions( tenantId, - saleInvoiceId, + saleInvoiceId ); } @@ -182,24 +191,54 @@ export default class SaleInvoiceSubscriber { * Schedules compute invoice items cost job. */ @On(events.saleInvoice.onInventoryTransactionsCreated) - public async handleComputeItemsCosts({ tenantId, saleInvoiceId }) { - this.logger.info('[sale_invoice] trying to compute the invoice items cost.', { - tenantId, saleInvoiceId, - }); - await this.saleInvoicesService.scheduleComputeInvoiceItemsCost( + public async handleComputeCostsOnInventoryTransactionsCreated({ + tenantId, + saleInvoiceId, + }) { + this.logger.info( + '[sale_invoice] trying to compute the invoice items cost.', + { + tenantId, + saleInvoiceId, + } + ); + await this.saleInvoicesService.scheduleComputeCostByInvoiceId( tenantId, - saleInvoiceId, + saleInvoiceId ); } /** - * Increments the sale invoice items once the invoice created. + * Schedules compute items cost once the inventory transactions deleted. + */ + @On(events.saleInvoice.onInventoryTransactionsDeleted) + public async handleComputeCostsOnInventoryTransactionsDeleted({ + tenantId, + saleInvoiceId, + oldInventoryTransactions, + }) { + const inventoryItemsIds = map(oldInventoryTransactions, 'itemId'); + const startingDates = map(oldInventoryTransactions, 'date'); + const startingDate = head(startingDates); + + await this.saleInvoicesService.scheduleComputeCostByItemsIds( + tenantId, + inventoryItemsIds, + startingDate + ); + } + + /** + * Increments the sale invoice items once the invoice created. */ @On(events.saleInvoice.onCreated) - public async handleDecrementSaleInvoiceItemsQuantity({ tenantId, saleInvoice }) { + public async handleDecrementSaleInvoiceItemsQuantity({ + tenantId, + saleInvoice, + }) { await this.itemsEntriesService.decrementItemsQuantity( tenantId, - saleInvoice.entries, + saleInvoice.entries ); } @@ -207,10 +246,13 @@ export default class SaleInvoiceSubscriber { * Decrements the sale invoice items once the invoice deleted. */ @On(events.saleInvoice.onDeleted) - public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldSaleInvoice }) { + public async handleIncrementSaleInvoiceItemsQuantity({ + tenantId, + oldSaleInvoice, + }) { await this.itemsEntriesService.incrementItemsEntries( tenantId, - oldSaleInvoice.entries, + oldSaleInvoice.entries ); } @@ -218,7 +260,11 @@ export default class SaleInvoiceSubscriber { * Handle increment/decrement the different items quantity once the sale invoice be edited. */ @On(events.saleInvoice.onEdited) - public async handleChangeSaleInvoiceItemsQuantityOnEdit({ tenantId, saleInvoice, oldSaleInvoice }) { + public async handleChangeSaleInvoiceItemsQuantityOnEdit({ + tenantId, + saleInvoice, + oldSaleInvoice, + }) { await this.itemsEntriesService.changeItemsQuantity( tenantId, saleInvoice.entries.map((entry) => ({ @@ -228,7 +274,7 @@ export default class SaleInvoiceSubscriber { oldSaleInvoice.entries.map((entry) => ({ ...entry, quantity: entry.quantity * -1, - })), + })) ); } }