From b5a849abda182908ccccdf8a09fb99601c18e671 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sat, 2 Jan 2021 11:59:20 +0200 Subject: [PATCH] fix: delete sale invoice issue. --- .../src/api/controllers/Contacts/Customers.ts | 43 +-- .../src/services/Contacts/CustomersService.ts | 246 ++++++++++++------ server/src/services/Sales/SalesInvoices.ts | 32 ++- 3 files changed, 204 insertions(+), 117 deletions(-) diff --git a/server/src/api/controllers/Contacts/Customers.ts b/server/src/api/controllers/Contacts/Customers.ts index c7962111b..5ad76980e 100644 --- a/server/src/api/controllers/Contacts/Customers.ts +++ b/server/src/api/controllers/Contacts/Customers.ts @@ -201,27 +201,6 @@ export default class CustomersController extends ContactsController { } } - /** - * Deletes the given customer from the storage. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next - */ - async deleteCustomer(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; - const { id: contactId } = req.params; - - try { - await this.customersService.deleteCustomer(tenantId, contactId) - return res.status(200).send({ - id: contactId, - message: 'The customer has been deleted successfully.', - }); - } catch (error) { - next(error); - } - } - /** * Retrieve details of the given customer id. * @param {Request} req @@ -243,6 +222,28 @@ export default class CustomersController extends ContactsController { } } + /** + * Deletes the given customer from the storage. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + async deleteCustomer(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + const { id: contactId } = req.params; + + try { + await this.customersService.deleteCustomer(tenantId, contactId); + + return res.status(200).send({ + id: contactId, + message: 'The customer has been deleted successfully.', + }); + } catch (error) { + next(error); + } + } + /** * Deletes customers in bulk. * @param {Request} req diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index 22997c36f..730b8aa52 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -4,10 +4,10 @@ import { EventDispatcher, EventDispatcherInterface, } from 'decorators/eventDispatcher'; -import JournalPoster from "services/Accounting/JournalPoster"; -import JournalCommands from "services/Accounting/JournalCommands"; +import JournalPoster from 'services/Accounting/JournalPoster'; +import JournalCommands from 'services/Accounting/JournalCommands'; import ContactsService from 'services/Contacts/ContactsService'; -import { +import { ICustomerNewDTO, ICustomerEditDTO, ICustomer, @@ -17,7 +17,7 @@ import { IContactEditDTO, IContact, ISaleInvoice, - } from 'interfaces'; +} from 'interfaces'; import { ServiceError } from 'exceptions'; import TenancyService from 'services/Tenancy/TenancyService'; import DynamicListingService from 'services/DynamicListing/DynamicListService'; @@ -43,11 +43,11 @@ export default class CustomersService { /** * Converts customer to contact DTO. - * @param {ICustomerNewDTO|ICustomerEditDTO} customerDTO + * @param {ICustomerNewDTO|ICustomerEditDTO} customerDTO * @returns {IContactDTO} */ private customerToContactDTO( - customerDTO: ICustomerNewDTO | ICustomerEditDTO, + customerDTO: ICustomerNewDTO | ICustomerEditDTO ): IContactNewDTO | IContactEditDTO { return { ...omit(customerDTO, ['customerType']), @@ -58,14 +58,17 @@ export default class CustomersService { /** * Transforms new customer DTO to contact. - * @param customerDTO + * @param customerDTO */ - private transformNewCustomerDTO(customerDTO: ICustomerNewDTO): IContactNewDTO { + private transformNewCustomerDTO( + customerDTO: ICustomerNewDTO + ): IContactNewDTO { return { ...this.customerToContactDTO(customerDTO), openingBalanceAt: customerDTO?.openingBalanceAt - ? moment(customerDTO.openingBalanceAt).toMySqlDateTime() : null, - } + ? moment(customerDTO.openingBalanceAt).toMySqlDateTime() + : null, + }; } private transformContactToCustomer(contactModel: IContact) { @@ -77,22 +80,34 @@ export default class CustomersService { /** * Creates a new customer. - * @param {number} tenantId - * @param {ICustomerNewDTO} customerDTO + * @param {number} tenantId + * @param {ICustomerNewDTO} customerDTO * @return {Promise} */ public async newCustomer( tenantId: number, customerDTO: ICustomerNewDTO ): Promise { - this.logger.info('[customer] trying to create a new customer.', { tenantId, customerDTO }); + this.logger.info('[customer] trying to create a new customer.', { + tenantId, + customerDTO, + }); const customerObj = this.transformNewCustomerDTO(customerDTO); - const customer = await this.contactService.newContact(tenantId, customerObj, 'customer'); + const customer = await this.contactService.newContact( + tenantId, + customerObj, + 'customer' + ); - this.logger.info('[customer] created successfully.', { tenantId, customerDTO }); + this.logger.info('[customer] created successfully.', { + tenantId, + customerDTO, + }); await this.eventDispatcher.dispatch(events.customers.onCreated, { - customer, tenantId, customerId: customer.id, + customer, + tenantId, + customerId: customer.id, }); return customer; @@ -100,24 +115,35 @@ export default class CustomersService { /** * Edits details of the given customer. - * @param {number} tenantId + * @param {number} tenantId * @param {number} customerId - * @param {ICustomerEditDTO} customerDTO + * @param {ICustomerEditDTO} customerDTO * @return {Promise} */ public async editCustomer( tenantId: number, customerId: number, - customerDTO: ICustomerEditDTO, + customerDTO: ICustomerEditDTO ): Promise { const contactDTO = this.customerToContactDTO(customerDTO); - this.logger.info('[customer] trying to edit customer.', { tenantId, customerId, customerDTO }); - const customer = this.contactService.editContact(tenantId, customerId, contactDTO, 'customer'); + this.logger.info('[customer] trying to edit customer.', { + tenantId, + customerId, + customerDTO, + }); + const customer = this.contactService.editContact( + tenantId, + customerId, + contactDTO, + 'customer' + ); this.eventDispatcher.dispatch(events.customers.onEdited); this.logger.info('[customer] edited successfully.', { - tenantId, customerId, customer, + tenantId, + customerId, + customer, }); return customer; @@ -125,20 +151,35 @@ export default class CustomersService { /** * Deletes the given customer from the storage. - * @param {number} tenantId - * @param {number} customerId + * @param {number} tenantId + * @param {number} customerId * @return {Promise} */ - public async deleteCustomer(tenantId: number, customerId: number): Promise { - this.logger.info('[customer] trying to delete customer.', { tenantId, customerId }); + public async deleteCustomer( + tenantId: number, + customerId: number + ): Promise { + this.logger.info('[customer] trying to delete customer.', { + tenantId, + customerId, + }); + // 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); await this.contactService.deleteContact(tenantId, customerId, 'customer'); - await this.eventDispatcher.dispatch(events.customers.onDeleted, { tenantId, customerId }); - this.logger.info('[customer] deleted successfully.', { tenantId, customerId }); + await this.eventDispatcher.dispatch(events.customers.onDeleted, { + tenantId, + customerId, + }); + this.logger.info('[customer] deleted successfully.', { + tenantId, + customerId, + }); } /** @@ -147,22 +188,34 @@ export default class CustomersService { * @param {number} customerId - * @return {Promise} */ - public async revertOpeningBalanceEntries(tenantId: number, customerId: number|number[]) { + public async revertOpeningBalanceEntries( + tenantId: number, + customerId: number | number[] + ) { const id = Array.isArray(customerId) ? customerId : [customerId]; - this.logger.info('[customer] trying to revert opening balance journal entries.', { tenantId, customerId }); + this.logger.info( + '[customer] trying to revert opening balance journal entries.', + { tenantId, customerId } + ); await this.contactService.revertJEntriesContactsOpeningBalance( - tenantId, id, 'customer', + tenantId, + id, + 'customer' ); } /** * Retrieve the given customer details. - * @param {number} tenantId - * @param {number} customerId + * @param {number} tenantId + * @param {number} customerId */ public async getCustomer(tenantId: number, customerId: number) { - const contact = await this.contactService.getContact(tenantId, customerId, 'customer'); + const contact = await this.contactService.getContact( + tenantId, + customerId, + 'customer' + ); return this.transformContactToCustomer(contact); } @@ -175,20 +228,23 @@ export default class CustomersService { tenantId: number, customersFilter: ICustomersFilter ): Promise<{ - customers: ICustomer[], - pagination: IPaginationMeta, - filterMeta: IFilterMeta, + customers: ICustomer[]; + pagination: IPaginationMeta; + filterMeta: IFilterMeta; }> { const { Customer } = this.tenancy.models(tenantId); - const dynamicList = await this.dynamicListService.dynamicList(tenantId, Customer, customersFilter); - - const { results, pagination } = await Customer.query().onBuild((query) => { - dynamicList.buildQuery()(query); - }).pagination( - customersFilter.page - 1, - customersFilter.pageSize, + const dynamicList = await this.dynamicListService.dynamicList( + tenantId, + Customer, + customersFilter ); + const { results, pagination } = await Customer.query() + .onBuild((query) => { + dynamicList.buildQuery()(query); + }) + .pagination(customersFilter.page - 1, customersFilter.pageSize); + return { customers: results.map(this.transformContactToCustomer), pagination, @@ -198,49 +254,57 @@ export default class CustomersService { /** * Writes customer opening balance journal entries. - * @param {number} tenantId - * @param {number} customerId - * @param {number} openingBalance + * @param {number} tenantId + * @param {number} customerId + * @param {number} openingBalance * @return {Promise} */ public async writeCustomerOpeningBalanceJournal( tenantId: number, customerId: number, - openingBalance: number, + openingBalance: number ) { const journal = new JournalPoster(tenantId); const journalCommands = new JournalCommands(journal); - await journalCommands.customerOpeningBalance(customerId, openingBalance) + await journalCommands.customerOpeningBalance(customerId, openingBalance); - await Promise.all([ - journal.saveBalance(), - journal.saveEntries(), - ]); + await Promise.all([journal.saveBalance(), journal.saveEntries()]); } /** * Retrieve the given customer by id or throw not found. - * @param {number} tenantId - * @param {number} customerId + * @param {number} tenantId + * @param {number} customerId */ public getCustomerByIdOrThrowError(tenantId: number, customerId: number) { - return this.contactService.getContactByIdOrThrowError(tenantId, customerId, 'customer'); + return this.contactService.getContactByIdOrThrowError( + tenantId, + customerId, + 'customer' + ); } /** * Retrieve the given customers or throw error if one of them not found. - * @param {numebr} tenantId + * @param {numebr} tenantId * @param {number[]} customersIds */ - private getCustomersOrThrowErrorNotFound(tenantId: number, customersIds: number[]) { - return this.contactService.getContactsOrThrowErrorNotFound(tenantId, customersIds, 'customer'); + 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 + * @param {number} tenantId + * @param {number[]} customersIds * @return {Promise} */ public async deleteBulkCustomers(tenantId: number, customersIds: number[]) { @@ -256,16 +320,21 @@ export default class CustomersService { /** * Validates the customer has no associated sales invoice * or throw service error. - * @param {number} tenantId - * @param {number} customerId + * @param {number} tenantId + * @param {number} customerId * @throws {ServiceError} * @return {Promise} */ - private async customerHasNoInvoicesOrThrowError(tenantId: number, customerId: number) { + 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 }); + const salesInvoice = await saleInvoiceRepository.find({ + customer_id: customerId, + }); if (salesInvoice.length > 0) { throw new ServiceError('customer_has_invoices'); @@ -274,21 +343,29 @@ export default class CustomersService { /** * Throws error in case one of customers have associated sales invoices. - * @param {number} tenantId - * @param {number[]} customersIds + * @param {number} tenantId + * @param {number[]} customersIds * @throws {ServiceError} * @return {Promise} */ - private async customersHaveNoInvoicesOrThrowError(tenantId: number, customersIds: number[]) { + private async customersHaveNoInvoicesOrThrowError( + tenantId: number, + customersIds: number[] + ) { const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); const customersInvoices = await saleInvoiceRepository.findWhereIn( - 'customer_id', customersIds + 'customer_id', + customersIds + ); + const customersIdsWithInvoice = customersInvoices.map( + (saleInvoice: ISaleInvoice) => saleInvoice.customerId ); - const customersIdsWithInvoice = customersInvoices - .map((saleInvoice: ISaleInvoice) => saleInvoice.customerId); - const customersHaveInvoices = difference(customersIds, customersIdsWithInvoice); + const customersHaveInvoices = difference( + customersIds, + customersIdsWithInvoice + ); if (customersHaveInvoices.length > 0) { throw new ServiceError('some_customers_have_invoices'); @@ -297,28 +374,33 @@ export default class CustomersService { /** * Changes the opening balance of the given customer. - * @param {number} tenantId - * @param {number} customerId - * @param {number} openingBalance - * @param {string|Date} openingBalanceAt + * @param {number} tenantId + * @param {number} customerId + * @param {number} openingBalance + * @param {string|Date} openingBalanceAt */ public async changeOpeningBalance( tenantId: number, customerId: number, openingBalance: number, - openingBalanceAt: Date|string, + openingBalanceAt: Date | string ) { - await this.contactService.changeOpeningBalance( tenantId, customerId, 'customer', openingBalance, - openingBalanceAt, + openingBalanceAt ); // Triggers `onOpeingBalanceChanged` event. - await this.eventDispatcher.dispatch(events.customers.onOpeningBalanceChanged, { - tenantId, customerId, openingBalance, openingBalanceAt - }); + await this.eventDispatcher.dispatch( + events.customers.onOpeningBalanceChanged, + { + tenantId, + customerId, + openingBalance, + openingBalanceAt, + } + ); } } diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index 1a74d94e9..5d502f6a8 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -24,7 +24,6 @@ import ItemsService from 'services/Items/ItemsService'; import ItemsEntriesService from 'services/Items/ItemsEntriesService'; import CustomersService from 'services/Contacts/CustomersService'; import SaleEstimateService from 'services/Sales/SalesEstimate'; -import { PaymentReceiveEntry } from 'models'; const ERRORS = { INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE', @@ -33,7 +32,8 @@ const ERRORS = { ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS', NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS', SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE', - INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES: 'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES', + INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES: + 'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES', }; /** @@ -107,11 +107,12 @@ export default class SaleInvoicesService extends SalesInvoicesCost { * @param {Function} next */ async getInvoiceOrThrowError(tenantId: number, saleInvoiceId: number) { - const { SaleInvoice } = this.tenancy.models(tenantId); - const saleInvoice = await SaleInvoice.query() - .findById(saleInvoiceId) - .withGraphFetched('entries'); + const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); + const saleInvoice = await saleInvoiceRepository.findOneById( + saleInvoiceId, + 'entries' + ); if (!saleInvoice) { throw new ServiceError(ERRORS.SALE_INVOICE_NOT_FOUND); } @@ -223,7 +224,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost { saleInvoiceId: number, saleInvoiceDTO: any ): Promise { - const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId); + const { SaleInvoice } = this.tenancy.models(tenantId); const oldSaleInvoice = await this.getInvoiceOrThrowError( tenantId, @@ -328,7 +329,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost { // Retrieve the sale invoice associated payment receive entries. const entries = await PaymentReceiveEntry.query().where( 'invoice_id', - saleInvoiceId, + saleInvoiceId ); if (entries.length > 0) { throw new ServiceError(ERRORS.INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES); @@ -346,7 +347,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost { tenantId: number, saleInvoiceId: number ): Promise { - const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId); + const { ItemEntry } = this.tenancy.models(tenantId); + const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); // Retrieve the given sale invoice with associated entries or throw not found error. const oldSaleInvoice = await this.getInvoiceOrThrowError( @@ -369,7 +371,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost { ); this.logger.info('[sale_invoice] delete sale invoice with entries.'); - await SaleInvoice.query().where('id', saleInvoiceId).delete(); + await saleInvoiceRepository.deleteById(saleInvoiceId); + await ItemEntry.query() .where('reference_id', saleInvoiceId) .where('reference_type', 'SaleInvoice') @@ -453,7 +456,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost { saleInvoiceId: number, override: boolean = false ): Promise { - const { SaleInvoice } = this.tenancy.models(tenantId); + const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); // Loads the inventory items entries of the given sale invoice. const inventoryEntries = await this.itemsEntriesService.getInventoryEntries( @@ -464,9 +467,10 @@ export default class SaleInvoicesService extends SalesInvoicesCost { // Can't continue if the sale invoice has inventory items entries. if (inventoryEntries.length > 0) return; - const saleInvoice = await SaleInvoice.query() - .findById(saleInvoiceId) - .withGraphFetched('entries.item'); + const saleInvoice = await saleInvoiceRepository.findOneById( + saleInvoiceId, + 'entries.item' + ); await this.writeNonInventoryInvoiceEntries(tenantId, saleInvoice, override); }