From df85c9b295d84acf31328b0ecda52a3d48aafe40 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Tue, 5 Jan 2021 17:06:42 +0200 Subject: [PATCH] fix: refactoring invoice calc cost service. --- client/src/containers/Vendors/VendorForm.js | 1 - .../FinancialStatements/GeneralLedger.ts | 6 +- .../FinancialStatements/JournalSheet.ts | 6 +- .../FinancialStatements/ProfitLossSheet.ts | 2 +- .../controllers/Purchases/BillsPayments.ts | 1 - .../api/controllers/Sales/SalesInvoices.ts | 5 +- ...2647_create_accounts_transactions_table.js | 1 - server/src/interfaces/InventoryTransaction.ts | 3 +- server/src/interfaces/ItemEntry.ts | 1 + server/src/interfaces/SaleReceipt.ts | 70 ++-- server/src/jobs/ComputeItemCost.ts | 2 - .../src/jobs/MailNotificationSubscribeEnd.ts | 17 +- server/src/jobs/MailNotificationTrialEnd.ts | 21 +- server/src/loaders/events.ts | 21 +- server/src/models/Vendor.js | 2 - .../services/Accounting/JournalCommands.ts | 49 ++- server/src/services/Inventory/Inventory.ts | 120 +++++- server/src/services/Purchases/Bills.ts | 220 +++++------ server/src/services/Sales/SalesEstimate.ts | 252 ++++++++----- server/src/services/Sales/SalesInvoices.ts | 65 +--- .../src/services/Sales/SalesInvoicesCost.ts | 83 +--- server/src/services/Sales/SalesReceipts.ts | 356 ++++++++++++++---- .../subscribers/Bills/SyncItemsQuantity.ts | 59 +++ .../subscribers/Bills/SyncVendorsBalances.ts | 68 ++++ .../Bills/WriteInventoryTransactions.ts | 60 +++ .../subscribers/Bills/WriteJournalEntries.ts | 54 +++ server/src/subscribers/Bills/index.ts | 22 ++ .../SaleInvoices/SyncCustomersBalance.ts | 82 ++++ .../SaleInvoices/SyncItemsQuantity.ts | 71 ++++ .../WriteInventoryTransactions.ts | 72 ++++ .../SaleInvoices/WriteJournalEntries.ts | 80 ++++ server/src/subscribers/SaleInvoices/index.ts | 57 +++ .../SaleReceipt/SyncItemsQuantity.ts | 67 ++++ .../SaleReceipt/WriteInventoryTransactions.ts | 68 ++++ .../SaleReceipt/WriteJournalEntries.ts | 75 ++++ .../{saleReceipts.ts => SaleReceipt/index.ts} | 6 +- server/src/subscribers/bills.ts | 227 ----------- server/src/subscribers/events.ts | 7 +- server/src/subscribers/inventory.ts | 58 ++- server/src/subscribers/saleInvoices.ts | 330 ---------------- server/src/utils/index.js | 2 - 41 files changed, 1684 insertions(+), 1085 deletions(-) create mode 100644 server/src/subscribers/Bills/SyncItemsQuantity.ts create mode 100644 server/src/subscribers/Bills/SyncVendorsBalances.ts create mode 100644 server/src/subscribers/Bills/WriteInventoryTransactions.ts create mode 100644 server/src/subscribers/Bills/WriteJournalEntries.ts create mode 100644 server/src/subscribers/Bills/index.ts create mode 100644 server/src/subscribers/SaleInvoices/SyncCustomersBalance.ts create mode 100644 server/src/subscribers/SaleInvoices/SyncItemsQuantity.ts create mode 100644 server/src/subscribers/SaleInvoices/WriteInventoryTransactions.ts create mode 100644 server/src/subscribers/SaleInvoices/WriteJournalEntries.ts create mode 100644 server/src/subscribers/SaleInvoices/index.ts create mode 100644 server/src/subscribers/SaleReceipt/SyncItemsQuantity.ts create mode 100644 server/src/subscribers/SaleReceipt/WriteInventoryTransactions.ts create mode 100644 server/src/subscribers/SaleReceipt/WriteJournalEntries.ts rename server/src/subscribers/{saleReceipts.ts => SaleReceipt/index.ts} (93%) delete mode 100644 server/src/subscribers/bills.ts delete mode 100644 server/src/subscribers/saleInvoices.ts diff --git a/client/src/containers/Vendors/VendorForm.js b/client/src/containers/Vendors/VendorForm.js index fff75d5d5..ed834db8d 100644 --- a/client/src/containers/Vendors/VendorForm.js +++ b/client/src/containers/Vendors/VendorForm.js @@ -97,7 +97,6 @@ function VendorForm({ }), [defaultInitialValues], ); - console.log(isNewMode, 'Val'); useEffect(() => { !isNewMode ? changePageTitle(formatMessage({ id: 'edit_vendor' })) diff --git a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts index 7f307d2e9..aed3e2da0 100644 --- a/server/src/api/controllers/FinancialStatements/GeneralLedger.ts +++ b/server/src/api/controllers/FinancialStatements/GeneralLedger.ts @@ -1,4 +1,4 @@ -import { Router, Request, Response } from 'express'; +import { Router, Request, Response, NextFunction } from 'express'; import { query, ValidationChain } from 'express-validator'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; import BaseController from '../BaseController'; @@ -48,7 +48,7 @@ export default class GeneralLedgerReportController extends BaseController{ * @param {Request} req - * @param {Response} res - */ - async generalLedger(req: Request, res: Response) { + async generalLedger(req: Request, res: Response, next: NextFunction) { const { tenantId, settings } = req; const filter = this.matchedQueryData(req); @@ -68,7 +68,7 @@ export default class GeneralLedgerReportController extends BaseController{ query: this.transfromToResponse(query), }); } catch (error) { - console.log(error); + next(error); } } } \ No newline at end of file diff --git a/server/src/api/controllers/FinancialStatements/JournalSheet.ts b/server/src/api/controllers/FinancialStatements/JournalSheet.ts index 2b86184fd..6a4214da4 100644 --- a/server/src/api/controllers/FinancialStatements/JournalSheet.ts +++ b/server/src/api/controllers/FinancialStatements/JournalSheet.ts @@ -1,5 +1,5 @@ import { Inject, Service } from 'typedi'; -import { Request, Response, Router } from 'express'; +import { Request, Response, Router, NextFunction } from 'express'; import { castArray } from 'lodash'; import { query, oneOf } from 'express-validator'; import JournalSheetService from 'services/FinancialStatements/JournalSheet/JournalSheetService'; @@ -55,7 +55,7 @@ export default class JournalSheetController extends BaseController { * @param {Request} req - * @param {Response} res - */ - async journal(req: Request, res: Response) { + async journal(req: Request, res: Response, next: NextFunction) { const { tenantId, settings } = req; let filter = this.matchedQueryData(req); @@ -76,7 +76,7 @@ export default class JournalSheetController extends BaseController { query: this.transfromToResponse(query), }); } catch (error) { - console.log(error); + next(error); } } } \ No newline at end of file diff --git a/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts index a7c96d3d5..be6720666 100644 --- a/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts +++ b/server/src/api/controllers/FinancialStatements/ProfitLossSheet.ts @@ -73,7 +73,7 @@ export default class ProfitLossSheetController extends BaseController { query: this.transfromToResponse(query), }); } catch (error) { - console.log(error); + next(error); } } } \ No newline at end of file diff --git a/server/src/api/controllers/Purchases/BillsPayments.ts b/server/src/api/controllers/Purchases/BillsPayments.ts index b31a33edc..dddddf8d2 100644 --- a/server/src/api/controllers/Purchases/BillsPayments.ts +++ b/server/src/api/controllers/Purchases/BillsPayments.ts @@ -140,7 +140,6 @@ export default class BillsPayments extends BaseController { message: 'Payment made has been created successfully.', }); } catch (error) { - console.log(error); next(error); } } diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index cb8ae6fc6..29c8cc91d 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -248,12 +248,13 @@ export default class SaleInvoicesController extends BaseController { */ async getSaleInvoice(req: Request, res: Response, next: NextFunction) { const { id: saleInvoiceId } = req.params; - const { tenantId } = req; + const { tenantId, user } = req; try { const saleInvoice = await this.saleInvoiceService.getSaleInvoice( tenantId, - saleInvoiceId + saleInvoiceId, + user ); return res.status(200).send({ sale_invoice: saleInvoice }); } catch (error) { diff --git a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js index f4e2e3902..f4f4ff3e7 100644 --- a/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js +++ b/server/src/database/migrations/20200104232647_create_accounts_transactions_table.js @@ -11,7 +11,6 @@ exports.up = function(knex) { table.string('contact_type').nullable().index(); table.integer('contact_id').unsigned().nullable().index(); table.string('note'); - table.boolean('draft').defaultTo(false); table.integer('user_id').unsigned().index(); table.integer('index').unsigned(); table.date('date').index(); diff --git a/server/src/interfaces/InventoryTransaction.ts b/server/src/interfaces/InventoryTransaction.ts index 04b3a5b89..bafeae34e 100644 --- a/server/src/interfaces/InventoryTransaction.ts +++ b/server/src/interfaces/InventoryTransaction.ts @@ -1,9 +1,10 @@ +export type TInventoryTransactionDirection = 'IN' | 'OUT'; export interface IInventoryTransaction { id?: number, date: Date, - direction: string, + direction: TInventoryTransactionDirection, itemId: number, quantity: number, rate: number, diff --git a/server/src/interfaces/ItemEntry.ts b/server/src/interfaces/ItemEntry.ts index 1cd7d6959..6883b3471 100644 --- a/server/src/interfaces/ItemEntry.ts +++ b/server/src/interfaces/ItemEntry.ts @@ -1,4 +1,5 @@ +export type IItemEntryTransactionType = 'SaleInvoice' | 'Bill' | 'SaleReceipt'; export interface IItemEntry { id?: number, diff --git a/server/src/interfaces/SaleReceipt.ts b/server/src/interfaces/SaleReceipt.ts index bac286033..fde3f1a90 100644 --- a/server/src/interfaces/SaleReceipt.ts +++ b/server/src/interfaces/SaleReceipt.ts @@ -1,40 +1,50 @@ -import { ISalesInvoicesFilter } from "./SaleInvoice"; - +import { ISalesInvoicesFilter } from './SaleInvoice'; export interface ISaleReceipt { - id?: number, - customerId: number, - depositAccountId: number, - receiptDate: Date, - sendToEmail: string, - referenceNo: string, - receiptMessage: string, - receiptNumber: string, - statement: string, - closedAt: Date|string, - entries: any[], -}; + id?: number; + customerId: number; + depositAccountId: number; + receiptDate: Date; + sendToEmail: string; + referenceNo: string; + receiptMessage: string; + receiptNumber: string; + amount: number; + statement: string; + closedAt: Date | string; + entries: any[]; +} -export interface ISalesReceiptsFilter { - -}; +export interface ISalesReceiptsFilter {} export interface ISaleReceiptDTO { - customerId: number, - depositAccountId: number, - receiptDate: Date, - sendToEmail: string, - referenceNo: string, - receiptMessage: string, - statement: string, - closed: boolean, - entries: any[], -}; + customerId: number; + depositAccountId: number; + receiptDate: Date; + sendToEmail: string; + referenceNo: string; + receiptMessage: string; + statement: string; + closed: boolean; + entries: any[]; +} export interface ISalesReceiptService { - createSaleReceipt(tenantId: number, saleReceiptDTO: ISaleReceiptDTO): Promise; + createSaleReceipt( + tenantId: number, + saleReceiptDTO: ISaleReceiptDTO + ): Promise; + editSaleReceipt(tenantId: number, saleReceiptId: number): Promise; deleteSaleReceipt(tenantId: number, saleReceiptId: number): Promise; - salesReceiptsList(tennatid: number, salesReceiptsFilter: ISalesReceiptsFilter): Promise<{ salesReceipts: ISaleReceipt[], pagination: IPaginationMeta, filterMeta: IFilterMeta }>; -}; \ No newline at end of file + + salesReceiptsList( + tennatid: number, + salesReceiptsFilter: ISalesReceiptsFilter + ): Promise<{ + salesReceipts: ISaleReceipt[]; + pagination: IPaginationMeta; + filterMeta: IFilterMeta; + }>; +} diff --git a/server/src/jobs/ComputeItemCost.ts b/server/src/jobs/ComputeItemCost.ts index dc3126df3..ecc24ea08 100644 --- a/server/src/jobs/ComputeItemCost.ts +++ b/server/src/jobs/ComputeItemCost.ts @@ -29,7 +29,6 @@ export default class ComputeItemCostJob { /** * The job handler. - * @param {} - */ public async handler(job, done: Function): Promise { const Logger = Container.get('logger'); @@ -51,7 +50,6 @@ export default class ComputeItemCostJob { /** * Handle the job started. - * @param {Job} job - . */ async onJobStart(job) { const { startingDate, itemId, tenantId } = job.attrs.data; diff --git a/server/src/jobs/MailNotificationSubscribeEnd.ts b/server/src/jobs/MailNotificationSubscribeEnd.ts index 4f7c9372e..1c197defc 100644 --- a/server/src/jobs/MailNotificationSubscribeEnd.ts +++ b/server/src/jobs/MailNotificationSubscribeEnd.ts @@ -12,15 +12,22 @@ export default class MailNotificationSubscribeEnd { const subscriptionService = Container.get(SubscriptionService); const Logger = Container.get('logger'); - Logger.info(`Send mail notification subscription end soon - started: ${job.attrs.data}`); + Logger.info( + `Send mail notification subscription end soon - started: ${job.attrs.data}` + ); try { subscriptionService.mailMessages.sendRemainingTrialPeriod( - phoneNumber, remainingDays, + phoneNumber, + remainingDays + ); + Logger.info( + `Send mail notification subscription end soon - finished: ${job.attrs.data}` + ); + } catch (error) { + Logger.info( + `Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}` ); - Logger.info(`Send mail notification subscription end soon - finished: ${job.attrs.data}`); - } catch(error) { - Logger.info(`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`); done(e); } } diff --git a/server/src/jobs/MailNotificationTrialEnd.ts b/server/src/jobs/MailNotificationTrialEnd.ts index 27eec67f8..faf39396f 100644 --- a/server/src/jobs/MailNotificationTrialEnd.ts +++ b/server/src/jobs/MailNotificationTrialEnd.ts @@ -3,7 +3,7 @@ import SubscriptionService from 'services/Subscription/Subscription'; export default class MailNotificationTrialEnd { /** - * + * * @param {Job} job - */ handler(job) { @@ -12,16 +12,23 @@ export default class MailNotificationTrialEnd { const subscriptionService = Container.get(SubscriptionService); const Logger = Container.get('logger'); - Logger.info(`Send mail notification subscription end soon - started: ${job.attrs.data}`); + Logger.info( + `Send mail notification subscription end soon - started: ${job.attrs.data}` + ); try { subscriptionService.mailMessages.sendRemainingTrialPeriod( - phoneNumber, remainingDays, + phoneNumber, + remainingDays + ); + Logger.info( + `Send mail notification subscription end soon - finished: ${job.attrs.data}` + ); + } catch (error) { + Logger.info( + `Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}` ); - Logger.info(`Send mail notification subscription end soon - finished: ${job.attrs.data}`); - } catch(error) { - Logger.info(`Send mail notification subscription end soon - failed: ${job.attrs.data}, error: ${e}`); done(e); } } -} \ No newline at end of file +} diff --git a/server/src/loaders/events.ts b/server/src/loaders/events.ts index 80ea77f2a..13ec089af 100644 --- a/server/src/loaders/events.ts +++ b/server/src/loaders/events.ts @@ -5,13 +5,28 @@ import 'subscribers/organization'; import 'subscribers/inviteUser'; import 'subscribers/manualJournals'; import 'subscribers/expenses'; -import 'subscribers/bills'; -import 'subscribers/saleInvoices'; + +import 'subscribers/Bills'; +import 'subscribers/Bills/SyncItemsQuantity'; +import 'subscribers/Bills/SyncVendorsBalances'; +import 'subscribers/Bills/WriteJournalEntries'; +import 'subscribers/Bills/WriteInventoryTransactions'; + +import 'subscribers/SaleInvoices'; +import 'subscribers/SaleInvoices/SyncCustomersBalance'; +import 'subscribers/SaleInvoices/SyncItemsQuantity'; +import 'subscribers/SaleInvoices/WriteInventoryTransactions'; +import 'subscribers/SaleInvoices/WriteJournalEntries'; + +import 'subscribers/SaleReceipt'; +import 'subscribers/SaleReceipt/SyncItemsQuantity'; +import 'subscribers/SaleReceipt/WriteInventoryTransactions'; +import 'subscribers/SaleReceipt/WriteJournalEntries'; + import 'subscribers/customers'; import 'subscribers/vendors'; import 'subscribers/paymentMades'; import 'subscribers/paymentReceives'; import 'subscribers/saleEstimates'; -import 'subscribers/saleReceipts'; import 'subscribers/inventory'; import 'subscribers/items'; \ No newline at end of file diff --git a/server/src/models/Vendor.js b/server/src/models/Vendor.js index f62c8d3eb..5e5ee96fb 100644 --- a/server/src/models/Vendor.js +++ b/server/src/models/Vendor.js @@ -129,8 +129,6 @@ export default class Vendor extends TenantModel { { key: 'unpaid', label: 'Unpaid' }, ], query: (query, role) => { - console.log(role); - switch(role.value) { case 'active': query.modify('active'); diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index f205e6444..1fb128516 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -1,6 +1,6 @@ import { sumBy, chain } from 'lodash'; import moment, { LongDateFormatKey } from 'moment'; -import { IBill, IManualJournalEntry, ISystemUser } from 'interfaces'; +import { IBill, IManualJournalEntry, ISaleReceipt, ISystemUser } from 'interfaces'; import JournalPoster from './JournalPoster'; import JournalEntry from './JournalEntry'; import { AccountTransaction } from 'models'; @@ -425,4 +425,51 @@ export default class JournalCommands { } ); } + + /** + * Writes the sale invoice income journal entries. + * ----- + * - Deposit account -> Debit -> XXXX + * - Income -> Credit -> XXXX + * + * @param {ISaleInvoice} saleInvoice + * @param {number} receivableAccountsId + * @param {number} authorizedUserId + */ + async saleReceiptIncomeEntries( + saleReceipt: ISaleReceipt & { + entries: IItemEntry & { item: IItem }; + }, + ): Promise { + const commonEntry = { + referenceType: 'SaleReceipt', + referenceId: saleReceipt.id, + date: saleReceipt.receiptDate, + userId: saleReceipt.userId, + }; + // XXX Debit - Deposit account. + const depositEntry = new JournalEntry({ + ...commonEntry, + debit: saleReceipt.amount, + account: saleReceipt.depositAccountId, + index: 1, + }); + this.journal.debit(depositEntry); + + saleReceipt.entries.forEach( + (entry: IItemEntry & { item: IItem }, index: number) => { + const income: number = entry.quantity * entry.rate; + + // XXX Credit - Income account. + const incomeEntry = new JournalEntry({ + ...commonEntry, + credit: income, + account: entry.item.sellAccountId, + note: entry.description, + index: index + 2, + }); + this.journal.credit(incomeEntry); + } + ); + } } diff --git a/server/src/services/Inventory/Inventory.ts b/server/src/services/Inventory/Inventory.ts index 270d8fe24..c2090727a 100644 --- a/server/src/services/Inventory/Inventory.ts +++ b/server/src/services/Inventory/Inventory.ts @@ -8,13 +8,15 @@ import { import { IInventoryLotCost, IInventoryTransaction, - IItem, + TInventoryTransactionDirection, IItemEntry, + IItemEntryTransactionType, } from 'interfaces'; import InventoryAverageCost from 'services/Inventory/InventoryAverageCost'; import InventoryCostLotTracker from 'services/Inventory/InventoryCostLotTracker'; import TenancyService from 'services/Tenancy/TenancyService'; import events from 'subscribers/events'; +import ItemsEntriesService from 'services/Items/ItemsEntriesService'; type TCostMethod = 'FIFO' | 'LIFO' | 'AVG'; @@ -26,22 +28,23 @@ export default class InventoryService { @EventDispatcher() eventDispatcher: EventDispatcherInterface; + @Inject() + itemsEntriesService: ItemsEntriesService; + /** * Transforms the items entries to inventory transactions. */ transformItemEntriesToInventory( itemEntries: IItemEntry[], - transactionType: string, - transactionId: number, - direction: 'IN' | 'OUT', + direction: TInventoryTransactionDirection, date: Date | string, lotNumber: number ) { return itemEntries.map((entry: IItemEntry) => ({ ...pick(entry, ['itemId', 'quantity', 'rate']), lotNumber, - transactionType, - transactionId, + transactionType: entry.referenceType, + transactionId: entry.referenceId, direction, date, entryId: entry.id, @@ -130,7 +133,6 @@ export default class InventoryService { tenantId, } ); - // Triggers `onComputeItemCostJobScheduled` event. await this.eventDispatcher.dispatch( events.inventory.onComputeItemCostJobScheduled, @@ -157,10 +159,12 @@ export default class InventoryService { } /** - * - * @param {number} tenantId - * @param {IInventoryTransaction} inventoryEntry - * @param {boolean} deleteOld + * Writes the inventory transactiosn on the storage from the given + * inventory transactions entries. + * + * @param {number} tenantId - + * @param {IInventoryTransaction} inventoryEntry - + * @param {boolean} deleteOld - */ async recordInventoryTransaction( tenantId: number, @@ -182,24 +186,104 @@ export default class InventoryService { }); } + /** + * Records the inventory transactions from items entries that have (inventory) type. + * + * @param {number} tenantId + * @param {number} transactionId + * @param {string} transactionType + * @param {Date|string} transactionDate + * @param {boolean} override + */ + async recordInventoryTransactionsFromItemsEntries( + tenantId: number, + transactionId: number, + transactionType: IItemEntryTransactionType, + transactionDate: Date | string, + transactionDirection: TInventoryTransactionDirection, + override: boolean = false + ): Promise { + // Gets the next inventory lot number. + const lotNumber = this.getNextLotNumber(tenantId); + + // Loads the inventory items entries of the given sale invoice. + const inventoryEntries = await this.itemsEntriesService.getInventoryEntries( + tenantId, + transactionType, + transactionId + ); + // Can't continue if there is no entries has inventory items in the invoice. + if (inventoryEntries.length <= 0) { + return; + } + // Inventory transactions. + const inventoryTranscations = this.transformItemEntriesToInventory( + inventoryEntries, + transactionDirection, + transactionDate, + lotNumber + ); + // Records the inventory transactions of the given sale invoice. + await this.recordInventoryTransactions( + tenantId, + inventoryTranscations, + override + ); + // Increment and save the next lot number settings. + await this.incrementNextLotNumber(tenantId); + + // Triggers `onInventoryTransactionsCreated` event. + this.eventDispatcher.dispatch( + events.inventory.onInventoryTransactionsCreated, + { + tenantId, + inventoryEntries, + transactionId, + transactionType, + transactionDate, + transactionDirection, + override, + } + ); + } + /** * Deletes the given inventory transactions. * @param {number} tenantId - Tenant id. * @param {string} transactionType * @param {number} transactionId - * @return {Promise} + * @return {Promise<{ + * oldInventoryTransactions: IInventoryTransaction[] + * }>} */ async deleteInventoryTransactions( tenantId: number, transactionId: number, transactionType: string - ): Promise { - const { InventoryTransaction } = this.tenancy.models(tenantId); + ): Promise<{ oldInventoryTransactions: IInventoryTransaction[] }> { + const { inventoryTransactionRepository } = this.tenancy.repositories(tenantId); - await InventoryTransaction.query() - .where('transaction_type', transactionType) - .where('transaction_id', transactionId) - .delete(); + // Retrieve the inventory transactions of the given sale invoice. + const oldInventoryTransactions = await inventoryTransactionRepository.find({ + transactionId, + transactionType, + }); + // Deletes the inventory transactions by the given transaction type and id. + await inventoryTransactionRepository.deleteBy({ + transactionType, + transactionId, + }); + // Triggers `onInventoryTransactionsDeleted` event. + this.eventDispatcher.dispatch( + events.inventory.onInventoryTransactionsDeleted, + { + tenantId, + oldInventoryTransactions, + transactionId, + transactionType, + } + ); + return { oldInventoryTransactions }; } /** diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index 5aa504dea..94289cef1 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -1,4 +1,4 @@ -import { omit, sumBy, pick, map } from 'lodash'; +import { omit, sumBy } from 'lodash'; import moment from 'moment'; import { Inject, Service } from 'typedi'; import { @@ -16,19 +16,17 @@ import { formatDateFields } from 'utils'; import { IBillDTO, IBill, - IItem, ISystemUser, IBillEditDTO, IPaginationMeta, IFilterMeta, IBillsFilter, - IItemEntry, - IInventoryTransaction, } from 'interfaces'; import { ServiceError } from 'exceptions'; import ItemsService from 'services/Items/ItemsService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import JournalCommands from 'services/Accounting/JournalCommands'; +import JournalPosterService from 'services/Sales/JournalPosterService'; const ERRORS = { BILL_NOT_FOUND: 'BILL_NOT_FOUND', @@ -71,6 +69,9 @@ export default class BillsService extends SalesInvoicesCost { @Inject() itemsEntriesService: ItemsEntriesService; + @Inject() + journalPosterService: JournalPosterService; + /** * Validates whether the vendor is exist. * @async @@ -362,106 +363,6 @@ export default class BillsService extends SalesInvoicesCost { }); } - /** - * Records the inventory transactions from the given bill input. - * @param {Bill} bill - Bill model object. - * @param {number} billId - Bill id. - * @return {Promise} - */ - public async recordInventoryTransactions( - tenantId: number, - billId: number, - billDate: Date, - override?: boolean - ): Promise { - // Retrieve the next inventory lot number. - const lotNumber = this.inventoryService.getNextLotNumber(tenantId); - - const inventoryEntries = await this.itemsEntriesService.getInventoryEntries( - tenantId, - 'Bill', - billId - ); - // Can't continue if there is no entries has inventory items in the bill. - if (inventoryEntries.length <= 0) return; - - // Inventory transactions. - const inventoryTranscations = this.inventoryService.transformItemEntriesToInventory( - inventoryEntries, - 'Bill', - billId, - 'IN', - billDate, - lotNumber - ); - // Records the inventory transactions. - await this.inventoryService.recordInventoryTransactions( - tenantId, - inventoryTranscations, - override - ); - // Save the next lot number settings. - await this.inventoryService.incrementNextLotNumber(tenantId); - - // Triggers `onInventoryTransactionsCreated` event. - this.eventDispatcher.dispatch(events.bill.onInventoryTransactionsCreated, { - tenantId, - billId, - billDate, - }); - } - - /** - * Reverts the inventory transactions of the given bill id. - * @param {number} tenantId - Tenant id. - * @param {number} billId - Bill id. - * @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 } - ); - } - - /** - * Records the bill journal transactions. - * @async - * @param {IBill} bill - * @param {Integer} billId - */ - public async recordJournalTransactions( - tenantId: number, - bill: IBill, - override: boolean = false - ) { - const journal = new JournalPoster(tenantId); - const journalCommands = new JournalCommands(journal); - - await journalCommands.bill(bill, override); - - return Promise.all([ - journal.deleteEntries(), - journal.saveEntries(), - journal.saveBalance(), - ]); - } - /** * Retrieve bills data table list. * @param {number} tenantId - @@ -544,36 +445,6 @@ export default class BillsService extends SalesInvoicesCost { return bill; } - /** - * Schedules compute bill items cost based on each item cost method. - * @param {number} tenantId - - * @param {IBill} bill - - * @return {Promise} - */ - public async scheduleComputeBillItemsCost(tenantId: number, billId: number) { - const { Item, Bill } = this.tenancy.models(tenantId); - - // Retrieve the bill with associated entries. - const bill = await Bill.query() - .findById(billId) - .withGraphFetched('entries'); - - // Retrieves inventory items only. - const inventoryItems = await Item.query() - .whereIn('id', map(bill.entries, 'itemId')) - .where('type', 'inventory'); - - const inventoryItemsIds = map(inventoryItems, 'id'); - - if (inventoryItemsIds.length > 0) { - await this.scheduleComputeItemsCost( - tenantId, - inventoryItemsIds, - bill.billDate - ); - } - } - /** * Mark the bill as open. * @param {number} tenantId @@ -588,10 +459,89 @@ export default class BillsService extends SalesInvoicesCost { if (oldBill.isOpen) { throw new ServiceError(ERRORS.BILL_ALREADY_OPEN); } - // Record the bill opened at on the storage. await Bill.query().findById(billId).patch({ openedAt: moment().toMySqlDateTime(), }); } + + /** + * Records the inventory transactions from the given bill input. + * @param {Bill} bill - Bill model object. + * @param {number} billId - Bill id. + * @return {Promise} + */ + public async recordInventoryTransactions( + tenantId: number, + bill: IBill, + override?: boolean + ): Promise { + await this.inventoryService.recordInventoryTransactionsFromItemsEntries( + tenantId, + bill.id, + 'Bill', + bill.billDate, + 'IN', + override + ); + // Triggers `onInventoryTransactionsCreated` event. + this.eventDispatcher.dispatch(events.bill.onInventoryTransactionsCreated, { + tenantId, + bill, + }); + } + + /** + * Reverts the inventory transactions of the given bill id. + * @param {number} tenantId - Tenant id. + * @param {number} billId - Bill id. + * @return {Promise} + */ + public async revertInventoryTransactions(tenantId: number, billId: number) { + // Deletes the inventory transactions by the given reference id and type. + await this.inventoryService.deleteInventoryTransactions( + tenantId, + billId, + 'Bill' + ); + } + + /** + * Records the bill journal transactions. + * @async + * @param {IBill} bill + * @param {Integer} billId + */ + public async recordJournalTransactions( + tenantId: number, + bill: IBill, + override: boolean = false + ) { + const journal = new JournalPoster(tenantId); + const journalCommands = new JournalCommands(journal); + + await journalCommands.bill(bill, override); + + return Promise.all([ + journal.deleteEntries(), + journal.saveEntries(), + journal.saveBalance(), + ]); + } + + /** + * Reverts the bill journal entries. + * @param {number} tenantId + * @param {number} billId + */ + public async revertJournalEntries( + tenantId: number, + billId: number + ): Promise { + await this.journalPosterService.revertJournalTransactions( + tenantId, + billId, + 'Bill' + ); + } } diff --git a/server/src/services/Sales/SalesEstimate.ts b/server/src/services/Sales/SalesEstimate.ts index 6ce5d7bac..12e0971d9 100644 --- a/server/src/services/Sales/SalesEstimate.ts +++ b/server/src/services/Sales/SalesEstimate.ts @@ -1,6 +1,12 @@ import { omit, sumBy } from 'lodash'; import { Service, Inject } from 'typedi'; -import { IEstimatesFilter, IFilterMeta, IPaginationMeta, ISaleEstimate, ISaleEstimateDTO } from 'interfaces'; +import { + IEstimatesFilter, + IFilterMeta, + IPaginationMeta, + ISaleEstimate, + ISaleEstimateDTO, +} from 'interfaces'; import { EventDispatcher, EventDispatcherInterface, @@ -14,7 +20,6 @@ import { ServiceError } from 'exceptions'; import CustomersService from 'services/Contacts/CustomersService'; import moment from 'moment'; - const ERRORS = { SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND', CUSTOMER_NOT_FOUND: 'CUSTOMER_NOT_FOUND', @@ -24,8 +29,9 @@ const ERRORS = { SALE_ESTIMATE_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_CONVERTED_TO_INVOICE', SALE_ESTIMATE_ALREADY_REJECTED: 'SALE_ESTIMATE_ALREADY_REJECTED', SALE_ESTIMATE_ALREADY_APPROVED: 'SALE_ESTIMATE_ALREADY_APPROVED', - SALE_ESTIMATE_NOT_DELIVERED: 'SALE_ESTIMATE_NOT_DELIVERED' + SALE_ESTIMATE_NOT_DELIVERED: 'SALE_ESTIMATE_NOT_DELIVERED', }; + /** * Sale estimate service. * @Service @@ -52,26 +58,32 @@ export default class SaleEstimateService { /** * Retrieve sale estimate or throw service error. - * @param {number} tenantId + * @param {number} tenantId * @return {ISaleEstimate} */ async getSaleEstimateOrThrowError(tenantId: number, saleEstimateId: number) { const { SaleEstimate } = this.tenancy.models(tenantId); - const foundSaleEstimate = await SaleEstimate.query().findById(saleEstimateId); - + const foundSaleEstimate = await SaleEstimate.query().findById( + saleEstimateId + ); + if (!foundSaleEstimate) { throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND); } return foundSaleEstimate; } - + /** * Validate the estimate number unique on the storage. - * @param {Request} req - * @param {Response} res - * @param {Function} next + * @param {Request} req + * @param {Response} res + * @param {Function} next */ - async validateEstimateNumberExistance(tenantId: number, estimateNumber: string, notEstimateId?: number) { + async validateEstimateNumberExistance( + tenantId: number, + estimateNumber: string, + notEstimateId?: number + ) { const { SaleEstimate } = this.tenancy.models(tenantId); const foundSaleEstimate = await SaleEstimate.query() @@ -88,34 +100,35 @@ export default class SaleEstimateService { /** * Transform DTO object ot model object. - * @param {number} tenantId - * @param {ISaleEstimateDTO} saleEstimateDTO - * @param {ISaleEstimate} oldSaleEstimate + * @param {number} tenantId + * @param {ISaleEstimateDTO} saleEstimateDTO + * @param {ISaleEstimate} oldSaleEstimate * @return {ISaleEstimate} */ transformDTOToModel( tenantId: number, estimateDTO: ISaleEstimateDTO, - oldSaleEstimate?: ISaleEstimate, + oldSaleEstimate?: ISaleEstimate ): ISaleEstimate { const { ItemEntry } = this.tenancy.models(tenantId); - const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e)); + const amount = sumBy(estimateDTO.entries, (e) => ItemEntry.calcAmount(e)); return { amount, - ...formatDateFields( - omit(estimateDTO, ['delivered', 'entries']), - ['estimateDate', 'expirationDate'] - ), - entries: estimateDTO.entries.map((entry) => ({ + ...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [ + 'estimateDate', + 'expirationDate', + ]), + entries: estimateDTO.entries.map((entry) => ({ reference_type: 'SaleEstimate', ...omit(entry, ['total', 'amount', 'id']), })), // Avoid rewrite the deliver date in edit mode when already published. - ...(estimateDTO.delivered && (!oldSaleEstimate?.deliveredAt)) && ({ - deliveredAt: moment().toMySqlDateTime(), - }), + ...(estimateDTO.delivered && + !oldSaleEstimate?.deliveredAt && { + deliveredAt: moment().toMySqlDateTime(), + }), }; } @@ -139,23 +152,35 @@ export default class SaleEstimateService { // Validate estimate number uniquiness on the storage. if (estimateDTO.estimateNumber) { - await this.validateEstimateNumberExistance(tenantId, estimateDTO.estimateNumber); + await this.validateEstimateNumberExistance( + tenantId, + estimateDTO.estimateNumber + ); } // Retrieve the given customer or throw not found service error. await this.customersService.getCustomer(tenantId, estimateDTO.customerId); // Validate items IDs existance on the storage. - await this.itemsEntriesService.validateItemsIdsExistance(tenantId, estimateDTO.entries); + await this.itemsEntriesService.validateItemsIdsExistance( + tenantId, + estimateDTO.entries + ); // Validate non-sellable items. - await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, estimateDTO.entries); + await this.itemsEntriesService.validateNonSellableEntriesItems( + tenantId, + estimateDTO.entries + ); - const saleEstimate = await SaleEstimate.query() - .upsertGraphAndFetch({ ...estimateObj }); + const saleEstimate = await SaleEstimate.query().upsertGraphAndFetch({ + ...estimateObj, + }); this.logger.info('[sale_estimate] insert sale estimated success.'); await this.eventDispatcher.dispatch(events.saleEstimate.onCreated, { - tenantId, saleEstimate, saleEstimateId: saleEstimate.id, + tenantId, + saleEstimate, + saleEstimateId: saleEstimate.id, }); return saleEstimate; @@ -175,38 +200,61 @@ export default class SaleEstimateService { estimateDTO: ISaleEstimateDTO ): Promise { const { SaleEstimate } = this.tenancy.models(tenantId); - const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId); - + const oldSaleEstimate = await this.getSaleEstimateOrThrowError( + tenantId, + estimateId + ); // Transform DTO object ot model object. - const estimateObj = this.transformDTOToModel(tenantId, estimateDTO, oldSaleEstimate); - + const estimateObj = this.transformDTOToModel( + tenantId, + estimateDTO, + oldSaleEstimate + ); // Validate estimate number uniquiness on the storage. if (estimateDTO.estimateNumber) { - await this.validateEstimateNumberExistance(tenantId, estimateDTO.estimateNumber, estimateId); + await this.validateEstimateNumberExistance( + tenantId, + estimateDTO.estimateNumber, + estimateId + ); } // Retrieve the given customer or throw not found service error. await this.customersService.getCustomer(tenantId, estimateDTO.customerId); // Validate sale estimate entries existance. - await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, estimateId, 'SaleEstiamte', estimateDTO.entries); - + await this.itemsEntriesService.validateEntriesIdsExistance( + tenantId, + estimateId, + 'SaleEstiamte', + estimateDTO.entries + ); // Validate items IDs existance on the storage. - await this.itemsEntriesService.validateItemsIdsExistance(tenantId, estimateDTO.entries); - + await this.itemsEntriesService.validateItemsIdsExistance( + tenantId, + estimateDTO.entries + ); // Validate non-sellable items. - await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, estimateDTO.entries); + await this.itemsEntriesService.validateNonSellableEntriesItems( + tenantId, + estimateDTO.entries + ); this.logger.info('[sale_estimate] editing sale estimate on the storage.'); - const saleEstimate = await SaleEstimate.query() - .upsertGraphAndFetch({ - id: estimateId, - ...estimateObj - }); + const saleEstimate = await SaleEstimate.query().upsertGraphAndFetch({ + id: estimateId, + ...estimateObj, + }); await this.eventDispatcher.dispatch(events.saleEstimate.onEdited, { - tenantId, estimateId, saleEstimate, oldSaleEstimate, + tenantId, + estimateId, + saleEstimate, + oldSaleEstimate, + }); + this.logger.info('[sale_estiamte] edited successfully', { + tenantId, + estimateId, }); - this.logger.info('[sale_estiamte] edited successfully', { tenantId, estimateId }); return saleEstimate; } @@ -218,28 +266,41 @@ export default class SaleEstimateService { * @param {IEstimate} estimateId * @return {void} */ - public async deleteEstimate(tenantId: number, estimateId: number): Promise { + public async deleteEstimate( + tenantId: number, + estimateId: number + ): Promise { const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId); // Retrieve sale estimate or throw not found service error. - const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId); + const oldSaleEstimate = await this.getSaleEstimateOrThrowError( + tenantId, + estimateId + ); // Throw error if the sale estimate converted to sale invoice. if (oldSaleEstimate.convertedToInvoiceId) { throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE); } - this.logger.info('[sale_estimate] delete sale estimate and associated entries from the storage.'); + this.logger.info( + '[sale_estimate] delete sale estimate and associated entries from the storage.' + ); await ItemEntry.query() .where('reference_id', estimateId) .where('reference_type', 'SaleEstimate') .delete(); await SaleEstimate.query().where('id', estimateId).delete(); - this.logger.info('[sale_estimate] deleted successfully.', { tenantId, estimateId }); + this.logger.info('[sale_estimate] deleted successfully.', { + tenantId, + estimateId, + }); await this.eventDispatcher.dispatch(events.saleEstimate.onDeleted, { - tenantId, saleEstimateId: estimateId, oldSaleEstimate, + tenantId, + saleEstimateId: estimateId, + oldSaleEstimate, }); } @@ -255,7 +316,7 @@ export default class SaleEstimateService { .findById(estimateId) .withGraphFetched('entries') .withGraphFetched('customer'); - + if (!estimate) { throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND); } @@ -270,19 +331,26 @@ export default class SaleEstimateService { public async estimatesList( tenantId: number, estimatesFilter: IEstimatesFilter - ): Promise<{ salesEstimates: ISaleEstimate[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { + ): Promise<{ + salesEstimates: ISaleEstimate[]; + pagination: IPaginationMeta; + filterMeta: IFilterMeta; + }> { const { SaleEstimate } = this.tenancy.models(tenantId); - const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleEstimate, estimatesFilter); - - const { results, pagination } = await SaleEstimate.query().onBuild(builder => { - builder.withGraphFetched('customer'); - builder.withGraphFetched('entries'); - dynamicFilter.buildQuery()(builder); - }).pagination( - estimatesFilter.page - 1, - estimatesFilter.pageSize, + const dynamicFilter = await this.dynamicListService.dynamicList( + tenantId, + SaleEstimate, + estimatesFilter ); + const { results, pagination } = await SaleEstimate.query() + .onBuild((builder) => { + builder.withGraphFetched('customer'); + builder.withGraphFetched('entries'); + dynamicFilter.buildQuery()(builder); + }) + .pagination(estimatesFilter.page - 1, estimatesFilter.pageSize); + return { salesEstimates: results, pagination, @@ -299,12 +367,15 @@ export default class SaleEstimateService { async convertEstimateToInvoice( tenantId: number, estimateId: number, - invoiceId: number, + invoiceId: number ): Promise { const { SaleEstimate } = this.tenancy.models(tenantId); // Retrieve details of the given sale estimate. - const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId); + const saleEstimate = await this.getSaleEstimateOrThrowError( + tenantId, + estimateId + ); await SaleEstimate.query().where('id', estimateId).patch({ convertedToInvoiceId: invoiceId, @@ -320,16 +391,18 @@ export default class SaleEstimateService { */ async unlinkConvertedEstimateFromInvoice( tenantId: number, - invoiceId: number, + invoiceId: number ): Promise { const { SaleEstimate } = this.tenancy.models(tenantId); - await SaleEstimate.query().where({ - convertedToInvoiceId: invoiceId, - }).patch({ - convertedToInvoiceId: null, - convertedToInvoiceAt: null, - }); + await SaleEstimate.query() + .where({ + convertedToInvoiceId: invoiceId, + }) + .patch({ + convertedToInvoiceId: null, + convertedToInvoiceAt: null, + }); } /** @@ -339,12 +412,15 @@ export default class SaleEstimateService { */ public async deliverSaleEstimate( tenantId: number, - saleEstimateId: number, + saleEstimateId: number ): Promise { const { SaleEstimate } = this.tenancy.models(tenantId); // Retrieve details of the given sale estimate id. - const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId); + const saleEstimate = await this.getSaleEstimateOrThrowError( + tenantId, + saleEstimateId + ); // Throws error in case the sale estimate already published. if (saleEstimate.isDelivered) { @@ -352,29 +428,32 @@ export default class SaleEstimateService { } // Record the delivered at on the storage. await SaleEstimate.query().where('id', saleEstimateId).patch({ - deliveredAt: moment().toMySqlDateTime() + deliveredAt: moment().toMySqlDateTime(), }); } /** * Mark the sale estimate as approved from the customer. - * @param {number} tenantId - * @param {number} saleEstimateId + * @param {number} tenantId + * @param {number} saleEstimateId */ public async approveSaleEstimate( tenantId: number, - saleEstimateId: number, + saleEstimateId: number ): Promise { const { SaleEstimate } = this.tenancy.models(tenantId); // Retrieve details of the given sale estimate id. - const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId); + const saleEstimate = await this.getSaleEstimateOrThrowError( + tenantId, + saleEstimateId + ); // Throws error in case the sale estimate still not delivered to customer. if (!saleEstimate.isDelivered) { throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_DELIVERED); } - // Throws error in case the sale estimate already approved. + // Throws error in case the sale estimate already approved. if (saleEstimate.isApproved) { throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_APPROVED); } @@ -386,17 +465,20 @@ export default class SaleEstimateService { /** * Mark the sale estimate as rejected from the customer. - * @param {number} tenantId - * @param {number} saleEstimateId + * @param {number} tenantId + * @param {number} saleEstimateId */ public async rejectSaleEstimate( tenantId: number, - saleEstimateId: number, + saleEstimateId: number ): Promise { const { SaleEstimate } = this.tenancy.models(tenantId); // Retrieve details of the given sale estimate id. - const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId); + const saleEstimate = await this.getSaleEstimateOrThrowError( + tenantId, + saleEstimateId + ); // Throws error in case the sale estimate still not delivered to customer. if (!saleEstimate.isDelivered) { @@ -412,4 +494,4 @@ export default class SaleEstimateService { approvedAt: null, }); } -} \ No newline at end of file +} diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index b9c0d9b06..0bbcb0dcf 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -341,7 +341,7 @@ export default class SaleInvoicesService { } // Record the delivered at on the storage. await saleInvoiceRepository.update( - { deliveredAt: moment().toMySqlDateTime(), }, + { deliveredAt: moment().toMySqlDateTime() }, { id: saleInvoiceId } ); // Triggers `onSaleInvoiceDelivered` event. @@ -416,48 +416,17 @@ export default class SaleInvoicesService { */ public async recordInventoryTranscactions( tenantId: number, - saleInvoiceId: number, - saleInvoiceDate: Date, + saleInvoice: ISaleInvoice, override?: boolean ): Promise { - // Gets the next inventory lot number. - const lotNumber = this.inventoryService.getNextLotNumber(tenantId); - - // Loads the inventory items entries of the given sale invoice. - const inventoryEntries = await this.itemsEntriesService.getInventoryEntries( + await this.inventoryService.recordInventoryTransactionsFromItemsEntries( tenantId, + saleInvoice.id, 'SaleInvoice', - saleInvoiceId - ); - // Can't continue if there is no entries has inventory items in the invoice. - if (inventoryEntries.length <= 0) return; - - // Inventory transactions. - const inventoryTranscations = this.inventoryService.transformItemEntriesToInventory( - inventoryEntries, - 'SaleInvoice', - saleInvoiceId, + saleInvoice.invoiceDate, 'OUT', - saleInvoiceDate, - lotNumber - ); - // Records the inventory transactions of the given sale invoice. - await this.inventoryService.recordInventoryTransactions( - tenantId, - inventoryTranscations, override ); - // Increment and save the next lot number settings. - await this.inventoryService.incrementNextLotNumber(tenantId); - - // Triggers `onInventoryTransactionsCreated` event. - await this.eventDispatcher.dispatch( - events.saleInvoice.onInventoryTransactionsCreated, - { - tenantId, - saleInvoiceId, - } - ); } /** @@ -491,7 +460,7 @@ export default class SaleInvoicesService { await Promise.all([ journal.deleteEntries(), journal.saveBalance(), - journal.saveEntries() + journal.saveEntries(), ]); } @@ -505,25 +474,14 @@ export default class SaleInvoicesService { 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( + const { + oldInventoryTransactions, + } = await this.inventoryService.deleteInventoryTransactions( tenantId, saleInvoiceId, 'SaleInvoice' ); - // Triggers 'onInventoryTransactionsDeleted' event. - this.eventDispatcher.dispatch( - events.saleInvoice.onInventoryTransactionsDeleted, - { tenantId, saleInvoiceId, oldInventoryTransactions } - ); } /** @@ -545,8 +503,9 @@ export default class SaleInvoicesService { /** * Retrieve sale invoice with associated entries. - * @async - * @param {Number} saleInvoiceId + * @param {Number} saleInvoiceId - + * @param {ISystemUser} authorizedUser - + * @return {Promise} */ public async getSaleInvoice( tenantId: number, diff --git a/server/src/services/Sales/SalesInvoicesCost.ts b/server/src/services/Sales/SalesInvoicesCost.ts index 291487f68..0ff67295e 100644 --- a/server/src/services/Sales/SalesInvoicesCost.ts +++ b/server/src/services/Sales/SalesInvoicesCost.ts @@ -1,9 +1,8 @@ import { Container, Service, Inject } from 'typedi'; -import { map } from 'lodash'; import JournalPoster from 'services/Accounting/JournalPoster'; import InventoryService from 'services/Inventory/Inventory'; import TenancyService from 'services/Tenancy/TenancyService'; -import { ISaleInvoice, IItemEntry, IInventoryLotCost, IItem } from 'interfaces'; +import { IInventoryLotCost, IItem } from 'interfaces'; import JournalCommands from 'services/Accounting/JournalCommands'; @Service() @@ -39,86 +38,6 @@ 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/services/Sales/SalesReceipts.ts b/server/src/services/Sales/SalesReceipts.ts index f6ae797e4..43dd81ef2 100644 --- a/server/src/services/Sales/SalesReceipts.ts +++ b/server/src/services/Sales/SalesReceipts.ts @@ -6,7 +6,9 @@ import { EventDispatcherInterface, } from 'decorators/eventDispatcher'; import events from 'subscribers/events'; -import { ISaleReceipt, ISaleReceiptDTO } from 'interfaces'; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalCommands from 'services/Accounting/JournalCommands'; +import { ISaleReceipt, ISaleReceiptDTO, IItemEntry, IItem } from 'interfaces'; import JournalPosterService from 'services/Sales/JournalPosterService'; import TenancyService from 'services/Tenancy/TenancyService'; import { formatDateFields } from 'utils'; @@ -15,15 +17,16 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { ServiceError } from 'exceptions'; import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import { ItemEntry } from 'models'; - +import InventoryService from 'services/Inventory/Inventory'; const ERRORS = { SALE_RECEIPT_NOT_FOUND: 'SALE_RECEIPT_NOT_FOUND', DEPOSIT_ACCOUNT_NOT_FOUND: 'DEPOSIT_ACCOUNT_NOT_FOUND', DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET', SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE', - SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED' + SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED', }; + @Service() export default class SalesReceiptService { @Inject() @@ -38,6 +41,9 @@ export default class SalesReceiptService { @Inject() itemsEntriesService: ItemsEntriesService; + @Inject() + inventoryService: InventoryService; + @EventDispatcher() eventDispatcher: EventDispatcherInterface; @@ -46,37 +52,56 @@ export default class SalesReceiptService { /** * Validate whether sale receipt exists on the storage. - * @param {number} tenantId - - * @param {number} saleReceiptId - + * @param {number} tenantId - + * @param {number} saleReceiptId - */ async getSaleReceiptOrThrowError(tenantId: number, saleReceiptId: number) { const { SaleReceipt } = this.tenancy.models(tenantId); - this.logger.info('[sale_receipt] trying to validate existance.', { tenantId, saleReceiptId }); - const foundSaleReceipt = await SaleReceipt.query().findById(saleReceiptId); + this.logger.info('[sale_receipt] trying to validate existance.', { + tenantId, + saleReceiptId, + }); + const foundSaleReceipt = await SaleReceipt.query() + .findById(saleReceiptId) + .withGraphFetched('entries'); if (!foundSaleReceipt) { - this.logger.info('[sale_receipt] not found on the storage.', { tenantId, saleReceiptId }); + this.logger.info('[sale_receipt] not found on the storage.', { + tenantId, + saleReceiptId, + }); throw new ServiceError(ERRORS.SALE_RECEIPT_NOT_FOUND); } return foundSaleReceipt; } - + /** * Validate whether sale receipt deposit account exists on the storage. - * @param {number} tenantId - + * @param {number} tenantId - * @param {number} accountId - */ - async validateReceiptDepositAccountExistance(tenantId: number, accountId: number) { - const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId); + async validateReceiptDepositAccountExistance( + tenantId: number, + accountId: number + ) { + const { + accountRepository, + accountTypeRepository, + } = this.tenancy.repositories(tenantId); const depositAccount = await accountRepository.findOneById(accountId); if (!depositAccount) { throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND); } - const depositAccountType = await accountTypeRepository.findOneById(depositAccount.accountTypeId); + const depositAccountType = await accountTypeRepository.findOneById( + depositAccount.accountTypeId + ); - if (!depositAccountType || depositAccountType.childRoot === 'current_asset') { + if ( + !depositAccountType || + depositAccountType.childRoot === 'current_asset' + ) { throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET); } } @@ -87,10 +112,17 @@ export default class SalesReceiptService { * @param {string} receiptNumber - * @param {number} notReceiptId - */ - async validateReceiptNumberUnique(tenantId: number, receiptNumber: string, notReceiptId?: number) { + async validateReceiptNumberUnique( + tenantId: number, + receiptNumber: string, + notReceiptId?: number + ) { const { SaleReceipt } = this.tenancy.models(tenantId); - this.logger.info('[sale_receipt] validate receipt number uniquiness.', { tenantId, receiptNumber }); + this.logger.info('[sale_receipt] validate receipt number uniquiness.', { + tenantId, + receiptNumber, + }); const saleReceipt = await SaleReceipt.query() .findOne('receipt_number', receiptNumber) .onBuild((builder) => { @@ -98,9 +130,11 @@ export default class SalesReceiptService { builder.whereNot('id', notReceiptId); } }); - + if (saleReceipt) { - this.logger.info('[sale_receipt] sale receipt number not unique.', { tenantId }); + this.logger.info('[sale_receipt] sale receipt number not unique.', { + tenantId, + }); throw new ServiceError(ERRORS.SALE_RECEIPT_NUMBER_NOT_UNIQUE); } } @@ -115,18 +149,20 @@ export default class SalesReceiptService { saleReceiptDTO: ISaleReceiptDTO, oldSaleReceipt?: ISaleReceipt ): ISaleReceipt { - const amount = sumBy(saleReceiptDTO.entries, e => ItemEntry.calcAmount(e)); + const amount = sumBy(saleReceiptDTO.entries, (e) => + ItemEntry.calcAmount(e) + ); return { amount, - ...formatDateFields( - omit(saleReceiptDTO, ['closed', 'entries']), - ['receiptDate'] - ), + ...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [ + 'receiptDate', + ]), // Avoid rewrite the deliver date in edit mode when already published. - ...(saleReceiptDTO.closed && (!oldSaleReceipt?.closedAt)) && ({ - closedAt: moment().toMySqlDateTime(), - }), + ...(saleReceiptDTO.closed && + !oldSaleReceipt?.closedAt && { + closedAt: moment().toMySqlDateTime(), + }), entries: saleReceiptDTO.entries.map((entry) => ({ reference_type: 'SaleReceipt', ...omit(entry, ['id', 'amount']), @@ -140,31 +176,56 @@ export default class SalesReceiptService { * @param {ISaleReceipt} saleReceipt * @return {Object} */ - public async createSaleReceipt(tenantId: number, saleReceiptDTO: any): Promise { + public async createSaleReceipt( + tenantId: number, + saleReceiptDTO: any + ): Promise { const { SaleReceipt } = this.tenancy.models(tenantId); // Transform sale receipt DTO to model. const saleReceiptObj = this.transformObjectDTOToModel(saleReceiptDTO); // Validate receipt deposit account existance and type. - await this.validateReceiptDepositAccountExistance(tenantId, saleReceiptDTO.depositAccountId); + await this.validateReceiptDepositAccountExistance( + tenantId, + saleReceiptDTO.depositAccountId + ); // Validate items IDs existance on the storage. - await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleReceiptDTO.entries); + await this.itemsEntriesService.validateItemsIdsExistance( + tenantId, + saleReceiptDTO.entries + ); // Validate the sellable items. - await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleReceiptDTO.entries); + await this.itemsEntriesService.validateNonSellableEntriesItems( + tenantId, + saleReceiptDTO.entries + ); // Validate sale receipt number uniuqiness. if (saleReceiptDTO.receiptNumber) { - await this.validateReceiptNumberUnique(tenantId, saleReceiptDTO.receiptNumber); + await this.validateReceiptNumberUnique( + tenantId, + saleReceiptDTO.receiptNumber + ); } - this.logger.info('[sale_receipt] trying to insert sale receipt graph.', { tenantId, saleReceiptDTO }); - const saleReceipt = await SaleReceipt.query().insertGraphAndFetch({ ...saleReceiptObj }); - - await this.eventDispatcher.dispatch(events.saleReceipt.onCreated, { tenantId, saleReceipt }); - - this.logger.info('[sale_receipt] sale receipt inserted successfully.', { tenantId }); + this.logger.info('[sale_receipt] trying to insert sale receipt graph.', { + tenantId, + saleReceiptDTO, + }); + const saleReceipt = await SaleReceipt.query().upsertGraph({ + ...saleReceiptObj, + }); + // Triggers `onSaleReceiptCreated` event. + await this.eventDispatcher.dispatch(events.saleReceipt.onCreated, { + tenantId, + saleReceipt, + saleReceiptId: saleReceipt.id, + }); + this.logger.info('[sale_receipt] sale receipt inserted successfully.', { + tenantId, + }); return saleReceipt; } @@ -175,37 +236,61 @@ export default class SalesReceiptService { * @param {ISaleReceipt} saleReceipt * @return {void} */ - public async editSaleReceipt(tenantId: number, saleReceiptId: number, saleReceiptDTO: any) { - const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId); + public async editSaleReceipt( + tenantId: number, + saleReceiptId: number, + saleReceiptDTO: any + ) { + const { SaleReceipt } = this.tenancy.models(tenantId); // Retrieve sale receipt or throw not found service error. - const oldSaleReceipt = await this.getSaleReceiptOrThrowError(tenantId, saleReceiptId); - + const oldSaleReceipt = await this.getSaleReceiptOrThrowError( + tenantId, + saleReceiptId + ); // Transform sale receipt DTO to model. - const saleReceiptObj = this.transformObjectDTOToModel(saleReceiptDTO, oldSaleReceipt); - + const saleReceiptObj = this.transformObjectDTOToModel( + saleReceiptDTO, + oldSaleReceipt + ); // Validate receipt deposit account existance and type. - await this.validateReceiptDepositAccountExistance(tenantId, saleReceiptDTO.depositAccountId); - + await this.validateReceiptDepositAccountExistance( + tenantId, + saleReceiptDTO.depositAccountId + ); // Validate items IDs existance on the storage. - await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleReceiptDTO.entries); - + await this.itemsEntriesService.validateItemsIdsExistance( + tenantId, + saleReceiptDTO.entries + ); // Validate the sellable items. - await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleReceiptDTO.entries); - + await this.itemsEntriesService.validateNonSellableEntriesItems( + tenantId, + saleReceiptDTO.entries + ); // Validate sale receipt number uniuqiness. if (saleReceiptDTO.receiptNumber) { - await this.validateReceiptNumberUnique(tenantId, saleReceiptDTO.receiptNumber, saleReceiptId); + await this.validateReceiptNumberUnique( + tenantId, + saleReceiptDTO.receiptNumber, + saleReceiptId + ); } - const saleReceipt = await SaleReceipt.query().upsertGraphAndFetch({ id: saleReceiptId, - ...saleReceiptObj + ...saleReceiptObj, }); - this.logger.info('[sale_receipt] edited successfully.', { tenantId, saleReceiptId }); + this.logger.info('[sale_receipt] edited successfully.', { + tenantId, + saleReceiptId, + }); + // Triggers `onSaleReceiptEdited` event. await this.eventDispatcher.dispatch(events.saleReceipt.onEdited, { - oldSaleReceipt, tenantId, saleReceiptId, saleReceipt, + tenantId, + oldSaleReceipt, + saleReceipt, + saleReceiptId, }); return saleReceipt; } @@ -218,20 +303,31 @@ export default class SalesReceiptService { public async deleteSaleReceipt(tenantId: number, saleReceiptId: number) { const { SaleReceipt, ItemEntry } = this.tenancy.models(tenantId); + const oldSaleReceipt = await this.getSaleReceiptOrThrowError( + tenantId, + saleReceiptId + ); await ItemEntry.query() .where('reference_id', saleReceiptId) .where('reference_type', 'SaleReceipt') .delete(); - + await SaleReceipt.query().where('id', saleReceiptId).delete(); - this.logger.info('[sale_receipt] deleted successfully.', { tenantId, saleReceiptId }); - await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted); + this.logger.info('[sale_receipt] deleted successfully.', { + tenantId, + saleReceiptId, + }); + await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted, { + tenantId, + saleReceiptId, + oldSaleReceipt + }); } /** * Retrieve sale receipt with associated entries. - * @param {Integer} saleReceiptId + * @param {Integer} saleReceiptId * @return {ISaleReceipt} */ async getSaleReceipt(tenantId: number, saleReceiptId: number) { @@ -242,7 +338,7 @@ export default class SalesReceiptService { .withGraphFetched('entries') .withGraphFetched('customer') .withGraphFetched('depositAccount'); - + if (!saleReceipt) { throw new ServiceError(ERRORS.SALE_RECEIPT_NOT_FOUND); } @@ -251,28 +347,37 @@ export default class SalesReceiptService { /** * Retrieve sales receipts paginated and filterable list. - * @param {number} tenantId - * @param {ISaleReceiptFilter} salesReceiptsFilter + * @param {number} tenantId + * @param {ISaleReceiptFilter} salesReceiptsFilter */ public async salesReceiptsList( tenantId: number, - salesReceiptsFilter: ISaleReceiptFilter, - ): Promise<{ salesReceipts: ISaleReceipt[], pagination: IPaginationMeta, filterMeta: IFilterMeta }> { + salesReceiptsFilter: ISaleReceiptFilter + ): Promise<{ + salesReceipts: ISaleReceipt[]; + pagination: IPaginationMeta; + filterMeta: IFilterMeta; + }> { const { SaleReceipt } = this.tenancy.models(tenantId); - const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, SaleReceipt, salesReceiptsFilter); - - this.logger.info('[sale_receipt] try to get sales receipts list.', { tenantId }); - const { results, pagination } = await SaleReceipt.query().onBuild((builder) => { - builder.withGraphFetched('depositAccount'); - builder.withGraphFetched('customer'); - builder.withGraphFetched('entries'); - - dynamicFilter.buildQuery()(builder); - }).pagination( - salesReceiptsFilter.page - 1, - salesReceiptsFilter.pageSize, + const dynamicFilter = await this.dynamicListService.dynamicList( + tenantId, + SaleReceipt, + salesReceiptsFilter ); + this.logger.info('[sale_receipt] try to get sales receipts list.', { + tenantId, + }); + const { results, pagination } = await SaleReceipt.query() + .onBuild((builder) => { + builder.withGraphFetched('depositAccount'); + builder.withGraphFetched('customer'); + builder.withGraphFetched('entries'); + + dynamicFilter.buildQuery()(builder); + }) + .pagination(salesReceiptsFilter.page - 1, salesReceiptsFilter.pageSize); + return { salesReceipts: results, pagination, @@ -282,8 +387,8 @@ export default class SalesReceiptService { /** * Mark the given sale receipt as closed. - * @param {number} tenantId - * @param {number} saleReceiptId + * @param {number} tenantId + * @param {number} saleReceiptId * @return {Promise} */ async closeSaleReceipt( @@ -293,7 +398,10 @@ export default class SalesReceiptService { const { SaleReceipt } = this.tenancy.models(tenantId); // Retrieve sale receipt or throw not found service error. - const oldSaleReceipt = await this.getSaleReceiptOrThrowError(tenantId, saleReceiptId); + const oldSaleReceipt = await this.getSaleReceiptOrThrowError( + tenantId, + saleReceiptId + ); // Throw service error if the sale receipt already closed. if (oldSaleReceipt.isClosed) { @@ -304,4 +412,96 @@ export default class SalesReceiptService { closedAt: moment().toMySqlDateTime(), }); } + + /** + * Writes the sale invoice income journal entries. + * @param {number} tenantId - Tenant id. + * @param {ISaleInvoice} saleInvoice - Sale invoice id. + */ + public async writesIncomeJournalEntries( + tenantId: number, + saleInvoice: ISaleReceipt & { + entries: IItemEntry & { item: IItem }; + }, + override: boolean = false + ): Promise { + const journal = new JournalPoster(tenantId); + const journalCommands = new JournalCommands(journal); + + if (override) { + await journalCommands.revertJournalEntries(saleInvoice.id, 'SaleReceipt'); + } + // Records the sale invoice journal entries. + await journalCommands.saleReceiptIncomeEntries(saleInvoice); + + await Promise.all([ + journal.deleteEntries(), + journal.saveBalance(), + journal.saveEntries(), + ]); + } + + /** + * Reverting the sale receipt journal entries. + * @param {number} tenantId + * @param {number} saleReceiptId + * @return {Promise} + */ + public async revertReceiptJournalEntries( + tenantId: number, + saleReceiptId: number | number[] + ): Promise { + return this.journalService.revertJournalTransactions( + tenantId, + saleReceiptId, + 'SaleReceipt' + ); + } + + /** + * Records the inventory transactions from the given bill input. + * @param {Bill} bill - Bill model object. + * @param {number} billId - Bill id. + * @return {Promise} + */ + public async recordInventoryTransactions( + tenantId: number, + saleReceipt: ISaleReceipt, + override?: boolean + ): Promise { + await this.inventoryService.recordInventoryTransactionsFromItemsEntries( + tenantId, + saleReceipt.id, + 'SaleReceipt', + saleReceipt.receiptDate, + 'OUT', + override, + ); + // Triggers `onInventoryTransactionsCreated` event. + this.eventDispatcher.dispatch( + events.saleReceipt.onInventoryTransactionsCreated, + { + tenantId, + saleReceipt, + saleReceiptId: saleReceipt.id, + } + ); + } + + /** + * Reverts the inventory transactions of the given bill id. + * @param {number} tenantId - Tenant id. + * @param {number} billId - Bill id. + * @return {Promise} + */ + public async revertInventoryTransactions( + tenantId: number, + receiptId: number + ) { + return this.inventoryService.deleteInventoryTransactions( + tenantId, + receiptId, + 'SaleReceipt' + ); + } } diff --git a/server/src/subscribers/Bills/SyncItemsQuantity.ts b/server/src/subscribers/Bills/SyncItemsQuantity.ts new file mode 100644 index 000000000..28bb86950 --- /dev/null +++ b/server/src/subscribers/Bills/SyncItemsQuantity.ts @@ -0,0 +1,59 @@ +import { Container } from 'typedi'; +import { EventSubscriber, On } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import ItemsEntriesService from 'services/Items/ItemsEntriesService'; + +@EventSubscriber() +export default class BillSubscriber { + tenancy: TenancyService; + logger: any; + itemsEntriesService: ItemsEntriesService; + + /** + * Constructor method. + */ + constructor() { + this.tenancy = Container.get(TenancyService); + this.logger = Container.get('logger'); + this.itemsEntriesService = Container.get(ItemsEntriesService); + } + + /** + * Increments the sale invoice items once the invoice created. + */ + @On(events.bill.onCreated) + public async handleDecrementSaleInvoiceItemsQuantity({ tenantId, bill }) { + await this.itemsEntriesService.incrementItemsEntries( + tenantId, + bill.entries + ); + } + + /** + * Decrements the sale invoice items once the invoice deleted. + */ + @On(events.bill.onDeleted) + public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldBill }) { + await this.itemsEntriesService.decrementItemsQuantity( + tenantId, + oldBill.entries + ); + } + + /** + * Handle increment/decrement the different items quantity once the sale invoice be edited. + */ + @On(events.bill.onEdited) + public async handleChangeSaleInvoiceItemsQuantityOnEdit({ + tenantId, + bill, + oldBill, + }) { + await this.itemsEntriesService.changeItemsQuantity( + tenantId, + bill.entries, + oldBill.entries + ); + } +} diff --git a/server/src/subscribers/Bills/SyncVendorsBalances.ts b/server/src/subscribers/Bills/SyncVendorsBalances.ts new file mode 100644 index 000000000..020e3bde7 --- /dev/null +++ b/server/src/subscribers/Bills/SyncVendorsBalances.ts @@ -0,0 +1,68 @@ +import { Container } from 'typedi'; +import { EventSubscriber, On } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; + +@EventSubscriber() +export default class BillSubscriber { + tenancy: TenancyService; + logger: any; + + /** + * Constructor method. + */ + constructor() { + this.tenancy = Container.get(TenancyService); + this.logger = Container.get('logger'); + } + + /** + * Handles vendor balance increment once bill created. + */ + @On(events.bill.onCreated) + async handleVendorBalanceIncrement({ tenantId, billId, bill }) { + const { vendorRepository } = this.tenancy.repositories(tenantId); + + // Increments vendor balance. + this.logger.info('[bill] trying to increment vendor balance.', { + tenantId, + billId, + }); + await vendorRepository.changeBalance(bill.vendorId, bill.amount); + } + + /** + * Handles vendor balance decrement once bill deleted. + */ + @On(events.bill.onDeleted) + async handleVendorBalanceDecrement({ tenantId, billId, oldBill }) { + const { vendorRepository } = this.tenancy.repositories(tenantId); + + // Decrements vendor balance. + this.logger.info('[bill] trying to decrement vendor balance.', { + tenantId, + billId, + }); + await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1); + } + + /** + * Handles vendor balance difference change. + */ + @On(events.bill.onEdited) + async handleVendorBalanceDiffChange({ tenantId, billId, oldBill, bill }) { + const { vendorRepository } = this.tenancy.repositories(tenantId); + + // Changes the diff vendor balance between old and new amount. + this.logger.info('[bill[ change vendor the different balance.', { + tenantId, + billId, + }); + await vendorRepository.changeDiffBalance( + bill.vendorId, + bill.amount, + oldBill.amount, + oldBill.vendorId + ); + } +} diff --git a/server/src/subscribers/Bills/WriteInventoryTransactions.ts b/server/src/subscribers/Bills/WriteInventoryTransactions.ts new file mode 100644 index 000000000..75ff3da4e --- /dev/null +++ b/server/src/subscribers/Bills/WriteInventoryTransactions.ts @@ -0,0 +1,60 @@ +import { Container } from 'typedi'; +import { EventSubscriber, On } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import BillsService from 'services/Purchases/Bills'; + +@EventSubscriber() +export default class BillSubscriber { + tenancy: TenancyService; + billsService: BillsService; + logger: any; + + /** + * Constructor method. + */ + constructor() { + this.tenancy = Container.get(TenancyService); + this.billsService = Container.get(BillsService); + this.logger = Container.get('logger'); + } + + /** + * Handles writing the inventory transactions once bill created. + */ + @On(events.bill.onCreated) + async handleWritingInventoryTransactions({ tenantId, bill }) { + this.logger.info('[bill] writing the inventory transactions', { tenantId }); + this.billsService.recordInventoryTransactions( + tenantId, + bill + ); + } + + /** + * Handles the overwriting the inventory transactions once bill edited. + */ + @On(events.bill.onEdited) + async handleOverwritingInventoryTransactions({ tenantId, bill }) { + this.logger.info('[bill] overwriting the inventory transactions.', { + tenantId, + }); + this.billsService.recordInventoryTransactions( + tenantId, + bill, + true + ); + } + + /** + * Handles the reverting the inventory transactions once the bill deleted. + */ + @On(events.bill.onDeleted) + async handleRevertInventoryTransactions({ tenantId, billId }) { + this.logger.info('[bill] reverting the bill inventory transactions', { + tenantId, + billId, + }); + this.billsService.revertInventoryTransactions(tenantId, billId); + } +} diff --git a/server/src/subscribers/Bills/WriteJournalEntries.ts b/server/src/subscribers/Bills/WriteJournalEntries.ts new file mode 100644 index 000000000..3a94f2c71 --- /dev/null +++ b/server/src/subscribers/Bills/WriteJournalEntries.ts @@ -0,0 +1,54 @@ +import { Container } from 'typedi'; +import { EventSubscriber, On } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import BillsService from 'services/Purchases/Bills'; + +@EventSubscriber() +export default class BillSubscriber { + tenancy: TenancyService; + billsService: BillsService; + logger: any; + + /** + * Constructor method. + */ + constructor() { + this.tenancy = Container.get(TenancyService); + this.billsService = Container.get(BillsService); + this.logger = Container.get('logger'); + } + + /** + * Handles writing journal entries once bill created. + */ + @On(events.bill.onCreated) + async handlerWriteJournalEntriesOnCreate({ tenantId, bill }) { + // Writes the journal entries for the given bill transaction. + this.logger.info('[bill] writing bill journal entries.', { tenantId }); + await this.billsService.recordJournalTransactions(tenantId, bill); + } + + /** + * Handles the overwriting journal entries once bill edited. + */ + @On(events.bill.onEdited) + async handleOverwriteJournalEntriesOnEdit({ tenantId, bill }) { + // Overwrite the journal entries for the given bill transaction. + this.logger.info('[bill] overwriting bill journal entries.', { tenantId }); + await this.billsService.recordJournalTransactions(tenantId, bill, true); + } + + /** + * Handles revert journal entries on bill deleted. + */ + @On(events.bill.onDeleted) + async handlerDeleteJournalEntries({ tenantId, billId }) { + // Delete associated bill journal transactions. + this.logger.info('[bill] trying to delete journal entries.', { + tenantId, + billId, + }); + await this.billsService.revertJournalEntries(tenantId, billId); + } +} diff --git a/server/src/subscribers/Bills/index.ts b/server/src/subscribers/Bills/index.ts new file mode 100644 index 000000000..984dae35e --- /dev/null +++ b/server/src/subscribers/Bills/index.ts @@ -0,0 +1,22 @@ +import { Container } from 'typedi'; +import { EventSubscriber, On } from 'event-dispatch'; + +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import BillsService from 'services/Purchases/Bills'; + +@EventSubscriber() +export default class BillSubscriber { + tenancy: TenancyService; + billsService: BillsService; + logger: any; + + /** + * Constructor method. + */ + constructor() { + this.tenancy = Container.get(TenancyService); + this.billsService = Container.get(BillsService); + this.logger = Container.get('logger'); + } +} diff --git a/server/src/subscribers/SaleInvoices/SyncCustomersBalance.ts b/server/src/subscribers/SaleInvoices/SyncCustomersBalance.ts new file mode 100644 index 000000000..2e1094348 --- /dev/null +++ b/server/src/subscribers/SaleInvoices/SyncCustomersBalance.ts @@ -0,0 +1,82 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; + +@EventSubscriber() +export default class SaleInvoiceSubscriber { + logger: any; + tenancy: TenancyService; + + /** + * Constructor method. + */ + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + } + + /** + * Handles customer balance increment once sale invoice created. + */ + @On(events.saleInvoice.onCreated) + public async handleCustomerBalanceIncrement({ + tenantId, + saleInvoice, + saleInvoiceId, + }) { + const { customerRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[sale_invoice] trying to increment customer balance.', { + tenantId, + }); + await customerRepository.changeBalance( + saleInvoice.customerId, + saleInvoice.balance + ); + } + + /** + * Handles customer balance diff balnace change once sale invoice edited. + */ + @On(events.saleInvoice.onEdited) + public async onSaleInvoiceEdited({ + tenantId, + saleInvoice, + oldSaleInvoice, + saleInvoiceId, + }) { + const { customerRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[sale_invoice] trying to change diff customer balance.', { + tenantId, + }); + await customerRepository.changeDiffBalance( + saleInvoice.customerId, + saleInvoice.balance, + oldSaleInvoice.balance, + oldSaleInvoice.customerId + ); + } + + + /** + * Handles customer balance decrement once sale invoice deleted. + */ + @On(events.saleInvoice.onDeleted) + public async handleCustomerBalanceDecrement({ + tenantId, + saleInvoiceId, + oldSaleInvoice, + }) { + const { customerRepository } = this.tenancy.repositories(tenantId); + + this.logger.info('[sale_invoice] trying to decrement customer balance.', { + tenantId, + }); + await customerRepository.changeBalance( + oldSaleInvoice.customerId, + oldSaleInvoice.balance * -1 + ); + } +} diff --git a/server/src/subscribers/SaleInvoices/SyncItemsQuantity.ts b/server/src/subscribers/SaleInvoices/SyncItemsQuantity.ts new file mode 100644 index 000000000..2eb8a0c76 --- /dev/null +++ b/server/src/subscribers/SaleInvoices/SyncItemsQuantity.ts @@ -0,0 +1,71 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import ItemsEntriesService from 'services/Items/ItemsEntriesService'; + +@EventSubscriber() +export default class SyncItemsQuantityWithInvoices { + logger: any; + tenancy: TenancyService; + itemsEntriesService: ItemsEntriesService; + + /** + * Constructor method. + */ + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + this.itemsEntriesService = Container.get(ItemsEntriesService); + } + + /** + * Increments the sale invoice items once the invoice created. + */ + @On(events.saleInvoice.onCreated) + public async handleDecrementSaleInvoiceItemsQuantity({ + tenantId, + saleInvoice, + }) { + await this.itemsEntriesService.decrementItemsQuantity( + tenantId, + saleInvoice.entries + ); + } + + /** + * Decrements the sale invoice items once the invoice deleted. + */ + @On(events.saleInvoice.onDeleted) + public async handleIncrementSaleInvoiceItemsQuantity({ + tenantId, + oldSaleInvoice, + }) { + await this.itemsEntriesService.incrementItemsEntries( + tenantId, + oldSaleInvoice.entries + ); + } + + /** + * Handle increment/decrement the different items quantity once the sale invoice be edited. + */ + @On(events.saleInvoice.onEdited) + public async handleChangeSaleInvoiceItemsQuantityOnEdit({ + tenantId, + saleInvoice, + oldSaleInvoice, + }) { + await this.itemsEntriesService.changeItemsQuantity( + tenantId, + saleInvoice.entries.map((entry) => ({ + ...entry, + quantity: entry.quantity * -1, + })), + oldSaleInvoice.entries.map((entry) => ({ + ...entry, + quantity: entry.quantity * -1, + })) + ); + } +} diff --git a/server/src/subscribers/SaleInvoices/WriteInventoryTransactions.ts b/server/src/subscribers/SaleInvoices/WriteInventoryTransactions.ts new file mode 100644 index 000000000..3c5e259e1 --- /dev/null +++ b/server/src/subscribers/SaleInvoices/WriteInventoryTransactions.ts @@ -0,0 +1,72 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import SaleInvoicesService from 'services/Sales/SalesInvoices'; + +@EventSubscriber() +export default class WriteInventoryTransactions { + logger: any; + tenancy: TenancyService; + saleInvoicesService: SaleInvoicesService; + + /** + * Constructor method. + */ + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + this.saleInvoicesService = Container.get(SaleInvoicesService); + } + + /** + * Handles the writing inventory transactions once the invoice created. + */ + @On(events.saleInvoice.onCreated) + public async handleWritingInventoryTransactions({ tenantId, saleInvoice }) { + this.logger.info('[sale_invoice] trying to write inventory transactions.', { + tenantId, + }); + await this.saleInvoicesService.recordInventoryTranscactions( + tenantId, + saleInvoice + ); + } + + /** + * Rewriting the inventory transactions once the sale invoice be edited. + */ + @On(events.saleInvoice.onEdited) + public async handleRewritingInventoryTransactions({ tenantId, saleInvoice }) { + this.logger.info('[sale_invoice] trying to write inventory transactions.', { + tenantId, + }); + await this.saleInvoicesService.recordInventoryTranscactions( + tenantId, + saleInvoice, + true + ); + } + + /** + * Handles deleting the inventory transactions once the invoice deleted. + */ + @On(events.saleInvoice.onDeleted) + public async handleDeletingInventoryTransactions({ + tenantId, + saleInvoiceId, + oldSaleInvoice, + }) { + this.logger.info( + '[sale_invoice] trying to revert inventory transactions.', + { + tenantId, + saleInvoiceId, + } + ); + await this.saleInvoicesService.revertInventoryTransactions( + tenantId, + saleInvoiceId + ); + } +} diff --git a/server/src/subscribers/SaleInvoices/WriteJournalEntries.ts b/server/src/subscribers/SaleInvoices/WriteJournalEntries.ts new file mode 100644 index 000000000..7ec3d2294 --- /dev/null +++ b/server/src/subscribers/SaleInvoices/WriteJournalEntries.ts @@ -0,0 +1,80 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import SaleInvoicesService from 'services/Sales/SalesInvoices'; + +@EventSubscriber() +export default class SaleInvoiceSubscriber { + logger: any; + tenancy: TenancyService; + saleInvoicesService: SaleInvoicesService; + + /** + * Constructor method. + */ + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + this.saleInvoicesService = Container.get(SaleInvoicesService); + } + + /** + * Records journal entries of the non-inventory invoice. + */ + @On(events.saleInvoice.onCreated) + public async handleWriteJournalEntriesOnInvoiceCreated({ + tenantId, + saleInvoiceId, + saleInvoice, + authorizedUser, + }) { + const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); + + const saleInvoiceWithItems = await saleInvoiceRepository.findOneById( + saleInvoiceId, + 'entries.item' + ); + await this.saleInvoicesService.writesIncomeJournalEntries( + tenantId, + saleInvoiceWithItems + ); + } + + /** + * Records journal entries of the non-inventory invoice. + */ + @On(events.saleInvoice.onEdited) + public async handleRewriteJournalEntriesOnceInvoiceEdit({ + tenantId, + saleInvoiceId, + saleInvoice, + authorizedUser, + }) { + const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); + + const saleInvoiceWithItems = await saleInvoiceRepository.findOneById( + saleInvoiceId, + 'entries.item' + ); + await this.saleInvoicesService.writesIncomeJournalEntries( + tenantId, + saleInvoiceWithItems, + true + ); + } + + /** + * Handle reverting journal entries once sale invoice delete. + */ + @On(events.saleInvoice.onDeleted) + public async handleRevertingInvoiceJournalEntriesOnDelete({ + tenantId, + saleInvoiceId, + }) { + await this.saleInvoicesService.revertInvoiceJournalEntries( + tenantId, + saleInvoiceId, + ); + } +} diff --git a/server/src/subscribers/SaleInvoices/index.ts b/server/src/subscribers/SaleInvoices/index.ts new file mode 100644 index 000000000..a6ee2848e --- /dev/null +++ b/server/src/subscribers/SaleInvoices/index.ts @@ -0,0 +1,57 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import SettingsService from 'services/Settings/SettingsService'; +import SaleEstimateService from 'services/Sales/SalesEstimate'; + +@EventSubscriber() +export default class SaleInvoiceSubscriber { + logger: any; + tenancy: TenancyService; + settingsService: SettingsService; + saleEstimatesService: SaleEstimateService; + + /** + * Constructor method. + */ + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + this.settingsService = Container.get(SettingsService); + this.saleEstimatesService = Container.get(SaleEstimateService); + } + + /** + * Marks the sale estimate as converted from the sale invoice once created. + */ + @On(events.saleInvoice.onCreated) + public async handleMarkEstimateConvert({ + tenantId, + saleInvoice, + saleInvoiceId, + }) { + if (saleInvoice.fromEstimateId) { + this.saleEstimatesService.convertEstimateToInvoice( + tenantId, + saleInvoice.fromEstiamteId, + saleInvoiceId + ); + } + } + + /** + * Handles sale invoice next number increment once invoice created. + */ + @On(events.saleInvoice.onCreated) + public async handleInvoiceNextNumberIncrement({ + tenantId, + saleInvoiceId, + saleInvoice, + }) { + await this.settingsService.incrementNextNumber(tenantId, { + key: 'next_number', + group: 'sales_invoices', + }); + } +} diff --git a/server/src/subscribers/SaleReceipt/SyncItemsQuantity.ts b/server/src/subscribers/SaleReceipt/SyncItemsQuantity.ts new file mode 100644 index 000000000..80f4aec37 --- /dev/null +++ b/server/src/subscribers/SaleReceipt/SyncItemsQuantity.ts @@ -0,0 +1,67 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import ItemsEntriesService from 'services/Items/ItemsEntriesService'; +import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost'; + +@EventSubscriber() +export default class SaleReceiptSubscriber { + logger: any; + tenancy: TenancyService; + itemsEntriesService: ItemsEntriesService; + + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + this.itemsEntriesService = Container.get(ItemsEntriesService); + } + + /** + * Increments the sale receipt items once be created. + */ + @On(events.saleReceipt.onCreated) + public async handleDecremenReceiptItemsQuantity({ tenantId, saleReceipt }) { + await this.itemsEntriesService.decrementItemsQuantity( + tenantId, + saleReceipt.entries + ); + } + + /** + * Decrements the sale receipt items once be deleted. + */ + @On(events.saleReceipt.onDeleted) + public async handleIncrementReceiptItemsQuantity({ + tenantId, + oldSaleReceipt, + }) { + await this.itemsEntriesService.incrementItemsEntries( + tenantId, + oldSaleReceipt.entries + ); + } + + /** + * Handle increment/decrement the different items quantity once + * the sale receipt be edited. + */ + @On(events.saleReceipt.onEdited) + public async handleChangeSaleInvoiceItemsQuantityOnEdit({ + tenantId, + saleReceipt, + oldSaleReceipt, + }) { + await this.itemsEntriesService.changeItemsQuantity( + tenantId, + saleReceipt.entries.map((entry) => ({ + ...entry, + quantity: entry.quantity * -1, + })), + oldSaleReceipt.entries.map((entry) => ({ + ...entry, + quantity: entry.quantity * -1, + })) + ); + } +} diff --git a/server/src/subscribers/SaleReceipt/WriteInventoryTransactions.ts b/server/src/subscribers/SaleReceipt/WriteInventoryTransactions.ts new file mode 100644 index 000000000..00f204725 --- /dev/null +++ b/server/src/subscribers/SaleReceipt/WriteInventoryTransactions.ts @@ -0,0 +1,68 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import SalesReceiptService from 'services/Sales/SalesReceipts'; + +@EventSubscriber() +export default class SaleReceiptSubscriber { + logger: any; + tenancy: TenancyService; + saleReceiptsService: SalesReceiptService; + + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + this.saleReceiptsService = Container.get(SalesReceiptService); + } + + /** + * Handles the writing inventory transactions once the receipt created. + */ + @On(events.saleReceipt.onCreated) + public async handleWritingInventoryTransactions({ tenantId, saleReceipt }) { + this.logger.info('[sale_receipt] trying to write inventory transactions.', { + tenantId, + }); + await this.saleReceiptsService.recordInventoryTransactions( + tenantId, + saleReceipt + ); + } + + /** + * Rewriting the inventory transactions once the sale invoice be edited. + */ + @On(events.saleReceipt.onEdited) + public async handleRewritingInventoryTransactions({ tenantId, saleReceipt }) { + this.logger.info('[sale_invoice] trying to write inventory transactions.', { + tenantId, + }); + await this.saleReceiptsService.recordInventoryTransactions( + tenantId, + saleReceipt, + true + ); + } + + /** + * Handles deleting the inventory transactions once the receipt deleted. + */ + @On(events.saleReceipt.onDeleted) + public async handleDeletingInventoryTransactions({ + tenantId, + saleReceiptId, + }) { + this.logger.info( + '[sale_invoice] trying to revert inventory transactions.', + { + tenantId, + saleReceiptId, + } + ); + await this.saleReceiptsService.revertInventoryTransactions( + tenantId, + saleReceiptId + ); + } +} diff --git a/server/src/subscribers/SaleReceipt/WriteJournalEntries.ts b/server/src/subscribers/SaleReceipt/WriteJournalEntries.ts new file mode 100644 index 000000000..c9e9cafef --- /dev/null +++ b/server/src/subscribers/SaleReceipt/WriteJournalEntries.ts @@ -0,0 +1,75 @@ +import { Container } from 'typedi'; +import { On, EventSubscriber } from 'event-dispatch'; +import events from 'subscribers/events'; +import TenancyService from 'services/Tenancy/TenancyService'; +import SalesReceiptService from 'services/Sales/SalesReceipts'; + +@EventSubscriber() +export default class SaleReceiptSubscriber { + logger: any; + tenancy: TenancyService; + saleReceiptsService: SalesReceiptService; + + constructor() { + this.logger = Container.get('logger'); + this.tenancy = Container.get(TenancyService); + this.saleReceiptsService = Container.get(SalesReceiptService); + } + + /** + * Handles writing sale receipt income journal entries once created. + */ + @On(events.saleReceipt.onCreated) + public async handleWriteReceiptIncomeJournalEntrieOnCreate({ + tenantId, + saleReceiptId, + }) { + const { SaleReceipt } = this.tenancy.models(tenantId); + + const saleReceiptWithItems = await SaleReceipt.query() + .findById(saleReceiptId) + .withGraphFetched('entries.item'); + + // Writes the sale receipt income journal entries. + await this.saleReceiptsService.writesIncomeJournalEntries( + tenantId, + saleReceiptWithItems + ); + } + + /** + * Handles sale receipt revert jouranl entries once be deleted. + */ + @On(events.saleReceipt.onDeleted) + public async handleRevertReceiptJournalEntriesOnDeleted({ + tenantId, + saleReceiptId, + }) { + await this.saleReceiptsService.revertReceiptJournalEntries( + tenantId, + saleReceiptId + ); + } + + /** + * Handles writing sale receipt income journal entries once be edited. + */ + @On(events.saleReceipt.onEdited) + public async handleWriteReceiptIncomeJournalEntrieOnEdited({ + tenantId, + saleReceiptId, + }) { + const { SaleReceipt } = this.tenancy.models(tenantId); + + const saleReceiptWithItems = await SaleReceipt.query() + .findById(saleReceiptId) + .withGraphFetched('entries.item'); + + // Writes the sale receipt income journal entries. + await this.saleReceiptsService.writesIncomeJournalEntries( + tenantId, + saleReceiptWithItems, + true + ); + } +} diff --git a/server/src/subscribers/saleReceipts.ts b/server/src/subscribers/SaleReceipt/index.ts similarity index 93% rename from server/src/subscribers/saleReceipts.ts rename to server/src/subscribers/SaleReceipt/index.ts index 510bda4d0..b678f8d9d 100644 --- a/server/src/subscribers/saleReceipts.ts +++ b/server/src/subscribers/SaleReceipt/index.ts @@ -1,5 +1,5 @@ import { Container } from 'typedi'; -import { On, EventSubscriber } from "event-dispatch"; +import { On, EventSubscriber } from 'event-dispatch'; import events from 'subscribers/events'; import TenancyService from 'services/Tenancy/TenancyService'; import SettingsService from 'services/Settings/SettingsService'; @@ -15,7 +15,7 @@ export default class SaleReceiptSubscriber { this.tenancy = Container.get(TenancyService); this.settingsService = Container.get(SettingsService); } - + /** * Handle sale receipt increment next number once be created. */ @@ -26,4 +26,4 @@ export default class SaleReceiptSubscriber { group: 'sales_receipts', }); } -} \ No newline at end of file +} diff --git a/server/src/subscribers/bills.ts b/server/src/subscribers/bills.ts deleted file mode 100644 index bae90cafe..000000000 --- a/server/src/subscribers/bills.ts +++ /dev/null @@ -1,227 +0,0 @@ -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'; -import JournalPosterService from 'services/Sales/JournalPosterService'; -import ItemsEntriesService from 'services/Items/ItemsEntriesService'; - -@EventSubscriber() -export default class BillSubscriber { - tenancy: TenancyService; - billsService: BillsService; - logger: any; - journalPosterService: JournalPosterService; - itemsEntriesService: ItemsEntriesService; - - /** - * Constructor method. - */ - constructor() { - this.tenancy = Container.get(TenancyService); - this.billsService = Container.get(BillsService); - this.logger = Container.get('logger'); - this.journalPosterService = Container.get(JournalPosterService); - this.itemsEntriesService = Container.get(ItemsEntriesService); - } - - /** - * Handles vendor balance increment once bill created. - */ - @On(events.bill.onCreated) - async handleVendorBalanceIncrement({ tenantId, billId, bill }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Increments vendor balance. - this.logger.info('[bill] trying to increment vendor balance.', { - tenantId, - billId, - }); - await vendorRepository.changeBalance(bill.vendorId, bill.amount); - } - - /** - * Handles writing journal entries once bill created. - */ - @On(events.bill.onCreated) - async handlerWriteJournalEntriesOnCreate({ tenantId, bill }) { - // Writes the journal entries for the given bill transaction. - this.logger.info('[bill] writing bill journal entries.', { tenantId }); - await this.billsService.recordJournalTransactions(tenantId, bill); - } - - /** - * Handles the overwriting journal entries once bill edited. - */ - @On(events.bill.onEdited) - async handleOverwriteJournalEntriesOnEdit({ tenantId, bill }) { - // Overwrite the journal entries for the given bill transaction. - this.logger.info('[bill] overwriting bill journal entries.', { tenantId }); - await this.billsService.recordJournalTransactions(tenantId, bill, true); - } - - /** - * Handles vendor balance decrement once bill deleted. - */ - @On(events.bill.onDeleted) - async handleVendorBalanceDecrement({ tenantId, billId, oldBill }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Decrements vendor balance. - this.logger.info('[bill] trying to decrement vendor balance.', { - tenantId, - billId, - }); - await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1); - } - - /** - * Handles revert journal entries on bill deleted. - */ - @On(events.bill.onDeleted) - async handlerDeleteJournalEntries({ tenantId, billId }) { - // Delete associated bill journal transactions. - this.logger.info('[bill] trying to delete journal entries.', { - tenantId, - billId, - }); - await this.journalPosterService.revertJournalTransactions( - tenantId, - billId, - 'Bill' - ); - } - - /** - * Handles vendor balance difference change. - */ - @On(events.bill.onEdited) - async handleVendorBalanceDiffChange({ tenantId, billId, oldBill, bill }) { - const { vendorRepository } = this.tenancy.repositories(tenantId); - - // Changes the diff vendor balance between old and new amount. - this.logger.info('[bill[ change vendor the different balance.', { - tenantId, - billId, - }); - await vendorRepository.changeDiffBalance( - bill.vendorId, - bill.amount, - oldBill.amount, - oldBill.vendorId - ); - } - - /** - * Handles writing the inventory transactions once bill created. - */ - @On(events.bill.onCreated) - async handleWritingInventoryTransactions({ tenantId, bill }) { - this.logger.info('[bill] writing the inventory transactions', { tenantId }); - this.billsService.recordInventoryTransactions( - tenantId, - bill.id, - bill.billDate - ); - } - - /** - * Handles the overwriting the inventory transactions once bill edited. - */ - @On(events.bill.onEdited) - async handleOverwritingInventoryTransactions({ tenantId, bill }) { - this.logger.info('[bill] overwriting the inventory transactions.', { - tenantId, - }); - this.billsService.recordInventoryTransactions( - tenantId, - bill.id, - bill.billDate, - true - ); - } - - /** - * Handles the reverting the inventory transactions once the bill deleted. - */ - @On(events.bill.onDeleted) - async handleRevertInventoryTransactions({ tenantId, billId }) { - this.logger.info('[bill] reverting the bill inventory transactions', { - tenantId, - billId, - }); - this.billsService.revertInventoryTransactions(tenantId, billId); - } - - /** - * Schedules items cost compute jobs once the inventory transactions created - * of the bill transaction. - */ - @On(events.bill.onInventoryTransactionsCreated) - public async handleComputeItemsCosts({ tenantId, billId }) { - this.logger.info('[bill] trying to compute the bill items cost.', { - 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 - ); - } - - /** - * Increments the sale invoice items once the invoice created. - */ - @On(events.bill.onCreated) - public async handleDecrementSaleInvoiceItemsQuantity({ tenantId, bill }) { - await this.itemsEntriesService.incrementItemsEntries( - tenantId, - bill.entries - ); - } - - /** - * Decrements the sale invoice items once the invoice deleted. - */ - @On(events.bill.onDeleted) - public async handleIncrementSaleInvoiceItemsQuantity({ tenantId, oldBill }) { - await this.itemsEntriesService.decrementItemsQuantity( - tenantId, - oldBill.entries - ); - } - - /** - * Handle increment/decrement the different items quantity once the sale invoice be edited. - */ - @On(events.bill.onEdited) - public async handleChangeSaleInvoiceItemsQuantityOnEdit({ - tenantId, - bill, - oldBill, - }) { - await this.itemsEntriesService.changeItemsQuantity( - tenantId, - bill.entries, - oldBill.entries - ); - } -} diff --git a/server/src/subscribers/events.ts b/server/src/subscribers/events.ts index 0660387b1..a6219b022 100644 --- a/server/src/subscribers/events.ts +++ b/server/src/subscribers/events.ts @@ -83,8 +83,6 @@ export default { onDelivered: 'onSaleInvoiceDelivered', onBulkDelete: 'onSaleInvoiceBulkDeleted', onPublished: 'onSaleInvoicePublished', - onInventoryTransactionsCreated: 'onInvoiceInventoryTransactionsCreated', - onInventoryTransactionsDeleted: 'onInvoiceInventoryTransactionsDeleted', }, /** @@ -128,8 +126,6 @@ export default { onDeleted: 'onBillDeleted', onBulkDeleted: 'onBillBulkDeleted', onPublished: 'onBillPublished', - onInventoryTransactionsCreated: 'onBillInventoryTransactionsCreated', - onInventoryTransactionsDeleted: 'onBillInventoryTransactionsDeleted' }, /** @@ -189,6 +185,9 @@ export default { * Inventory service. */ inventory: { + onInventoryTransactionsCreated: 'onInventoryTransactionsCreated', + onInventoryTransactionsDeleted: 'onInventoryTransactionsDeleted', + onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled', onComputeItemCostJobStarted: 'onComputeItemCostJobStarted', onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted' diff --git a/server/src/subscribers/inventory.ts b/server/src/subscribers/inventory.ts index 9d2ecdc23..f1f9ee94c 100644 --- a/server/src/subscribers/inventory.ts +++ b/server/src/subscribers/inventory.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 SaleInvoicesCost from 'services/Sales/SalesInvoicesCost'; @@ -7,16 +8,20 @@ import SaleInvoicesCost from 'services/Sales/SalesInvoicesCost'; export class InventorySubscriber { depends: number = 0; startingDate: Date; + saleInvoicesCost: SaleInvoicesCost; + agenda: any; + + constructor() { + this.saleInvoicesCost = Container.get(SaleInvoicesCost); + this.agenda = Container.get('agenda'); + } /** * Handle run writing the journal entries once the compute items jobs completed. */ @On(events.inventory.onComputeItemCostJobCompleted) async onComputeItemCostJobFinished({ itemId, tenantId, startingDate }) { - const saleInvoicesCost = Container.get(SaleInvoicesCost); - const agenda = Container.get('agenda'); - - const dependsComputeJobs = await agenda.jobs({ + const dependsComputeJobs = await this.agenda.jobs({ name: 'compute-item-cost', nextRunAt: { $ne: null }, 'data.tenantId': tenantId, @@ -25,10 +30,53 @@ export class InventorySubscriber { if (dependsComputeJobs.length === 0) { this.startingDate = null; - await saleInvoicesCost.scheduleWriteJournalEntries( + await this.saleInvoicesCost.scheduleWriteJournalEntries( tenantId, startingDate ); } } + + /** + * + */ + @On(events.inventory.onInventoryTransactionsCreated) + async handleScheduleItemsCostOnInventoryTransactionsCreated({ + tenantId, + inventoryEntries, + transactionId, + transactionType, + transactionDate, + transactionDirection, + override + }) { + const inventoryItemsIds = map(inventoryEntries, 'itemId'); + + await this.saleInvoicesCost.scheduleComputeCostByItemsIds( + tenantId, + inventoryItemsIds, + transactionDate, + ); + } + + /** + * Schedules compute items cost once the inventory transactions deleted. + */ + @On(events.inventory.onInventoryTransactionsDeleted) + async handleScheduleItemsCostOnInventoryTransactionsDeleted({ + tenantId, + transactionType, + transactionId, + oldInventoryTransactions + }) { + const inventoryItemsIds = map(oldInventoryTransactions, 'itemId'); + const startingDates = map(oldInventoryTransactions, 'date'); + const startingDate = head(startingDates); + + await this.saleInvoicesCost.scheduleComputeCostByItemsIds( + tenantId, + inventoryItemsIds, + startingDate + ); + } } diff --git a/server/src/subscribers/saleInvoices.ts b/server/src/subscribers/saleInvoices.ts deleted file mode 100644 index ff34f97d7..000000000 --- a/server/src/subscribers/saleInvoices.ts +++ /dev/null @@ -1,330 +0,0 @@ -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'; -import SettingsService from 'services/Settings/SettingsService'; -import SaleEstimateService from 'services/Sales/SalesEstimate'; -import SaleInvoicesService from 'services/Sales/SalesInvoices'; -import ItemsEntriesService from 'services/Items/ItemsEntriesService'; -import SalesInvoicesCost from 'services/Sales/SalesInvoicesCost'; - -@EventSubscriber() -export default class SaleInvoiceSubscriber { - logger: any; - tenancy: TenancyService; - settingsService: SettingsService; - saleEstimatesService: SaleEstimateService; - saleInvoicesService: SaleInvoicesService; - itemsEntriesService: ItemsEntriesService; - salesInvoicesCost: SalesInvoicesCost; - - constructor() { - this.logger = Container.get('logger'); - this.tenancy = Container.get(TenancyService); - this.settingsService = Container.get(SettingsService); - this.saleEstimatesService = Container.get(SaleEstimateService); - this.saleInvoicesService = Container.get(SaleInvoicesService); - this.itemsEntriesService = Container.get(ItemsEntriesService); - this.salesInvoicesCost = Container.get(SalesInvoicesCost); - } - - /** - * Handles customer balance increment once sale invoice created. - */ - @On(events.saleInvoice.onCreated) - public async handleCustomerBalanceIncrement({ - tenantId, - saleInvoice, - saleInvoiceId, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[sale_invoice] trying to increment customer balance.', { - tenantId, - }); - await customerRepository.changeBalance( - saleInvoice.customerId, - saleInvoice.balance - ); - } - - /** - * Marks the sale estimate as converted from the sale invoice once created. - */ - @On(events.saleInvoice.onCreated) - public async handleMarkEstimateConvert({ - tenantId, - saleInvoice, - saleInvoiceId, - }) { - if (saleInvoice.fromEstimateId) { - this.saleEstimatesService.convertEstimateToInvoice( - tenantId, - saleInvoice.fromEstiamteId, - saleInvoiceId - ); - } - } - - /** - * Handles sale invoice next number increment once invoice created. - */ - @On(events.saleInvoice.onCreated) - public async handleInvoiceNextNumberIncrement({ - tenantId, - saleInvoiceId, - saleInvoice, - }) { - await this.settingsService.incrementNextNumber(tenantId, { - key: 'next_number', - group: 'sales_invoices', - }); - } - - /** - * Handles the writing inventory transactions once the invoice created. - */ - @On(events.saleInvoice.onCreated) - public async handleWritingInventoryTransactions({ tenantId, saleInvoice }) { - this.logger.info('[sale_invoice] trying to write inventory transactions.', { - tenantId, - }); - await this.saleInvoicesService.recordInventoryTranscactions( - tenantId, - saleInvoice.id, - saleInvoice.invoiceDate - ); - } - - /** - * Handles handle write income journal entries of sale invoice. - */ - @On(events.saleInvoice.onCreated) - public async handleWriteInvoiceIncomeJournalEntries({ - tenantId, - saleInvoiceId, - saleInvoice, - authorizedUser, - }) { - const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); - - const saleInvoiceWithItems = await saleInvoiceRepository.findOneById( - saleInvoiceId, - 'entries.item' - ); - await this.saleInvoicesService.writesIncomeJournalEntries( - tenantId, - saleInvoiceWithItems - ); - } - - /** - * Increments the sale invoice items once the invoice created. - */ - @On(events.saleInvoice.onCreated) - public async handleDecrementSaleInvoiceItemsQuantity({ - tenantId, - saleInvoice, - }) { - await this.itemsEntriesService.decrementItemsQuantity( - tenantId, - saleInvoice.entries - ); - } - - /** - * Rewriting the inventory transactions once the sale invoice be edited. - */ - @On(events.saleInvoice.onEdited) - public async handleRewritingInventoryTransactions({ tenantId, saleInvoice }) { - this.logger.info('[sale_invoice] trying to write inventory transactions.', { - tenantId, - }); - await this.saleInvoicesService.recordInventoryTranscactions( - tenantId, - saleInvoice.id, - saleInvoice.invoiceDate, - true - ); - } - - /** - * Handles customer balance diff balnace change once sale invoice edited. - */ - @On(events.saleInvoice.onEdited) - public async onSaleInvoiceEdited({ - tenantId, - saleInvoice, - oldSaleInvoice, - saleInvoiceId, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[sale_invoice] trying to change diff customer balance.', { - tenantId, - }); - await customerRepository.changeDiffBalance( - saleInvoice.customerId, - saleInvoice.balance, - oldSaleInvoice.balance, - oldSaleInvoice.customerId - ); - } - - /** - * Records journal entries of the non-inventory invoice. - */ - @On(events.saleInvoice.onEdited) - public async handleRewriteJournalEntriesOnceInvoiceEdit({ - tenantId, - saleInvoiceId, - saleInvoice, - authorizedUser, - }) { - const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); - - const saleInvoiceWithItems = await saleInvoiceRepository.findOneById( - saleInvoiceId, - 'entries.item' - ); - await this.saleInvoicesService.writesIncomeJournalEntries( - tenantId, - saleInvoiceWithItems, - true - ); - } - - /** - * Handles customer balance decrement once sale invoice deleted. - */ - @On(events.saleInvoice.onDeleted) - public async handleCustomerBalanceDecrement({ - tenantId, - saleInvoiceId, - oldSaleInvoice, - }) { - const { customerRepository } = this.tenancy.repositories(tenantId); - - this.logger.info('[sale_invoice] trying to decrement customer balance.', { - tenantId, - }); - await customerRepository.changeBalance( - oldSaleInvoice.customerId, - oldSaleInvoice.balance * -1 - ); - } - - /** - * Handle reverting journal entries once sale invoice delete. - */ - @On(events.saleInvoice.onDelete) - public async handleRevertingInvoiceJournalEntriesOnDelete({ - tenantId, - saleInvoiceId, - }) { - await this.saleInvoicesService.revertInvoiceJournalEntries( - tenantId, - saleInvoiceId, - ); - } - - /** - * Handles deleting the inventory transactions once the invoice deleted. - */ - @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 - ); - } - - /** - * Schedules compute invoice items cost job. - */ - @On(events.saleInvoice.onInventoryTransactionsCreated) - public async handleComputeCostsOnInventoryTransactionsCreated({ - tenantId, - saleInvoiceId, - }) { - this.logger.info( - '[sale_invoice] trying to compute the invoice items cost.', - { - tenantId, - saleInvoiceId, - } - ); - await this.salesInvoicesCost.scheduleComputeCostByInvoiceId( - tenantId, - saleInvoiceId - ); - } - - /** - * 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.salesInvoicesCost.scheduleComputeCostByItemsIds( - tenantId, - inventoryItemsIds, - startingDate - ); - } - - /** - * Decrements the sale invoice items once the invoice deleted. - */ - @On(events.saleInvoice.onDeleted) - public async handleIncrementSaleInvoiceItemsQuantity({ - tenantId, - oldSaleInvoice, - }) { - await this.itemsEntriesService.incrementItemsEntries( - tenantId, - oldSaleInvoice.entries - ); - } - - /** - * Handle increment/decrement the different items quantity once the sale invoice be edited. - */ - @On(events.saleInvoice.onEdited) - public async handleChangeSaleInvoiceItemsQuantityOnEdit({ - tenantId, - saleInvoice, - oldSaleInvoice, - }) { - await this.itemsEntriesService.changeItemsQuantity( - tenantId, - saleInvoice.entries.map((entry) => ({ - ...entry, - quantity: entry.quantity * -1, - })), - oldSaleInvoice.entries.map((entry) => ({ - ...entry, - quantity: entry.quantity * -1, - })) - ); - } -} diff --git a/server/src/utils/index.js b/server/src/utils/index.js index 1c9fc2ea3..8995bc7b6 100644 --- a/server/src/utils/index.js +++ b/server/src/utils/index.js @@ -174,8 +174,6 @@ const getDefinedOption = (key, group) => { const isDefinedOptionConfigurable = (key, group) => { const definedOption = getDefinedOption(key, group); - console.log(definedOption, 'definedOption'); - return definedOption?.config || false; };