This commit is contained in:
elforjani3
2021-03-07 21:19:37 +02:00
17 changed files with 482 additions and 201 deletions

View File

@@ -1,4 +1,4 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import { Request, Response, Router, NextFunction } from 'express'; import { Request, Response, Router, NextFunction } from 'express';
import asyncMiddleware from 'api/middleware/asyncMiddleware'; import asyncMiddleware from 'api/middleware/asyncMiddleware';
import BaseController from 'api/controllers/BaseController'; import BaseController from 'api/controllers/BaseController';

View File

@@ -17,33 +17,32 @@ export default class CurrenciesController extends BaseController {
router() { router() {
const router = Router(); const router = Router();
router.get('/', [ router.get(
...this.listSchema, '/',
], [...this.listSchema],
this.validationResult, this.validationResult,
asyncMiddleware(this.all.bind(this)) asyncMiddleware(this.all.bind(this))
); );
router.post('/', [ router.post(
...this.currencyDTOSchemaValidation, '/',
], [...this.currencyDTOSchemaValidation],
this.validationResult, this.validationResult,
asyncMiddleware(this.newCurrency.bind(this)), asyncMiddleware(this.newCurrency.bind(this)),
this.handlerServiceError, this.handlerServiceError
); );
router.post('/:id', [ router.post(
...this.currencyIdParamSchema, '/:id',
...this.currencyEditDTOSchemaValidation [...this.currencyIdParamSchema, ...this.currencyEditDTOSchemaValidation],
],
this.validationResult, this.validationResult,
asyncMiddleware(this.editCurrency.bind(this)), asyncMiddleware(this.editCurrency.bind(this)),
this.handlerServiceError, this.handlerServiceError
); );
router.delete('/:currency_code', [ router.delete(
...this.currencyParamSchema, '/:currency_code',
], [...this.currencyParamSchema],
this.validationResult, this.validationResult,
asyncMiddleware(this.deleteCurrency.bind(this)), asyncMiddleware(this.deleteCurrency.bind(this)),
this.handlerServiceError, this.handlerServiceError
); );
return router; return router;
} }
@@ -56,21 +55,15 @@ export default class CurrenciesController extends BaseController {
} }
get currencyEditDTOSchemaValidation(): ValidationChain[] { get currencyEditDTOSchemaValidation(): ValidationChain[] {
return [ return [check('currency_name').exists().trim().escape()];
check('currency_name').exists().trim().escape(),
];
} }
get currencyIdParamSchema(): ValidationChain[] { get currencyIdParamSchema(): ValidationChain[] {
return [ return [param('id').exists().isNumeric().toInt()];
param('id').exists().isNumeric().toInt(),
];
} }
get currencyParamSchema(): ValidationChain[] { get currencyParamSchema(): ValidationChain[] {
return [ return [param('currency_code').exists().trim().escape()];
param('currency_code').exists().trim().escape(),
];
} }
get listSchema(): ValidationChain[] { get listSchema(): ValidationChain[] {
@@ -91,7 +84,7 @@ export default class CurrenciesController extends BaseController {
try { try {
const currencies = await this.currenciesService.listCurrencies(tenantId); const currencies = await this.currenciesService.listCurrencies(tenantId);
return res.status(200).send({ currencies: [ ...currencies, ] }); return res.status(200).send({ currencies: [...currencies] });
} catch (error) { } catch (error) {
next(error); next(error);
} }
@@ -152,7 +145,11 @@ export default class CurrenciesController extends BaseController {
const { body: editCurrencyDTO } = req; const { body: editCurrencyDTO } = req;
try { try {
const currency = await this.currenciesService.editCurrency(tenantId, currencyId, editCurrencyDTO); const currency = await this.currenciesService.editCurrency(
tenantId,
currencyId,
editCurrencyDTO
);
return res.status(200).send({ return res.status(200).send({
currency_code: currency.currencyCode, currency_code: currency.currencyCode,
message: 'The currency has been edited successfully.', message: 'The currency has been edited successfully.',
@@ -169,19 +166,24 @@ export default class CurrenciesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
handlerServiceError(error: Error, req: Request, res: Response, next: NextFunction) { handlerServiceError(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (error.errorType === 'currency_not_found') { if (error.errorType === 'currency_not_found') {
return res.boom.badRequest(null, { return res.boom.badRequest(null, {
errors: [{ type: 'CURRENCY_NOT_FOUND', code: 100, }], errors: [{ type: 'CURRENCY_NOT_FOUND', code: 100 }],
}); });
} }
if (error.errorType === 'currency_code_exists') { if (error.errorType === 'currency_code_exists') {
return res.boom.badRequest(null, { return res.boom.badRequest(null, {
errors: [{ type: 'CURRENCY_CODE_EXISTS', code: 200, }], errors: [{ type: 'CURRENCY_CODE_EXISTS', code: 200 }],
}); });
} }
} }
next(error); next(error);
} }
}; }

View File

@@ -444,6 +444,11 @@ export default class PaymentReceivesController extends BaseController {
], ],
}); });
} }
if (error.errorType === 'PAYMENT_RECEIVE_NO_IS_REQUIRED') {
return res.boom.badRequest(null, {
errors: [{ type: 'PAYMENT_RECEIVE_NO_IS_REQUIRED', code: 1100 }],
});
}
} }
next(error); next(error);
} }

View File

