feat(sales): currency code associated from invoice customer.

feat(purchases): currency code associated from vendor customer.
This commit is contained in:
a.bouhuolia
2021-03-08 09:47:04 +02:00
parent 6ec4ee4f0f
commit 3a3d881f67
26 changed files with 292 additions and 196 deletions

View File

@@ -63,6 +63,11 @@ export default class ContactsService {
* @param {IContactNewDTO | IContactEditDTO} contactDTO
*/
private transformContactObj(contactDTO: IContactNewDTO | IContactEditDTO) {
const baseCurrency = 'USD';
const currencyCode = typeof contactDTO.currencyCode !== 'undefined'
? contactDTO.currencyCode
: baseCurrency;
return {
...omit(contactDTO, [
'billingAddress1',
@@ -74,6 +79,7 @@ export default class ContactsService {
billing_address_2: contactDTO?.billingAddress2,
shipping_address_1: contactDTO?.shippingAddress1,
shipping_address_2: contactDTO?.shippingAddress2,
...(currencyCode ? ({ currencyCode }) : {}),
};
}
@@ -99,7 +105,6 @@ export default class ContactsService {
contactService,
...contactObj,
});
this.logger.info('[contacts] contact inserted successfully.', {
tenantId,
contact,
@@ -123,12 +128,12 @@ export default class ContactsService {
const { contactRepository } = this.tenancy.repositories(tenantId);
const contactObj = this.transformContactObj(contactDTO);
// Retrieve the given contact by id or throw not found service error.
const contact = await this.getContactByIdOrThrowError(
tenantId,
contactId,
contactService
);
this.logger.info('[contacts] trying to edit the given contact details.', {
tenantId,
contactId,
@@ -196,7 +201,7 @@ export default class ContactsService {
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
Contact,
contactsFilter,
contactsFilter
);
// Retrieve contacts list by the given query.
const contacts = await Contact.query().onBuild((builder) => {

View File

@@ -116,7 +116,7 @@ export default class VendorsService {
* @param {number} tenantId
* @param {number} customerId
*/
private getVendorByIdOrThrowError(tenantId: number, customerId: number) {
public getVendorByIdOrThrowError(tenantId: number, customerId: number) {
return this.contactService.getContactByIdOrThrowError(
tenantId,
customerId,

View File

@@ -54,9 +54,9 @@ export default class InventoryService {
/**
* Computes the given item cost and records the inventory lots transactions
* and journal entries based on the cost method FIFO, LIFO or average cost rate.
* @param {number} tenantId -
* @param {Date} fromDate -
* @param {number} itemId -
* @param {number} tenantId - Tenant id.
* @param {Date} fromDate - From date.
* @param {number} itemId - Item id.
*/
async computeItemCost(tenantId: number, fromDate: Date, itemId: number) {
const { Item } = this.tenancy.models(tenantId);

View File

@@ -27,6 +27,7 @@ import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { entriesAmountDiff, formatDateFields } from 'utils';
import { ServiceError } from 'exceptions';
import { ACCOUNT_PARENT_TYPE } from 'data/AccountTypes';
import VendorsService from 'services/Contacts/VendorsService';
const ERRORS = {
BILL_VENDOR_NOT_FOUND: 'VENDOR_NOT_FOUND',
@@ -38,6 +39,7 @@ const ERRORS = {
BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND',
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',
};
/**
@@ -58,6 +60,9 @@ export default class BillPaymentsService {
@Inject()
dynamicListService: DynamicListingService;
@Inject()
vendorsService: VendorsService;
@EventDispatcher()
eventDispatcher: EventDispatcherInterface;
@@ -118,7 +123,6 @@ export default class BillPaymentsService {
const paymentAccount = await accountRepository.findOneById(
paymentAccountId
);
if (!paymentAccount) {
throw new ServiceError(ERRORS.PAYMENT_ACCOUNT_NOT_FOUND);
}
@@ -263,6 +267,45 @@ export default class BillPaymentsService {
}
}
/**
* * Validate the payment vendor whether modified.
* @param {string} billPaymentNo
*/
validateVendorNotModified(
billPaymentDTO: IBillPaymentDTO,
oldBillPayment: IBillPayment
) {
if (billPaymentDTO.vendorId !== oldBillPayment.vendorId) {
throw new ServiceError(ERRORS.PAYMENT_NUMBER_SHOULD_NOT_MODIFY);
}
}
/**
* Transforms create/edit DTO to model.
* @param {number} tenantId
* @param {IBillPaymentDTO} billPaymentDTO - Bill payment.
* @param {IBillPayment} oldBillPayment - Old bill payment.
* @return {Promise<IBillPayment>}
*/
async transformDTOToModel(
tenantId: number,
billPaymentDTO: IBillPaymentDTO,
oldBillPayment?: IBillPayment
): Promise<IBillPayment> {
// Retrieve vendor details by the given vendor id.
const vendor = await this.vendorsService.getVendorByIdOrThrowError(
tenantId,
billPaymentDTO.vendorId
);
return {
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
currencyCode: vendor.currencyCode,
...formatDateFields(billPaymentDTO, ['paymentDate']),
entries: billPaymentDTO.entries,
};
}
/**
* Creates a new bill payment transcations and store it to the storage
* with associated bills entries and journal transactions.
@@ -288,11 +331,11 @@ export default class BillPaymentsService {
});
const { BillPayment } = this.tenancy.models(tenantId);
const billPaymentObj = {
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
...formatDateFields(billPaymentDTO, ['paymentDate']),
};
// Transform create DTO to model object.
const billPaymentObj = await this.transformDTOToModel(
tenantId,
billPaymentDTO
);
// Validate vendor existance on the storage.
await this.getVendorOrThrowError(tenantId, billPaymentObj.vendorId);
@@ -301,7 +344,6 @@ export default class BillPaymentsService {
tenantId,
billPaymentObj.paymentAccountId
);
// Validate the payment number uniquiness.
if (billPaymentObj.paymentNumber) {
await this.validatePaymentNumber(tenantId, billPaymentObj.paymentNumber);
@@ -312,15 +354,13 @@ export default class BillPaymentsService {
billPaymentObj.entries,
billPaymentDTO.vendorId
);
// Validates the bills due payment amount.
await this.validateBillsDueAmount(tenantId, billPaymentObj.entries);
const billPayment = await BillPayment.query().insertGraphAndFetch({
...omit(billPaymentObj, ['entries']),
entries: billPaymentDTO.entries,
...billPaymentObj,
});
// Triggers `onBillPaymentCreated` event.
await this.eventDispatcher.dispatch(events.billPayment.onCreated, {
tenantId,
billPayment,
@@ -363,11 +403,14 @@ export default class BillPaymentsService {
tenantId,
billPaymentId
);
const billPaymentObj = {
amount: sumBy(billPaymentDTO.entries, 'paymentAmount'),
...formatDateFields(billPaymentDTO, ['paymentDate']),
};
// Transform bill payment DTO to model object.
const billPaymentObj = await this.transformDTOToModel(
tenantId,
billPaymentDTO,
oldBillPayment
);
// Validate vendor not modified.
this.validateVendorNotModified(billPaymentDTO, oldBillPayment);
// Validate vendor existance on the storage.
await this.getVendorOrThrowError(tenantId, billPaymentObj.vendorId);
@@ -377,28 +420,24 @@ export default class BillPaymentsService {
tenantId,
billPaymentObj.paymentAccountId
);
// Validate the items entries IDs existance on the storage.
await this.validateEntriesIdsExistance(
tenantId,
billPaymentId,
billPaymentObj.entries
);
// Validate the bills existance and associated to the given vendor.
await this.validateBillsExistance(
tenantId,
billPaymentObj.entries,
billPaymentDTO.vendorId
);
// Validates the bills due payment amount.
await this.validateBillsDueAmount(
tenantId,
billPaymentObj.entries,
oldBillPayment.entries
);
// Validate the payment number uniquiness.
if (billPaymentObj.paymentNumber) {
await this.validatePaymentNumber(
@@ -409,8 +448,7 @@ export default class BillPaymentsService {
}
const billPayment = await BillPayment.query().upsertGraphAndFetch({
id: billPaymentId,
...omit(billPaymentObj, ['entries']),
entries: billPaymentDTO.entries,
...billPaymentObj,
});
await this.eventDispatcher.dispatch(events.billPayment.onEdited, {
tenantId,

View File

@@ -27,6 +27,7 @@ import ItemsService from 'services/Items/ItemsService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import JournalCommands from 'services/Accounting/JournalCommands';
import JournalPosterService from 'services/Sales/JournalPosterService';
import VendorsService from 'services/Contacts/VendorsService';
import { ERRORS } from './constants';
/**
@@ -46,7 +47,7 @@ export default class BillsService extends SalesInvoicesCost {
@Inject()
tenancy: TenancyService;
@EventDispatcher()
eventDispatcher: EventDispatcherInterface;
@@ -62,6 +63,9 @@ export default class BillsService extends SalesInvoicesCost {
@Inject()
journalPosterService: JournalPosterService;
@Inject()
vendorsService: VendorsService;
/**
* Validates whether the vendor is exist.
* @async
@@ -136,16 +140,25 @@ export default class BillsService extends SalesInvoicesCost {
}
/**
* Converts bill DTO to model.
* Validate the bill number require.
* @param {string} billNo -
*/
validateBillNoRequire(billNo: string) {
if (!billNo) {
throw new ServiceError(ERRORS.BILL_NO_IS_REQUIRED);
}
}
/**
* Converts create bill DTO to model.
* @param {number} tenantId
* @param {IBillDTO} billDTO
* @param {IBill} oldBill
*
* @returns {IBill}
*/
private async billDTOToModel(
tenantId: number,
billDTO: IBillDTO | IBillEditDTO,
billDTO: IBillDTO,
authorizedUser: ISystemUser,
oldBill?: IBill
) {
@@ -157,15 +170,25 @@ export default class BillsService extends SalesInvoicesCost {
}));
const amount = sumBy(entries, 'amount');
// Bill number from DTO or from auto-increment.
const billNumber = billDTO.billNumber || oldBill?.billNumber;
// Retrieve vendor details by the given vendor id.
const vendor = await this.vendorsService.getVendorByIdOrThrowError(
tenantId,
billDTO.vendorId
);
return {
...formatDateFields(omit(billDTO, ['open', 'entries']), [
'billDate',
'dueDate',
]),
amount,
currencyCode: vendor.currencyCode,
billNumber,
entries: entries.map((entry) => ({
reference_type: 'Bill',
...omit(entry, ['amount', 'id']),
...omit(entry, ['amount']),
})),
// Avoid rewrite the open date in edit mode when already opened.
...(billDTO.open &&
@@ -205,16 +228,14 @@ export default class BillsService extends SalesInvoicesCost {
const billObj = await this.billDTOToModel(
tenantId,
billDTO,
authorizedUser,
null
authorizedUser
);
// Retrieve vendor or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
// Validate the bill number uniqiness on the storage.
if (billDTO.billNumber) {
await this.validateBillNumberExists(tenantId, billDTO.billNumber);
}
await this.validateBillNumberExists(tenantId, billDTO.billNumber);
// Validate items IDs existance.
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
@@ -277,7 +298,6 @@ export default class BillsService extends SalesInvoicesCost {
authorizedUser,
oldBill
);
// Retrieve vendor details or throw not found service error.
await this.getVendorOrThrowError(tenantId, billDTO.vendorId);
@@ -292,7 +312,6 @@ export default class BillsService extends SalesInvoicesCost {
'Bill',
billDTO.entries
);
// Validate the items ids existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,

View File

@@ -7,4 +7,5 @@ export const ERRORS = {
BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND',
NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS',
BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN',
BILL_NO_IS_REQUIRED: 'BILL_NO_IS_REQUIRED'
};

View File

@@ -42,7 +42,9 @@ const ERRORS = {
INVOICES_IDS_NOT_FOUND: 'INVOICES_IDS_NOT_FOUND',
ENTRIES_IDS_NOT_EXISTS: 'ENTRIES_IDS_NOT_EXISTS',
INVOICES_NOT_DELIVERED_YET: 'INVOICES_NOT_DELIVERED_YET',
PAYMENT_RECEIVE_NO_IS_REQUIRED: 'PAYMENT_RECEIVE_NO_IS_REQUIRED'
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',
};
/**
* Payment receive service.
@@ -264,28 +266,6 @@ export default class PaymentReceiveService {
}
}
/**
* Retrieve estimate number to object model.
* @param {number} tenantId
* @param {IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO} paymentReceiveDTO - Payment receive DTO.
* @param {IPaymentReceive} oldPaymentReceive - Old payment model object.
*/
transformPaymentNumberToModel(
tenantId: number,
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
oldPaymentReceive?: IPaymentReceive
): string {
// Retreive the next invoice number.
const autoNextNumber = this.getNextPaymentReceiveNumber(tenantId);
if (paymentReceiveDTO.paymentReceiveNo) {
return paymentReceiveDTO.paymentReceiveNo;
}
return oldPaymentReceive
? oldPaymentReceive.paymentReceiveNo
: autoNextNumber;
}
/**
* Validate the payment receive entries IDs existance.
* @param {number} tenantId
@@ -315,32 +295,69 @@ export default class PaymentReceiveService {
}
}
/**
* Validates the payment receive number require.
* @param {string} paymentReceiveNo
*/
validatePaymentNoRequire(paymentReceiveNo: string) {
if (!paymentReceiveNo) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NO_REQUIRED);
}
}
/**
* Validate the payment customer whether modified.
* @param {IPaymentReceiveEditDTO} paymentReceiveDTO
* @param {IPaymentReceive} oldPaymentReceive
*/
validateCustomerNotModified(
paymentReceiveDTO: IPaymentReceiveEditDTO,
oldPaymentReceive: IPaymentReceive
) {
if (paymentReceiveDTO.customerId !== oldPaymentReceive.customerId) {
throw new ServiceError(ERRORS.PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE);
}
}
/**
* Transformes the create payment receive DTO to model object.
* @param {number} tenantId
* @param {IPaymentReceiveCreateDTO} paymentReceiveDTO
* @param {IPaymentReceiveCreateDTO|IPaymentReceiveEditDTO} paymentReceiveDTO - Payment receive DTO.
* @param {IPaymentReceive} oldPaymentReceive -
* @return {IPaymentReceive}
*/
transformPaymentReceiveDTOToModel(
async transformPaymentReceiveDTOToModel(
tenantId: number,
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
oldPaymentReceive?: IPaymentReceive
): IPaymentReceive {
): Promise<IPaymentReceive> {
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
// Retrieve the next payment receive number.
const paymentReceiveNo = this.transformPaymentNumberToModel(
// Retrieve customer details.
const customer = await this.customersService.getCustomerByIdOrThrowError(
tenantId,
paymentReceiveDTO,
oldPaymentReceive
paymentReceiveDTO.customerId
);
// Retreive the next invoice number.
const autoNextNumber = this.getNextPaymentReceiveNumber(tenantId);
// Retrieve the next payment receive number.
const paymentReceiveNo =
paymentReceiveDTO.paymentReceiveNo ||
oldPaymentReceive?.paymentReceiveNo ||
autoNextNumber;
this.validatePaymentNoRequire(paymentReceiveNo);
return {
amount: paymentAmount,
currencyCode: customer.currencyCode,
...formatDateFields(omit(paymentReceiveDTO, ['entries']), [
'paymentDate',
]),
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
entries: paymentReceiveDTO.entries.map((entry) => ({
...omit(entry, ['id']),
...entry,
})),
};
}
@@ -360,13 +377,10 @@ export default class PaymentReceiveService {
const { PaymentReceive } = this.tenancy.models(tenantId);
// Transformes the payment receive DTO to model.
const paymentReceiveObj = this.transformPaymentReceiveDTOToModel(
const paymentReceiveObj = await this.transformPaymentReceiveDTOToModel(
tenantId,
paymentReceiveDTO
);
// Validate payment receive is required.
this.validatePaymentReceiveNoRequire(paymentReceiveObj);
// Validate payment receive number uniquiness.
await this.validatePaymentReceiveNoExistance(
tenantId,
@@ -393,7 +407,6 @@ export default class PaymentReceiveService {
tenantId,
paymentReceiveDTO.entries
);
this.logger.info('[payment_receive] inserting to the storage.');
const paymentReceive = await PaymentReceive.query().insertGraphAndFetch({
...paymentReceiveObj,
@@ -447,13 +460,13 @@ export default class PaymentReceiveService {
paymentReceiveId
);
// Transformes the payment receive DTO to model.
const paymentReceiveObj = this.transformPaymentReceiveDTOToModel(
const paymentReceiveObj = await this.transformPaymentReceiveDTOToModel(
tenantId,
paymentReceiveDTO,
oldPaymentReceive
);
// Validate payment receive number existance.
this.validatePaymentReceiveNoRequire(paymentReceiveObj);
// Validate customer whether modified.
this.validateCustomerNotModified(paymentReceiveDTO, oldPaymentReceive);
// Validate payment receive number uniquiness.
if (paymentReceiveDTO.paymentReceiveNo) {
@@ -527,7 +540,7 @@ export default class PaymentReceiveService {
const { PaymentReceive, PaymentReceiveEntry } = this.tenancy.models(
tenantId
);
// Retreive payment receive or throw not found service error.
const oldPaymentReceive = await this.getPaymentReceiveOrThrowError(
tenantId,
paymentReceiveId

View File

@@ -31,7 +31,7 @@ const ERRORS = {
SALE_ESTIMATE_ALREADY_REJECTED: 'SALE_ESTIMATE_ALREADY_REJECTED',
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'
SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED',
};
/**
@@ -157,25 +157,34 @@ export default class SaleEstimateService {
}
/**
* Transform DTO object ot model object.
* Transform create DTO object ot model object.
* @param {number} tenantId
* @param {ISaleEstimateDTO} saleEstimateDTO
* @param {ISaleEstimate} oldSaleEstimate
* @param {ISaleEstimateDTO} saleEstimateDTO - Sale estimate DTO.
* @return {ISaleEstimate}
*/
transformDTOToModel(
async transformDTOToModel(
tenantId: number,
estimateDTO: ISaleEstimateDTO,
oldSaleEstimate?: ISaleEstimate
): ISaleEstimate {
): Promise<ISaleEstimate> {
const { ItemEntry } = this.tenancy.models(tenantId);
const amount = sumBy(estimateDTO.entries, (e) => ItemEntry.calcAmount(e));
// Retreive the next invoice number.
const autoNextNumber = this.getNextEstimateNumber(tenantId);
// Retreive the next estimate number.
const estimateNumber = this.transformEstimateNumberToModel(
const estimateNumber = estimateDTO.estimateNumber ||
oldSaleEstimate?.estimateNumber ||
autoNextNumber;
// Validate the sale estimate number require.
this.validateEstimateNoRequire(estimateNumber);
// Retrieve customer details.
const customer = await this.customersService.getCustomerByIdOrThrowError(
tenantId,
estimateDTO,
oldSaleEstimate
estimateDTO.customerId
);
return {
@@ -184,25 +193,26 @@ export default class SaleEstimateService {
'estimateDate',
'expirationDate',
]),
currencyCode: customer.currencyCode,
...(estimateNumber ? { estimateNumber } : {}),
entries: estimateDTO.entries.map((entry) => ({
reference_type: 'SaleEstimate',
...omit(entry, ['total', 'amount', 'id']),
...entry,
})),
// Avoid rewrite the deliver date in edit mode when already published.
...(estimateDTO.delivered &&
!oldSaleEstimate?.deliveredAt && {
deliveredAt: moment().toMySqlDateTime(),
}),
!oldSaleEstimate?.deliveredAt && {
deliveredAt: moment().toMySqlDateTime(),
}),
};
}
/**
* Validate the sale estimate number require.
* @param {ISaleEstimate} saleInvoiceObj
*/
validateEstimateNoRequire(saleInvoiceObj: ISaleEstimate) {
if (!saleInvoiceObj.estimateNumber) {
validateEstimateNoRequire(estimateNumber: string) {
if (!estimateNumber) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NO_IS_REQUIRED);
}
}
@@ -223,27 +233,25 @@ export default class SaleEstimateService {
this.logger.info('[sale_estimate] inserting sale estimate to the storage.');
// Transform DTO object ot model object.
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO);
// Validate the sale estimate number require.
this.validateEstimateNoRequire(estimateObj);
const estimateObj = await this.transformDTOToModel(
tenantId,
estimateDTO
);
// Validate estimate number uniquiness on the storage.
if (estimateObj.estimateNumber) {
await this.validateEstimateNumberExistance(
tenantId,
estimateObj.estimateNumber
);
}
await this.validateEstimateNumberExistance(
tenantId,
estimateObj.estimateNumber
);
// Retrieve the given customer or throw not found service error.
await this.customersService.getCustomer(tenantId, estimateDTO.customerId);
await this.customersService.getCustomerByIdOrThrowError(
tenantId,
estimateDTO.customerId
);
// Validate items IDs existance on the storage.
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
estimateDTO.entries
);
// Validate non-sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
@@ -284,14 +292,11 @@ export default class SaleEstimateService {
estimateId
);
// Transform DTO object ot model object.
const estimateObj = this.transformDTOToModel(
const estimateObj = await this.transformDTOToModel(
tenantId,
estimateDTO,
oldSaleEstimate
);
// Validate the sale estimate number require.
this.validateEstimateNoRequire(estimateObj);
// Validate estimate number uniquiness on the storage.
if (estimateDTO.estimateNumber) {
await this.validateEstimateNumberExistance(
@@ -301,13 +306,15 @@ export default class SaleEstimateService {
);
}
// Retrieve the given customer or throw not found service error.
await this.customersService.getCustomer(tenantId, estimateDTO.customerId);
await this.customersService.getCustomerByIdOrThrowError(
tenantId,
estimateDTO.customerId
);
// Validate sale estimate entries existance.
await this.itemsEntriesService.validateEntriesIdsExistance(
tenantId,
estimateId,
'SaleEstiamte',
'SaleEstimate',
estimateDTO.entries
);
// Validate items IDs existance on the storage.

View File

@@ -181,46 +181,36 @@ export default class SaleInvoicesService {
}
/**
* Retrieve invoice number to object model.
* @param tenantId
* @param saleInvoiceDTO
* @param oldSaleInvoice
* Transformes the create DTO to invoice object model.
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO - Sale invoice DTO.
* @param {ISaleInvoice} oldSaleInvoice - Old sale invoice.
* @return {ISaleInvoice}
*/
transformInvoiceNumberToModel(
private async transformDTOToModel(
tenantId: number,
saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO,
oldSaleInvoice?: ISaleInvoice
): string {
// Retreive the next invoice number.
const autoNextNumber = this.getNextInvoiceNumber(tenantId);
if (saleInvoiceDTO.invoiceNo) {
return saleInvoiceDTO.invoiceNo;
}
return oldSaleInvoice ? oldSaleInvoice.invoiceNo : autoNextNumber;
}
/**
* Transform DTO object to model object.
* @param {number} tenantId - Tenant id.
* @param {ISaleInvoiceDTO} saleInvoiceDTO - Sale invoice DTO.
*/
transformDTOToModel(
tenantId: number,
saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO,
oldSaleInvoice?: ISaleInvoice
): ISaleInvoice {
): Promise<ISaleInvoice> {
const { ItemEntry } = this.tenancy.models(tenantId);
const balance = sumBy(saleInvoiceDTO.entries, (e) =>
ItemEntry.calcAmount(e)
);
const invoiceNo = this.transformInvoiceNumberToModel(
// Retrieve customer details.
const customer = await this.customersService.getCustomerByIdOrThrowError(
tenantId,
saleInvoiceDTO,
oldSaleInvoice
saleInvoiceDTO.customerId
);
// Retreive the next invoice number.
const autoNextNumber = this.getNextInvoiceNumber(tenantId);
// Invoice number.
const invoiceNo =
saleInvoiceDTO.invoiceNo || oldSaleInvoice?.invoiceNo || autoNextNumber;
// Validate the invoice is required.
this.validateInvoiceNoRequire(invoiceNo);
return {
...formatDateFields(
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']),
@@ -228,20 +218,17 @@ export default class SaleInvoicesService {
),
// Avoid rewrite the deliver date in edit mode when already published.
balance,
currencyCode: customer.currencyCode,
...(saleInvoiceDTO.delivered &&
!oldSaleInvoice?.deliveredAt && {
deliveredAt: moment().toMySqlDateTime(),
}),
// Avoid add payment amount in edit mode.
...(!oldSaleInvoice
? {
paymentAmount: 0,
}
: {}),
// Avoid override payment amount in edit mode.
...(!oldSaleInvoice && { paymentAmount: 0 }),
...(invoiceNo ? { invoiceNo } : {}),
entries: saleInvoiceDTO.entries.map((entry) => ({
referenceType: 'SaleInvoice',
...omit(entry, ['amount', 'id']),
...entry,
})),
};
}
@@ -250,8 +237,8 @@ export default class SaleInvoicesService {
* Validate the invoice number require.
* @param {ISaleInvoice} saleInvoiceObj
*/
validateInvoiceNoRequire(saleInvoiceObj: ISaleInvoice) {
if (!saleInvoiceObj.invoiceNo) {
validateInvoiceNoRequire(invoiceNo: string) {
if (!invoiceNo) {
throw new ServiceError(ERRORS.SALE_INVOICE_NO_IS_REQUIRED);
}
}
@@ -272,19 +259,15 @@ export default class SaleInvoicesService {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
// Transform DTO object to model object.
const saleInvoiceObj = this.transformDTOToModel(
const saleInvoiceObj = await this.transformDTOToModel(
tenantId,
saleInvoiceDTO,
null
saleInvoiceDTO
);
this.validateInvoiceNoRequire(saleInvoiceObj);
// Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError(
tenantId,
saleInvoiceDTO.customerId
);
// Validate sale invoice number uniquiness.
if (saleInvoiceObj.invoiceNo) {
await this.validateInvoiceNumberUnique(
@@ -354,7 +337,7 @@ export default class SaleInvoicesService {
saleInvoiceId
);
// Transform DTO object to model object.
const saleInvoiceObj = this.transformDTOToModel(
const saleInvoiceObj = await this.transformDTOToModel(
tenantId,
saleInvoiceDTO,
oldSaleInvoice
@@ -396,10 +379,10 @@ export default class SaleInvoicesService {
);
this.logger.info('[sale_invoice] trying to update sale invoice.');
const saleInvoice: ISaleInvoice = await saleInvoiceRepository.update(
{ ...omit(saleInvoiceObj, ['paymentAmount']) },
{ id: saleInvoiceId }
);
const saleInvoice: ISaleInvoice = await saleInvoiceRepository.upsertGraph({
id: saleInvoiceId,
...saleInvoiceObj,
});
// Triggers `onSaleInvoiceEdited` event.
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
tenantId,

View File

@@ -20,6 +20,7 @@ import { ItemEntry } from 'models';
import InventoryService from 'services/Inventory/Inventory';
import { ACCOUNT_PARENT_TYPE } from 'data/AccountTypes';
import AutoIncrementOrdersService from './AutoIncrementOrdersService';
import CustomersService from 'services/Contacts/CustomersService';
const ERRORS = {
SALE_RECEIPT_NOT_FOUND: 'SALE_RECEIPT_NOT_FOUND',
@@ -27,7 +28,7 @@ const ERRORS = {
DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET: 'DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET',
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'
SALE_RECEIPT_NO_IS_REQUIRED: 'SALE_RECEIPT_NO_IS_REQUIRED',
};
@Service()
@@ -56,6 +57,9 @@ export default class SalesReceiptService {
@Inject()
autoIncrementOrdersService: AutoIncrementOrdersService;
@Inject()
customersService: CustomersService;
/**
* Validate whether sale receipt exists on the storage.
* @param {number} tenantId -
@@ -139,8 +143,8 @@ export default class SalesReceiptService {
* Validate the sale receipt number require.
* @param {ISaleReceipt} saleReceipt
*/
validateReceiptNoRequire(saleReceipt: ISaleReceipt) {
if (!saleReceipt.receiptNumber) {
validateReceiptNoRequire(receiptNumber: string) {
if (!receiptNumber) {
throw new ServiceError(ERRORS.SALE_RECEIPT_NO_IS_REQUIRED);
}
}
@@ -189,40 +193,52 @@ export default class SalesReceiptService {
}
/**
* Transform DTO object to model object.
* Transform create DTO object to model object.
* @param {ISaleReceiptDTO} saleReceiptDTO -
* @param {ISaleReceipt} oldSaleReceipt -
* @returns {ISaleReceipt}
*/
transformObjectDTOToModel(
async transformDTOToModel(
tenantId: number,
saleReceiptDTO: ISaleReceiptDTO,
oldSaleReceipt?: ISaleReceipt
): ISaleReceipt {
): Promise<ISaleReceipt> {
const amount = sumBy(saleReceiptDTO.entries, (e) =>
ItemEntry.calcAmount(e)
);
// Retreive the next invoice number.
const autoNextNumber = this.getNextReceiptNumber(tenantId);
// Retreive the receipt number.
const receiptNumber = this.transformReceiptNumberToModel(
const receiptNumber =
saleReceiptDTO.receiptNumber ||
oldSaleReceipt?.receiptNumber ||
autoNextNumber;
// Validate receipt number require.
this.validateReceiptNoRequire(receiptNumber);
// Retrieve customer details.
const customer = await this.customersService.getCustomerByIdOrThrowError(
tenantId,
saleReceiptDTO,
oldSaleReceipt
saleReceiptDTO.customerId
);
return {
amount,
currencyCode: customer.currencyCode,
...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [
'receiptDate',
]),
...(receiptNumber ? { receiptNumber } : {}),
receiptNumber,
// Avoid rewrite the deliver date in edit mode when already published.
...(saleReceiptDTO.closed &&
!oldSaleReceipt?.closedAt && {
!oldSaleReceipt.closedAt && {
closedAt: moment().toMySqlDateTime(),
}),
entries: saleReceiptDTO.entries.map((entry) => ({
reference_type: 'SaleReceipt',
...omit(entry, ['id', 'amount']),
...entry,
})),
};
}
@@ -240,13 +256,10 @@ export default class SalesReceiptService {
const { SaleReceipt } = this.tenancy.models(tenantId);
// Transform sale receipt DTO to model.
const saleReceiptObj = this.transformObjectDTOToModel(
const saleReceiptObj = await this.transformDTOToModel(
tenantId,
saleReceiptDTO
);
// Validate receipt number is required.
this.validateReceiptNoRequire(saleReceiptObj);
// Validate receipt deposit account existance and type.
await this.validateReceiptDepositAccountExistance(
tenantId,
@@ -308,14 +321,11 @@ export default class SalesReceiptService {
saleReceiptId
);
// Transform sale receipt DTO to model.
const saleReceiptObj = this.transformObjectDTOToModel(
const saleReceiptObj = await this.transformDTOToModel(
tenantId,
saleReceiptDTO,
oldSaleReceipt
);
// Validate receipt number is required.
this.validateReceiptNoRequire(saleReceiptObj);
// Validate receipt deposit account existance and type.
await this.validateReceiptDepositAccountExistance(
tenantId,