fix: delete customer with associated opening journal entries.

This commit is contained in:
a.bouhuolia
2021-01-02 13:17:43 +02:00
parent b5a849abda
commit db70d04743
6 changed files with 70 additions and 38 deletions

View File

@@ -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(),
];
}

View File

@@ -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));
}

View File

@@ -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()]);

View File

@@ -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<void>}
*/
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<void>}
*/
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');
}

View File

@@ -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);

View File

@@ -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 }) {