From d79be910f9eac77a31f6ed9a24e0eabc2e1e8aa4 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Mon, 22 Mar 2021 15:21:52 +0200 Subject: [PATCH] fix(Contacts): validate contact associated transcations. --- .../src/api/controllers/Contacts/Customers.ts | 48 +---- .../src/api/controllers/Contacts/Vendors.ts | 22 +-- server/src/interfaces/Bill.ts | 4 + server/src/interfaces/BillPayment.ts | 7 +- server/src/interfaces/PaymentReceive.ts | 114 ++++++------ server/src/interfaces/SaleEstimate.ts | 8 + server/src/interfaces/SaleInvoice.ts | 69 ++++---- server/src/interfaces/SaleReceipt.ts | 11 +- .../src/services/Contacts/CustomersService.ts | 165 +++++++----------- .../src/services/Contacts/VendorsService.ts | 133 ++++---------- .../Purchases/BillPayments/BillPayments.ts | 21 ++- .../Purchases/BillPayments/constants.ts | 3 +- server/src/services/Purchases/Bills.ts | 30 +++- server/src/services/Purchases/constants.ts | 3 +- .../Sales/PaymentReceives/PaymentsReceives.ts | 27 ++- .../Sales/PaymentReceives/constants.ts | 1 + server/src/services/Sales/SalesEstimate.ts | 40 +++-- server/src/services/Sales/SalesInvoices.ts | 23 ++- server/src/services/Sales/SalesReceipts.ts | 34 +++- server/src/services/Sales/constants.ts | 3 +- 20 files changed, 382 insertions(+), 384 deletions(-) diff --git a/server/src/api/controllers/Contacts/Customers.ts b/server/src/api/controllers/Contacts/Customers.ts index d6971307f..97041a348 100644 --- a/server/src/api/controllers/Contacts/Customers.ts +++ b/server/src/api/controllers/Contacts/Customers.ts @@ -63,13 +63,6 @@ export default class CustomersController extends ContactsController { asyncMiddleware(this.deleteCustomer.bind(this)), this.handlerServiceErrors ); - router.delete( - '/', - [...this.bulkContactsSchema], - this.validationResult, - asyncMiddleware(this.deleteBulkCustomers.bind(this)), - this.handlerServiceErrors - ); router.get( '/', [...this.validateListQuerySchema], @@ -263,32 +256,6 @@ export default class CustomersController extends ContactsController { } } - /** - * Deletes customers in bulk. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async deleteBulkCustomers(req: Request, res: Response, next: NextFunction) { - const { ids: contactsIds } = req.query; - const { tenantId, user } = req; - - try { - await this.customersService.deleteBulkCustomers( - tenantId, - contactsIds, - user - ); - - return res.status(200).send({ - ids: contactsIds, - message: 'The customers have been deleted successfully.', - }); - } catch (error) { - next(error); - } - } - /** * Retrieve customers paginated and filterable list. * @param {Request} req @@ -350,21 +317,16 @@ export default class CustomersController extends ContactsController { errors: [{ type: 'CUSTOMERS.NOT.FOUND', code: 200 }], }); } - if (error.errorType === 'some_customers_have_invoices') { - return res.boom.badRequest(null, { - errors: [{ type: 'SOME.CUSTOMERS.HAVE.SALES_INVOICES', code: 300 }], - }); - } - if (error.errorType === 'customer_has_invoices') { - return res.boom.badRequest(null, { - errors: [{ type: 'CUSTOMER.HAS.SALES_INVOICES', code: 400 }], - }); - } if (error.errorType === 'OPENING_BALANCE_DATE_REQUIRED') { return res.boom.badRequest(null, { errors: [{ type: 'OPENING_BALANCE_DATE_REQUIRED', code: 500 }], }); } + if (error.errorType === 'CUSTOMER_HAS_TRANSACTIONS') { + return res.boom.badRequest(null, { + errors: [{ type: 'CUSTOMER_HAS_TRANSACTIONS', code: 600 }], + }); + } } next(error); } diff --git a/server/src/api/controllers/Contacts/Vendors.ts b/server/src/api/controllers/Contacts/Vendors.ts index cc03a1ba8..31a4f50f7 100644 --- a/server/src/api/controllers/Contacts/Vendors.ts +++ b/server/src/api/controllers/Contacts/Vendors.ts @@ -55,13 +55,6 @@ export default class VendorsController extends ContactsController { asyncMiddleware(this.deleteVendor.bind(this)), this.handlerServiceErrors, ); - router.delete('/', [ - ...this.bulkContactsSchema, - ], - this.validationResult, - asyncMiddleware(this.deleteBulkVendors.bind(this)), - this.handlerServiceErrors, - ); router.get('/:id', [ ...this.specificContactSchema, ], @@ -297,21 +290,16 @@ export default class VendorsController extends ContactsController { errors: [{ type: 'VENDORS.NOT.FOUND', code: 200 }], }); } - if (error.errorType === 'some_vendors_have_bills') { - return res.boom.badRequest(null, { - errors: [{ type: 'SOME.VENDORS.HAVE.ASSOCIATED.BILLS', code: 300 }], - }); - } - if (error.errorType === 'vendor_has_bills') { - return res.status(400).send({ - errors: [{ type: 'VENDOR.HAS.ASSOCIATED.BILLS', code: 400 }], - }); - } if (error.errorType === 'OPENING_BALANCE_DATE_REQUIRED') { return res.boom.badRequest(null, { errors: [{ type: 'OPENING_BALANCE_DATE_REQUIRED', code: 500 }], }); } + if (error.errorType === 'VENDOR_HAS_TRANSACTIONS') { + return res.boom.badRequest(null, { + errors: [{ type: 'VENDOR_HAS_TRANSACTIONS', code: 600 }], + }); + } } next(error); } diff --git a/server/src/interfaces/Bill.ts b/server/src/interfaces/Bill.ts index 815ef3aef..c90bd1387 100644 --- a/server/src/interfaces/Bill.ts +++ b/server/src/interfaces/Bill.ts @@ -55,4 +55,8 @@ export interface IBill { export interface IBillsFilter extends IDynamicListFilterDTO { stringifiedFilterRoles?: string, +} + +export interface IBillsService { + validateVendorHasNoBills(tenantId: number, vendorId: number): Promise; } \ No newline at end of file diff --git a/server/src/interfaces/BillPayment.ts b/server/src/interfaces/BillPayment.ts index e7227b6cc..e444a9688 100644 --- a/server/src/interfaces/BillPayment.ts +++ b/server/src/interfaces/BillPayment.ts @@ -45,4 +45,9 @@ export interface IBillReceivePageEntry { paymentAmount: number, currencyCode: string, date: Date|string, -}; \ No newline at end of file +}; + + +export interface IBillPaymentsService { + validateVendorHasNoPayments(tenantId: number, vendorId): Promise; +} \ No newline at end of file diff --git a/server/src/interfaces/PaymentReceive.ts b/server/src/interfaces/PaymentReceive.ts index 1359ced52..e9e6fdca3 100644 --- a/server/src/interfaces/PaymentReceive.ts +++ b/server/src/interfaces/PaymentReceive.ts @@ -1,71 +1,77 @@ - -import { IDynamicListFilterDTO } from "./DynamicFilter"; +import { IDynamicListFilterDTO } from './DynamicFilter'; export interface IPaymentReceive { - id?: number, - customerId: number, - paymentDate: Date, - amount: number, - referenceNo: string, - depositAccountId: number, - paymentReceiveNo: string, - statement: string, - entries: IPaymentReceiveEntry[], - userId: number, -}; + id?: number; + customerId: number; + paymentDate: Date; + amount: number; + referenceNo: string; + depositAccountId: number; + paymentReceiveNo: string; + statement: string; + entries: IPaymentReceiveEntry[]; + userId: number; +} export interface IPaymentReceiveCreateDTO { - customerId: number, - paymentDate: Date, - amount: number, - referenceNo: string, - depositAccountId: number, - paymentReceiveNo?: string, - statement: string, - entries: IPaymentReceiveEntryDTO[], -}; + customerId: number; + paymentDate: Date; + amount: number; + referenceNo: string; + depositAccountId: number; + paymentReceiveNo?: string; + statement: string; + entries: IPaymentReceiveEntryDTO[]; +} export interface IPaymentReceiveEditDTO { - customerId: number, - paymentDate: Date, - amount: number, - referenceNo: string, - depositAccountId: number, - paymentReceiveNo?: string, - statement: string, - entries: IPaymentReceiveEntryDTO[], -}; + customerId: number; + paymentDate: Date; + amount: number; + referenceNo: string; + depositAccountId: number; + paymentReceiveNo?: string; + statement: string; + entries: IPaymentReceiveEntryDTO[]; +} export interface IPaymentReceiveEntry { - id?: number, - paymentReceiveId: number, - invoiceId: number, - paymentAmount: number, -}; + id?: number; + paymentReceiveId: number; + invoiceId: number; + paymentAmount: number; +} export interface IPaymentReceiveEntryDTO { - id?: number, - paymentReceiveId: number, - invoiceId: number, - paymentAmount: number, -}; + id?: number; + paymentReceiveId: number; + invoiceId: number; + paymentAmount: number; +} export interface IPaymentReceivesFilter extends IDynamicListFilterDTO { - stringifiedFilterRoles?: string, + stringifiedFilterRoles?: string; } export interface IPaymentReceivePageEntry { - invoiceId: number, - entryType: string, - invoiceNo: string, - dueAmount: number, - amount: number, - totalPaymentAmount: number, - paymentAmount: number, - currencyCode: string, - date: Date|string, -}; + invoiceId: number; + entryType: string; + invoiceNo: string; + dueAmount: number; + amount: number; + totalPaymentAmount: number; + paymentAmount: number; + currencyCode: string; + date: Date | string; +} export interface IPaymentReceiveEditPage { - paymentReceive: IPaymentReceive, + paymentReceive: IPaymentReceive; entries: IPaymentReceivePageEntry[]; -}; \ No newline at end of file +} + +export interface IPaymentsReceiveService { + validateCustomerHasNoPayments( + tenantId: number, + customerId: number + ): Promise; +} diff --git a/server/src/interfaces/SaleEstimate.ts b/server/src/interfaces/SaleEstimate.ts index f3679570d..1b672d770 100644 --- a/server/src/interfaces/SaleEstimate.ts +++ b/server/src/interfaces/SaleEstimate.ts @@ -32,4 +32,12 @@ export interface ISaleEstimateDTO { export interface ISalesEstimatesFilter extends IDynamicListFilterDTO { stringifiedFilterRoles?: string, +} + + +export interface ISalesEstimatesService { + validateCustomerHasNoEstimates( + tenantId: number, + customerId: number, + ): Promise; } \ No newline at end of file diff --git a/server/src/interfaces/SaleInvoice.ts b/server/src/interfaces/SaleInvoice.ts index d3789b011..dcdaf5fca 100644 --- a/server/src/interfaces/SaleInvoice.ts +++ b/server/src/interfaces/SaleInvoice.ts @@ -1,44 +1,49 @@ import { IDynamicListFilter } from 'interfaces/DynamicFilter'; -import { IItemEntry, IItemEntryDTO } from "./ItemEntry"; +import { IItemEntry, IItemEntryDTO } from './ItemEntry'; export interface ISaleInvoice { - id: number, - balance: number, - paymentAmount: number, - currencyCode: string, - invoiceDate: Date, - dueDate: Date, - dueAmount: number, - overdueDays: number, - customerId: number, - referenceNo: string, - invoiceNo: string, - entries: IItemEntry[], - deliveredAt: string | Date, - userId: number, + id: number; + balance: number; + paymentAmount: number; + currencyCode: string; + invoiceDate: Date; + dueDate: Date; + dueAmount: number; + overdueDays: number; + customerId: number; + referenceNo: string; + invoiceNo: string; + entries: IItemEntry[]; + deliveredAt: string | Date; + userId: number; } export interface ISaleInvoiceDTO { - invoiceDate: Date, - dueDate: Date, - referenceNo: string, - invoiceNo: string, - customerId: number, - invoiceMessage: string, - termsConditions: string, - entries: IItemEntryDTO[], - delivered: boolean, + invoiceDate: Date; + dueDate: Date; + referenceNo: string; + invoiceNo: string; + customerId: number; + invoiceMessage: string; + termsConditions: string; + entries: IItemEntryDTO[]; + delivered: boolean; } export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO { - fromEstimateId: number, -}; + fromEstimateId: number; +} -export interface ISaleInvoiceEditDTO extends ISaleInvoiceDTO { +export interface ISaleInvoiceEditDTO extends ISaleInvoiceDTO {} -}; +export interface ISalesInvoicesFilter extends IDynamicListFilter { + page: number; + pageSize: number; +} -export interface ISalesInvoicesFilter extends IDynamicListFilter{ - page: number, - pageSize: number, -}; \ No newline at end of file +export interface ISalesInvoicesService { + validateCustomerHasNoInvoices( + tenantId: number, + customerId: number + ): Promise; +} diff --git a/server/src/interfaces/SaleReceipt.ts b/server/src/interfaces/SaleReceipt.ts index 2046eddae..fb73c2ed9 100644 --- a/server/src/interfaces/SaleReceipt.ts +++ b/server/src/interfaces/SaleReceipt.ts @@ -10,7 +10,7 @@ export interface ISaleReceipt { receiptMessage: string; receiptNumber: string; amount: number; - currencyCode: string, + currencyCode: string; statement: string; closedAt: Date | string; entries: any[]; @@ -24,14 +24,14 @@ export interface ISaleReceiptDTO { receiptDate: Date; sendToEmail: string; referenceNo?: string; - receiptNumber?: string, + receiptNumber?: string; receiptMessage: string; statement: string; closed: boolean; entries: any[]; } -export interface ISalesReceiptService { +export interface ISalesReceiptsService { createSaleReceipt( tenantId: number, saleReceiptDTO: ISaleReceiptDTO @@ -49,4 +49,9 @@ export interface ISalesReceiptService { pagination: IPaginationMeta; filterMeta: IFilterMeta; }>; + + validateCustomerHasNoReceipts( + tenantId: number, + customerId: number + ): Promise; } diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index f7fe7e0dc..efeb789bc 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -1,5 +1,6 @@ import { Inject, Service } from 'typedi'; -import { omit, intersection, defaultTo } from 'lodash'; +import { omit, defaultTo } from 'lodash'; +import async from 'async'; import { EventDispatcher, EventDispatcherInterface, @@ -7,6 +8,7 @@ import { import JournalPoster from 'services/Accounting/JournalPoster'; import JournalCommands from 'services/Accounting/JournalCommands'; import ContactsService from 'services/Contacts/ContactsService'; +import moment from 'moment'; import { ICustomerNewDTO, ICustomerEditDTO, @@ -16,15 +18,20 @@ import { IContactNewDTO, IContactEditDTO, IContact, - ISaleInvoice, ISystemUser, + ISalesInvoicesService, + ISalesReceiptsService, + ISalesEstimatesService, + IPaymentsReceiveService, } from 'interfaces'; import { ServiceError } from 'exceptions'; import TenancyService from 'services/Tenancy/TenancyService'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import events from 'subscribers/events'; -import moment from 'moment'; +const ERRORS = { + CUSTOMER_HAS_TRANSACTIONS: 'CUSTOMER_HAS_TRANSACTIONS', +}; @Service() export default class CustomersService { @Inject() @@ -42,6 +49,18 @@ export default class CustomersService { @EventDispatcher() eventDispatcher: EventDispatcherInterface; + @Inject('SalesInvoices') + invoicesService: ISalesInvoicesService; + + @Inject('SalesReceipts') + receiptsService: ISalesReceiptsService; + + @Inject('PaymentReceives') + paymentsService: IPaymentsReceiveService; + + @Inject('SalesEstimates') + estimatesService: ISalesEstimatesService; + /** * Converts customer to contact DTO. * @param {ICustomerNewDTO|ICustomerEditDTO} customerDTO @@ -158,6 +177,40 @@ export default class CustomersService { return customer; } + /** + * Validate the customer associated relations. + * @param {number} tenantId + * @param {number} customerId - Customer id. + */ + private async validateCustomerAssociatedRelations( + tenantId: number, + customerId: number + ) { + try { + // Validate whether the customer has no associated estimates transactions. + await this.estimatesService.validateCustomerHasNoEstimates( + tenantId, + customerId + ); + // Validate whether the customer has no assocaited invoices tranasctions. + await this.invoicesService.validateCustomerHasNoInvoices( + tenantId, + customerId + ); + // Validate whether the customer has no associated receipts transactions. + await this.receiptsService.validateCustomerHasNoReceipts( + tenantId, + customerId + ); + // Validate whether the customer has no associated payment receives transactions. + await this.paymentsService.validateCustomerHasNoPayments( + tenantId, + customerId + ); + } catch (error) { + throw new ServiceError(ERRORS.CUSTOMER_HAS_TRANSACTIONS); + } + } /** * Deletes the given customer from the storage. * @param {number} tenantId @@ -176,9 +229,10 @@ export default class CustomersService { // Retrieve the customer of throw not found service error. await this.getCustomerByIdOrThrowError(tenantId, customerId); - // Validate whether the customer has no assocaited invoices tranasctions. - await this.customerHasNoInvoicesOrThrowError(tenantId, customerId); + // Validate the customer associated relations. + await this.validateCustomerAssociatedRelations(tenantId, customerId); + // Delete the customer from the storage. await this.contactService.deleteContact(tenantId, customerId, 'customer'); // Throws `onCustomerDeleted` event. @@ -225,12 +279,13 @@ export default class CustomersService { filterMeta: IFilterMeta; }> { const { Customer } = this.tenancy.models(tenantId); + + // Dynamic list. const dynamicList = await this.dynamicListService.dynamicList( tenantId, Customer, customersFilter ); - const { results, pagination } = await Customer.query() .onBuild((query) => { dynamicList.buildQuery()(query); @@ -310,104 +365,6 @@ export default class CustomersService { ); } - /** - * Retrieve the given customers or throw error if one of them not found. - * @param {numebr} tenantId - * @param {number[]} customersIds - */ - private getCustomersOrThrowErrorNotFound( - tenantId: number, - customersIds: number[] - ) { - return this.contactService.getContactsOrThrowErrorNotFound( - tenantId, - customersIds, - 'customer' - ); - } - - /** - * Deletes the given customers from the storage. - * @param {number} tenantId - * @param {number[]} customersIds - * @return {Promise} - */ - public async deleteBulkCustomers( - tenantId: number, - customersIds: number[], - authorizedUser: ISystemUser, - ): Promise { - const { Contact } = this.tenancy.models(tenantId); - - // Validate the customers existance on the storage. - await this.getCustomersOrThrowErrorNotFound(tenantId, customersIds); - - // Validate the customers have no associated invoices. - await this.customersHaveNoInvoicesOrThrowError(tenantId, customersIds); - - // Deletes the given customers. - await Contact.query().whereIn('id', customersIds).delete(); - - // Triggers `onCustomersBulkDeleted` event. - await this.eventDispatcher.dispatch(events.customers.onBulkDeleted, { - tenantId, - customersIds, - authorizedUser, - }); - } - - /** - * Validates the customer has no associated sales invoice - * or throw service error. - * @param {number} tenantId - * @param {number} customerId - * @throws {ServiceError} - * @return {Promise} - */ - private async customerHasNoInvoicesOrThrowError( - tenantId: number, - customerId: number - ) { - const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); - - // Retrieve the sales invoices that assocaited to the given customer. - const salesInvoice = await saleInvoiceRepository.find({ - customer_id: customerId, - }); - if (salesInvoice.length > 0) { - throw new ServiceError('customer_has_invoices'); - } - } - - /** - * Throws error in case one of customers have associated sales invoices. - * @param {number} tenantId - * @param {number[]} customersIds - * @throws {ServiceError} - * @return {Promise} - */ - private async customersHaveNoInvoicesOrThrowError( - tenantId: number, - customersIds: number[] - ) { - const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); - - const customersInvoices = await saleInvoiceRepository.findWhereIn( - 'customer_id', - customersIds - ); - const customersIdsWithInvoice = customersInvoices.map( - (saleInvoice: ISaleInvoice) => saleInvoice.customerId - ); - const customersHaveInvoices = intersection( - customersIds, - customersIdsWithInvoice - ); - if (customersHaveInvoices.length > 0) { - throw new ServiceError('some_customers_have_invoices'); - } - } - /** * Changes the opening balance of the given customer. * @param {number} tenantId diff --git a/server/src/services/Contacts/VendorsService.ts b/server/src/services/Contacts/VendorsService.ts index ada57f3d2..6efa0f939 100644 --- a/server/src/services/Contacts/VendorsService.ts +++ b/server/src/services/Contacts/VendorsService.ts @@ -15,12 +15,18 @@ import { IPaginationMeta, IFilterMeta, ISystemUser, + IBillsService, + IBillPaymentsService, } from 'interfaces'; import { ServiceError } from 'exceptions'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import TenancyService from 'services/Tenancy/TenancyService'; import events from 'subscribers/events'; +const ERRORS = { + VENDOR_HAS_TRANSACTIONS: 'VENDOR_HAS_TRANSACTIONS', +}; + @Service() export default class VendorsService { @Inject() @@ -38,6 +44,12 @@ export default class VendorsService { @Inject('logger') logger: any; + @Inject('Bills') + billsService: IBillsService; + + @Inject('BillPayments') + billPaymentsService: IBillPaymentsService; + /** * Converts vendor to contact DTO. * @param {IVendorNewDTO|IVendorEditDTO} vendorDTO @@ -124,6 +136,27 @@ export default class VendorsService { ); } + /** + * Validate the given vendor has no associated transactions. + * @param {number} tenantId + * @param {number} vendorId + */ + private async validateAssociatedTransactions( + tenantId: number, + vendorId: number + ) { + try { + // Validate vendor has no bills. + await this.billsService.validateVendorHasNoBills(tenantId, vendorId); + + // Validate vendor has no paymentys. + await this.billPaymentsService.validateVendorHasNoPayments(tenantId, vendorId); + + } catch (error) { + throw new ServiceError(ERRORS.VENDOR_HAS_TRANSACTIONS); + } + } + /** * Deletes the given vendor from the storage. * @param {number} tenantId @@ -138,8 +171,8 @@ export default class VendorsService { // Validate the vendor existance on the storage. await this.getVendorByIdOrThrowError(tenantId, vendorId); - // Validate the vendor has no associated bills. - await this.vendorHasNoBillsOrThrowError(tenantId, vendorId); + // Validate associated vendor transactions. + await this.validateAssociatedTransactions(tenantId, vendorId); this.logger.info('[vendor] trying to delete vendor.', { tenantId, @@ -222,102 +255,6 @@ export default class VendorsService { ); } - /** - * Retrieve the given vendors or throw error if one of them not found. - * @param {numebr} tenantId - * @param {number[]} vendorsIds - */ - private getVendorsOrThrowErrorNotFound( - tenantId: number, - vendorsIds: number[] - ) { - return this.contactService.getContactsOrThrowErrorNotFound( - tenantId, - vendorsIds, - 'vendor' - ); - } - - /** - * Deletes the given vendors from the storage. - * @param {number} tenantId - * @param {number[]} vendorsIds - * @return {Promise} - */ - public async deleteBulkVendors( - tenantId: number, - vendorsIds: number[], - authorizedUser: ISystemUser - ): Promise { - const { Contact } = this.tenancy.models(tenantId); - - // Validate the given vendors exists on the storage. - await this.getVendorsOrThrowErrorNotFound(tenantId, vendorsIds); - - // Validate the given vendors have no assocaited bills. - await this.vendorsHaveNoBillsOrThrowError(tenantId, vendorsIds); - - await Contact.query().whereIn('id', vendorsIds).delete(); - - // Triggers `onVendorsBulkDeleted` event. - await this.eventDispatcher.dispatch(events.vendors.onBulkDeleted, { - tenantId, - vendorsIds, - authorizedUser, - }); - - this.logger.info('[vendor] bulk deleted successfully.', { - tenantId, - vendorsIds, - }); - } - - /** - * Validates the vendor has no associated bills or throw service error. - * @param {number} tenantId - * @param {number} vendorId - */ - private async vendorHasNoBillsOrThrowError( - tenantId: number, - vendorId: number - ) { - const { billRepository } = this.tenancy.repositories(tenantId); - - // Retrieve the bill that associated to the given vendor id. - const bills = await billRepository.find({ vendor_id: vendorId }); - - if (bills.length > 0) { - throw new ServiceError('vendor_has_bills'); - } - } - - /** - * Throws error in case one of vendors have associated bills. - * @param {number} tenantId - * @param {number[]} customersIds - * @throws {ServiceError} - */ - private async vendorsHaveNoBillsOrThrowError( - tenantId: number, - vendorsIds: number[] - ) { - const { billRepository } = this.tenancy.repositories(tenantId); - - // Retrieves bills that assocaited to the given vendors. - const vendorsBills = await billRepository.findWhereIn( - 'vendor_id', - vendorsIds - ); - const billsVendorsIds = vendorsBills.map((bill) => bill.vendorId); - - // The intersection between vendors and vendors that have bills. - const vendorsHaveInvoices = intersection(vendorsIds, billsVendorsIds); - - if (vendorsHaveInvoices.length > 0) { - throw new ServiceError('some_vendors_have_bills'); - } - } - /** * Retrieve vendors datatable list. * @param {number} tenantId - Tenant id. diff --git a/server/src/services/Purchases/BillPayments/BillPayments.ts b/server/src/services/Purchases/BillPayments/BillPayments.ts index 3b8ada9b2..7e17fb66f 100644 --- a/server/src/services/Purchases/BillPayments/BillPayments.ts +++ b/server/src/services/Purchases/BillPayments/BillPayments.ts @@ -33,8 +33,8 @@ import { ERRORS } from './constants'; * Bill payments service. * @service */ -@Service() -export default class BillPaymentsService { +@Service('BillPayments') +export default class BillPaymentsService implements IBillPaymentsService { @Inject() accountsService: AccountsService; @@ -178,7 +178,7 @@ export default class BillPaymentsService { if (notOpenedBills.length > 0) { throw new ServiceError(ERRORS.BILLS_NOT_OPENED_YET, null, { - notOpenedBills + notOpenedBills, }); } } @@ -696,4 +696,19 @@ export default class BillPaymentsService { ); await Promise.all(opers); } + + /** + * Validates the given vendor has no associated payments. + * @param {number} tenantId + * @param {number} vendorId + */ + public async validateVendorHasNoPayments(tenantId: number, vendorId: number) { + const { BillPayment } = this.tenancy.models(tenantId); + + const payments = await BillPayment.query().where('vendor_id', vendorId); + + if (payments.length > 0) { + throw new ServiceError(ERRORS.VENDOR_HAS_PAYMENTS); + } + } } diff --git a/server/src/services/Purchases/BillPayments/constants.ts b/server/src/services/Purchases/BillPayments/constants.ts index 0cf82d0d2..c730ff17f 100644 --- a/server/src/services/Purchases/BillPayments/constants.ts +++ b/server/src/services/Purchases/BillPayments/constants.ts @@ -9,5 +9,6 @@ export const ERRORS = { BILL_PAYMENT_ENTRIES_NOT_FOUND: 'BILL_PAYMENT_ENTRIES_NOT_FOUND', INVALID_BILL_PAYMENT_AMOUNT: 'INVALID_BILL_PAYMENT_AMOUNT', PAYMENT_NUMBER_SHOULD_NOT_MODIFY: 'PAYMENT_NUMBER_SHOULD_NOT_MODIFY', - BILLS_NOT_OPENED_YET: 'BILLS_NOT_OPENED_YET' + BILLS_NOT_OPENED_YET: 'BILLS_NOT_OPENED_YET', + VENDOR_HAS_PAYMENTS: 'VENDOR_HAS_PAYMENTS' }; diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts index 6c239623d..8b5548d08 100644 --- a/server/src/services/Purchases/Bills.ts +++ b/server/src/services/Purchases/Bills.ts @@ -21,6 +21,7 @@ import { IPaginationMeta, IFilterMeta, IBillsFilter, + IBillsService } from 'interfaces'; import { ServiceError } from 'exceptions'; import ItemsService from 'services/Items/ItemsService'; @@ -34,8 +35,8 @@ import { ERRORS } from './constants'; * Vendor bills services. * @service */ -@Service() -export default class BillsService extends SalesInvoicesCost { +@Service('Bills') +export default class BillsService extends SalesInvoicesCost implements IBillsService { @Inject() inventoryService: InventoryService; @@ -141,13 +142,10 @@ export default class BillsService extends SalesInvoicesCost { /** * Validate the bill has no payment entries. - * @param {number} tenantId + * @param {number} tenantId * @param {number} billId - Bill id. */ - private async validateBillHasNoEntries( - tenantId, - billId: number, - ) { + private async validateBillHasNoEntries(tenantId, billId: number) { const { BillPaymentEntry } = this.tenancy.models(tenantId); // Retireve the bill associate payment made entries. @@ -578,4 +576,22 @@ export default class BillsService extends SalesInvoicesCost { 'Bill' ); } + + /** + * Validate the given vendor has no associated bills transactions. + * @param {number} tenantId + * @param {number} vendorId - Vendor id. + */ + public async validateVendorHasNoBills( + tenantId: number, + vendorId: number + ) { + const { Bill } = this.tenancy.models(tenantId); + + const bills = await Bill.query().where('vendor_id', vendorId); + + if (bills.length > 0) { + throw new ServiceError(ERRORS.VENDOR_HAS_BILLS); + } + } } diff --git a/server/src/services/Purchases/constants.ts b/server/src/services/Purchases/constants.ts index b0d0d87e4..09b31979d 100644 --- a/server/src/services/Purchases/constants.ts +++ b/server/src/services/Purchases/constants.ts @@ -8,5 +8,6 @@ export const ERRORS = { NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS', BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN', BILL_NO_IS_REQUIRED: 'BILL_NO_IS_REQUIRED', - BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES: 'BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES' + BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES: 'BILL_HAS_ASSOCIATED_PAYMENT_ENTRIES', + VENDOR_HAS_BILLS: 'VENDOR_HAS_BILLS' }; diff --git a/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts b/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts index 07977f5fe..069d2a1b4 100644 --- a/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts +++ b/server/src/services/Sales/PaymentReceives/PaymentsReceives.ts @@ -15,6 +15,7 @@ import { IPaymentReceiveEntry, IPaymentReceiveEntryDTO, IPaymentReceivesFilter, + IPaymentsReceiveService, ISaleInvoice, ISystemUser, } from 'interfaces'; @@ -37,8 +38,8 @@ import { ERRORS } from './constants'; * Payment receive service. * @service */ -@Service() -export default class PaymentReceiveService { +@Service('PaymentReceives') +export default class PaymentReceiveService implements IPaymentsReceiveService { @Inject() accountsService: AccountsService; @@ -522,7 +523,7 @@ export default class PaymentReceiveService { public async deletePaymentReceive( tenantId: number, paymentReceiveId: number, - authorizedUser: ISystemUser, + authorizedUser: ISystemUser ) { const { PaymentReceive, PaymentReceiveEntry } = this.tenancy.models( tenantId @@ -772,4 +773,24 @@ export default class PaymentReceiveService { }); await Promise.all([...opers]); } + + /** + * Validate the given customer has no payments receives. + * @param {number} tenantId + * @param {number} customerId - Customer id. + */ + public async validateCustomerHasNoPayments( + tenantId: number, + customerId: number + ) { + const { PaymentReceive } = this.tenancy.models(tenantId); + + const paymentReceives = await PaymentReceive.query().where( + 'customer_id', + customerId + ); + if (paymentReceives.length > 0) { + throw new ServiceError(ERRORS.CUSTOMER_HAS_PAYMENT_RECEIVES); + } + } } diff --git a/server/src/services/Sales/PaymentReceives/constants.ts b/server/src/services/Sales/PaymentReceives/constants.ts index d17c64d0a..c1eceb7a7 100644 --- a/server/src/services/Sales/PaymentReceives/constants.ts +++ b/server/src/services/Sales/PaymentReceives/constants.ts @@ -10,4 +10,5 @@ export const ERRORS = { PAYMENT_RECEIVE_NO_IS_REQUIRED: 'PAYMENT_RECEIVE_NO_IS_REQUIRED', PAYMENT_RECEIVE_NO_REQUIRED: 'PAYMENT_RECEIVE_NO_REQUIRED', PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE: 'PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE', + CUSTOMER_HAS_PAYMENT_RECEIVES: 'CUSTOMER_HAS_PAYMENT_RECEIVES' }; diff --git a/server/src/services/Sales/SalesEstimate.ts b/server/src/services/Sales/SalesEstimate.ts index b4022bdd2..0b6b81f96 100644 --- a/server/src/services/Sales/SalesEstimate.ts +++ b/server/src/services/Sales/SalesEstimate.ts @@ -6,6 +6,7 @@ import { IPaginationMeta, ISaleEstimate, ISaleEstimateDTO, + ISalesEstimatesService, } from 'interfaces'; import { EventDispatcher, @@ -32,14 +33,15 @@ const ERRORS = { SALE_ESTIMATE_ALREADY_APPROVED: 'SALE_ESTIMATE_ALREADY_APPROVED', SALE_ESTIMATE_NOT_DELIVERED: 'SALE_ESTIMATE_NOT_DELIVERED', SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED', + CUSTOMER_HAS_SALES_ESTIMATES: 'CUSTOMER_HAS_SALES_ESTIMATES', }; /** * Sale estimate service. * @Service */ -@Service() -export default class SaleEstimateService { +@Service('SalesEstimates') +export default class SaleEstimateService implements ISalesEstimatesService{ @Inject() tenancy: TenancyService; @@ -174,7 +176,8 @@ export default class SaleEstimateService { const autoNextNumber = this.getNextEstimateNumber(tenantId); // Retreive the next estimate number. - const estimateNumber = estimateDTO.estimateNumber || + const estimateNumber = + estimateDTO.estimateNumber || oldSaleEstimate?.estimateNumber || autoNextNumber; @@ -201,9 +204,9 @@ export default class SaleEstimateService { })), // Avoid rewrite the deliver date in edit mode when already published. ...(estimateDTO.delivered && - !oldSaleEstimate?.deliveredAt && { - deliveredAt: moment().toMySqlDateTime(), - }), + !oldSaleEstimate?.deliveredAt && { + deliveredAt: moment().toMySqlDateTime(), + }), }; } @@ -233,10 +236,7 @@ export default class SaleEstimateService { this.logger.info('[sale_estimate] inserting sale estimate to the storage.'); // Transform DTO object ot model object. - const estimateObj = await this.transformDTOToModel( - tenantId, - estimateDTO - ); + const estimateObj = await this.transformDTOToModel(tenantId, estimateDTO); // Validate estimate number uniquiness on the storage. await this.validateEstimateNumberExistance( tenantId, @@ -583,4 +583,24 @@ export default class SaleEstimateService { approvedAt: null, }); } + + /** + * Validate the given customer has no sales estimates. + * @param {number} tenantId + * @param {number} customerId - Customer id. + */ + public async validateCustomerHasNoEstimates( + tenantId: number, + customerId: number + ) { + const { SaleEstimate } = this.tenancy.models(tenantId); + + const estimates = await SaleEstimate.query().where( + 'customer_id', + customerId + ); + if (estimates.length > 0) { + throw new ServiceError(ERRORS.CUSTOMER_HAS_SALES_ESTIMATES); + } + } } diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index 487a63377..e791aad74 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -15,6 +15,7 @@ import { ISystemUser, IItem, IItemEntry, + ISalesInvoicesService } from 'interfaces'; import JournalPoster from 'services/Accounting/JournalPoster'; import JournalCommands from 'services/Accounting/JournalCommands'; @@ -36,8 +37,8 @@ import { ERRORS } from './constants'; * Sales invoices service * @service */ -@Service() -export default class SaleInvoicesService { +@Service('SalesInvoices') +export default class SaleInvoicesService implements ISalesInvoicesService { @Inject() tenancy: TenancyService; @@ -665,4 +666,22 @@ export default class SaleInvoicesService { return salesInvoices; } + + /** + * Validate the given customer has no sales invoices. + * @param {number} tenantId + * @param {number} customerId - Customer id. + */ + public async validateCustomerHasNoInvoices( + tenantId: number, + customerId: number + ) { + const { SaleInvoice } = this.tenancy.models(tenantId); + + const invoices = await SaleInvoice.query().where('customer_id', customerId); + + if (invoices.length > 0) { + throw new ServiceError(ERRORS.CUSTOMER_HAS_SALES_INVOICES); + } + } } diff --git a/server/src/services/Sales/SalesReceipts.ts b/server/src/services/Sales/SalesReceipts.ts index 25360d6ca..ae4a8a934 100644 --- a/server/src/services/Sales/SalesReceipts.ts +++ b/server/src/services/Sales/SalesReceipts.ts @@ -8,11 +8,18 @@ import { import events from 'subscribers/events'; import JournalPoster from 'services/Accounting/JournalPoster'; import JournalCommands from 'services/Accounting/JournalCommands'; -import { ISaleReceipt, ISaleReceiptDTO, IItemEntry, IItem } from 'interfaces'; +import { + IFilterMeta, + IPaginationMeta, + ISaleReceipt, + ISaleReceiptDTO, + ISalesReceiptsService, + IItemEntry, + IItem, +} from 'interfaces'; import JournalPosterService from 'services/Sales/JournalPosterService'; import TenancyService from 'services/Tenancy/TenancyService'; import { formatDateFields } from 'utils'; -import { IFilterMeta, IPaginationMeta } from 'interfaces'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; import { ServiceError } from 'exceptions'; import ItemsEntriesService from 'services/Items/ItemsEntriesService'; @@ -29,10 +36,11 @@ const ERRORS = { SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE', SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED', SALE_RECEIPT_NO_IS_REQUIRED: 'SALE_RECEIPT_NO_IS_REQUIRED', + CUSTOMER_HAS_SALES_INVOICES: 'CUSTOMER_HAS_SALES_INVOICES', }; -@Service() -export default class SalesReceiptService { +@Service('SalesReceipts') +export default class SalesReceiptService implements ISalesReceiptsService { @Inject() tenancy: TenancyService; @@ -548,4 +556,22 @@ export default class SalesReceiptService { 'SaleReceipt' ); } + + /** + * Validate the given customer has no sales receipts. + * @param {number} tenantId + * @param {number} customerId - Customer id. + */ + public async validateCustomerHasNoReceipts( + tenantId: number, + customerId: number + ) { + const { SaleReceipt } = this.tenancy.models(tenantId); + + const receipts = await SaleReceipt.query().where('customer_id', customerId); + + if (receipts.length > 0) { + throw new ServiceError(ERRORS.CUSTOMER_HAS_SALES_INVOICES); + } + } } diff --git a/server/src/services/Sales/constants.ts b/server/src/services/Sales/constants.ts index a17e6e85a..e1e596dfb 100644 --- a/server/src/services/Sales/constants.ts +++ b/server/src/services/Sales/constants.ts @@ -9,5 +9,6 @@ export const ERRORS = { 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT', INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES: 'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES', - SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED' + SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED', + CUSTOMER_HAS_SALES_INVOICES: 'CUSTOMER_HAS_SALES_INVOICES' };