@@ -101,7 +101,7 @@ export default class SalesEstimatesController extends BaseController {
check('estimate_date').exists().isISO8601(), check('estimate_date').exists().isISO8601(),
check('expiration_date').optional().isISO8601(), check('expiration_date').optional().isISO8601(),
check('reference').optional(), check('reference').optional(),
check('estimate_number').exists().trim().escape(), check('estimate_number').optional().trim().escape(),
check('delivered').default(false).isBoolean().toBoolean(), check('delivered').default(false).isBoolean().toBoolean(),
check('entries').exists().isArray({ min: 1 }), check('entries').exists().isArray({ min: 1 }),
@@ -401,6 +401,11 @@ export default class SalesEstimatesController extends BaseController {
errors: [{ type: 'CUSTOMER_NOT_FOUND', code: 1300 }], errors: [{ type: 'CUSTOMER_NOT_FOUND', code: 1300 }],
}); });
} }
if (error.errorType === 'SALE_ESTIMATE_NO_IS_REQUIRED') {
return res.boom.badRequest(null, {
errors: [{ type: 'SALE_ESTIMATE_NO_IS_REQUIRED', code: 1400 }],
});
}
} }
next(error); next(error);
} }

View File

@@ -432,6 +432,13 @@ export default class SaleInvoicesController extends BaseController {
], ],
}); });
} }
if (error.errorType === 'SALE_INVOICE_NO_IS_REQUIRED') {
return res.boom.badRequest(null, {
errors: [
{ type: 'SALE_INVOICE_NO_IS_REQUIRED', code: 1500 },
],
});
}
} }
next(error); next(error);
} }

View File

