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

@@ -103,7 +103,7 @@ export default class ExchangeRatesController extends BaseController {
const { tenantId } = req; const { tenantId } = req;
const filter = { const filter = {
page: 1, page: 1,
pageSize: 100, pageSize: 12,
filterRoles: [], filterRoles: [],
columnSortBy: 'created_at', columnSortBy: 'created_at',
sortOrder: 'asc', sortOrder: 'asc',

View File

@@ -48,7 +48,7 @@ export default class BillsController extends BaseController {
); );
router.post( router.post(
'/:id', [ '/:id', [
...this.billValidationSchema, ...this.billEditValidationSchema,
...this.specificBillValidationSchema, ...this.specificBillValidationSchema,
], ],
this.validationResult, this.validationResult,
@@ -106,7 +106,6 @@ export default class BillsController extends BaseController {
check('entries').isArray({ min: 1 }), check('entries').isArray({ min: 1 }),
check('entries.*.id').optional().isNumeric().toInt(),
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
@@ -121,7 +120,7 @@ export default class BillsController extends BaseController {
*/ */
get billEditValidationSchema() { get billEditValidationSchema() {
return [ return [
check('bill_number').exists().trim().escape(), check('bill_number').optional().trim().escape(),
check('reference_no').optional().trim().escape(), check('reference_no').optional().trim().escape(),
check('bill_date').exists().isISO8601(), check('bill_date').exists().isISO8601(),
check('due_date').optional().isISO8601(), check('due_date').optional().isISO8601(),

View File

@@ -407,6 +407,11 @@ export default class BillsPayments extends BaseController {
errors: [{ type: 'BILLS_NOT_FOUND', code: 1000 }], errors: [{ type: 'BILLS_NOT_FOUND', code: 1000 }],
}); });
} }
if (error.errorType === 'PAYMENT_NUMBER_SHOULD_NOT_MODIFY') {
return res.status(400).send({
errors: [{ type: 'PAYMENT_NUMBER_SHOULD_NOT_MODIFY', code: 1100 }],
});
}
} }
next(error); next(error);
} }

View File

@@ -91,6 +91,7 @@ export default class PaymentReceivesController extends BaseController {
*/ */
get paymentReceiveSchema(): ValidationChain[] { get paymentReceiveSchema(): ValidationChain[] {
return [ return [
check('customer_id').exists().isNumeric().toInt(),
check('payment_date').exists(), check('payment_date').exists(),
check('reference_no').optional(), check('reference_no').optional(),
check('deposit_account_id').exists().isNumeric().toInt(), check('deposit_account_id').exists().isNumeric().toInt(),
@@ -132,7 +133,6 @@ export default class PaymentReceivesController extends BaseController {
*/ */
get newPaymentReceiveValidation() { get newPaymentReceiveValidation() {
return [ return [
check('customer_id').exists().isNumeric().toInt(),
...this.paymentReceiveSchema, ...this.paymentReceiveSchema,
]; ];
} }
@@ -449,6 +449,11 @@ export default class PaymentReceivesController extends BaseController {
errors: [{ type: 'PAYMENT_RECEIVE_NO_IS_REQUIRED', code: 1100 }], errors: [{ type: 'PAYMENT_RECEIVE_NO_IS_REQUIRED', code: 1100 }],
}); });
} }
if (error.errorType === 'PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE') {
return res.boom.badRequest(null, {
errors: [{ type: 'PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE', code: 1200 }],
});
}
} }
next(error); next(error);
} }

View File

