From cc47314a625c7506c3280e84d5ba605aba899614 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sat, 19 Dec 2020 13:44:02 +0200 Subject: [PATCH] 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(