mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 22:30:31 +00:00
fix: handle make journal errors with contacts.
This commit is contained in:
@@ -12,6 +12,7 @@ const ERROR = {
|
|||||||
RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS: 'RECEIVABLE.ENTRIES.HAS.NO.CUSTOMERS',
|
RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS: 'RECEIVABLE.ENTRIES.HAS.NO.CUSTOMERS',
|
||||||
CREDIT_DEBIT_SUMATION_SHOULD_NOT_EQUAL_ZERO:
|
CREDIT_DEBIT_SUMATION_SHOULD_NOT_EQUAL_ZERO:
|
||||||
'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO',
|
'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO',
|
||||||
|
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Transform API errors in toasts messages.
|
// Transform API errors in toasts messages.
|
||||||
@@ -27,14 +28,6 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
|
|||||||
newErrors = setWith(newErrors, `entries.[${index}].${prop}`, message);
|
newErrors = setWith(newErrors, `entries.[${index}].${prop}`, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((error = getError(ERROR.PAYABLE_ENTRIES_HAS_NO_VENDORS))) {
|
|
||||||
toastMessages.push(
|
|
||||||
formatMessage({
|
|
||||||
id: 'vendors_should_selected_with_payable_account_only',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
setEntriesErrors(error.indexes, 'contact_id', 'error');
|
|
||||||
}
|
|
||||||
if ((error = getError(ERROR.RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS))) {
|
if ((error = getError(ERROR.RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS))) {
|
||||||
toastMessages.push(
|
toastMessages.push(
|
||||||
formatMessage({
|
formatMessage({
|
||||||
@@ -43,21 +36,20 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
|
|||||||
);
|
);
|
||||||
setEntriesErrors(error.indexes, 'contact_id', 'error');
|
setEntriesErrors(error.indexes, 'contact_id', 'error');
|
||||||
}
|
}
|
||||||
if ((error = getError(ERROR.CUSTOMERS_NOT_WITH_RECEVIABLE_ACC))) {
|
if ((error = getError(ERROR.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT))) {
|
||||||
toastMessages.push(
|
if (error.meta.contact_type === 'customer') {
|
||||||
formatMessage({
|
toastMessages.push(
|
||||||
id: 'customers_should_selected_with_receivable_account_only',
|
formatMessage({
|
||||||
}),
|
id: 'receivable_accounts_should_assign_with_customers',
|
||||||
);
|
}),
|
||||||
setEntriesErrors(error.indexes, 'account_id', 'error');
|
);
|
||||||
}
|
}
|
||||||
if ((error = getError(ERROR.VENDORS_NOT_WITH_PAYABLE_ACCOUNT))) {
|
if (error.meta.contact_type === 'vendor') {
|
||||||
toastMessages.push(
|
toastMessages.push(
|
||||||
formatMessage({
|
formatMessage({ id: 'payable_accounts_should_assign_with_vendors' }),
|
||||||
id: 'vendors_should_selected_with_payable_account_only',
|
);
|
||||||
}),
|
}
|
||||||
);
|
setEntriesErrors(error.meta.indexes, 'contact_id', 'error');
|
||||||
setEntriesErrors(error.indexes, 'account_id', 'error');
|
|
||||||
}
|
}
|
||||||
if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) {
|
if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) {
|
||||||
newErrors = setWith(
|
newErrors = setWith(
|
||||||
@@ -78,4 +70,4 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
|
|||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getManualJournalsList.bind(this)),
|
asyncMiddleware(this.getManualJournalsList.bind(this)),
|
||||||
this.dynamicListService.handlerErrorsToResponse,
|
this.dynamicListService.handlerErrorsToResponse,
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
asyncMiddleware(this.getManualJournal.bind(this)),
|
asyncMiddleware(this.getManualJournal.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/publish', [
|
'/publish', [
|
||||||
@@ -43,7 +43,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.publishManualJournals.bind(this)),
|
asyncMiddleware(this.publishManualJournals.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/publish', [
|
'/:id/publish', [
|
||||||
@@ -51,7 +51,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.publishManualJournal.bind(this)),
|
asyncMiddleware(this.publishManualJournal.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id', [
|
'/:id', [
|
||||||
@@ -60,7 +60,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editManualJournal.bind(this)),
|
asyncMiddleware(this.editManualJournal.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id', [
|
'/:id', [
|
||||||
@@ -68,7 +68,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deleteManualJournal.bind(this)),
|
asyncMiddleware(this.deleteManualJournal.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/', [
|
'/', [
|
||||||
@@ -76,7 +76,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deleteBulkManualJournals.bind(this)),
|
asyncMiddleware(this.deleteBulkManualJournals.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/', [
|
'/', [
|
||||||
@@ -84,7 +84,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.makeJournalEntries.bind(this)),
|
asyncMiddleware(this.makeJournalEntries.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors.bind(this),
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
.escape()
|
.escape()
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
check('description')
|
check('description')
|
||||||
.optional()
|
.optional({ nullable: true })
|
||||||
.isString()
|
.isString()
|
||||||
.trim()
|
.trim()
|
||||||
.escape()
|
.escape()
|
||||||
@@ -153,7 +153,7 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.account_id').isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(),
|
check('entries.*.account_id').isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(),
|
||||||
check('entries.*.note')
|
check('entries.*.note')
|
||||||
.optional()
|
.optional({ nullable: true })
|
||||||
.isString()
|
.isString()
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
check('entries.*.contact_id')
|
check('entries.*.contact_id')
|
||||||
@@ -368,43 +368,45 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
if (error.errorType === 'credit_debit_not_equal_zero') {
|
if (error.errorType === 'credit_debit_not_equal_zero') {
|
||||||
return res.boom.badRequest(
|
return res.boom.badRequest(
|
||||||
'Credit and debit should not be equal zero.',
|
'Credit and debit should not be equal zero.',
|
||||||
{ errors: [{ type: 'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO', code: 400, }] }
|
{ errors: [{ type: 'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO', code: 200, }] }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (error.errorType === 'credit_debit_not_equal') {
|
if (error.errorType === 'credit_debit_not_equal') {
|
||||||
return res.boom.badRequest(
|
return res.boom.badRequest(
|
||||||
'Credit and debit should be equal.',
|
'Credit and debit should be equal.',
|
||||||
{ errors: [{ type: 'CREDIT.DEBIT.NOT.EQUALS', code: 100 }] }
|
{ errors: [{ type: 'CREDIT.DEBIT.NOT.EQUALS', code: 300 }] }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (error.errorType === 'acccounts_ids_not_found') {
|
if (error.errorType === 'acccounts_ids_not_found') {
|
||||||
return res.boom.badRequest(
|
return res.boom.badRequest(
|
||||||
'Journal entries some of accounts ids not exists.',
|
'Journal entries some of accounts ids not exists.',
|
||||||
{ errors: [{ type: 'ACCOUNTS.IDS.NOT.FOUND', code: 200 }] }
|
{ errors: [{ type: 'ACCOUNTS.IDS.NOT.FOUND', code: 400 }] }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (error.errorType === 'journal_number_exists') {
|
if (error.errorType === 'journal_number_exists') {
|
||||||
return res.boom.badRequest(
|
return res.boom.badRequest(
|
||||||
'Journal number should be unique.',
|
'Journal number should be unique.',
|
||||||
{ errors: [{ type: 'JOURNAL.NUMBER.ALREADY.EXISTS', code: 300 }] },
|
{ errors: [{ type: 'JOURNAL.NUMBER.ALREADY.EXISTS', code: 500 }] },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (error.errorType === 'payabel_entries_have_no_vendors') {
|
if (error.errorType === 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT') {
|
||||||
return res.boom.badRequest(
|
return res.boom.badRequest(
|
||||||
'',
|
'',
|
||||||
{ errors: [{ type: '' }] },
|
{
|
||||||
);
|
errors: [
|
||||||
}
|
{
|
||||||
if (error.errorType === 'receivable_entries_have_no_customers') {
|
type: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||||
return res.boom.badRequest(
|
code: 600,
|
||||||
'',
|
meta: this.transfromToResponse(error.payload),
|
||||||
{ errors: [{ type: '' }] },
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (error.errorType === 'contacts_not_found') {
|
if (error.errorType === 'contacts_not_found') {
|
||||||
return res.boom.badRequest(
|
return res.boom.badRequest(
|
||||||
'',
|
'',
|
||||||
{ errors: [{ type: '' }] },
|
{ errors: [{ type: 'CONTACTS_NOT_FOUND', code: 700 }] },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
export default class ServiceError {
|
export default class ServiceError {
|
||||||
errorType: string;
|
errorType: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
payload: any;
|
||||||
|
|
||||||
constructor(errorType: string, message?: string) {
|
constructor(errorType: string, message?: string, payload?: any) {
|
||||||
this.errorType = errorType;
|
this.errorType = errorType;
|
||||||
this.message = message || null;
|
this.message = message || null;
|
||||||
|
|
||||||
|
this.payload = payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { difference, sumBy, omit } from 'lodash';
|
import { difference, sumBy, omit, groupBy } from 'lodash';
|
||||||
import { Service, Inject } from "typedi";
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ServiceError } from "exceptions";
|
import { ServiceError } from 'exceptions';
|
||||||
import {
|
import {
|
||||||
IManualJournalDTO,
|
IManualJournalDTO,
|
||||||
IManualJournalsService,
|
IManualJournalsService,
|
||||||
@@ -27,11 +27,15 @@ const ERRORS = {
|
|||||||
CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal',
|
CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal',
|
||||||
ACCCOUNTS_IDS_NOT_FOUND: 'acccounts_ids_not_found',
|
ACCCOUNTS_IDS_NOT_FOUND: 'acccounts_ids_not_found',
|
||||||
JOURNAL_NUMBER_EXISTS: 'journal_number_exists',
|
JOURNAL_NUMBER_EXISTS: 'journal_number_exists',
|
||||||
RECEIVABLE_ENTRIES_NO_CUSTOMERS: 'receivable_entries_have_no_customers',
|
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||||
PAYABLE_ENTRIES_NO_VENDORS: 'payabel_entries_have_no_vendors',
|
|
||||||
CONTACTS_NOT_FOUND: 'contacts_not_found',
|
CONTACTS_NOT_FOUND: 'contacts_not_found',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CONTACTS_CONFIG = [
|
||||||
|
{ accountBySlug: 'accounts-receivable', contactService: 'customer', assignRequired: false, },
|
||||||
|
{ accountBySlug: 'accounts-payable', contactService: 'vendor', assignRequired: true },
|
||||||
|
];
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ManualJournalsService implements IManualJournalsService {
|
export default class ManualJournalsService implements IManualJournalsService {
|
||||||
@Inject()
|
@Inject()
|
||||||
@@ -48,31 +52,46 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the manual journal existance.
|
* Validates the manual journal existance.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} manualJournalId
|
* @param {number} manualJournalId
|
||||||
*/
|
*/
|
||||||
private async validateManualJournalExistance(tenantId: number, manualJournalId: number) {
|
private async validateManualJournalExistance(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalId: number
|
||||||
|
) {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
this.logger.info('[manual_journal] trying to validate existance.', { tenantId, manualJournalId });
|
this.logger.info('[manual_journal] trying to validate existance.', {
|
||||||
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
|
});
|
||||||
const manualJournal = await ManualJournal.query().findById(manualJournalId);
|
const manualJournal = await ManualJournal.query().findById(manualJournalId);
|
||||||
|
|
||||||
if (!manualJournal) {
|
if (!manualJournal) {
|
||||||
this.logger.warn('[manual_journal] not exists on the storage.', { tenantId, manualJournalId });
|
this.logger.warn('[manual_journal] not exists on the storage.', {
|
||||||
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
|
});
|
||||||
throw new ServiceError(ERRORS.NOT_FOUND);
|
throw new ServiceError(ERRORS.NOT_FOUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate manual journals existance.
|
* Validate manual journals existance.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number[]} manualJournalsIds
|
* @param {number[]} manualJournalsIds
|
||||||
* @throws {ServiceError}
|
* @throws {ServiceError}
|
||||||
*/
|
*/
|
||||||
private async validateManualJournalsExistance(tenantId: number, manualJournalsIds: number[]) {
|
private async validateManualJournalsExistance(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalsIds: number[]
|
||||||
|
) {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const manualJournals = await ManualJournal.query().whereIn('id', manualJournalsIds);
|
const manualJournals = await ManualJournal.query().whereIn(
|
||||||
|
'id',
|
||||||
|
manualJournalsIds
|
||||||
|
);
|
||||||
|
|
||||||
const notFoundManualJournals = difference(
|
const notFoundManualJournals = difference(
|
||||||
manualJournalsIds,
|
manualJournalsIds,
|
||||||
@@ -85,7 +104,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate manual journal credit and debit should be equal.
|
* Validate manual journal credit and debit should be equal.
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
*/
|
*/
|
||||||
private valdiateCreditDebitTotalEquals(manualJournalDTO: IManualJournalDTO) {
|
private valdiateCreditDebitTotalEquals(manualJournalDTO: IManualJournalDTO) {
|
||||||
let totalCredit = 0;
|
let totalCredit = 0;
|
||||||
@@ -100,23 +119,30 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (totalCredit <= 0 || totalDebit <= 0) {
|
if (totalCredit <= 0 || totalDebit <= 0) {
|
||||||
this.logger.info('[manual_journal] the total credit and debit equals zero.');
|
this.logger.info(
|
||||||
|
'[manual_journal] the total credit and debit equals zero.'
|
||||||
|
);
|
||||||
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL_ZERO);
|
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL_ZERO);
|
||||||
}
|
}
|
||||||
if (totalCredit !== totalDebit) {
|
if (totalCredit !== totalDebit) {
|
||||||
this.logger.info('[manual_journal] the total credit not equals total debit.');
|
this.logger.info(
|
||||||
|
'[manual_journal] the total credit not equals total debit.'
|
||||||
|
);
|
||||||
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL);
|
throw new ServiceError(ERRORS.CREDIT_DEBIT_NOT_EQUAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate manual entries accounts existance on the storage.
|
* Validate manual entries accounts existance on the storage.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
*/
|
*/
|
||||||
private async validateAccountsExistance(tenantId: number, manualJournalDTO: IManualJournalDTO) {
|
private async validateAccountsExistance(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalDTO: IManualJournalDTO
|
||||||
|
) {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
const { Account } = this.tenancy.models(tenantId);
|
||||||
const manualAccountsIds = manualJournalDTO.entries.map(e => e.accountId);
|
const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId);
|
||||||
|
|
||||||
const accounts = await Account.query()
|
const accounts = await Account.query()
|
||||||
.whereIn('id', manualAccountsIds)
|
.whereIn('id', manualAccountsIds)
|
||||||
@@ -125,96 +151,146 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
const storedAccountsIds = accounts.map((account) => account.id);
|
const storedAccountsIds = accounts.map((account) => account.id);
|
||||||
|
|
||||||
if (difference(manualAccountsIds, storedAccountsIds).length > 0) {
|
if (difference(manualAccountsIds, storedAccountsIds).length > 0) {
|
||||||
this.logger.info('[manual_journal] some entries accounts not exist.', { tenantId, manualAccountsIds });
|
this.logger.info('[manual_journal] some entries accounts not exist.', {
|
||||||
|
tenantId,
|
||||||
|
manualAccountsIds,
|
||||||
|
});
|
||||||
throw new ServiceError(ERRORS.ACCCOUNTS_IDS_NOT_FOUND);
|
throw new ServiceError(ERRORS.ACCCOUNTS_IDS_NOT_FOUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate manual journal number unique.
|
* Validate manual journal number unique.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
*/
|
*/
|
||||||
private async validateManualJournalNoUnique(tenantId: number, manualJournalDTO: IManualJournalDTO, notId?: numebr) {
|
private async validateManualJournalNoUnique(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalDTO: IManualJournalDTO,
|
||||||
|
notId?: number
|
||||||
|
) {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
const journalNumber = await ManualJournal.query().where(
|
const journalNumber = await ManualJournal.query()
|
||||||
'journal_number',
|
.where('journal_number', manualJournalDTO.journalNumber)
|
||||||
manualJournalDTO.journalNumber,
|
.onBuild((builder) => {
|
||||||
).onBuild((builder) => {
|
if (notId) {
|
||||||
if (notId) {
|
builder.whereNot('id', notId);
|
||||||
builder.whereNot('id', notId);
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
if (journalNumber.length > 0) {
|
if (journalNumber.length > 0) {
|
||||||
throw new ServiceError(ERRORS.JOURNAL_NUMBER_EXISTS);
|
throw new ServiceError(ERRORS.JOURNAL_NUMBER_EXISTS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate entries that have receivable account should have customer type.
|
*
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
|
* @param {string} accountBySlug
|
||||||
|
* @param {string} contactType
|
||||||
*/
|
*/
|
||||||
private async validateReceivableEntries(tenantId: number, manualJournalDTO: IManualJournalDTO): Promise<void> {
|
private async validateAccountsWithContactType(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalDTO: IManualJournalDTO,
|
||||||
|
accountBySlug: string,
|
||||||
|
contactType: string,
|
||||||
|
contactRequired: boolean = true,
|
||||||
|
): Promise<void> {
|
||||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||||
const receivableAccount = await accountRepository.getBySlug('accounts-receivable');
|
const payableAccount = await accountRepository.getBySlug(accountBySlug);
|
||||||
|
|
||||||
const entriesHasNoReceivableAccount = manualJournalDTO.entries.filter(
|
|
||||||
(e) => (e.accountId === receivableAccount.id) &&
|
|
||||||
(!e.contactId || e.contactType !== 'customer')
|
|
||||||
);
|
|
||||||
if (entriesHasNoReceivableAccount.length > 0) {
|
|
||||||
throw new ServiceError(ERRORS.RECEIVABLE_ENTRIES_NO_CUSTOMERS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates payable entries should have vendor type.
|
|
||||||
* @param {number} tenantId -
|
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
|
||||||
*/
|
|
||||||
private async validatePayableEntries(tenantId: number, manualJournalDTO: IManualJournalDTO): Promise<void> {
|
|
||||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
|
||||||
const payableAccount = await accountRepository.getBySlug('accounts-payable');
|
|
||||||
|
|
||||||
const entriesHasNoVendorContact = manualJournalDTO.entries.filter(
|
const entriesHasNoVendorContact = manualJournalDTO.entries.filter(
|
||||||
(e) => (e.accountId === payableAccount.id) &&
|
(e) =>
|
||||||
(!e.contactId || e.contactType !== 'vendor')
|
e.accountId === payableAccount.id &&
|
||||||
|
((!e.contactId && contactRequired) || e.contactType !== contactType)
|
||||||
);
|
);
|
||||||
if (entriesHasNoVendorContact.length > 0) {
|
if (entriesHasNoVendorContact.length > 0) {
|
||||||
throw new ServiceError(ERRORS.PAYABLE_ENTRIES_NO_VENDORS);
|
const indexes = entriesHasNoVendorContact.map(e => e.index);
|
||||||
|
|
||||||
|
throw new ServiceError(ERRORS.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT, '', {
|
||||||
|
contactType,
|
||||||
|
accountBySlug,
|
||||||
|
indexes
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vaplidate entries contacts existance.
|
* Dynamic validates accounts with contacts.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
*/
|
*/
|
||||||
private async validateContactsExistance(tenantId: number, manualJournalDTO: IManualJournalDTO) {
|
private async dynamicValidateAccountsWithContactType(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalDTO: IManualJournalDTO,
|
||||||
|
): Promise<any>{
|
||||||
|
return Promise.all(
|
||||||
|
CONTACTS_CONFIG.map(({ accountBySlug, contactService, assignRequired }) =>
|
||||||
|
this.validateAccountsWithContactType(
|
||||||
|
tenantId,
|
||||||
|
manualJournalDTO,
|
||||||
|
accountBySlug,
|
||||||
|
contactService,
|
||||||
|
assignRequired
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vaplidate entries contacts existance.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
|
*/
|
||||||
|
private async validateContactsExistance(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalDTO: IManualJournalDTO,
|
||||||
|
) {
|
||||||
const { contactRepository } = this.tenancy.repositories(tenantId);
|
const { contactRepository } = this.tenancy.repositories(tenantId);
|
||||||
const manualJCotactsIds = manualJournalDTO.entries
|
|
||||||
.filter((entry) => entry.contactId)
|
|
||||||
.map((entry) => entry.contactId);
|
|
||||||
|
|
||||||
if (manualJCotactsIds.length > 0) {
|
// Filters the entries that have contact only.
|
||||||
const storedContacts = await contactRepository.findByIds(manualJCotactsIds);
|
const entriesContactPairs = manualJournalDTO.entries
|
||||||
const storedContactsIds = storedContacts.map((c) => c.id);
|
.filter((entry) => entry.contactId);
|
||||||
|
|
||||||
const notFoundContactsIds = difference(manualJCotactsIds, storedContactsIds);
|
if (entriesContactPairs.length > 0) {
|
||||||
|
const entriesContactsIds = entriesContactPairs.map(entry => entry.contactId);
|
||||||
|
|
||||||
|
// Retrieve all stored contacts on the storage from contacts entries.
|
||||||
|
const storedContacts = await contactRepository.findByIds(
|
||||||
|
entriesContactsIds,
|
||||||
|
);
|
||||||
|
// Converts the stored contacts to map with id as key and entry as value.
|
||||||
|
const storedContactsMap = new Map(storedContacts.map(contact => [contact.id, contact]));
|
||||||
|
const notFoundContactsIds = [];
|
||||||
|
|
||||||
|
entriesContactPairs.forEach((contactEntry) => {
|
||||||
|
const storedContact = storedContactsMap.get(contactEntry.contactId);
|
||||||
|
|
||||||
|
// in case the contact id not found or contact type no equals pushed to
|
||||||
|
// not found contacts.
|
||||||
|
if (
|
||||||
|
!storedContact ||
|
||||||
|
storedContact.contactService !== contactEntry.contactType
|
||||||
|
) {
|
||||||
|
notFoundContactsIds.push(storedContact);
|
||||||
|
}
|
||||||
|
});
|
||||||
if (notFoundContactsIds.length > 0) {
|
if (notFoundContactsIds.length > 0) {
|
||||||
throw new ServiceError(ERRORS.CONTACTS_NOT_FOUND);
|
throw new ServiceError(ERRORS.CONTACTS_NOT_FOUND, '', {
|
||||||
|
contactsIds: notFoundContactsIds,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform manual journal DTO to graphed model to save it.
|
* Transform manual journal DTO to graphed model to save it.
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
* @param {ISystemUser} authorizedUser
|
* @param {ISystemUser} authorizedUser
|
||||||
*/
|
*/
|
||||||
private transformDTOToModel(manualJournalDTO: IManualJournalDTO, user: ISystemUser): IManualJournal {
|
private transformDTOToModel(
|
||||||
|
manualJournalDTO: IManualJournalDTO,
|
||||||
|
user: ISystemUser
|
||||||
|
): IManualJournal {
|
||||||
const amount = sumBy(manualJournalDTO.entries, 'credit') || 0;
|
const amount = sumBy(manualJournalDTO.entries, 'credit') || 0;
|
||||||
const date = moment(manualJournalDTO.date).format('YYYY-MM-DD');
|
const date = moment(manualJournalDTO.date).format('YYYY-MM-DD');
|
||||||
|
|
||||||
@@ -229,20 +305,20 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform DTO to model.
|
* Transform DTO to model.
|
||||||
* @param {IManualJournalEntryDTO[]} entries
|
* @param {IManualJournalEntryDTO[]} entries
|
||||||
*/
|
*/
|
||||||
private transformDTOToEntriesModel(entries: IManualJournalEntryDTO[]) {
|
private transformDTOToEntriesModel(entries: IManualJournalEntryDTO[]) {
|
||||||
return entries.map((entry: IManualJournalEntryDTO) => ({
|
return entries.map((entry: IManualJournalEntryDTO) => ({
|
||||||
...omit(entry, ['accountId']),
|
...omit(entry, ['accountId']),
|
||||||
account: entry.accountId,
|
account: entry.accountId,
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make journal entries.
|
* Make journal entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IManualJournalDTO} manualJournalDTO
|
* @param {IManualJournalDTO} manualJournalDTO
|
||||||
* @param {ISystemUser} authorizedUser
|
* @param {ISystemUser} authorizedUser
|
||||||
*/
|
*/
|
||||||
public async makeJournalEntries(
|
public async makeJournalEntries(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
@@ -251,38 +327,51 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
): Promise<{ manualJournal: IManualJournal }> {
|
): Promise<{ manualJournal: IManualJournal }> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Validate the total credit should equals debit.
|
||||||
this.valdiateCreditDebitTotalEquals(manualJournalDTO);
|
this.valdiateCreditDebitTotalEquals(manualJournalDTO);
|
||||||
|
|
||||||
await this.validateReceivableEntries(tenantId, manualJournalDTO);
|
// Validate the contacts existance.
|
||||||
await this.validatePayableEntries(tenantId, manualJournalDTO);
|
|
||||||
await this.validateContactsExistance(tenantId, manualJournalDTO);
|
await this.validateContactsExistance(tenantId, manualJournalDTO);
|
||||||
|
|
||||||
|
// Validate entries accounts existance.
|
||||||
await this.validateAccountsExistance(tenantId, manualJournalDTO);
|
await this.validateAccountsExistance(tenantId, manualJournalDTO);
|
||||||
|
|
||||||
|
// Validate manual journal uniquiness on the storage.
|
||||||
await this.validateManualJournalNoUnique(tenantId, manualJournalDTO);
|
await this.validateManualJournalNoUnique(tenantId, manualJournalDTO);
|
||||||
|
|
||||||
this.logger.info('[manual_journal] trying to save manual journal to the storage.', { tenantId, manualJournalDTO });
|
// Validate accounts with contact type from the given config.
|
||||||
const manualJournalObj = this.transformDTOToModel(manualJournalDTO, authorizedUser);
|
await this.dynamicValidateAccountsWithContactType(tenantId, manualJournalDTO);
|
||||||
|
|
||||||
const storedManualJournal = await ManualJournal.query().insert({
|
this.logger.info(
|
||||||
|
'[manual_journal] trying to save manual journal to the storage.',
|
||||||
|
{ tenantId, manualJournalDTO }
|
||||||
|
);
|
||||||
|
const manualJournalObj = this.transformDTOToModel(
|
||||||
|
manualJournalDTO,
|
||||||
|
authorizedUser
|
||||||
|
);
|
||||||
|
const manualJournal = await ManualJournal.query().insertAndFetch({
|
||||||
...omit(manualJournalObj, ['entries']),
|
...omit(manualJournalObj, ['entries']),
|
||||||
});
|
});
|
||||||
const manualJournal: IManualJournal = { ...manualJournalObj, id: storedManualJournal.id };
|
|
||||||
|
|
||||||
// Triggers `onManualJournalCreated` event.
|
// Triggers `onManualJournalCreated` event.
|
||||||
this.eventDispatcher.dispatch(events.manualJournals.onCreated, {
|
this.eventDispatcher.dispatch(events.manualJournals.onCreated, {
|
||||||
tenantId, manualJournal,
|
tenantId,
|
||||||
|
manualJournal,
|
||||||
});
|
});
|
||||||
this.logger.info('[manual_journal] the manual journal inserted successfully.', { tenantId });
|
this.logger.info(
|
||||||
|
'[manual_journal] the manual journal inserted successfully.',
|
||||||
|
{ tenantId }
|
||||||
|
);
|
||||||
|
|
||||||
return { manualJournal };
|
return { manualJournal };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits jouranl entries.
|
* Edits jouranl entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} manualJournalId
|
* @param {number} manualJournalId
|
||||||
* @param {IMakeJournalDTO} manualJournalDTO
|
* @param {IMakeJournalDTO} manualJournalDTO
|
||||||
* @param {ISystemUser} authorizedUser
|
* @param {ISystemUser} authorizedUser
|
||||||
*/
|
*/
|
||||||
public async editJournalEntries(
|
public async editJournalEntries(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
@@ -292,123 +381,200 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
): Promise<{ manualJournal: IManualJournal }> {
|
): Promise<{ manualJournal: IManualJournal }> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Validates the manual journal existance on the storage.
|
||||||
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
||||||
|
|
||||||
|
// Validates the total credit and debit to be equals.
|
||||||
this.valdiateCreditDebitTotalEquals(manualJournalDTO);
|
this.valdiateCreditDebitTotalEquals(manualJournalDTO);
|
||||||
|
|
||||||
|
// Validate the contacts existance.
|
||||||
|
await this.validateContactsExistance(tenantId, manualJournalDTO);
|
||||||
|
|
||||||
|
// Validates entries accounts existance.
|
||||||
await this.validateAccountsExistance(tenantId, manualJournalDTO);
|
await this.validateAccountsExistance(tenantId, manualJournalDTO);
|
||||||
await this.validateManualJournalNoUnique(tenantId, manualJournalDTO, manualJournalId);
|
|
||||||
|
|
||||||
const manualJournalObj = this.transformDTOToModel(manualJournalDTO, authorizedUser);
|
// Validates the manual journal number uniquiness.
|
||||||
|
await this.validateManualJournalNoUnique(
|
||||||
|
tenantId,
|
||||||
|
manualJournalDTO,
|
||||||
|
manualJournalId
|
||||||
|
);
|
||||||
|
// Validate accounts with contact type from the given config.
|
||||||
|
await this.dynamicValidateAccountsWithContactType(tenantId, manualJournalDTO);
|
||||||
|
|
||||||
const storedManualJournal = await ManualJournal.query().where('id', manualJournalId)
|
const manualJournalObj = this.transformDTOToModel(
|
||||||
|
manualJournalDTO,
|
||||||
|
authorizedUser
|
||||||
|
);
|
||||||
|
|
||||||
|
const storedManualJournal = await ManualJournal.query()
|
||||||
|
.where('id', manualJournalId)
|
||||||
.patch({
|
.patch({
|
||||||
...omit(manualJournalObj, ['entries']),
|
...omit(manualJournalObj, ['entries']),
|
||||||
});
|
});
|
||||||
const manualJournal: IManualJournal = { ...manualJournalObj, id: manualJournalId };
|
const manualJournal: IManualJournal = {
|
||||||
|
...manualJournalObj,
|
||||||
|
id: manualJournalId,
|
||||||
|
};
|
||||||
|
|
||||||
// Triggers `onManualJournalEdited` event.
|
// Triggers `onManualJournalEdited` event.
|
||||||
this.eventDispatcher.dispatch(events.manualJournals.onEdited, {
|
this.eventDispatcher.dispatch(events.manualJournals.onEdited, {
|
||||||
tenantId, manualJournal,
|
tenantId,
|
||||||
|
manualJournal,
|
||||||
});
|
});
|
||||||
return { manualJournal };
|
return { manualJournal };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the given manual journal
|
* Deletes the given manual journal
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} manualJournalId
|
* @param {number} manualJournalId
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async deleteManualJournal(tenantId: number, manualJournalId: number): Promise<void> {
|
public async deleteManualJournal(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalId: number
|
||||||
|
): Promise<void> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
||||||
|
|
||||||
this.logger.info('[manual_journal] trying to delete the manual journal.', { tenantId, manualJournalId });
|
this.logger.info('[manual_journal] trying to delete the manual journal.', {
|
||||||
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
|
});
|
||||||
await ManualJournal.query().findById(manualJournalId).delete();
|
await ManualJournal.query().findById(manualJournalId).delete();
|
||||||
|
|
||||||
// Triggers `onManualJournalDeleted` event.
|
// Triggers `onManualJournalDeleted` event.
|
||||||
this.eventDispatcher.dispatch(events.manualJournals.onDeleted, {
|
this.eventDispatcher.dispatch(events.manualJournals.onDeleted, {
|
||||||
tenantId, manualJournalId,
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
});
|
});
|
||||||
this.logger.info('[manual_journal] the given manual journal deleted successfully.', { tenantId, manualJournalId });
|
this.logger.info(
|
||||||
|
'[manual_journal] the given manual journal deleted successfully.',
|
||||||
|
{ tenantId, manualJournalId }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the given manual journals.
|
* Deletes the given manual journals.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number[]} manualJournalsIds
|
* @param {number[]} manualJournalsIds
|
||||||
* @return {Promise<void>}
|
* @return {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async deleteManualJournals(tenantId: number, manualJournalsIds: number[]): Promise<void> {
|
public async deleteManualJournals(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalsIds: number[]
|
||||||
|
): Promise<void> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
await this.validateManualJournalsExistance(tenantId, manualJournalsIds);
|
await this.validateManualJournalsExistance(tenantId, manualJournalsIds);
|
||||||
|
|
||||||
this.logger.info('[manual_journal] trying to delete the manual journals.', { tenantId, manualJournalsIds });
|
this.logger.info('[manual_journal] trying to delete the manual journals.', {
|
||||||
|
tenantId,
|
||||||
|
manualJournalsIds,
|
||||||
|
});
|
||||||
await ManualJournal.query().whereIn('id', manualJournalsIds).delete();
|
await ManualJournal.query().whereIn('id', manualJournalsIds).delete();
|
||||||
|
|
||||||
// Triggers `onManualJournalDeletedBulk` event.
|
// Triggers `onManualJournalDeletedBulk` event.
|
||||||
this.eventDispatcher.dispatch(events.manualJournals.onDeletedBulk, {
|
this.eventDispatcher.dispatch(events.manualJournals.onDeletedBulk, {
|
||||||
tenantId, manualJournalsIds,
|
tenantId,
|
||||||
|
manualJournalsIds,
|
||||||
});
|
});
|
||||||
this.logger.info('[manual_journal] the given manual journals deleted successfully.', { tenantId, manualJournalsIds });
|
this.logger.info(
|
||||||
|
'[manual_journal] the given manual journals deleted successfully.',
|
||||||
|
{ tenantId, manualJournalsIds }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish the given manual journals.
|
* Publish the given manual journals.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number[]} manualJournalsIds
|
* @param {number[]} manualJournalsIds
|
||||||
*/
|
*/
|
||||||
public async publishManualJournals(tenantId: number, manualJournalsIds: number[]): Promise<void> {
|
public async publishManualJournals(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalsIds: number[]
|
||||||
|
): Promise<void> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
await this.validateManualJournalsExistance(tenantId, manualJournalsIds);
|
await this.validateManualJournalsExistance(tenantId, manualJournalsIds);
|
||||||
|
|
||||||
this.logger.info('[manual_journal] trying to publish the manual journal.', { tenantId, manualJournalsIds });
|
this.logger.info('[manual_journal] trying to publish the manual journal.', {
|
||||||
await ManualJournal.query().whereIn('id', manualJournalsIds).patch({ status: 1, });
|
tenantId,
|
||||||
|
manualJournalsIds,
|
||||||
|
});
|
||||||
|
await ManualJournal.query()
|
||||||
|
.whereIn('id', manualJournalsIds)
|
||||||
|
.patch({ status: 1 });
|
||||||
|
|
||||||
// Triggers `onManualJournalPublishedBulk` event.
|
// Triggers `onManualJournalPublishedBulk` event.
|
||||||
this.eventDispatcher.dispatch(events.manualJournals.onPublishedBulk, {
|
this.eventDispatcher.dispatch(events.manualJournals.onPublishedBulk, {
|
||||||
tenantId, manualJournalsIds,
|
tenantId,
|
||||||
|
manualJournalsIds,
|
||||||
});
|
});
|
||||||
this.logger.info('[manual_journal] the given manula journal published successfully.', { tenantId, manualJournalId });
|
this.logger.info(
|
||||||
|
'[manual_journal] the given manula journal published successfully.',
|
||||||
|
{ tenantId, manualJournalId }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish the given manual journal.
|
* Publish the given manual journal.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} manualJournalId
|
* @param {number} manualJournalId
|
||||||
*/
|
*/
|
||||||
public async publishManualJournal(tenantId: number, manualJournalId: number): Promise<void> {
|
public async publishManualJournal(
|
||||||
|
tenantId: number,
|
||||||
|
manualJournalId: number
|
||||||
|
): Promise<void> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
||||||
|
|
||||||
this.logger.info('[manual_journal] trying to publish the manual journal.', { tenantId, manualJournalId });
|
this.logger.info('[manual_journal] trying to publish the manual journal.', {
|
||||||
await ManualJournal.query().findById(manualJournalId).patch({ status: 1, });
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
|
});
|
||||||
|
await ManualJournal.query().findById(manualJournalId).patch({ status: 1 });
|
||||||
|
|
||||||
// Triggers `onManualJournalPublishedBulk` event.
|
// Triggers `onManualJournalPublishedBulk` event.
|
||||||
this.eventDispatcher.dispatch(events.manualJournals.onPublished, {
|
this.eventDispatcher.dispatch(events.manualJournals.onPublished, {
|
||||||
tenantId, manualJournalId,
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
});
|
});
|
||||||
this.logger.info('[manual_journal] the given manula journal published successfully.', { tenantId, manualJournalId });
|
this.logger.info(
|
||||||
|
'[manual_journal] the given manula journal published successfully.',
|
||||||
|
{ tenantId, manualJournalId }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve manual journals datatable list.
|
* Retrieve manual journals datatable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IManualJournalsFilter} filter
|
* @param {IManualJournalsFilter} filter
|
||||||
*/
|
*/
|
||||||
public async getManualJournals(
|
public async getManualJournals(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: IManualJournalsFilter
|
filter: IManualJournalsFilter
|
||||||
): Promise<{ manualJournals: IManualJournal, pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
|
): Promise<{
|
||||||
|
manualJournals: IManualJournal;
|
||||||
|
pagination: IPaginationMeta;
|
||||||
|
filterMeta: IFilterMeta;
|
||||||
|
}> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
const dynamicList = await this.dynamicListService.dynamicList(tenantId, ManualJournal, filter);
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
ManualJournal,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.info('[manual_journals] trying to get manual journals list.', { tenantId, filter });
|
this.logger.info('[manual_journals] trying to get manual journals list.', {
|
||||||
const { results, pagination } = await ManualJournal.query().onBuild((builder) => {
|
tenantId,
|
||||||
dynamicList.buildQuery()(builder);
|
filter,
|
||||||
builder.withGraphFetched('entries.account');
|
});
|
||||||
}).pagination(filter.page - 1, filter.pageSize);
|
const { results, pagination } = await ManualJournal.query()
|
||||||
|
.onBuild((builder) => {
|
||||||
|
dynamicList.buildQuery()(builder);
|
||||||
|
builder.withGraphFetched('entries.account');
|
||||||
|
})
|
||||||
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
manualJournals: results,
|
manualJournals: results,
|
||||||
@@ -416,18 +582,21 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
filterMeta: dynamicList.getResponseMeta(),
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve manual journal details with assocaited journal transactions.
|
* Retrieve manual journal details with assocaited journal transactions.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} manualJournalId
|
* @param {number} manualJournalId
|
||||||
*/
|
*/
|
||||||
public async getManualJournal(tenantId: number, manualJournalId: number) {
|
public async getManualJournal(tenantId: number, manualJournalId: number) {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
await this.validateManualJournalExistance(tenantId, manualJournalId);
|
||||||
|
|
||||||
this.logger.info('[manual_journals] trying to get specific manual journal.', { tenantId, manualJournalId });
|
this.logger.info(
|
||||||
|
'[manual_journals] trying to get specific manual journal.',
|
||||||
|
{ tenantId, manualJournalId }
|
||||||
|
);
|
||||||
const manualJournal = await ManualJournal.query()
|
const manualJournal = await ManualJournal.query()
|
||||||
.findById(manualJournalId)
|
.findById(manualJournalId)
|
||||||
.withGraphFetched('entries')
|
.withGraphFetched('entries')
|
||||||
@@ -438,33 +607,42 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Write manual journal entries.
|
* Write manual journal entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} manualJournalId
|
* @param {number} manualJournalId
|
||||||
* @param {IManualJournal|null} manualJournalObj
|
* @param {IManualJournal|null} manualJournalObj
|
||||||
* @param {boolean} override
|
* @param {boolean} override
|
||||||
*/
|
*/
|
||||||
public async writeJournalEntries(
|
public async writeJournalEntries(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
manualJournalId: number,
|
manualJournalId: number,
|
||||||
manualJournalObj?: IManualJournal|null,
|
manualJournalObj?: IManualJournal | null,
|
||||||
override?: Boolean,
|
override?: Boolean
|
||||||
) {
|
) {
|
||||||
const journal = new JournalPoster(tenantId);
|
const journal = new JournalPoster(tenantId);
|
||||||
const journalCommands = new JournalCommands(journal);
|
const journalCommands = new JournalCommands(journal);
|
||||||
|
|
||||||
if (override) {
|
if (override) {
|
||||||
this.logger.info('[manual_journal] trying to revert journal entries.', { tenantId, manualJournalId });
|
this.logger.info('[manual_journal] trying to revert journal entries.', {
|
||||||
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
|
});
|
||||||
await journalCommands.revertJournalEntries(manualJournalId, 'Journal');
|
await journalCommands.revertJournalEntries(manualJournalId, 'Journal');
|
||||||
}
|
}
|
||||||
if (manualJournalObj) {
|
if (manualJournalObj) {
|
||||||
journalCommands.manualJournal(manualJournalObj, manualJournalId);
|
journalCommands.manualJournal(manualJournalObj, manualJournalId);
|
||||||
}
|
}
|
||||||
this.logger.info('[manual_journal] trying to save journal entries.', { tenantId, manualJournalId });
|
this.logger.info('[manual_journal] trying to save journal entries.', {
|
||||||
|
tenantId,
|
||||||
|
manualJournalId,
|
||||||
|
});
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
journal.saveBalance(),
|
journal.saveBalance(),
|
||||||
journal.deleteEntries(),
|
journal.deleteEntries(),
|
||||||
journal.saveEntries(),
|
journal.saveEntries(),
|
||||||
]);
|
]);
|
||||||
this.logger.info('[manual_journal] the journal entries saved successfully.', { tenantId, manualJournalId });
|
this.logger.info(
|
||||||
|
'[manual_journal] the journal entries saved successfully.',
|
||||||
|
{ tenantId, manualJournalId }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user