@@ -301,6 +301,7 @@ export default class SaleInvoicesController extends BaseController {
filterMeta, filterMeta,
pagination, pagination,
} = await this.saleInvoiceService.salesInvoicesList(tenantId, filter); } = await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
return res.status(200).send({ return res.status(200).send({
sales_invoices: salesInvoices, sales_invoices: salesInvoices,
pagination: this.transfromToResponse(pagination), pagination: this.transfromToResponse(pagination),

View File

@@ -4,6 +4,7 @@ exports.up = function(knex) {
return knex.schema.createTable('sales_estimates', (table) => { return knex.schema.createTable('sales_estimates', (table) => {
table.increments(); table.increments();
table.decimal('amount', 13, 3); table.decimal('amount', 13, 3);
table.string('currency_code', 3);
table.integer('customer_id').unsigned().index().references('id').inTable('contacts'); table.integer('customer_id').unsigned().index().references('id').inTable('contacts');
table.date('estimate_date').index(); table.date('estimate_date').index();
table.date('expiration_date').index(); table.date('expiration_date').index();

View File

@@ -3,6 +3,7 @@ exports.up = function(knex) {
return knex.schema.createTable('sales_receipts', table => { return knex.schema.createTable('sales_receipts', table => {
table.increments(); table.increments();
table.decimal('amount', 13, 3); table.decimal('amount', 13, 3);
table.string('currency_code', 3);
table.integer('deposit_account_id').unsigned().index().references('id').inTable('accounts'); table.integer('deposit_account_id').unsigned().index().references('id').inTable('accounts');
table.integer('customer_id').unsigned().index().references('id').inTable('contacts'); table.integer('customer_id').unsigned().index().references('id').inTable('contacts');
table.date('receipt_date').index(); table.date('receipt_date').index();

View File

@@ -13,6 +13,7 @@ exports.up = function(knex) {
table.decimal('balance', 13, 3); table.decimal('balance', 13, 3);
table.decimal('payment_amount', 13, 3); table.decimal('payment_amount', 13, 3);
table.string('currency_code', 3);
table.string('inv_lot_number').index(); table.string('inv_lot_number').index();

View File

@@ -6,6 +6,7 @@ exports.up = function(knex) {
table.integer('customer_id').unsigned().index().references('id').inTable('contacts'); table.integer('customer_id').unsigned().index().references('id').inTable('contacts');
table.date('payment_date').index(); table.date('payment_date').index();
table.decimal('amount', 13, 3).defaultTo(0); table.decimal('amount', 13, 3).defaultTo(0);
table.string('currency_code', 3);
table.string('reference_no').index(); table.string('reference_no').index();
table.integer('deposit_account_id').unsigned().references('id').inTable('accounts'); table.integer('deposit_account_id').unsigned().references('id').inTable('accounts');
table.string('payment_receive_no').nullable(); table.string('payment_receive_no').nullable();

View File

@@ -10,6 +10,7 @@ exports.up = function(knex) {
table.string('status').index(); table.string('status').index();
table.text('note'); table.text('note');
table.decimal('amount', 13, 3).defaultTo(0); table.decimal('amount', 13, 3).defaultTo(0);
table.string('currency_code');
table.decimal('payment_amount', 13, 3).defaultTo(0); table.decimal('payment_amount', 13, 3).defaultTo(0);
table.string('inv_lot_number').index(); table.string('inv_lot_number').index();
table.date('opened_at').index(); table.date('opened_at').index();

View File

@@ -4,6 +4,7 @@ exports.up = function(knex) {
table.increments(); table.increments();
table.integer('vendor_id').unsigned().index().references('id').inTable('contacts'); table.integer('vendor_id').unsigned().index().references('id').inTable('contacts');
table.decimal('amount', 13, 3).defaultTo(0); table.decimal('amount', 13, 3).defaultTo(0);
table.string('currency_code');
table.integer('payment_account_id').unsigned().references('id').inTable('accounts'); table.integer('payment_account_id').unsigned().references('id').inTable('accounts');
table.string('payment_number').nullable().index(); table.string('payment_number').nullable().index();
table.date('payment_date').index(); table.date('payment_date').index();

View File

@@ -25,6 +25,7 @@ export interface IPaymentReceiveCreateDTO {
}; };
export interface IPaymentReceiveEditDTO { export interface IPaymentReceiveEditDTO {
customerId: number,
paymentDate: Date, paymentDate: Date,
amount: number, amount: number,
referenceNo: string, referenceNo: string,

View File

@@ -4,6 +4,7 @@ import { IDynamicListFilterDTO } from 'interfaces/DynamicFilter';
export interface ISaleEstimate { export interface ISaleEstimate {
id?: number, id?: number,
amount: number, amount: number,
currencyCode: string,
customerId: number, customerId: number,
estimateDate: Date, estimateDate: Date,
estimateNumber: string, estimateNumber: string,

View File

@@ -5,6 +5,7 @@ export interface ISaleInvoice {
id: number, id: number,
balance: number, balance: number,
paymentAmount: number, paymentAmount: number,
currencyCode: string,
invoiceDate: Date, invoiceDate: Date,
dueDate: Date, dueDate: Date,
dueAmount: number, dueAmount: number,

View File

@@ -10,6 +10,7 @@ export interface ISaleReceipt {
receiptMessage: string; receiptMessage: string;
receiptNumber: string; receiptNumber: string;
amount: number; amount: number;
currencyCode: string,
statement: string; statement: string;
closedAt: Date | string; closedAt: Date | string;
entries: any[]; entries: any[];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,17 +3,20 @@ import { On, EventSubscriber } from 'event-dispatch';
import events from 'subscribers/events'; import events from 'subscribers/events';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import SettingsService from 'services/Settings/SettingsService'; import SettingsService from 'services/Settings/SettingsService';
import SalesReceiptService from 'services/Sales/SalesReceipts';
@EventSubscriber() @EventSubscriber()
export default class SaleReceiptSubscriber { export default class SaleReceiptSubscriber {
logger: any; logger: any;
tenancy: TenancyService; tenancy: TenancyService;
settingsService: SettingsService; settingsService: SettingsService;
saleReceiptsService: SalesReceiptService;
constructor() { constructor() {
this.logger = Container.get('logger'); this.logger = Container.get('logger');
this.tenancy = Container.get(TenancyService); this.tenancy = Container.get(TenancyService);
this.settingsService = Container.get(SettingsService); this.settingsService = Container.get(SettingsService);
this.saleReceiptsService = Container.get(SalesReceiptService);
} }
/** /**
@@ -21,9 +24,6 @@ export default class SaleReceiptSubscriber {
*/ */
@On(events.saleReceipt.onCreated) @On(events.saleReceipt.onCreated)
public async handleReceiptNextNumberIncrement({ tenantId, saleReceiptId }) { public async handleReceiptNextNumberIncrement({ tenantId, saleReceiptId }) {
await this.settingsService.incrementNextNumber(tenantId, { await this.saleReceiptsService.incrementNextReceiptNumber(tenantId);
key: 'next_number',
group: 'sales_receipts',
});
} }
} }