fix: handle make journal errors with contacts.

This commit is contained in:
Ahmed Bouhuolia
2020-11-30 17:56:13 +02:00
parent 5c5a9438ee
commit b5b9764676
4 changed files with 370 additions and 195 deletions

View File

@@ -12,6 +12,7 @@ const ERROR = {
RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS: 'RECEIVABLE.ENTRIES.HAS.NO.CUSTOMERS',
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.
@@ -27,14 +28,6 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
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))) {
toastMessages.push(
formatMessage({
@@ -43,21 +36,20 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
);
setEntriesErrors(error.indexes, 'contact_id', 'error');
}
if ((error = getError(ERROR.CUSTOMERS_NOT_WITH_RECEVIABLE_ACC))) {
toastMessages.push(
formatMessage({
id: 'customers_should_selected_with_receivable_account_only',
}),
);
setEntriesErrors(error.indexes, 'account_id', 'error');
}
if ((error = getError(ERROR.VENDORS_NOT_WITH_PAYABLE_ACCOUNT))) {
toastMessages.push(
formatMessage({
id: 'vendors_should_selected_with_payable_account_only',
}),
);
setEntriesErrors(error.indexes, 'account_id', 'error');
if ((error = getError(ERROR.ENTRIES_SHOULD_ASSIGN_WITH_CONTACT))) {
if (error.meta.contact_type === 'customer') {
toastMessages.push(
formatMessage({
id: 'receivable_accounts_should_assign_with_customers',
}),
);
}
if (error.meta.contact_type === 'vendor') {
toastMessages.push(
formatMessage({ id: 'payable_accounts_should_assign_with_vendors' }),
);
}
setEntriesErrors(error.meta.indexes, 'contact_id', 'error');
}
if ((error = getError(ERROR.JOURNAL_NUMBER_ALREADY_EXISTS))) {
newErrors = setWith(
@@ -78,4 +70,4 @@ export const transformErrors = (resErrors, { setErrors, errors }) => {
intent: Intent.DANGER,
});
}
};
};

View File

@@ -30,12 +30,12 @@ export default class ManualJournalsController extends BaseController {
this.validationResult,
asyncMiddleware(this.getManualJournalsList.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
router.get(
'/:id',
asyncMiddleware(this.getManualJournal.bind(this)),
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
router.post(
'/publish', [
@@ -43,7 +43,7 @@ export default class ManualJournalsController extends BaseController {
],
this.validationResult,
asyncMiddleware(this.publishManualJournals.bind(this)),
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
router.post(
'/:id/publish', [
@@ -51,7 +51,7 @@ export default class ManualJournalsController extends BaseController {
],
this.validationResult,
asyncMiddleware(this.publishManualJournal.bind(this)),
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
router.post(
'/:id', [
@@ -60,7 +60,7 @@ export default class ManualJournalsController extends BaseController {
],
this.validationResult,
asyncMiddleware(this.editManualJournal.bind(this)),
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
router.delete(
'/:id', [
@@ -68,7 +68,7 @@ export default class ManualJournalsController extends BaseController {
],
this.validationResult,
asyncMiddleware(this.deleteManualJournal.bind(this)),
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
router.delete(
'/', [
@@ -76,7 +76,7 @@ export default class ManualJournalsController extends BaseController {
],
this.validationResult,
asyncMiddleware(this.deleteBulkManualJournals.bind(this)),
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
router.post(
'/', [
@@ -84,7 +84,7 @@ export default class ManualJournalsController extends BaseController {
],
this.validationResult,
asyncMiddleware(this.makeJournalEntries.bind(this)),
this.catchServiceErrors,
this.catchServiceErrors.bind(this),
);
return router;
}
@@ -133,7 +133,7 @@ export default class ManualJournalsController extends BaseController {
.escape()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('description')
.optional()
.optional({ nullable: true })
.isString()
.trim()
.escape()
@@ -153,7 +153,7 @@ export default class ManualJournalsController extends BaseController {
.toFloat(),
check('entries.*.account_id').isInt({ max: DATATYPES_LENGTH.INT_10 }).toInt(),
check('entries.*.note')
.optional()
.optional({ nullable: true })
.isString()
.isLength({ max: DATATYPES_LENGTH.STRING }),
check('entries.*.contact_id')
@@ -368,43 +368,45 @@ export default class ManualJournalsController extends BaseController {
if (error.errorType === 'credit_debit_not_equal_zero') {
return res.boom.badRequest(
'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') {
return res.boom.badRequest(
'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') {
return res.boom.badRequest(
'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') {
return res.boom.badRequest(
'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(
'',
{ errors: [{ type: '' }] },
);
}
if (error.errorType === 'receivable_entries_have_no_customers') {
return res.boom.badRequest(
'',
{ errors: [{ type: '' }] },
{
errors: [
{
type: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
code: 600,
meta: this.transfromToResponse(error.payload),
}
]
},
);
}
if (error.errorType === 'contacts_not_found') {
return res.boom.badRequest(
'',
{ errors: [{ type: '' }] },
{ errors: [{ type: 'CONTACTS_NOT_FOUND', code: 700 }] },
);
}
}

View File

@@ -3,9 +3,12 @@
export default class ServiceError {
errorType: string;
message: string;
payload: any;
constructor(errorType: string, message?: string) {
constructor(errorType: string, message?: string, payload?: any) {
this.errorType = errorType;
this.message = message || null;
this.payload = payload;
}
}

View File

@@ -1,7 +1,7 @@
import { difference, sumBy, omit } from 'lodash';
import { Service, Inject } from "typedi";
import { difference, sumBy, omit, groupBy } from 'lodash';
import { Service, Inject } from 'typedi';
import moment from 'moment';
import { ServiceError } from "exceptions";
import { ServiceError } from 'exceptions';
import {
IManualJournalDTO,
IManualJournalsService,
@@ -27,11 +27,15 @@ const ERRORS = {
CREDIT_DEBIT_NOT_EQUAL: 'credit_debit_not_equal',
ACCCOUNTS_IDS_NOT_FOUND: 'acccounts_ids_not_found',
JOURNAL_NUMBER_EXISTS: 'journal_number_exists',
RECEIVABLE_ENTRIES_NO_CUSTOMERS: 'receivable_entries_have_no_customers',
PAYABLE_ENTRIES_NO_VENDORS: 'payabel_entries_have_no_vendors',
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
CONTACTS_NOT_FOUND: 'contacts_not_found',
};
const CONTACTS_CONFIG = [
{ accountBySlug: 'accounts-receivable', contactService: 'customer', assignRequired: false, },
{ accountBySlug: 'accounts-payable', contactService: 'vendor', assignRequired: true },
];
@Service()
export default class ManualJournalsService implements IManualJournalsService {
@Inject()
@@ -48,31 +52,46 @@ export default class ManualJournalsService implements IManualJournalsService {
/**
* Validates the manual journal existance.
* @param {number} tenantId
* @param {number} manualJournalId
* @param {number} tenantId
* @param {number} manualJournalId
*/
private async validateManualJournalExistance(tenantId: number, manualJournalId: number) {
private async validateManualJournalExistance(
tenantId: number,
manualJournalId: number
) {
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);
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);
}
}
/**
* Validate manual journals existance.
* @param {number} tenantId
* @param {number} tenantId
* @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 manualJournals = await ManualJournal.query().whereIn('id', manualJournalsIds);
const manualJournals = await ManualJournal.query().whereIn(
'id',
manualJournalsIds
);
const notFoundManualJournals = difference(
manualJournalsIds,
@@ -85,7 +104,7 @@ export default class ManualJournalsService implements IManualJournalsService {
/**
* Validate manual journal credit and debit should be equal.
* @param {IManualJournalDTO} manualJournalDTO
* @param {IManualJournalDTO} manualJournalDTO
*/
private valdiateCreditDebitTotalEquals(manualJournalDTO: IManualJournalDTO) {
let totalCredit = 0;
@@ -100,23 +119,30 @@ export default class ManualJournalsService implements IManualJournalsService {
}
});
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);
}
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);
}
}
/**
* Validate manual entries accounts existance on the storage.
* @param {number} tenantId
* @param {IManualJournalDTO} manualJournalDTO
* @param {number} tenantId
* @param {IManualJournalDTO} manualJournalDTO
*/
private async validateAccountsExistance(tenantId: number, manualJournalDTO: IManualJournalDTO) {
private async validateAccountsExistance(
tenantId: number,
manualJournalDTO: IManualJournalDTO
) {
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()
.whereIn('id', manualAccountsIds)
@@ -125,96 +151,146 @@ export default class ManualJournalsService implements IManualJournalsService {
const storedAccountsIds = accounts.map((account) => account.id);
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);
}
}
/**
* Validate manual journal number unique.
* @param {number} tenantId
* @param {IManualJournalDTO} manualJournalDTO
* @param {number} tenantId
* @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 journalNumber = await ManualJournal.query().where(
'journal_number',
manualJournalDTO.journalNumber,
).onBuild((builder) => {
if (notId) {
builder.whereNot('id', notId);
}
});
const journalNumber = await ManualJournal.query()
.where('journal_number', manualJournalDTO.journalNumber)
.onBuild((builder) => {
if (notId) {
builder.whereNot('id', notId);
}
});
if (journalNumber.length > 0) {
throw new ServiceError(ERRORS.JOURNAL_NUMBER_EXISTS);
}
}
/**
* Validate entries that have receivable account should have customer type.
* @param {number} tenantId -
* @param {IManualJournalDTO} manualJournalDTO
*
* @param {number} tenantId
* @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 receivableAccount = await accountRepository.getBySlug('accounts-receivable');
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 payableAccount = await accountRepository.getBySlug(accountBySlug);
const entriesHasNoVendorContact = manualJournalDTO.entries.filter(
(e) => (e.accountId === payableAccount.id) &&
(!e.contactId || e.contactType !== 'vendor')
(e) =>
e.accountId === payableAccount.id &&
((!e.contactId && contactRequired) || e.contactType !== contactType)
);
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.
* @param {number} tenantId -
* @param {IManualJournalDTO} manualJournalDTO
* Dynamic validates accounts with contacts.
* @param {number} tenantId
* @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 manualJCotactsIds = manualJournalDTO.entries
.filter((entry) => entry.contactId)
.map((entry) => entry.contactId);
if (manualJCotactsIds.length > 0) {
const storedContacts = await contactRepository.findByIds(manualJCotactsIds);
const storedContactsIds = storedContacts.map((c) => c.id);
// Filters the entries that have contact only.
const entriesContactPairs = manualJournalDTO.entries
.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) {
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.
* @param {IManualJournalDTO} manualJournalDTO
* @param {IManualJournalDTO} manualJournalDTO
* @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 date = moment(manualJournalDTO.date).format('YYYY-MM-DD');
@@ -229,20 +305,20 @@ export default class ManualJournalsService implements IManualJournalsService {
/**
* Transform DTO to model.
* @param {IManualJournalEntryDTO[]} entries
* @param {IManualJournalEntryDTO[]} entries
*/
private transformDTOToEntriesModel(entries: IManualJournalEntryDTO[]) {
return entries.map((entry: IManualJournalEntryDTO) => ({
...omit(entry, ['accountId']),
account: entry.accountId,
}))
}));
}
/**
* Make journal entries.
* @param {number} tenantId
* @param {IManualJournalDTO} manualJournalDTO
* @param {ISystemUser} authorizedUser
* @param {number} tenantId
* @param {IManualJournalDTO} manualJournalDTO
* @param {ISystemUser} authorizedUser
*/
public async makeJournalEntries(
tenantId: number,
@@ -251,38 +327,51 @@ export default class ManualJournalsService implements IManualJournalsService {
): Promise<{ manualJournal: IManualJournal }> {
const { ManualJournal } = this.tenancy.models(tenantId);
// Validate the total credit should equals debit.
this.valdiateCreditDebitTotalEquals(manualJournalDTO);
await this.validateReceivableEntries(tenantId, manualJournalDTO);
await this.validatePayableEntries(tenantId, manualJournalDTO);
// Validate the contacts existance.
await this.validateContactsExistance(tenantId, manualJournalDTO);
// Validate entries accounts existance.
await this.validateAccountsExistance(tenantId, manualJournalDTO);
// Validate manual journal uniquiness on the storage.
await this.validateManualJournalNoUnique(tenantId, manualJournalDTO);
this.logger.info('[manual_journal] trying to save manual journal to the storage.', { tenantId, manualJournalDTO });
const manualJournalObj = this.transformDTOToModel(manualJournalDTO, authorizedUser);
// Validate accounts with contact type from the given config.
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']),
});
const manualJournal: IManualJournal = { ...manualJournalObj, id: storedManualJournal.id };
// Triggers `onManualJournalCreated` event.
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 };
}
/**
* Edits jouranl entries.
* @param {number} tenantId
* @param {number} manualJournalId
* @param {IMakeJournalDTO} manualJournalDTO
* @param {ISystemUser} authorizedUser
* @param {number} tenantId
* @param {number} manualJournalId
* @param {IMakeJournalDTO} manualJournalDTO
* @param {ISystemUser} authorizedUser
*/
public async editJournalEntries(
tenantId: number,
@@ -292,123 +381,200 @@ export default class ManualJournalsService implements IManualJournalsService {
): Promise<{ manualJournal: IManualJournal }> {
const { ManualJournal } = this.tenancy.models(tenantId);
// Validates the manual journal existance on the storage.
await this.validateManualJournalExistance(tenantId, manualJournalId);
// Validates the total credit and debit to be equals.
this.valdiateCreditDebitTotalEquals(manualJournalDTO);
// Validate the contacts existance.
await this.validateContactsExistance(tenantId, manualJournalDTO);
// Validates entries accounts existance.
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({
...omit(manualJournalObj, ['entries']),
});
const manualJournal: IManualJournal = { ...manualJournalObj, id: manualJournalId };
const manualJournal: IManualJournal = {
...manualJournalObj,
id: manualJournalId,
};
// Triggers `onManualJournalEdited` event.
this.eventDispatcher.dispatch(events.manualJournals.onEdited, {
tenantId, manualJournal,
tenantId,
manualJournal,
});
return { manualJournal };
}
/**
* Deletes the given manual journal
* @param {number} tenantId
* @param {number} manualJournalId
* @param {number} tenantId
* @param {number} manualJournalId
* @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);
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();
// Triggers `onManualJournalDeleted` event.
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.
* @param {number} tenantId
* @param {number[]} manualJournalsIds
* @param {number} tenantId
* @param {number[]} manualJournalsIds
* @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);
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();
// Triggers `onManualJournalDeletedBulk` event.
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.
* @param {number} tenantId
* @param {number[]} manualJournalsIds
* @param {number} tenantId
* @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);
await this.validateManualJournalsExistance(tenantId, manualJournalsIds);
this.logger.info('[manual_journal] trying to publish the manual journal.', { tenantId, manualJournalsIds });
await ManualJournal.query().whereIn('id', manualJournalsIds).patch({ status: 1, });
this.logger.info('[manual_journal] trying to publish the manual journal.', {
tenantId,
manualJournalsIds,
});
await ManualJournal.query()
.whereIn('id', manualJournalsIds)
.patch({ status: 1 });
// Triggers `onManualJournalPublishedBulk` event.
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.
* @param {number} tenantId
* @param {number} manualJournalId
* @param {number} tenantId
* @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);
await this.validateManualJournalExistance(tenantId, manualJournalId);
this.logger.info('[manual_journal] trying to publish the manual journal.', { tenantId, manualJournalId });
await ManualJournal.query().findById(manualJournalId).patch({ status: 1, });
this.logger.info('[manual_journal] trying to publish the manual journal.', {
tenantId,
manualJournalId,
});
await ManualJournal.query().findById(manualJournalId).patch({ status: 1 });
// Triggers `onManualJournalPublishedBulk` event.
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.
* @param {number} tenantId
* @param {IManualJournalsFilter} filter
* @param {number} tenantId
* @param {IManualJournalsFilter} filter
*/
public async getManualJournals(
tenantId: number,
filter: IManualJournalsFilter
): Promise<{ manualJournals: IManualJournal, pagination: IPaginationMeta, filterMeta: IFilterMeta }> {
): Promise<{
manualJournals: IManualJournal;
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
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 });
const { results, pagination } = await ManualJournal.query().onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.withGraphFetched('entries.account');
}).pagination(filter.page - 1, filter.pageSize);
this.logger.info('[manual_journals] trying to get manual journals list.', {
tenantId,
filter,
});
const { results, pagination } = await ManualJournal.query()
.onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.withGraphFetched('entries.account');
})
.pagination(filter.page - 1, filter.pageSize);
return {
manualJournals: results,
@@ -416,18 +582,21 @@ export default class ManualJournalsService implements IManualJournalsService {
filterMeta: dynamicList.getResponseMeta(),
};
}
/**
* Retrieve manual journal details with assocaited journal transactions.
* @param {number} tenantId
* @param {number} manualJournalId
* @param {number} tenantId
* @param {number} manualJournalId
*/
public async getManualJournal(tenantId: number, manualJournalId: number) {
const { ManualJournal } = this.tenancy.models(tenantId);
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()
.findById(manualJournalId)
.withGraphFetched('entries')
@@ -438,33 +607,42 @@ export default class ManualJournalsService implements IManualJournalsService {
/**
* Write manual journal entries.
* @param {number} tenantId
* @param {number} manualJournalId
* @param {IManualJournal|null} manualJournalObj
* @param {boolean} override
* @param {number} tenantId
* @param {number} manualJournalId
* @param {IManualJournal|null} manualJournalObj
* @param {boolean} override
*/
public async writeJournalEntries(
tenantId: number,
manualJournalId: number,
manualJournalObj?: IManualJournal|null,
override?: Boolean,
manualJournalObj?: IManualJournal | null,
override?: Boolean
) {
const journal = new JournalPoster(tenantId);
const journalCommands = new JournalCommands(journal);
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');
}
if (manualJournalObj) {
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([
journal.saveBalance(),
journal.deleteEntries(),
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 }
);
}
}
}