feat: Publish manual journal api.

This commit is contained in:
Ahmed Bouhuolia
2020-04-08 20:18:46 +02:00
parent 4920d5f419
commit b1d924c3cc
3 changed files with 125 additions and 6 deletions

View File

@@ -37,6 +37,10 @@ export default {
this.makeJournalEntries.validation,
asyncMiddleware(this.makeJournalEntries.handler));
router.post('/manual-journals/:id/publish',
this.publishManualJournal.validation,
asyncMiddleware(this.publishManualJournal.handler));
router.post('/manual-journals/:id',
this.editManualJournal.validation,
asyncMiddleware(this.editManualJournal.handler));
@@ -143,6 +147,7 @@ export default {
check('transaction_type').optional({ nullable: true }).trim().escape(),
check('reference').optional({ nullable: true }),
check('description').optional().trim().escape(),
check('status').optional().isBoolean().toBoolean(),
check('entries').isArray({ min: 2 }),
check('entries.*.credit').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.debit').optional({ nullable: true }).isNumeric().toInt(),
@@ -163,7 +168,6 @@ export default {
reference: '',
...req.body,
};
let totalCredit = 0;
let totalDebit = 0;
@@ -180,7 +184,6 @@ export default {
totalDebit += entry.debit;
}
});
if (totalCredit <= 0 || totalDebit <= 0) {
errorReasons.push({
type: 'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO',
@@ -209,7 +212,6 @@ export default {
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
// Save manual journal transaction.
const manualJournal = await ManualJournal.query().insert({
reference: form.reference,
@@ -218,13 +220,13 @@ export default {
amount: totalCredit,
date: formattedDate,
description: form.description,
status: form.status,
user_id: user.id,
});
const journalPoster = new JournalPoster();
entries.forEach((entry) => {
const account = accounts.find((a) => a.id === entry.account_id);
const jouranlEntry = new JournalEntry({
debit: entry.debit,
credit: entry.credit,
@@ -235,6 +237,7 @@ export default {
note: entry.note,
date: formattedDate,
userId: user.id,
draft: !form.status,
});
if (entry.debit) {
journalPoster.debit(jouranlEntry);
@@ -246,7 +249,7 @@ export default {
// Saves the journal entries and accounts balance changes.
await Promise.all([
journalPoster.saveEntries(),
journalPoster.saveBalance(),
(form.status) && journalPoster.saveBalance(),
]);
return res.status(200).send({ id: manualJournal.id });
},
@@ -409,6 +412,53 @@ export default {
},
},
publishManualJournal: {
validation: [
param('id').exists().isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { id } = req.params;
const manualJournal = await ManualJournal.query()
.where('id', id).first();
if (!manualJournal) {
return res.status(404).send({
errors: [{ type: 'MANUAL.JOURNAL.NOT.FOUND', code: 100 }],
});
}
if (!manualJournal.status) {
return res.status(400).send({
errors: [{ type: 'MANUAL.JOURNAL.PUBLISHED.ALREADY', code: 200 }],
});
}
const transactions = await AccountTransaction.query()
.whereIn('reference_type', ['Journal', 'ManualJournal'])
.where('reference_id', manualJournal.id)
.withGraphFetched('account.type');
const journal = new JournalPoster();
journal.loadEntries(transactions);
journal.calculateEntriesBalanceChange();
const updateAccountsTransactionsOper = AccountTransaction.query()
.whereIn('id', transactions.map((t) => t.id))
.update({ draft: 0 });
await Promise.all([
updateAccountsTransactionsOper,
journal.saveBalance(),
]);
return res.status(200).send({ id });
},
},
getManualJournal: {
validation: [
param('id').exists().isNumeric().toInt(),

View File

@@ -255,6 +255,17 @@ export default class JournalPoster {
});
}
calculateEntriesBalanceChange() {
this.entries.forEach((entry) => {
if (entry.credit) {
this.setAccountBalanceChange(entry, 'credit');
}
if (entry.debit) {
this.setAccountBalanceChange(entry, 'debit');
}
});
}
static loadAccounts() {
}

View File

@@ -560,7 +560,7 @@ describe('routes: `/accounting`', () => {
});
});
describe.only('route: GET `accounting/manual-journals/:id`', () => {
describe('route: GET `accounting/manual-journals/:id`', () => {
it('Should response not found in case manual transaction id was not exists.', async () => {
const res = await request()
.delete('/api/accounting/manual-journals/100')
@@ -608,6 +608,64 @@ describe('routes: `/accounting`', () => {
});
});
describe.only('route: POST `accounting/manual-journals/:id/publish`', () => {
it('Should response not found in case the manual journal id was not exists.', async () => {
const manualJournal = await create('manual_journal');
const res = await request()
.post('/api/accounting/manual-journals/123/publish')
.set('x-access-token', loginRes.body.token)
.send();
expect(res.status).equals(404);
expect(res.body.errors).include.something.that.deep.equals({
type: 'MANUAL.JOURNAL.NOT.FOUND', code: 100,
});
});
it('Should response published ready.', async () => {
const manualJournal = await create('manual_journal', { status: 0 });
const res = await request()
.post(`/api/accounting/manual-journals/${manualJournal.id}/publish`)
.set('x-access-token', loginRes.body.token)
.send();
expect(res.status).equals(400);
expect(res.body.errors).include.something.that.deep.equals({
type: 'MANUAL.JOURNAL.PUBLISHED.ALREADY', code: 200,
});
});
it('Should update all accounts transactions to not draft.', async () => {
const manualJournal = await create('manual_journal');
const transaction = await create('account_transaction', {
reference_type: 'Journal',
reference_id: manualJournal.id,
draft: 1,
});
const transaction2 = await create('account_transaction', {
reference_type: 'Journal',
reference_id: manualJournal.id,
draft: 1,
});
const res = await request()
.post(`/api/accounting/manual-journals/${manualJournal.id}/publish`)
.set('x-access-token', loginRes.body.token)
.send();
const foundTransactions = await AccountTransaction.query()
.whereIn('id', [transaction.id, transaction2.id]);
expect(foundTransactions[0].draft).equals(0);
expect(foundTransactions[1].draft).equals(0);
});
it('Should increment/decrement accounts balance.', () => {
});
});
describe('route: `/accounting/quick-journal-entries`', async () => {
it('Shoud `credit_account_id` be required', () => {