From 920875d7d9566cf92f6963e33f0d8160b3cad857 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sat, 19 Dec 2020 12:08:42 +0200 Subject: [PATCH 1/3] fix: writing the bill journal entries. --- server/src/api/controllers/Purchases/Bills.ts | 4 +- server/src/interfaces/Bill.ts | 1 + .../AccountTransactionRepository.ts | 8 +++ server/src/repositories/CachableRepository.ts | 15 +++- server/src/repositories/EntityRepository.ts | 2 +- .../services/Accounting/JournalCommands.ts | 65 +++++++++++++++++ .../src/services/Accounting/JournalPoster.ts | 15 ++-- server/src/services/Purchases/Bills.ts | 70 ++++--------------- server/src/subscribers/bills.ts | 15 +++- server/src/subscribers/saleInvoices.ts | 67 +++++++++++++----- 10 files changed, 170 insertions(+), 92 deletions(-) diff --git a/server/src/api/controllers/Purchases/Bills.ts b/server/src/api/controllers/Purchases/Bills.ts index dbea7503e..89c14e317 100644 --- a/server/src/api/controllers/Purchases/Bills.ts +++ b/server/src/api/controllers/Purchases/Bills.ts @@ -197,8 +197,8 @@ export default class BillsController extends BaseController { * @param {Response} res */ async editBill(req: Request, res: Response, next: NextFunction) { - const { id: billId, user } = req.params; - const { tenantId } = req; + const { id: billId } = req.params; + const { tenantId, user } = req; const billDTO: IBillEditDTO = this.matchedBodyData(req); try { diff --git a/server/src/interfaces/Bill.ts b/server/src/interfaces/Bill.ts index df111a888..dfe2b3acd 100644 --- a/server/src/interfaces/Bill.ts +++ b/server/src/interfaces/Bill.ts @@ -46,6 +46,7 @@ export interface IBill { openedAt: Date | string, entries: IItemEntry[], + userId: number, }; export interface IBillsFilter extends IDynamicListFilterDTO { diff --git a/server/src/repositories/AccountTransactionRepository.ts b/server/src/repositories/AccountTransactionRepository.ts index 69fe9076e..e0bbdfeba 100644 --- a/server/src/repositories/AccountTransactionRepository.ts +++ b/server/src/repositories/AccountTransactionRepository.ts @@ -11,6 +11,8 @@ interface IJournalTransactionsFilter { toAmount: number, contactsIds?: number[], contactType?: string, + referenceType?: string[], + referenceId?: number[], }; export default class AccountTransactionsRepository extends TenantRepository { @@ -42,6 +44,12 @@ export default class AccountTransactionsRepository extends TenantRepository { if (filter.contactType) { query.where('contact_type', filter.contactType); } + if (filter.referenceType && filter.referenceType.length > 0) { + query.whereIn('reference_type', filter.referenceType); + } + if (filter.referenceId && filter.referenceId.length > 0) { + query.whereIn('reference_id', filter.referenceId); + } }); }); } diff --git a/server/src/repositories/CachableRepository.ts b/server/src/repositories/CachableRepository.ts index 8bbd25874..7c0401e48 100644 --- a/server/src/repositories/CachableRepository.ts +++ b/server/src/repositories/CachableRepository.ts @@ -177,7 +177,20 @@ export default class CachableRepository extends EntityRepository{ * * @param {string|number[]} values - */ - async deleteWhereIn(values: string | number[]) { + async deleteWhereIn(field: string, values: string | number[]) { + const result = await super.deleteWhereIn(field, values); + + // Flushes the repository cache after delete operation. + this.flushCache(); + + return result; + } + + /** + * + * @param {string|number[]} values + */ + async deleteWhereIdIn(values: string | number[]) { const result = await super.deleteWhereIdIn(values); // Flushes the repository cache after delete operation. diff --git a/server/src/repositories/EntityRepository.ts b/server/src/repositories/EntityRepository.ts index a015e6081..e948d6068 100644 --- a/server/src/repositories/EntityRepository.ts +++ b/server/src/repositories/EntityRepository.ts @@ -175,7 +175,7 @@ export default class EntityRepository { } /** - * + * Deletes the given entries in the array on the specific field. * @param {string} field - * @param {number|string} values - */ diff --git a/server/src/services/Accounting/JournalCommands.ts b/server/src/services/Accounting/JournalCommands.ts index 0a5e15b13..e1d999bf3 100644 --- a/server/src/services/Accounting/JournalCommands.ts +++ b/server/src/services/Accounting/JournalCommands.ts @@ -1,4 +1,6 @@ import { sumBy, chain } from 'lodash'; +import moment from 'moment'; +import { IBill } from 'interfaces'; import JournalPoster from "./JournalPoster"; import JournalEntry from "./JournalEntry"; import { AccountTransaction } from 'models'; @@ -7,6 +9,7 @@ import { IManualJournal, IExpense, IExpenseCategory, + IItem, } from 'interfaces'; interface IInventoryCostEntity { @@ -57,6 +60,68 @@ export default class JournalCommands{ this.models = this.journal.models; } + /** + * Records the bill journal entries. + * @param {IBill} bill + * @param {boolean} override - Override the old bill entries. + */ + async bill(bill: IBill, override: boolean = false): Promise { + const { transactionsRepository, accountRepository } = this.repositories; + const { Item, ItemEntry } = this.models; + + const entriesItemsIds = bill.entries.map((entry) => entry.itemId); + + // Retrieve the bill transaction items. + const storedItems = await Item.query().whereIn('id', entriesItemsIds); + + const storedItemsMap = new Map(storedItems.map((item) => [item.id, item])); + const payableAccount = await accountRepository.findOne({ slug: 'accounts-payable' }); + const formattedDate = moment(bill.billDate).format('YYYY-MM-DD'); + + const commonJournalMeta = { + debit: 0, + credit: 0, + referenceId: bill.id, + referenceType: 'Bill', + date: formattedDate, + userId: bill.userId, + }; + // Overrides the old bill entries. + if (override) { + const entries = await transactionsRepository.journal({ + referenceType: ['Bill'], + referenceId: [bill.id], + }); + this.journal.fromTransactions(entries); + this.journal.removeEntries(); + } + const payableEntry = new JournalEntry({ + ...commonJournalMeta, + credit: bill.amount, + account: payableAccount.id, + contactId: bill.vendorId, + contactType: 'Vendor', + index: 1, + }); + this.journal.credit(payableEntry); + + bill.entries.forEach((entry, index) => { + const item: IItem = storedItemsMap.get(entry.itemId); + const amount = ItemEntry.calcAmount(entry); + + const debitEntry = new JournalEntry({ + ...commonJournalMeta, + debit: amount, + account: + ['inventory'].indexOf(item.type) !== -1 + ? item.inventoryAccountId + : item.costAccountId, + index: index + 2, + }); + this.journal.debit(debitEntry); + }); + } + /** * Customer opening balance journals. * @param {number} customerId diff --git a/server/src/services/Accounting/JournalPoster.ts b/server/src/services/Accounting/JournalPoster.ts index 3aa3c2572..f7569607b 100644 --- a/server/src/services/Accounting/JournalPoster.ts +++ b/server/src/services/Accounting/JournalPoster.ts @@ -21,7 +21,7 @@ export default class JournalPoster implements IJournalPoster { deletedEntriesIds: number[] = []; entries: IJournalEntry[] = []; balancesChange: IAccountsChange = {}; - accountsDepGraph: IAccountsChange = {}; + accountsDepGraph: IAccountsChange; accountsBalanceTable: { [key: number]: number; } = {}; @@ -250,12 +250,12 @@ export default class JournalPoster implements IJournalPoster { * @returns {Promise} */ public async saveEntries() { - const { AccountTransaction } = this.models; + const { transactionsRepository } = this.repositories; const saveOperations: Promise[] = []; this.entries.forEach((entry) => { - const oper = AccountTransaction.query() - .insert({ + const oper = transactionsRepository + .create({ accountId: entry.account, ...omit(entry, ['account']), }); @@ -309,12 +309,10 @@ export default class JournalPoster implements IJournalPoster { * @return {Promise} */ public async deleteEntries() { - const { AccountTransaction } = this.models; + const { transactionsRepository } = this.repositories; if (this.deletedEntriesIds.length > 0) { - await AccountTransaction.query() - .whereIn('id', this.deletedEntriesIds) - .delete(); + await transactionsRepository.deleteWhereIdIn(this.deletedEntriesIds); } } @@ -332,7 +330,6 @@ export default class JournalPoster implements IJournalPoster { }); } - /** * Calculates the entries balance change. * @public diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index fab143c62..ecfbfd734 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -27,6 +27,7 @@ import { import { ServiceError } from 'exceptions'; import ItemsService from 'services/Items/ItemsService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService'; +import JournalCommands from 'services/Accounting/JournalCommands'; const ERRORS = { BILL_NOT_FOUND: 'BILL_NOT_FOUND', @@ -139,8 +140,8 @@ export default class BillsService extends SalesInvoicesCost { private async billDTOToModel( tenantId: number, billDTO: IBillDTO | IBillEditDTO, - oldBill?: IBill, authorizedUser: ISystemUser, + oldBill?: IBill, ) { const { ItemEntry } = this.tenancy.models(tenantId); let invLotNumber = oldBill?.invLotNumber; @@ -156,7 +157,7 @@ export default class BillsService extends SalesInvoicesCost { return { ...formatDateFields( - omit(billDTO, ['open']), + omit(billDTO, ['open', 'entries']), ['billDate', 'dueDate'] ), amount, @@ -165,7 +166,6 @@ export default class BillsService extends SalesInvoicesCost { reference_type: 'Bill', ...omit(entry, ['amount', 'id']), })), - // Avoid rewrite the open date in edit mode when already opened. ...(billDTO.open && (!oldBill?.openedAt)) && ({ openedAt: moment().toMySqlDateTime(), @@ -197,7 +197,7 @@ export default class BillsService extends SalesInvoicesCost { const { Bill } = this.tenancy.models(tenantId); this.logger.info('[bill] trying to create a new bill', { tenantId, billDTO }); - const billObj = await this.billDTOToModel(tenantId, billDTO, null, authorizedUser); + const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, null); // Retrieve vendor or throw not found service error. await this.getVendorOrThrowError(tenantId, billDTO.vendorId); @@ -253,7 +253,7 @@ export default class BillsService extends SalesInvoicesCost { const oldBill = await this.getBillOrThrowError(tenantId, billId); // Transforms the bill DTO object to model object. - const billObj = await this.billDTOToModel(tenantId, billDTO, oldBill, authorizedUser); + const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, oldBill); // Retrieve vendor details or throw not found service error. await this.getVendorOrThrowError(tenantId, billDTO.vendorId); @@ -342,62 +342,16 @@ export default class BillsService extends SalesInvoicesCost { * @param {IBill} bill * @param {Integer} billId */ - public async recordJournalTransactions(tenantId: number, bill: IBill, billId?: number) { - const { AccountTransaction, Item, ItemEntry } = this.tenancy.models(tenantId); - const { accountRepository } = this.tenancy.repositories(tenantId); - - const entriesItemsIds = bill.entries.map((entry) => entry.itemId); - const formattedDate = moment(bill.billDate).format('YYYY-MM-DD'); - - const storedItems = await Item.query().whereIn('id', entriesItemsIds); - - const storedItemsMap = new Map(storedItems.map((item) => [item.id, item])); - const payableAccount = await accountRepository.find({ slug: 'accounts-payable' }); - + public async recordJournalTransactions( + tenantId: number, + bill: IBill, + override: boolean = false, + ) { const journal = new JournalPoster(tenantId); + const journalCommands = new JournalCommands(journal); - const commonJournalMeta = { - debit: 0, - credit: 0, - referenceId: bill.id, - referenceType: 'Bill', - date: formattedDate, - userId: bill.userId, - }; - if (billId) { - const transactions = await AccountTransaction.query() - .whereIn('reference_type', ['Bill']) - .whereIn('reference_id', [billId]) - .withGraphFetched('account.type'); + await journalCommands.bill(bill, override) - journal.loadEntries(transactions); - journal.removeEntries(); - } - const payableEntry = new JournalEntry({ - ...commonJournalMeta, - credit: bill.amount, - account: payableAccount.id, - contactId: bill.vendorId, - contactType: 'Vendor', - index: 1, - }); - journal.credit(payableEntry); - - bill.entries.forEach((entry, index) => { - const item: IItem = storedItemsMap.get(entry.itemId); - const amount = ItemEntry.calcAmount(entry); - - const debitEntry = new JournalEntry({ - ...commonJournalMeta, - debit: amount, - account: - ['inventory'].indexOf(item.type) !== -1 - ? item.inventoryAccountId - : item.costAccountId, - index: index + 2, - }); - journal.debit(debitEntry); - }); return Promise.all([ journal.deleteEntries(), journal.saveEntries(), diff --git a/server/src/subscribers/bills.ts b/server/src/subscribers/bills.ts index 080b7588d..d4ab5f1a8 100644 --- a/server/src/subscribers/bills.ts +++ b/server/src/subscribers/bills.ts @@ -37,11 +37,20 @@ export default class BillSubscriber { * Handles writing journal entries once bill created. */ @On(events.bill.onCreated) - @On(events.bill.onEdited) - async handlerWriteJournalEntries({ tenantId, billId, bill }) { + 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, billId); + 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); } /** diff --git a/server/src/subscribers/saleInvoices.ts b/server/src/subscribers/saleInvoices.ts index 5a7226cc8..6fc279471 100644 --- a/server/src/subscribers/saleInvoices.ts +++ b/server/src/subscribers/saleInvoices.ts @@ -1,9 +1,10 @@ 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'; import SaleEstimateService from 'services/Sales/SalesEstimate'; + @EventSubscriber() export default class SaleInvoiceSubscriber { logger: any; @@ -22,23 +23,36 @@ export default class SaleInvoiceSubscriber { * Handles customer balance increment once sale invoice created. */ @On(events.saleInvoice.onCreated) - public async handleCustomerBalanceIncrement({ tenantId, saleInvoice, saleInvoiceId }) { + 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); + 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.fromEstiamteId) { + public async handleMarkEstimateConvert({ + tenantId, + saleInvoice, + saleInvoiceId, + }) { + if (saleInvoice.fromEstimateId) { this.saleEstimatesService.convertEstimateToInvoice( tenantId, saleInvoice.fromEstiamteId, - saleInvoiceId, + saleInvoiceId ); } } @@ -47,29 +61,42 @@ export default class SaleInvoiceSubscriber { * Handles customer balance diff balnace change once sale invoice edited. */ @On(events.saleInvoice.onEdited) - public async onSaleInvoiceEdited({ tenantId, saleInvoice, oldSaleInvoice, saleInvoiceId }) { + 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 }); + this.logger.info('[sale_invoice] trying to change diff customer balance.', { + tenantId, + }); await customerRepository.changeDiffBalance( saleInvoice.customerId, saleInvoice.balance, oldSaleInvoice.balance, - oldSaleInvoice.customerId, - ) + oldSaleInvoice.customerId + ); } /** * Handles customer balance decrement once sale invoice deleted. */ @On(events.saleInvoice.onDeleted) - public async handleCustomerBalanceDecrement({ tenantId, saleInvoiceId, oldSaleInvoice }) { + 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( + this.logger.info('[sale_invoice] trying to decrement customer balance.', { + tenantId, + }); + await customerRepository.changeBalance( oldSaleInvoice.customerId, - oldSaleInvoice.balance * -1, + oldSaleInvoice.balance * -1 ); } @@ -77,10 +104,14 @@ export default class SaleInvoiceSubscriber { * Handles sale invoice next number increment once invoice created. */ @On(events.saleInvoice.onCreated) - public async handleInvoiceNextNumberIncrement({ tenantId, saleInvoiceId, saleInvoice }) { + public async handleInvoiceNextNumberIncrement({ + tenantId, + saleInvoiceId, + saleInvoice, + }) { await this.settingsService.incrementNextNumber(tenantId, { key: 'next_number', group: 'sales_invoices', }); } -} \ No newline at end of file +} From cc47314a625c7506c3280e84d5ba605aba899614 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sat, 19 Dec 2020 13:44:02 +0200 Subject: [PATCH 2/3] feat: validate the payment not delivered on make payment receive. --- .../api/controllers/Sales/PaymentReceives.ts | 13 +- .../api/controllers/Sales/SalesInvoices.ts | 107 +++++---- server/src/interfaces/SaleInvoice.ts | 6 +- server/src/services/Purchases/Bills.ts | 227 ++++++++++++------ server/src/services/Sales/PaymentsReceives.ts | 10 +- server/src/services/Sales/SalesInvoices.ts | 40 ++- server/src/subscribers/bills.ts | 37 ++- server/src/subscribers/vendors.ts | 9 + 8 files changed, 286 insertions(+), 163 deletions(-) diff --git a/server/src/api/controllers/Sales/PaymentReceives.ts b/server/src/api/controllers/Sales/PaymentReceives.ts index ea78563c5..9d26a7f2f 100644 --- a/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/server/src/api/controllers/Sales/PaymentReceives.ts @@ -339,7 +339,18 @@ export default class PaymentReceivesController extends BaseController { errors: [{ type: 'INVALID_PAYMENT_AMOUNT', code: 1000 }], }); } - console.log(error.errorType); + if (error.errorType === 'INVOICES_NOT_DELIVERED_YET') { + return res.boom.badRequest(null, { + errors: [{ + type: 'INVOICES_NOT_DELIVERED_YET', code: 200, + data: { + not_delivered_invoices_ids: error.payload.notDeliveredInvoices.map( + (invoice) => invoice.id + ) + } + }], + }); + } } next(error); } diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts index 67783d9a6..46b206914 100644 --- a/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/server/src/api/controllers/Sales/SalesInvoices.ts @@ -7,10 +7,10 @@ import SaleInvoiceService from 'services/Sales/SalesInvoices'; import ItemsService from 'services/Items/ItemsService'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { ServiceError } from 'exceptions'; -import { ISaleInvoiceOTD, ISalesInvoicesFilter } from 'interfaces'; +import { ISaleInvoiceDTO, ISalesInvoicesFilter } from 'interfaces'; @Service() -export default class SaleInvoicesController extends BaseController{ +export default class SaleInvoicesController extends BaseController { @Inject() itemsService: ItemsService; @@ -34,17 +34,15 @@ export default class SaleInvoicesController extends BaseController{ ], this.validationResult, asyncMiddleware(this.newSaleInvoice.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.post( '/:id/deliver', - [ - ...this.specificSaleInvoiceValidation, - ], + [...this.specificSaleInvoiceValidation], this.validationResult, asyncMiddleware(this.deliverSaleInvoice.bind(this)), - this.handleServiceErrors, - ) + this.handleServiceErrors + ); router.post( '/:id', [ @@ -53,29 +51,28 @@ export default class SaleInvoicesController extends BaseController{ ], this.validationResult, asyncMiddleware(this.editSaleInvoice.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.delete( '/:id', this.specificSaleInvoiceValidation, this.validationResult, asyncMiddleware(this.deleteSaleInvoice.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.get( - '/payable', [ - ...this.dueSalesInvoicesListValidationSchema, - ], + '/payable', + [...this.dueSalesInvoicesListValidationSchema], this.validationResult, asyncMiddleware(this.getPayableInvoices.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.get( '/:id', this.specificSaleInvoiceValidation, this.validationResult, asyncMiddleware(this.getSaleInvoice.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.get( '/', @@ -83,8 +80,8 @@ export default class SaleInvoicesController extends BaseController{ this.validationResult, asyncMiddleware(this.getSalesInvoices.bind(this)), this.handleServiceErrors, - this.dynamicListService.handlerErrorsToResponse, - ) + this.dynamicListService.handlerErrorsToResponse + ); return router; } @@ -131,7 +128,7 @@ export default class SaleInvoicesController extends BaseController{ query('column_sort_by').optional(), query('sort_order').optional().isIn(['desc', 'asc']), query('page').optional().isNumeric().toInt(), - query('page_size').optional().isNumeric().toInt(), + query('page_size').optional().isNumeric().toInt(), ]; } @@ -139,9 +136,7 @@ export default class SaleInvoicesController extends BaseController{ * Due sale invoice list validation schema. */ get dueSalesInvoicesListValidationSchema() { - return [ - query('customer_id').optional().isNumeric().toInt(), - ]; + return [query('customer_id').optional().isNumeric().toInt()]; } /** @@ -152,19 +147,20 @@ export default class SaleInvoicesController extends BaseController{ */ async newSaleInvoice(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; - const saleInvoiceOTD: ISaleInvoiceOTD = this.matchedBodyData(req); + const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req); try { // Creates a new sale invoice with associated entries. const storedSaleInvoice = await this.saleInvoiceService.createSaleInvoice( - tenantId, saleInvoiceOTD, + tenantId, + saleInvoiceOTD ); return res.status(200).send({ id: storedSaleInvoice.id, message: 'The sale invoice has been created successfully.', }); } catch (error) { - next(error) + next(error); } } @@ -177,20 +173,24 @@ export default class SaleInvoicesController extends BaseController{ async editSaleInvoice(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; const { id: saleInvoiceId } = req.params; - const saleInvoiceOTD: ISaleInvoiceOTD = this.matchedBodyData(req); + const saleInvoiceOTD: ISaleInvoiceDTO = this.matchedBodyData(req); try { // Update the given sale invoice details. - await this.saleInvoiceService.editSaleInvoice(tenantId, saleInvoiceId, saleInvoiceOTD); + await this.saleInvoiceService.editSaleInvoice( + tenantId, + saleInvoiceId, + saleInvoiceOTD + ); return res.status(200).send({ id: saleInvoiceId, - message: 'The sale invoice has beeen edited successfully.', + message: 'The sale invoice has been edited successfully.', }); } catch (error) { next(error); } } - + /** * Deliver the given sale invoice. * @param {Request} req - @@ -226,7 +226,7 @@ export default class SaleInvoicesController extends BaseController{ try { // Deletes the sale invoice with associated entries and journal transaction. await this.saleInvoiceService.deleteSaleInvoice(tenantId, saleInvoiceId); - + return res.status(200).send({ id: saleInvoiceId, message: 'The sale invoice has been deleted successfully.', @@ -247,7 +247,8 @@ export default class SaleInvoicesController extends BaseController{ try { const saleInvoice = await this.saleInvoiceService.getSaleInvoice( - tenantId, saleInvoiceId, + tenantId, + saleInvoiceId ); return res.status(200).send({ sale_invoice: saleInvoice }); } catch (error) { @@ -260,7 +261,11 @@ export default class SaleInvoicesController extends BaseController{ * @param {Response} res * @param {Function} next */ - public async getSalesInvoices(req: Request, res: Response, next: NextFunction) { + public async getSalesInvoices( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId } = req; const filter: ISalesInvoicesFilter = { filterRoles: [], @@ -277,9 +282,7 @@ export default class SaleInvoicesController extends BaseController{ salesInvoices, filterMeta, pagination, - } = await this.saleInvoiceService.salesInvoicesList( - tenantId, filter, - ); + } = await this.saleInvoiceService.salesInvoicesList(tenantId, filter); return res.status(200).send({ sales_invoices: salesInvoices, pagination: this.transfromToResponse(pagination), @@ -292,17 +295,24 @@ export default class SaleInvoicesController extends BaseController{ /** * Retrieve due sales invoices. - * @param {Request} req - - * @param {Response} res - - * @param {NextFunction} next - + * @param {Request} req - + * @param {Response} res - + * @param {NextFunction} next - * @return {Response|void} */ - public async getPayableInvoices(req: Request, res: Response, next: NextFunction) { + public async getPayableInvoices( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId } = req; const { customerId } = this.matchedQueryData(req); try { - const salesInvoices = await this.saleInvoiceService.getPayableInvoices(tenantId, customerId); + const salesInvoices = await this.saleInvoiceService.getPayableInvoices( + tenantId, + customerId + ); return res.status(200).send({ sales_invoices: this.transfromToResponse(salesInvoices), @@ -314,12 +324,17 @@ export default class SaleInvoicesController extends BaseController{ /** * Handles service errors. - * @param {Error} error - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Error} error + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ - handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) { + handleServiceErrors( + error: Error, + req: Request, + res: Response, + next: NextFunction + ) { if (error instanceof ServiceError) { if (error.errorType === 'INVOICE_NUMBER_NOT_UNIQUE') { return res.boom.badRequest(null, { @@ -328,7 +343,7 @@ export default class SaleInvoicesController extends BaseController{ } if (error.errorType === 'SALE_INVOICE_NOT_FOUND') { return res.status(404).send({ - errors: [{ type: 'SALE.INVOICE.NOT.FOUND', code: 200 }] + errors: [{ type: 'SALE.INVOICE.NOT.FOUND', code: 200 }], }); } if (error.errorType === 'ENTRIES_ITEMS_IDS_NOT_EXISTS') { @@ -372,6 +387,6 @@ export default class SaleInvoicesController extends BaseController{ }); } } - next(error); + next(error); } } diff --git a/server/src/interfaces/SaleInvoice.ts b/server/src/interfaces/SaleInvoice.ts index fd16ce2b4..e5e77d898 100644 --- a/server/src/interfaces/SaleInvoice.ts +++ b/server/src/interfaces/SaleInvoice.ts @@ -12,7 +12,7 @@ export interface ISaleInvoice { deliveredAt: string|Date, } -export interface ISaleInvoiceOTD { +export interface ISaleInvoiceDTO { invoiceDate: Date, dueDate: Date, referenceNo: string, @@ -24,11 +24,11 @@ export interface ISaleInvoiceOTD { delivered: boolean, } -export interface ISaleInvoiceCreateDTO extends ISaleInvoiceOTD { +export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO { fromEstiamteId: number, }; -export interface ISaleInvoiceEditDTO extends ISaleInvoiceOTD { +export interface ISaleInvoiceEditDTO extends ISaleInvoiceDTO { }; diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index ecfbfd734..a74c5f3ee 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -37,7 +37,7 @@ const ERRORS = { BILL_ITEMS_NOT_FOUND: 'BILL_ITEMS_NOT_FOUND', BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND', NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS', - BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN' + BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN', }; /** @@ -84,7 +84,10 @@ export default class BillsService extends SalesInvoicesCost { const foundVendor = await vendorRepository.findOneById(vendorId); if (!foundVendor) { - this.logger.info('[bill] the given vendor not found.', { tenantId, vendorId }); + this.logger.info('[bill] the given vendor not found.', { + tenantId, + vendorId, + }); throw new ServiceError(ERRORS.BILL_VENDOR_NOT_FOUND); } return foundVendor; @@ -94,16 +97,21 @@ export default class BillsService extends SalesInvoicesCost { * Validates the given bill existance. * @async * @param {number} tenantId - - * @param {number} billId - + * @param {number} billId - */ private async getBillOrThrowError(tenantId: number, billId: number) { const { Bill } = this.tenancy.models(tenantId); this.logger.info('[bill] trying to get bill.', { tenantId, billId }); - const foundBill = await Bill.query().findById(billId).withGraphFetched('entries'); + const foundBill = await Bill.query() + .findById(billId) + .withGraphFetched('entries'); if (!foundBill) { - this.logger.info('[bill] the given bill not found.', { tenantId, billId }); + this.logger.info('[bill] the given bill not found.', { + tenantId, + billId, + }); throw new ServiceError(ERRORS.BILL_NOT_FOUND); } return foundBill; @@ -116,13 +124,19 @@ export default class BillsService extends SalesInvoicesCost { * @param {Response} res * @param {Function} next */ - private async validateBillNumberExists(tenantId: number, billNumber: string, notBillId?: number) { + private async validateBillNumberExists( + tenantId: number, + billNumber: string, + notBillId?: number + ) { const { Bill } = this.tenancy.models(tenantId); - const foundBills = await Bill.query().where('bill_number', billNumber).onBuild((builder) => { - if (notBillId) { - builder.whereNot('id', notBillId); - } - }); + const foundBills = await Bill.query() + .where('bill_number', billNumber) + .onBuild((builder) => { + if (notBillId) { + builder.whereNot('id', notBillId); + } + }); if (foundBills.length > 0) { throw new ServiceError(ERRORS.BILL_NUMBER_EXISTS); @@ -131,17 +145,17 @@ export default class BillsService extends SalesInvoicesCost { /** * Converts bill DTO to model. - * @param {number} tenantId - * @param {IBillDTO} billDTO - * @param {IBill} oldBill - * + * @param {number} tenantId + * @param {IBillDTO} billDTO + * @param {IBill} oldBill + * * @returns {IBill} */ private async billDTOToModel( tenantId: number, billDTO: IBillDTO | IBillEditDTO, authorizedUser: ISystemUser, - oldBill?: IBill, + oldBill?: IBill ) { const { ItemEntry } = this.tenancy.models(tenantId); let invLotNumber = oldBill?.invLotNumber; @@ -156,10 +170,10 @@ export default class BillsService extends SalesInvoicesCost { const amount = sumBy(entries, 'amount'); return { - ...formatDateFields( - omit(billDTO, ['open', 'entries']), - ['billDate', 'dueDate'] - ), + ...formatDateFields(omit(billDTO, ['open', 'entries']), [ + 'billDate', + 'dueDate', + ]), amount, invLotNumber, entries: entries.map((entry) => ({ @@ -167,9 +181,10 @@ export default class BillsService extends SalesInvoicesCost { ...omit(entry, ['amount', 'id']), })), // Avoid rewrite the open date in edit mode when already opened. - ...(billDTO.open && (!oldBill?.openedAt)) && ({ - openedAt: moment().toMySqlDateTime(), - }), + ...(billDTO.open && + !oldBill?.openedAt && { + openedAt: moment().toMySqlDateTime(), + }), userId: authorizedUser.id, }; } @@ -196,8 +211,16 @@ export default class BillsService extends SalesInvoicesCost { ): Promise { const { Bill } = this.tenancy.models(tenantId); - this.logger.info('[bill] trying to create a new bill', { tenantId, billDTO }); - const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, null); + this.logger.info('[bill] trying to create a new bill', { + tenantId, + billDTO, + }); + const billObj = await this.billDTOToModel( + tenantId, + billDTO, + authorizedUser, + null + ); // Retrieve vendor or throw not found service error. await this.getVendorOrThrowError(tenantId, billDTO.vendorId); @@ -207,19 +230,28 @@ export default class BillsService extends SalesInvoicesCost { await this.validateBillNumberExists(tenantId, billDTO.billNumber); } // Validate items IDs existance. - await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries); - + await this.itemsEntriesService.validateItemsIdsExistance( + tenantId, + billDTO.entries + ); // Validate non-purchasable items. - await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries); - + await this.itemsEntriesService.validateNonPurchasableEntriesItems( + tenantId, + billDTO.entries + ); // Inserts the bill graph object to the storage. const bill = await Bill.query().insertGraph({ ...billObj }); - // Triggers `onBillCreated` event. + // Triggers `onBillCreated` event. await this.eventDispatcher.dispatch(events.bill.onCreated, { - tenantId, bill, billId: bill.id, + tenantId, + bill, + billId: bill.id, + }); + this.logger.info('[bill] bill inserted successfully.', { + tenantId, + billId: bill.id, }); - this.logger.info('[bill] bill inserted successfully.', { tenantId, billId: bill.id }); return bill; } @@ -253,7 +285,12 @@ export default class BillsService extends SalesInvoicesCost { const oldBill = await this.getBillOrThrowError(tenantId, billId); // Transforms the bill DTO object to model object. - const billObj = await this.billDTOToModel(tenantId, billDTO, authorizedUser, oldBill); + const billObj = await this.billDTOToModel( + tenantId, + billDTO, + authorizedUser, + oldBill + ); // Retrieve vendor details or throw not found service error. await this.getVendorOrThrowError(tenantId, billDTO.vendorId); @@ -263,22 +300,39 @@ export default class BillsService extends SalesInvoicesCost { await this.validateBillNumberExists(tenantId, billDTO.billNumber, billId); } // Validate the entries ids existance. - await this.itemsEntriesService.validateEntriesIdsExistance(tenantId, billId, 'Bill', billDTO.entries); + await this.itemsEntriesService.validateEntriesIdsExistance( + tenantId, + billId, + 'Bill', + billDTO.entries + ); // Validate the items ids existance on the storage. - await this.itemsEntriesService.validateItemsIdsExistance(tenantId, billDTO.entries); - + await this.itemsEntriesService.validateItemsIdsExistance( + tenantId, + billDTO.entries + ); // Accept the purchasable items only. - await this.itemsEntriesService.validateNonPurchasableEntriesItems(tenantId, billDTO.entries); - + await this.itemsEntriesService.validateNonPurchasableEntriesItems( + tenantId, + billDTO.entries + ); // Update the bill transaction. const bill = await Bill.query().upsertGraphAndFetch({ id: billId, ...billObj, }); // Triggers event `onBillEdited`. - await this.eventDispatcher.dispatch(events.bill.onEdited, { tenantId, billId, oldBill, bill }); - this.logger.info('[bill] bill upserted successfully.', { tenantId, billId }); + await this.eventDispatcher.dispatch(events.bill.onEdited, { + tenantId, + billId, + oldBill, + bill, + }); + this.logger.info('[bill] bill upserted successfully.', { + tenantId, + billId, + }); return bill; } @@ -306,13 +360,17 @@ export default class BillsService extends SalesInvoicesCost { await Promise.all([deleteBillEntriesOper, deleteBillOper]); // Triggers `onBillDeleted` event. - await this.eventDispatcher.dispatch(events.bill.onDeleted, { tenantId, billId, oldBill }); + await this.eventDispatcher.dispatch(events.bill.onDeleted, { + tenantId, + billId, + oldBill, + }); } /** * Records the inventory transactions from the given bill input. - * @param {Bill} bill - * @param {number} billId + * @param {Bill} bill + * @param {number} billId */ public recordInventoryTransactions( tenantId: number, @@ -320,19 +378,20 @@ export default class BillsService extends SalesInvoicesCost { billId: number, override?: boolean ) { - const inventoryTransactions = bill.entries - .map((entry) => ({ - ...pick(entry, ['item_id', 'quantity', 'rate']), - lotNumber: bill.invLotNumber, - transactionType: 'Bill', - transactionId: billId, - direction: 'IN', - date: bill.bill_date, - entryId: entry.id, - })); + const inventoryTransactions = bill.entries.map((entry) => ({ + ...pick(entry, ['item_id', 'quantity', 'rate']), + lotNumber: bill.invLotNumber, + transactionType: 'Bill', + transactionId: billId, + direction: 'IN', + date: bill.bill_date, + entryId: entry.id, + })); return this.inventoryService.recordInventoryTransactions( - tenantId, inventoryTransactions, override + tenantId, + inventoryTransactions, + override ); } @@ -345,12 +404,12 @@ export default class BillsService extends SalesInvoicesCost { public async recordJournalTransactions( tenantId: number, bill: IBill, - override: boolean = false, + override: boolean = false ) { const journal = new JournalPoster(tenantId); const journalCommands = new JournalCommands(journal); - await journalCommands.bill(bill, override) + await journalCommands.bill(bill, override); return Promise.all([ journal.deleteEntries(), @@ -366,16 +425,29 @@ export default class BillsService extends SalesInvoicesCost { */ public async getBills( tenantId: number, - billsFilter: IBillsFilter, - ): Promise<{ bills: IBill, pagination: IPaginationMeta, filterMeta: IFilterMeta }> { + billsFilter: IBillsFilter + ): Promise<{ + bills: IBill; + pagination: IPaginationMeta; + filterMeta: IFilterMeta; + }> { const { Bill } = this.tenancy.models(tenantId); - const dynamicFilter = await this.dynamicListService.dynamicList(tenantId, Bill, billsFilter); + const dynamicFilter = await this.dynamicListService.dynamicList( + tenantId, + Bill, + billsFilter + ); - this.logger.info('[bills] trying to get bills data table.', { tenantId, billsFilter }); - const { results, pagination } = await Bill.query().onBuild((builder) => { - builder.withGraphFetched('vendor'); - dynamicFilter.buildQuery()(builder); - }).pagination(billsFilter.page - 1, billsFilter.pageSize); + this.logger.info('[bills] trying to get bills data table.', { + tenantId, + billsFilter, + }); + const { results, pagination } = await Bill.query() + .onBuild((builder) => { + builder.withGraphFetched('vendor'); + dynamicFilter.buildQuery()(builder); + }) + .pagination(billsFilter.page - 1, billsFilter.pageSize); return { bills: results, @@ -386,8 +458,8 @@ export default class BillsService extends SalesInvoicesCost { /** * Retrieve all due bills or for specific given vendor id. - * @param {number} tenantId - - * @param {number} vendorId - + * @param {number} tenantId - + * @param {number} vendorId - */ public async getDueBills( tenantId: number, @@ -414,8 +486,12 @@ export default class BillsService extends SalesInvoicesCost { public async getBill(tenantId: number, billId: number): Promise { const { Bill } = this.tenancy.models(tenantId); - this.logger.info('[bills] trying to fetch specific bill with metadata.', { tenantId, billId }); - const bill = await Bill.query().findById(billId) + this.logger.info('[bills] trying to fetch specific bill with metadata.', { + tenantId, + billId, + }); + const bill = await Bill.query() + .findById(billId) .withGraphFetched('vendor') .withGraphFetched('entries'); @@ -440,9 +516,9 @@ export default class BillsService extends SalesInvoicesCost { .whereIn('id', billItemsIds) .where('type', 'inventory'); - const inventoryItemsIds = inventoryItems.map(i => i.id); + const inventoryItemsIds = inventoryItems.map((i) => i.id); - if (inventoryItemsIds.length > 0) { + if (inventoryItemsIds.length > 0) { await this.scheduleComputeItemsCost( tenantId, inventoryItemsIds, @@ -453,13 +529,10 @@ export default class BillsService extends SalesInvoicesCost { /** * Mark the bill as open. - * @param {number} tenantId - * @param {number} billId + * @param {number} tenantId + * @param {number} billId */ - public async openBill( - tenantId: number, - billId: number, - ): Promise { + public async openBill(tenantId: number, billId: number): Promise { const { Bill } = this.tenancy.models(tenantId); // Retrieve the given bill or throw not found error. @@ -474,4 +547,4 @@ export default class BillsService extends SalesInvoicesCost { openedAt: moment().toMySqlDateTime(), }); } -} \ No newline at end of file +} diff --git a/server/src/services/Sales/PaymentsReceives.ts b/server/src/services/Sales/PaymentsReceives.ts index 8f1a2261e..d4fd3028a 100644 --- a/server/src/services/Sales/PaymentsReceives.ts +++ b/server/src/services/Sales/PaymentsReceives.ts @@ -35,7 +35,8 @@ const ERRORS = { DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET_TYPE', INVALID_PAYMENT_AMOUNT: 'INVALID_PAYMENT_AMOUNT', INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND', - ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS' + ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS', + INVOICES_NOT_DELIVERED_YET: 'INVOICES_NOT_DELIVERED_YET' }; /** * Payment receive service. @@ -151,6 +152,13 @@ export default class PaymentReceiveService { if (notFoundInvoicesIDs.length > 0) { throw new ServiceError(ERRORS.INVOICES_IDS_NOT_FOUND); } + // Filters the not delivered invoices. + const notDeliveredInvoices = storedInvoices.filter((invoice) => !invoice.isDelivered); + + if (notDeliveredInvoices.length > 0) { + throw new ServiceError(ERRORS.INVOICES_NOT_DELIVERED_YET, null, { notDeliveredInvoices }); + } + return storedInvoices; } /** diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index 9b2eff3ea..8c5c38884 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -7,7 +7,7 @@ import { } from 'decorators/eventDispatcher'; import { ISaleInvoice, - ISaleInvoiceOTD, + ISaleInvoiceDTO, IItemEntry, ISalesInvoicesFilter, IPaginationMeta, @@ -121,7 +121,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost { /** * Transform DTO object to model object. * @param {number} tenantId - Tenant id. - * @param {ISaleInvoiceOTD} saleInvoiceDTO - Sale invoice DTO. + * @param {ISaleInvoiceDTO} saleInvoiceDTO - Sale invoice DTO. */ transformDTOToModel( tenantId: number, @@ -134,10 +134,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost { ); return { - ...formatDateFields( - omit(saleInvoiceDTO, ['delivered', 'entries']), - ['invoiceDate', 'dueDate'] - ), + ...formatDateFields(omit(saleInvoiceDTO, ['delivered', 'entries']), [ + 'invoiceDate', + 'dueDate', + ]), // Avoid rewrite the deliver date in edit mode when already published. ...(saleInvoiceDTO.delivered && !oldSaleInvoice?.deliveredAt && { @@ -156,9 +156,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost { * Creates a new sale invoices and store it to the storage * with associated to entries and journal transactions. * @async - * @param {number} tenantId = - * @param {ISaleInvoice} saleInvoiceDTO - - * @return {ISaleInvoice} + * @param {number} tenantId - Tenant id. + * @param {ISaleInvoice} saleInvoiceDTO - Sale invoice object DTO. + * @return {Promise} */ public async createSaleInvoice( tenantId: number, @@ -200,7 +200,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost { const saleInvoice = await saleInvoiceRepository.upsertGraph({ ...saleInvoiceObj, }); - + // Triggers the event `onSaleInvoiceCreated`. await this.eventDispatcher.dispatch(events.saleInvoice.onCreated, { tenantId, saleInvoice, @@ -217,9 +217,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost { /** * Edit the given sale invoice. * @async - * @param {number} tenantId - - * @param {Number} saleInvoiceId - - * @param {ISaleInvoice} saleInvoice - + * @param {number} tenantId - Tenant id. + * @param {Number} saleInvoiceId - Sale invoice id. + * @param {ISaleInvoice} saleInvoice - Sale invoice DTO object. + * @return {Promise} */ public async editSaleInvoice( tenantId: number, @@ -228,9 +229,6 @@ export default class SaleInvoicesService extends SalesInvoicesCost { ): Promise { const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId); - const balance = sumBy(saleInvoiceDTO.entries, (e) => - ItemEntry.calcAmount(e) - ); const oldSaleInvoice = await this.getInvoiceOrThrowError( tenantId, saleInvoiceId @@ -242,13 +240,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost { saleInvoiceDTO, oldSaleInvoice ); - // Validate customer existance. await this.customersService.getCustomerByIdOrThrowError( tenantId, saleInvoiceDTO.customerId ); - // Validate sale invoice number uniquiness. if (saleInvoiceDTO.invoiceNo) { await this.validateInvoiceNumberUnique( @@ -281,15 +277,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost { const saleInvoice: ISaleInvoice = await SaleInvoice.query().upsertGraphAndFetch( { id: saleInvoiceId, - ...omit(saleInvoiceObj, ['entries', 'invLotNumber']), - - entries: saleInvoiceObj.entries.map((entry) => ({ - reference_type: 'SaleInvoice', - ...omit(entry, ['amount']), - })), + ...saleInvoiceObj, } ); - // Triggers `onSaleInvoiceEdited` event. await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, { saleInvoice, diff --git a/server/src/subscribers/bills.ts b/server/src/subscribers/bills.ts index d4ab5f1a8..99480315e 100644 --- a/server/src/subscribers/bills.ts +++ b/server/src/subscribers/bills.ts @@ -1,10 +1,9 @@ -import { Container, Inject, Service } from 'typedi'; +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'; import JournalPosterService from 'services/Sales/JournalPosterService'; -import VendorRepository from 'repositories/VendorRepository'; @EventSubscriber() export default class BillSubscriber { @@ -13,11 +12,13 @@ export default class BillSubscriber { logger: any; journalPosterService: JournalPosterService; + /** + * Constructor method. + */ constructor() { this.tenancy = Container.get(TenancyService); this.billsService = Container.get(BillsService); this.logger = Container.get('logger'); - this.journalPosterService = Container.get(JournalPosterService); } @@ -29,7 +30,10 @@ export default class BillSubscriber { const { vendorRepository } = this.tenancy.repositories(tenantId); // Increments vendor balance. - this.logger.info('[bill] trying to increment vendor balance.', { tenantId, billId }); + this.logger.info('[bill] trying to increment vendor balance.', { + tenantId, + billId, + }); await vendorRepository.changeBalance(bill.vendorId, bill.amount); } @@ -61,7 +65,10 @@ export default class BillSubscriber { const { vendorRepository } = this.tenancy.repositories(tenantId); // Decrements vendor balance. - this.logger.info('[bill] trying to decrement vendor balance.', { tenantId, billId }); + this.logger.info('[bill] trying to decrement vendor balance.', { + tenantId, + billId, + }); await vendorRepository.changeBalance(oldBill.vendorId, oldBill.amount * -1); } @@ -71,8 +78,15 @@ export default class BillSubscriber { @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'); + this.logger.info('[bill] trying to delete journal entries.', { + tenantId, + billId, + }); + await this.journalPosterService.revertJournalTransactions( + tenantId, + billId, + 'Bill' + ); } /** @@ -83,12 +97,15 @@ export default class BillSubscriber { 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 }); + this.logger.info('[bill[ change vendor the different balance.', { + tenantId, + billId, + }); await vendorRepository.changeDiffBalance( bill.vendorId, bill.amount, oldBill.amount, - oldBill.vendorId, + oldBill.vendorId ); } -} \ No newline at end of file +} diff --git a/server/src/subscribers/vendors.ts b/server/src/subscribers/vendors.ts index 52a510213..c0db44a27 100644 --- a/server/src/subscribers/vendors.ts +++ b/server/src/subscribers/vendors.ts @@ -18,6 +18,9 @@ export default class VendorsSubscriber { this.vendorsService = Container.get(VendorsService); } + /** + * Writes the open balance journal entries once the vendor created. + */ @On(events.vendors.onCreated) async handleWriteOpeningBalanceEntries({ tenantId, vendorId, vendor }) { // Writes the vendor opening balance journal entries. @@ -30,6 +33,9 @@ export default class VendorsSubscriber { } } + /** + * Revert the opening balance journal entries once the vendor deleted. + */ @On(events.vendors.onDeleted) async handleRevertOpeningBalanceEntries({ tenantId, vendorId }) { await this.vendorsService.revertOpeningBalanceEntries( @@ -37,6 +43,9 @@ export default class VendorsSubscriber { ); } + /** + * Revert the opening balance journal entries once the vendors deleted in bulk. + */ @On(events.vendors.onBulkDeleted) async handleBulkRevertOpeningBalanceEntries({ tenantId, vendorsIds }) { await this.vendorsService.revertOpeningBalanceEntries( From 1a0aad7dc490ae95a027696d00b1f8ac7e2a95b0 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sat, 19 Dec 2020 13:51:44 +0200 Subject: [PATCH 3/3] fix: partially paid invoice transaction. --- server/src/models/SaleInvoice.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/server/src/models/SaleInvoice.js b/server/src/models/SaleInvoice.js index bb2beee26..cf9fb4b3b 100644 --- a/server/src/models/SaleInvoice.js +++ b/server/src/models/SaleInvoice.js @@ -5,13 +5,6 @@ import { defaultToTransform } from 'utils'; import { QueryBuilder } from 'knex'; export default class SaleInvoice extends TenantModel { - /** - * Virtual attributes. - */ - static get virtualAttributes() { - return ['dueAmount']; - } - /** * Table name */ @@ -60,10 +53,10 @@ export default class SaleInvoice extends TenantModel { /** * Retrieve the invoice due amount. - * (Invoice amount - payment amount = Due amount) + * Equation (Invoice amount - payment amount = Due amount) * @return {boolean} */ - dueAmount() { + get dueAmount() { return Math.max(this.balance - this.paymentAmount, 0); }