fix(Contacts): validate contact associated transcations.

This commit is contained in:
a.bouhuolia
2021-03-22 15:21:52 +02:00
parent 1f6aca63e2
commit d79be910f9
20 changed files with 382 additions and 384 deletions

View File

@@ -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<void>}
*/
public async deleteBulkCustomers(
tenantId: number,
customersIds: number[],
authorizedUser: ISystemUser,
): Promise<void> {
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<void>}
*/
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<void>}
*/
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

View File

@@ -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<void>}
*/
public async deleteBulkVendors(
tenantId: number,
vendorsIds: number[],
authorizedUser: ISystemUser
): Promise<void> {
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.