mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
feat(sales): currency code associated from invoice customer.
feat(purchases): currency code associated from vendor customer.
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user