@@ -6,6 +6,7 @@ export interface ISaleEstimate {
amount: number, amount: number,
customerId: number, customerId: number,
estimateDate: Date, estimateDate: Date,
estimateNumber: string,
reference: string, reference: string,
note: string, note: string,
termsConditions: string, termsConditions: string,
@@ -19,8 +20,8 @@ export interface ISaleEstimate {
export interface ISaleEstimateDTO { export interface ISaleEstimateDTO {
customerId: number, customerId: number,
estimateDate?: Date, estimateDate?: Date,
reference: string, reference?: string,
estimateNumber: string, estimateNumber?: string,
entries: IItemEntry[], entries: IItemEntry[],
note: string, note: string,
termsConditions: string, termsConditions: string,

View File

@@ -22,7 +22,8 @@ export interface ISaleReceiptDTO {
depositAccountId: number; depositAccountId: number;
receiptDate: Date; receiptDate: Date;
sendToEmail: string; sendToEmail: string;
referenceNo: string; referenceNo?: string;
receiptNumber?: string,
receiptMessage: string; receiptMessage: string;
statement: string; statement: string;
closed: boolean; closed: boolean;

View File

@@ -1,13 +1,9 @@
import { Model } from 'objection'; import { Model } from 'objection';
import { omit, isEmpty } from 'lodash'; import { omit, isEmpty } from 'lodash';
import { import { IMetadata, IMetaQuery, IMetableStore } from 'interfaces';
IMetadata,
IMetaQuery,
IMetableStore,
} from 'interfaces';
import { itemsStartWith } from 'utils'; import { itemsStartWith } from 'utils';
export default class MetableStore implements IMetableStore{ export default class MetableStore implements IMetableStore {
metadata: IMetadata[]; metadata: IMetadata[];
model: Model; model: Model;
extraColumns: string[]; extraColumns: string[];
@@ -34,15 +30,16 @@ export default class MetableStore implements IMetableStore{
* @param {string|IMetaQuery} query - * @param {string|IMetaQuery} query -
* @returns {IMetadata} - Metadata object. * @returns {IMetadata} - Metadata object.
*/ */
find(query: string|IMetaQuery): IMetadata { find(query: string | IMetaQuery): IMetadata {
const { key, value, ...extraColumns } = this.parseQuery(query); const { key, value, ...extraColumns } = this.parseQuery(query);
return this.metadata.find((meta: IMetadata) => { return this.metadata.find((meta: IMetadata) => {
const isSameKey = meta.key === key; const isSameKey = meta.key === key;
const sameExtraColumns = this.extraColumns const sameExtraColumns = this.extraColumns.some(
.some((extraColumn: string) => extraColumns[extraColumn] === meta[extraColumn]); (extraColumn: string) => extraColumns[extraColumn] === meta[extraColumn]
);
const isSameExtraColumns = (sameExtraColumns || isEmpty(extraColumns)); const isSameExtraColumns = sameExtraColumns || isEmpty(extraColumns);
return isSameKey && isSameExtraColumns; return isSameKey && isSameExtraColumns;
}); });
@@ -55,10 +52,9 @@ export default class MetableStore implements IMetableStore{
all(): IMetadata[] { all(): IMetadata[] {
return this.metadata return this.metadata
.filter((meta: IMetadata) => !meta._markAsDeleted) .filter((meta: IMetadata) => !meta._markAsDeleted)
.map((meta: IMetadata) => omit( .map((meta: IMetadata) =>
meta, omit(meta, itemsStartWith(Object.keys(meta), '_'))
itemsStartWith(Object.keys(meta), '_') );
));
} }
/** /**
@@ -66,16 +62,20 @@ export default class MetableStore implements IMetableStore{
* @param {String} key - * @param {String} key -
* @param {Mixied} defaultValue - * @param {Mixied} defaultValue -
*/ */
get(query: string|IMetaQuery, defaultValue: any): any|false { get(query: string | IMetaQuery, defaultValue: any): any | false {
const metadata = this.find(query); const metadata = this.find(query);
return metadata ? metadata.value : defaultValue || false; return metadata
? metadata.value
: typeof defaultValue !== 'undefined'
? defaultValue
: false;
} }
/** /**
* Markes the metadata to should be deleted. * Markes the metadata to should be deleted.
* @param {String} key - * @param {String} key -
*/ */
remove(query: string|IMetaQuery): void { remove(query: string | IMetaQuery): void {
const metadata: IMetadata = this.find(query); const metadata: IMetadata = this.find(query);
if (metadata) { if (metadata) {
@@ -99,7 +99,7 @@ export default class MetableStore implements IMetableStore{
* @param {String} key - * @param {String} key -
* @param {String} value - * @param {String} value -
*/ */
set(query: IMetaQuery|IMetadata[]|string, metaValue?: any): void { set(query: IMetaQuery | IMetadata[] | string, metaValue?: any): void {
if (Array.isArray(query)) { if (Array.isArray(query)) {
const metadata = query; const metadata = query;
@@ -130,7 +130,7 @@ export default class MetableStore implements IMetableStore{
* @param query * @param query
* @param value * @param value
*/ */
parseQuery(query: string|IMetaQuery): IMetaQuery { parseQuery(query: string | IMetaQuery): IMetaQuery {
return typeof query !== 'object' ? { key: query } : { ...query }; return typeof query !== 'object' ? { key: query } : { ...query };
} }
@@ -141,9 +141,9 @@ export default class MetableStore implements IMetableStore{
* @return {string|number|boolean} - * @return {string|number|boolean} -
*/ */
static formatMetaValue( static formatMetaValue(
value: string|boolean|number, value: string | boolean | number,
valueType: string valueType: string
) : string|number|boolean { ): string | number | boolean {
let parsedValue; let parsedValue;
switch (valueType) { switch (valueType) {
@@ -168,7 +168,9 @@ export default class MetableStore implements IMetableStore{
* @param {Array} collection - * @param {Array} collection -
*/ */
mapMetadataToCollection(metadata: IMetadata[], parseType: string = 'parse') { mapMetadataToCollection(metadata: IMetadata[], parseType: string = 'parse') {
return metadata.map((model) => this.mapMetadataToCollection(model, parseType)); return metadata.map((model) =>
this.mapMetadataToCollection(model, parseType)
);
} }
/** /**
@@ -177,7 +179,9 @@ export default class MetableStore implements IMetableStore{
*/ */
from(meta: []) { from(meta: []) {
if (Array.isArray(meta)) { if (Array.isArray(meta)) {
meta.forEach((m) => { this.from(m); }); meta.forEach((m) => {
this.from(m);
});
return; return;
} }
this.metadata.push(meta); this.metadata.push(meta);

View File

@@ -1,6 +1,6 @@
import { Service, Inject } from 'typedi'; import { Service, Inject } from 'typedi';
import TenancyService from 'services/Tenancy/TenancyService'; import TenancyService from 'services/Tenancy/TenancyService';
import { transactionIncrement } from 'utils'; import { transactionIncrement, parseBoolean } from 'utils';
/** /**
* Auto increment orders service. * Auto increment orders service.
@@ -15,38 +15,18 @@ export default class AutoIncrementOrdersService {
* @param {number} tenantId * @param {number} tenantId
* @param {string} settingsGroup * @param {string} settingsGroup
* @param {Function} getMaxTransactionNo * @param {Function} getMaxTransactionNo
* @return {Promise<[string, string]>} * @return {Promise<string>}
*/ */
async getNextTransactionNumber( getNextTransactionNumber(tenantId: number, settingsGroup: string): string {
tenantId: number,
settingsGroup: string,
getOrderTransaction: (prefix: string, number: string) => Promise<boolean>,
getMaxTransactionNumber: (prefix: string, number: string) => Promise<string>
): Promise<[string, string]> {
const settings = this.tenancy.settings(tenantId); const settings = this.tenancy.settings(tenantId);
const group = settingsGroup; const group = settingsGroup;
// Settings service transaction number and prefix. // Settings service transaction number and prefix.
const settingNo = settings.get({ group, key: 'next_number' }); const autoIncrement = settings.get({ group, key: 'auto_increment' }, false);
const settingPrefix = settings.get({ group, key: 'number_prefix' }); const settingNo = settings.get({ group, key: 'next_number' }, '');
const settingPrefix = settings.get({ group, key: 'number_prefix' }, '');
let nextInvoiceNumber = settingNo; return parseBoolean(autoIncrement, false) ? `${settingPrefix}${settingNo}` : '';
const orderTransaction = await getOrderTransaction(
settingPrefix,
settingNo
);
if (orderTransaction) {
// Retrieve the max invoice number in the given prefix.
const maxInvoiceNo = await getMaxTransactionNumber(
settingPrefix,
settingNo
);
if (maxInvoiceNo) {
nextInvoiceNumber = transactionIncrement(maxInvoiceNo);
}
}
return [settingPrefix, nextInvoiceNumber];
} }
/** /**
@@ -55,16 +35,17 @@ export default class AutoIncrementOrdersService {
* @param {string} orderGroup - Order group. * @param {string} orderGroup - Order group.
* @param {string} orderNumber -Order number. * @param {string} orderNumber -Order number.
*/ */
async incrementSettingsNextNumber( async incrementSettingsNextNumber(tenantId: number, group: string) {
tenantId,
orderGroup: string,
orderNumber: string
) {
const settings = this.tenancy.settings(tenantId); const settings = this.tenancy.settings(tenantId);
const settingNo = settings.get({ group, key: 'next_number' });
const autoIncrement = settings.get({ group, key: 'auto_increment' });
// Can't continue if the auto-increment of the service was disabled.
if (!autoIncrement) return;
settings.set( settings.set(
{ group: orderGroup, key: 'next_number' }, { group, key: 'next_number' },
transactionIncrement(orderNumber) transactionIncrement(settingNo)
); );
await settings.save(); await settings.save();
} }

View File

@@ -31,6 +31,7 @@ import CustomersService from 'services/Contacts/CustomersService';
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 { ACCOUNT_PARENT_TYPE } from 'data/AccountTypes'; import { ACCOUNT_PARENT_TYPE } from 'data/AccountTypes';
import AutoIncrementOrdersService from './AutoIncrementOrdersService';
const ERRORS = { const ERRORS = {
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS', PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
@@ -41,6 +42,7 @@ 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 service. * Payment receive service.
@@ -66,6 +68,9 @@ export default class PaymentReceiveService {
@Inject() @Inject()
dynamicListService: DynamicListingService; dynamicListService: DynamicListingService;
@Inject()
autoIncrementOrdersService: AutoIncrementOrdersService;
@Inject('logger') @Inject('logger')
logger: any; logger: any;
@@ -144,7 +149,8 @@ export default class PaymentReceiveService {
/** /**
* Validates the invoices IDs existance. * Validates the invoices IDs existance.
* @param {number} tenantId - * @param {number} tenantId -
* @param {} paymentReceiveEntries - * @param {number} customerId -
* @param {IPaymentReceiveEntryDTO[]} paymentReceiveEntries -
*/ */
async validateInvoicesIDsExistance( async validateInvoicesIDsExistance(
tenantId: number, tenantId: number,
@@ -225,6 +231,61 @@ export default class PaymentReceiveService {
} }
} }
/**
* Retrieve the next unique payment receive number.
* @param {number} tenantId - Tenant id.
* @return {string}
*/
getNextPaymentReceiveNumber(tenantId: number): string {
return this.autoIncrementOrdersService.getNextTransactionNumber(
tenantId,
'payment_receives'
);
}
/**
* Increment the payment receive next number.
* @param {number} tenantId
*/
incrementNextPaymentReceiveNumber(tenantId: number) {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId,
'payment_receives'
);
}
/**
* Validate the payment receive number require.
* @param {IPaymentReceive} paymentReceiveObj
*/
validatePaymentReceiveNoRequire(paymentReceiveObj: IPaymentReceive) {
if (!paymentReceiveObj.paymentReceiveNo) {
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NO_IS_REQUIRED);
}
}
/**
* 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
@@ -246,7 +307,6 @@ export default class PaymentReceiveService {
'payment_receive_id', 'payment_receive_id',
paymentReceiveId paymentReceiveId
); );
const storedEntriesIds = storedEntries.map((entry: any) => entry.id); const storedEntriesIds = storedEntries.map((entry: any) => entry.id);
const notFoundEntriesIds = difference(entriesIds, storedEntriesIds); const notFoundEntriesIds = difference(entriesIds, storedEntriesIds);
@@ -255,6 +315,36 @@ export default class PaymentReceiveService {
} }
} }
/**
* Transformes the create payment receive DTO to model object.
* @param {number} tenantId
* @param {IPaymentReceiveCreateDTO} paymentReceiveDTO
*/
transformPaymentReceiveDTOToModel(
tenantId: number,
paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO,
oldPaymentReceive?: IPaymentReceive
): IPaymentReceive {
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
// Retrieve the next payment receive number.
const paymentReceiveNo = this.transformPaymentNumberToModel(
tenantId,
paymentReceiveDTO,
oldPaymentReceive
);
return {
amount: paymentAmount,
...formatDateFields(omit(paymentReceiveDTO, ['entries']), [
'paymentDate',
]),
...(paymentReceiveNo ? { paymentReceiveNo } : {}),
entries: paymentReceiveDTO.entries.map((entry) => ({
...omit(entry, ['id']),
})),
};
}
/** /**
* Creates a new payment receive and store it to the storage * Creates a new payment receive and store it to the storage
* with associated invoices payment and journal transactions. * with associated invoices payment and journal transactions.
@@ -268,34 +358,36 @@ export default class PaymentReceiveService {
authorizedUser: ISystemUser authorizedUser: ISystemUser
) { ) {
const { PaymentReceive } = this.tenancy.models(tenantId); const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
// Transformes the payment receive DTO to model.
const paymentReceiveObj = this.transformPaymentReceiveDTOToModel(
tenantId,
paymentReceiveDTO
);
// Validate payment receive is required.
this.validatePaymentReceiveNoRequire(paymentReceiveObj);
// Validate payment receive number uniquiness. // Validate payment receive number uniquiness.
if (paymentReceiveDTO.paymentReceiveNo) { await this.validatePaymentReceiveNoExistance(
await this.validatePaymentReceiveNoExistance( tenantId,
tenantId, paymentReceiveObj.paymentReceiveNo
paymentReceiveDTO.paymentReceiveNo );
);
}
// Validate customer existance. // Validate customer existance.
await this.customersService.getCustomerByIdOrThrowError( await this.customersService.getCustomerByIdOrThrowError(
tenantId, tenantId,
paymentReceiveDTO.customerId paymentReceiveDTO.customerId
); );
// Validate the deposit account existance and type. // Validate the deposit account existance and type.
await this.getDepositAccountOrThrowError( await this.getDepositAccountOrThrowError(
tenantId, tenantId,
paymentReceiveDTO.depositAccountId paymentReceiveDTO.depositAccountId
); );
// Validate payment receive invoices IDs existance. // Validate payment receive invoices IDs existance.
await this.validateInvoicesIDsExistance( await this.validateInvoicesIDsExistance(
tenantId, tenantId,
paymentReceiveDTO.customerId, paymentReceiveDTO.customerId,
paymentReceiveDTO.entries paymentReceiveDTO.entries
); );
// Validate invoice payment amount. // Validate invoice payment amount.
await this.validateInvoicesPaymentsAmount( await this.validateInvoicesPaymentsAmount(
tenantId, tenantId,
@@ -304,15 +396,9 @@ export default class PaymentReceiveService {
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({
amount: paymentAmount, ...paymentReceiveObj,
...formatDateFields(omit(paymentReceiveDTO, ['entries']), [
'paymentDate',
]),
entries: paymentReceiveDTO.entries.map((entry) => ({
...omit(entry, ['id']),
})),
}); });
// Triggers `onPaymentReceiveCreated` event.
await this.eventDispatcher.dispatch(events.paymentReceive.onCreated, { await this.eventDispatcher.dispatch(events.paymentReceive.onCreated, {
tenantId, tenantId,
paymentReceive, paymentReceive,
@@ -349,19 +435,26 @@ export default class PaymentReceiveService {
authorizedUser: ISystemUser authorizedUser: ISystemUser
) { ) {
const { PaymentReceive } = this.tenancy.models(tenantId); const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount');
this.logger.info('[payment_receive] trying to edit payment receive.', { this.logger.info('[payment_receive] trying to edit payment receive.', {
tenantId, tenantId,
paymentReceiveId, paymentReceiveId,
paymentReceiveDTO, paymentReceiveDTO,
}); });
// Validate the payment receive existance. // Validate the payment receive existance.
const oldPaymentReceive = await this.getPaymentReceiveOrThrowError( const oldPaymentReceive = await this.getPaymentReceiveOrThrowError(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
); );
// Transformes the payment receive DTO to model.
const paymentReceiveObj = this.transformPaymentReceiveDTOToModel(
tenantId,
paymentReceiveDTO,
oldPaymentReceive
);
// Validate payment receive number existance.
this.validatePaymentReceiveNoRequire(paymentReceiveObj);
// Validate payment receive number uniquiness. // Validate payment receive number uniquiness.
if (paymentReceiveDTO.paymentReceiveNo) { if (paymentReceiveDTO.paymentReceiveNo) {
await this.validatePaymentReceiveNoExistance( await this.validatePaymentReceiveNoExistance(
@@ -375,7 +468,6 @@ export default class PaymentReceiveService {
tenantId, tenantId,
paymentReceiveDTO.depositAccountId paymentReceiveDTO.depositAccountId
); );
// Validate the entries ids existance on payment receive type. // Validate the entries ids existance on payment receive type.
await this.validateEntriesIdsExistance( await this.validateEntriesIdsExistance(
tenantId, tenantId,
@@ -397,11 +489,7 @@ export default class PaymentReceiveService {
// Update the payment receive transaction. // Update the payment receive transaction.
const paymentReceive = await PaymentReceive.query().upsertGraphAndFetch({ const paymentReceive = await PaymentReceive.query().upsertGraphAndFetch({
id: paymentReceiveId, id: paymentReceiveId,
amount: paymentAmount, ...paymentReceiveObj,
...formatDateFields(omit(paymentReceiveDTO, ['entries']), [
'paymentDate',
]),
entries: paymentReceiveDTO.entries,
}); });
await this.eventDispatcher.dispatch(events.paymentReceive.onEdited, { await this.eventDispatcher.dispatch(events.paymentReceive.onEdited, {

View File

@@ -19,6 +19,7 @@ import events from 'subscribers/events';
import { ServiceError } from 'exceptions'; import { ServiceError } from 'exceptions';
import CustomersService from 'services/Contacts/CustomersService'; import CustomersService from 'services/Contacts/CustomersService';
import moment from 'moment'; import moment from 'moment';
import AutoIncrementOrdersService from './AutoIncrementOrdersService';
const ERRORS = { const ERRORS = {
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND', SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND',
@@ -30,6 +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'
}; };
/** /**
@@ -56,6 +58,9 @@ export default class SaleEstimateService {
@EventDispatcher() @EventDispatcher()
eventDispatcher: EventDispatcherInterface; eventDispatcher: EventDispatcherInterface;
@Inject()
autoIncrementOrdersService: AutoIncrementOrdersService;
/** /**
* Retrieve sale estimate or throw service error. * Retrieve sale estimate or throw service error.
* @param {number} tenantId * @param {number} tenantId
@@ -108,6 +113,49 @@ export default class SaleEstimateService {
} }
} }
/**
* Retrieve the next unique estimate number.
* @param {number} tenantId - Tenant id.
* @return {string}
*/
getNextEstimateNumber(tenantId: number): string {
return this.autoIncrementOrdersService.getNextTransactionNumber(
tenantId,
'sales_estimates'
);
}
/**
* Increment the estimate next number.
* @param {number} tenantId -
*/
incrementNextEstimateNumber(tenantId: number) {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId,
'sales_estimates'
);
}
/**
* Retrieve estimate number to object model.
* @param {number} tenantId
* @param {ISaleEstimateDTO} saleEstimateDTO
* @param {ISaleEstimate} oldSaleEstimate
*/
transformEstimateNumberToModel(
tenantId: number,
saleEstimateDTO: ISaleEstimateDTO,
oldSaleEstimate?: ISaleEstimate
): string {
// Retreive the next invoice number.
const autoNextNumber = this.getNextEstimateNumber(tenantId);
if (saleEstimateDTO.estimateNumber) {
return saleEstimateDTO.estimateNumber;
}
return oldSaleEstimate ? oldSaleEstimate.estimateNumber : autoNextNumber;
}
/** /**
* Transform DTO object ot model object. * Transform DTO object ot model object.
* @param {number} tenantId * @param {number} tenantId
@@ -123,17 +171,24 @@ export default class SaleEstimateService {
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 estimate number.
const estimateNumber = this.transformEstimateNumberToModel(
tenantId,
estimateDTO,
oldSaleEstimate
);
return { return {
amount, amount,
...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [ ...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [
'estimateDate', 'estimateDate',
'expirationDate', 'expirationDate',
]), ]),
...(estimateNumber ? { estimateNumber } : {}),
entries: estimateDTO.entries.map((entry) => ({ entries: estimateDTO.entries.map((entry) => ({
reference_type: 'SaleEstimate', reference_type: 'SaleEstimate',
...omit(entry, ['total', 'amount', 'id']), ...omit(entry, ['total', 'amount', 'id']),
})), })),
// 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 && {
@@ -142,6 +197,16 @@ export default class SaleEstimateService {
}; };
} }
/**
* Validate the sale estimate number require.
* @param {ISaleEstimate} saleInvoiceObj
*/
validateEstimateNoRequire(saleInvoiceObj: ISaleEstimate) {
if (!saleInvoiceObj.estimateNumber) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NO_IS_REQUIRED);
}
}
/** /**
* Creates a new estimate with associated entries. * Creates a new estimate with associated entries.
* @async * @async
@@ -160,11 +225,14 @@ export default class SaleEstimateService {
// Transform DTO object ot model object. // Transform DTO object ot model object.
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO); const estimateObj = this.transformDTOToModel(tenantId, estimateDTO);
// 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 (estimateObj.estimateNumber) {
await this.validateEstimateNumberExistance( await this.validateEstimateNumberExistance(
tenantId, tenantId,
estimateDTO.estimateNumber estimateObj.estimateNumber
); );
} }
// Retrieve the given customer or throw not found service error. // Retrieve the given customer or throw not found service error.
@@ -221,6 +289,9 @@ export default class SaleEstimateService {
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(

View File

@@ -162,28 +162,44 @@ export default class SaleInvoicesService {
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @return {string} * @return {string}
*/ */
async getNextInvoiceNumber(tenantId: number): Promise<[string, string]> { getNextInvoiceNumber(tenantId: number): string {
const { SaleInvoice } = this.tenancy.models(tenantId);
// Retrieve the max invoice number in the given prefix.
const getMaxInvoicesNo = (prefix, number) => {
return SaleInvoice.query()
.modify('maxInvoiceNo', prefix, number)
.then((res) => res?.invNumber);
};
// Retrieve the order transaction number by number.
const getTransactionNumber = (prefix, number) => {
return SaleInvoice.query().modify('byPrefixAndNumber', prefix, number);
};
return this.autoIncrementOrdersService.getNextTransactionNumber( return this.autoIncrementOrdersService.getNextTransactionNumber(
tenantId, tenantId,
'sales_invoices', 'sales_invoices'
getTransactionNumber,
getMaxInvoicesNo
); );
} }
/**
* Increment the invoice next number.
* @param {number} tenantId -
*/
incrementNextInvoiceNumber(tenantId: number) {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId,
'sales_invoices'
);
}
/**
* Retrieve invoice number to object model.
* @param tenantId
* @param saleInvoiceDTO
* @param oldSaleInvoice
*/
transformInvoiceNumberToModel(
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. * Transform DTO object to model object.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
@@ -192,33 +208,37 @@ export default class SaleInvoicesService {
transformDTOToModel( transformDTOToModel(
tenantId: number, tenantId: number,
saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO, saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO,
oldSaleInvoice?: ISaleInvoice, oldSaleInvoice?: ISaleInvoice
autoNextNumber?: [string, string] // prefix, number
): 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)
); );
const invoiceNo = this.transformInvoiceNumberToModel(
tenantId,
saleInvoiceDTO,
oldSaleInvoice
);
return { return {
...formatDateFields( ...formatDateFields(
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']), omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']),
['invoiceDate', 'dueDate'] ['invoiceDate', 'dueDate']
), ),
// Avoid rewrite the deliver date in edit mode when already published. // Avoid rewrite the deliver date in edit mode when already published.
balance,
...(saleInvoiceDTO.delivered && ...(saleInvoiceDTO.delivered &&
!oldSaleInvoice?.deliveredAt && { !oldSaleInvoice?.deliveredAt && {
deliveredAt: moment().toMySqlDateTime(), deliveredAt: moment().toMySqlDateTime(),
}), }),
balance, // Avoid add payment amount in edit mode.
paymentAmount: 0, ...(!oldSaleInvoice
...(saleInvoiceDTO.invoiceNo || autoNextNumber
? { ? {
invoiceNo: saleInvoiceDTO.invoiceNo paymentAmount: 0,
? saleInvoiceDTO.invoiceNo
: join(autoNextNumber, ''),
} }
: {}), : {}),
...(invoiceNo ? { invoiceNo } : {}),
entries: saleInvoiceDTO.entries.map((entry) => ({ entries: saleInvoiceDTO.entries.map((entry) => ({
referenceType: 'SaleInvoice', referenceType: 'SaleInvoice',
...omit(entry, ['amount', 'id']), ...omit(entry, ['amount', 'id']),
@@ -226,6 +246,16 @@ export default class SaleInvoicesService {
}; };
} }
/**
* Validate the invoice number require.
* @param {ISaleInvoice} saleInvoiceObj
*/
validateInvoiceNoRequire(saleInvoiceObj: ISaleInvoice) {
if (!saleInvoiceObj.invoiceNo) {
throw new ServiceError(ERRORS.SALE_INVOICE_NO_IS_REQUIRED);
}
}
/** /**
* Creates a new sale invoices and store it to the storage * Creates a new sale invoices and store it to the storage
* with associated to entries and journal transactions. * with associated to entries and journal transactions.
@@ -241,28 +271,25 @@ export default class SaleInvoicesService {
): Promise<ISaleInvoice> { ): Promise<ISaleInvoice> {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
// The next invoice number automattically or manually.
const autoNextNumber = !saleInvoiceDTO.invoiceNo
? await this.getNextInvoiceNumber(tenantId)
: null;
// Transform DTO object to model object. // Transform DTO object to model object.
const saleInvoiceObj = this.transformDTOToModel( const saleInvoiceObj = this.transformDTOToModel(
tenantId, tenantId,
saleInvoiceDTO, saleInvoiceDTO,
null, null
autoNextNumber
); );
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 (saleInvoiceDTO.invoiceNo) { if (saleInvoiceObj.invoiceNo) {
await this.validateInvoiceNumberUnique( await this.validateInvoiceNumberUnique(
tenantId, tenantId,
saleInvoiceDTO.invoiceNo saleInvoiceObj.invoiceNo
); );
} }
// Validate the from estimate id exists on the storage. // Validate the from estimate id exists on the storage.
@@ -296,7 +323,6 @@ export default class SaleInvoicesService {
saleInvoiceDTO, saleInvoiceDTO,
saleInvoiceId: saleInvoice.id, saleInvoiceId: saleInvoice.id,
authorizedUser, authorizedUser,
autoNextNumber,
}); });
this.logger.info('[sale_invoice] successfully inserted.', { this.logger.info('[sale_invoice] successfully inserted.', {
tenantId, tenantId,
@@ -317,11 +343,12 @@ export default class SaleInvoicesService {
public async editSaleInvoice( public async editSaleInvoice(
tenantId: number, tenantId: number,
saleInvoiceId: number, saleInvoiceId: number,
saleInvoiceDTO: any, saleInvoiceDTO: ISaleInvoiceEditDTO,
authorizedUser: ISystemUser authorizedUser: ISystemUser
): Promise<ISaleInvoice> { ): Promise<ISaleInvoice> {
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId); const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
// Retrieve the sale invoice or throw not found service error.
const oldSaleInvoice = await this.getInvoiceOrThrowError( const oldSaleInvoice = await this.getInvoiceOrThrowError(
tenantId, tenantId,
saleInvoiceId saleInvoiceId

View File

@@ -19,6 +19,7 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import { ItemEntry } from 'models'; 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';
const ERRORS = { const ERRORS = {
SALE_RECEIPT_NOT_FOUND: 'SALE_RECEIPT_NOT_FOUND', SALE_RECEIPT_NOT_FOUND: 'SALE_RECEIPT_NOT_FOUND',
@@ -26,6 +27,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'
}; };
@Service() @Service()
@@ -51,6 +53,9 @@ export default class SalesReceiptService {
@Inject('logger') @Inject('logger')
logger: any; logger: any;
@Inject()
autoIncrementOrdersService: AutoIncrementOrdersService;
/** /**
* Validate whether sale receipt exists on the storage. * Validate whether sale receipt exists on the storage.
* @param {number} tenantId - * @param {number} tenantId -
@@ -130,6 +135,59 @@ export default class SalesReceiptService {
} }
} }
/**
* Validate the sale receipt number require.
* @param {ISaleReceipt} saleReceipt
*/
validateReceiptNoRequire(saleReceipt: ISaleReceipt) {
if (!saleReceipt.receiptNumber) {
throw new ServiceError(ERRORS.SALE_RECEIPT_NO_IS_REQUIRED);
}
}
/**
* Retrieve the next unique receipt number.
* @param {number} tenantId - Tenant id.
* @return {string}
*/
getNextReceiptNumber(tenantId: number): string {
return this.autoIncrementOrdersService.getNextTransactionNumber(
tenantId,
'sales_receipts'
);
}
/**
* Increment the receipt next number.
* @param {number} tenantId -
*/
incrementNextReceiptNumber(tenantId: number) {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId,
'sales_receipts'
);
}
/**
* Retrieve estimate number to object model.
* @param {number} tenantId
* @param {ISaleReceiptDTO} saleReceiptDTO - Sale receipt DTO.
* @param {ISaleReceipt} oldSaleReceipt - Old receipt model object.
*/
transformReceiptNumberToModel(
tenantId: number,
saleReceiptDTO: ISaleReceiptDTO,
oldSaleReceipt?: ISaleReceipt
): string {
// Retreive the next invoice number.
const autoNextNumber = this.getNextReceiptNumber(tenantId);
if (saleReceiptDTO.receiptNumber) {
return saleReceiptDTO.receiptNumber;
}
return oldSaleReceipt ? oldSaleReceipt.receiptNumber : autoNextNumber;
}
/** /**
* Transform DTO object to model object. * Transform DTO object to model object.
* @param {ISaleReceiptDTO} saleReceiptDTO - * @param {ISaleReceiptDTO} saleReceiptDTO -
@@ -137,18 +195,26 @@ export default class SalesReceiptService {
* @returns {ISaleReceipt} * @returns {ISaleReceipt}
*/ */
transformObjectDTOToModel( transformObjectDTOToModel(
tenantId: number,
saleReceiptDTO: ISaleReceiptDTO, saleReceiptDTO: ISaleReceiptDTO,
oldSaleReceipt?: ISaleReceipt oldSaleReceipt?: ISaleReceipt
): ISaleReceipt { ): ISaleReceipt {
const amount = sumBy(saleReceiptDTO.entries, (e) => const amount = sumBy(saleReceiptDTO.entries, (e) =>
ItemEntry.calcAmount(e) ItemEntry.calcAmount(e)
); );
// Retreive the receipt number.
const receiptNumber = this.transformReceiptNumberToModel(
tenantId,
saleReceiptDTO,
oldSaleReceipt
);
return { return {
amount, amount,
...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [ ...formatDateFields(omit(saleReceiptDTO, ['closed', 'entries']), [
'receiptDate', 'receiptDate',
]), ]),
...(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 && {
@@ -174,7 +240,12 @@ 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(saleReceiptDTO); const saleReceiptObj = this.transformObjectDTOToModel(
tenantId,
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(
@@ -238,9 +309,13 @@ export default class SalesReceiptService {
); );
// Transform sale receipt DTO to model. // Transform sale receipt DTO to model.
const saleReceiptObj = this.transformObjectDTOToModel( const saleReceiptObj = this.transformObjectDTOToModel(
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,
@@ -309,7 +384,7 @@ export default class SalesReceiptService {
await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted, { await this.eventDispatcher.dispatch(events.saleReceipt.onDeleted, {
tenantId, tenantId,
saleReceiptId, saleReceiptId,
oldSaleReceipt oldSaleReceipt,
}); });
} }
@@ -463,7 +538,7 @@ export default class SalesReceiptService {
'SaleReceipt', 'SaleReceipt',
saleReceipt.receiptDate, saleReceipt.receiptDate,
'OUT', 'OUT',
override, override
); );
} }

View File

@@ -9,4 +9,5 @@ export const ERRORS = {
'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT', 'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT',
INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES: INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES:
'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES', 'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES',
SALE_INVOICE_NO_IS_REQUIRED: 'SALE_INVOICE_NO_IS_REQUIRED'
}; };

View File

@@ -50,17 +50,9 @@ export default class SaleInvoiceSubscriber {
@On(events.saleInvoice.onCreated) @On(events.saleInvoice.onCreated)
public async handleInvoiceNextNumberIncrement({ public async handleInvoiceNextNumberIncrement({
tenantId, tenantId,
saleInvoiceId,
saleInvoice,
saleInvoiceDTO,
autoNextNumber,
}) { }) {
if (saleInvoiceDTO.invoiceNo || !autoNextNumber) return; await this.saleInvoicesService.incrementNextInvoiceNumber(
await this.saleInvoicesService.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId, tenantId,
'sales_invoices',
autoNextNumber[1]
); );
} }
} }

View File

@@ -130,9 +130,6 @@ export default class PaymentReceivesSubscriber {
tenantId, tenantId,
paymentReceiveId, paymentReceiveId,
}) { }) {
await this.settingsService.incrementNextNumber(tenantId, { await this.paymentReceivesService.incrementNextPaymentReceiveNumber(tenantId);
key: 'next_number',
group: 'payment_receives',
});
} }
} }

View File

@@ -289,6 +289,30 @@ const transformToMap = (objects, key) => {
const transactionIncrement = (s) => s.replace(/([0-8]|\d?9+)?$/, (e) => ++e); const transactionIncrement = (s) => s.replace(/([0-8]|\d?9+)?$/, (e) => ++e);
const booleanValuesRepresentingTrue: string[] = [
'true',
'1',
];
const booleanValuesRepresentingFalse: string[] = [
'false',
'0',
];
const normalizeValue = (value: any): string => value.toString().trim().toLowerCase();
const booleanValues: string[] = [
...booleanValuesRepresentingTrue,
...booleanValuesRepresentingFalse,
].map((value) => normalizeValue(value));
export const parseBoolean = <T>(value: any, defaultValue: T): T | boolean => {
const normalizedValue = normalizeValue(value);
if (booleanValues.indexOf(normalizedValue) === -1) {
return defaultValue;
}
return booleanValuesRepresentingTrue.indexOf(normalizedValue) !== -1;
};
export { export {
hashPassword, hashPassword,
origin, origin,