diff --git a/server/src/api/controllers/Contacts/Contacts.ts b/server/src/api/controllers/Contacts/Contacts.ts index cd3cd08bc..8666d19c1 100644 --- a/server/src/api/controllers/Contacts/Contacts.ts +++ b/server/src/api/controllers/Contacts/Contacts.ts @@ -212,7 +212,7 @@ export default class ContactsController extends BaseController { */ get bulkContactsSchema(): ValidationChain[] { return [ - query('ids').isArray({ min: 2 }), + query('ids').isArray({ min: 1 }), query('ids.*').isNumeric().toInt(), ]; } diff --git a/server/src/loaders/tenantModels.ts b/server/src/loaders/tenantModels.ts index 0babf0eed..887f919c7 100644 --- a/server/src/loaders/tenantModels.ts +++ b/server/src/loaders/tenantModels.ts @@ -10,6 +10,7 @@ import Bill from 'models/Bill'; import BillPayment from 'models/BillPayment'; import BillPaymentEntry from 'models/BillPaymentEntry'; import Currency from 'models/Currency'; +import Contact from 'models/Contact'; import Vendor from 'models/Vendor'; import Customer from 'models/Customer'; import ExchangeRate from 'models/ExchangeRate'; @@ -69,6 +70,7 @@ export default (knex) => { MediaLink, Vendor, Customer, + Contact, }; return mapValues(models, (model) => model.bindKnex(knex)); } \ No newline at end of file diff --git a/server/src/services/Contacts/ContactsService.ts b/server/src/services/Contacts/ContactsService.ts index c249766a1..e398bcc2f 100644 --- a/server/src/services/Contacts/ContactsService.ts +++ b/server/src/services/Contacts/ContactsService.ts @@ -182,14 +182,14 @@ export default class ContactsService { async getContactsOrThrowErrorNotFound( tenantId: number, contactsIds: number[], - contactService: TContactService + contactService: TContactService, ) { const { Contact } = this.tenancy.models(tenantId); const contacts = await Contact.query() .whereIn('id', contactsIds) .where('contact_service', contactService); - const storedContactsIds = contacts.map((contact: IContact) => contact.id); + const storedContactsIds = contacts.map((contact: IContact) => contact.id); const notFoundCustomers = difference(contactsIds, storedContactsIds); if (notFoundCustomers.length > 0) { @@ -211,6 +211,8 @@ export default class ContactsService { contactService: TContactService ) { const { contactRepository } = this.tenancy.repositories(tenantId); + + // Retrieve the given contacts or throw not found service error. this.getContactsOrThrowErrorNotFound(tenantId, contactsIds, contactService); await contactRepository.deleteWhereIdIn(contactsIds); @@ -230,11 +232,12 @@ export default class ContactsService { const { AccountTransaction } = this.tenancy.models(tenantId); const journal = new JournalPoster(tenantId); + // Loads the contact opening balance journal transactions. const contactsTransactions = await AccountTransaction.query() .whereIn('reference_id', contactsIds) .where('reference_type', `${upperFirst(contactService)}OpeningBalance`); - journal.loadEntries(contactsTransactions); + journal.fromTransactions(contactsTransactions); journal.removeEntries(); await Promise.all([journal.saveBalance(), journal.deleteEntries()]); diff --git a/server/src/services/Contacts/CustomersService.ts b/server/src/services/Contacts/CustomersService.ts index 730b8aa52..d2a49b5d2 100644 --- a/server/src/services/Contacts/CustomersService.ts +++ b/server/src/services/Contacts/CustomersService.ts @@ -1,5 +1,5 @@ import { Inject, Service } from 'typedi'; -import { omit, difference, defaultTo } from 'lodash'; +import { omit, intersection, defaultTo } from 'lodash'; import { EventDispatcher, EventDispatcherInterface, @@ -71,6 +71,10 @@ export default class CustomersService { }; } + /** + * Transforms the contact model to customer model. + * @param {IContact} contactModel + */ private transformContactToCustomer(contactModel: IContact) { return { ...omit(contactModel.toJSON(), ['contactService', 'contactType']), @@ -172,6 +176,7 @@ export default class CustomersService { await this.contactService.deleteContact(tenantId, customerId, 'customer'); + // Throws `onCustomerDeleted` event. await this.eventDispatcher.dispatch(events.customers.onDeleted, { tenantId, customerId, @@ -182,29 +187,6 @@ export default class CustomersService { }); } - /** - * Reverts customer opening balance journal entries. - * @param {number} tenantId - - * @param {number} customerId - - * @return {Promise} - */ - 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 } - ); - await this.contactService.revertJEntriesContactsOpeningBalance( - tenantId, - id, - 'customer' - ); - } - /** * Retrieve the given customer details. * @param {number} tenantId @@ -262,16 +244,43 @@ export default class CustomersService { public async writeCustomerOpeningBalanceJournal( tenantId: number, customerId: number, - openingBalance: number + openingBalance: number, + openingBalanceAt: Date | string ) { const journal = new JournalPoster(tenantId); const journalCommands = new JournalCommands(journal); - await journalCommands.customerOpeningBalance(customerId, openingBalance); - + await journalCommands.customerOpeningBalance( + customerId, + openingBalance, + openingBalanceAt + ); await Promise.all([journal.saveBalance(), journal.saveEntries()]); } + /** + * Reverts customer opening balance journal entries. + * @param {number} tenantId - + * @param {number} customerId - + * @return {Promise} + */ + 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 } + ); + await this.contactService.revertJEntriesContactsOpeningBalance( + tenantId, + id, + 'customer' + ); + } + /** * Retrieve the given customer by id or throw not found. * @param {number} tenantId @@ -310,11 +319,20 @@ export default class CustomersService { public async deleteBulkCustomers(tenantId: number, customersIds: number[]) { 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(); - await this.eventDispatcher.dispatch(events.customers.onBulkDeleted); + + // Triggers `onCustomersBulkDeleted` event. + await this.eventDispatcher.dispatch(events.customers.onBulkDeleted, { + tenantId, + customersIds, + }); } /** @@ -361,12 +379,10 @@ export default class CustomersService { const customersIdsWithInvoice = customersInvoices.map( (saleInvoice: ISaleInvoice) => saleInvoice.customerId ); - - const customersHaveInvoices = difference( + const customersHaveInvoices = intersection( customersIds, customersIdsWithInvoice ); - if (customersHaveInvoices.length > 0) { throw new ServiceError('some_customers_have_invoices'); } diff --git a/server/src/services/Items/ItemsService.ts b/server/src/services/Items/ItemsService.ts index 79157313a..8d2f900f5 100644 --- a/server/src/services/Items/ItemsService.ts +++ b/server/src/services/Items/ItemsService.ts @@ -465,7 +465,7 @@ export default class ItemsService implements IItemsService { const { Item } = this.tenancy.models(tenantId); const storedItems = await Item.query().whereIn('id', itemsIDs); - const storedItemsIds = storedItems.map((t) => t.id); + const storedItemsIds = storedItems.map((t: IItem) => t.id); const notFoundItemsIds = difference(itemsIDs, storedItemsIds); return notFoundItemsIds; @@ -474,7 +474,7 @@ export default class ItemsService implements IItemsService { /** * Deletes items in bulk. * @param {number} tenantId - * @param {number[]} itemsIds + * @param {number[]} itemsIds - Items ids. */ public async bulkDeleteItems(tenantId: number, itemsIds: number[]) { const { Item } = this.tenancy.models(tenantId); diff --git a/server/src/subscribers/customers.ts b/server/src/subscribers/customers.ts index 7eac98037..f2e0b652f 100644 --- a/server/src/subscribers/customers.ts +++ b/server/src/subscribers/customers.ts @@ -1,4 +1,4 @@ -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'; @@ -15,6 +15,9 @@ export default class CustomersSubscriber { this.customersService = Container.get(CustomersService); } + /** + * Handles the writing opening balance journal entries once the customer created. + */ @On(events.customers.onCreated) async handleWriteOpenBalanceEntries({ tenantId, customerId, customer }) { @@ -24,10 +27,14 @@ export default class CustomersSubscriber { tenantId, customer.id, customer.openingBalance, + customer.openingBalanceAt, ); } } + /** + * Handles the deleting opeing balance journal entrise once the customer deleted. + */ @On(events.customers.onDeleted) async handleRevertOpeningBalanceEntries({ tenantId, customerId }) { @@ -36,6 +43,10 @@ export default class CustomersSubscriber { ); } + /** + * Handles the deleting opening balance journal entries once the given + * customers deleted. + */ @On(events.customers.onBulkDeleted) async handleBulkRevertOpeningBalanceEntries({ tenantId, customersIds }) {