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, this.makeJournalEntries.validation,
asyncMiddleware(this.makeJournalEntries.handler)); asyncMiddleware(this.makeJournalEntries.handler));
router.post('/manual-journals/:id/publish',
this.publishManualJournal.validation,
asyncMiddleware(this.publishManualJournal.handler));
router.post('/manual-journals/:id', router.post('/manual-journals/:id',
this.editManualJournal.validation, this.editManualJournal.validation,
asyncMiddleware(this.editManualJournal.handler)); asyncMiddleware(this.editManualJournal.handler));
@@ -143,6 +147,7 @@ export default {
check('transaction_type').optional({ nullable: true }).trim().escape(), check('transaction_type').optional({ nullable: true }).trim().escape(),
check('reference').optional({ nullable: true }), check('reference').optional({ nullable: true }),
check('description').optional().trim().escape(), check('description').optional().trim().escape(),
check('status').optional().isBoolean().toBoolean(),
check('entries').isArray({ min: 2 }), check('entries').isArray({ min: 2 }),
check('entries.*.credit').optional({ nullable: true }).isNumeric().toInt(), check('entries.*.credit').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.debit').optional({ nullable: true }).isNumeric().toInt(), check('entries.*.debit').optional({ nullable: true }).isNumeric().toInt(),
@@ -163,7 +168,6 @@ export default {
reference: '', reference: '',
...req.body, ...req.body,
}; };
let totalCredit = 0; let totalCredit = 0;
let totalDebit = 0; let totalDebit = 0;
@@ -180,7 +184,6 @@ export default {
totalDebit += entry.debit; totalDebit += entry.debit;
} }
}); });
if (totalCredit <= 0 || totalDebit <= 0) { if (totalCredit <= 0 || totalDebit <= 0) {
errorReasons.push({ errorReasons.push({
type: 'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO', type: 'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO',
@@ -209,7 +212,6 @@ export default {
if (errorReasons.length > 0) { if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons }); return res.status(400).send({ errors: errorReasons });
} }
// Save manual journal transaction. // Save manual journal transaction.
const manualJournal = await ManualJournal.query().insert({ const manualJournal = await ManualJournal.query().insert({
reference: form.reference, reference: form.reference,
@@ -218,13 +220,13 @@ export default {
amount: totalCredit, amount: totalCredit,
date: formattedDate, date: formattedDate,
description: form.description, description: form.description,
status: form.status,
user_id: user.id, user_id: user.id,
}); });
const journalPoster = new JournalPoster(); const journalPoster = new JournalPoster();
entries.forEach((entry) => { entries.forEach((entry) => {
const account = accounts.find((a) => a.id === entry.account_id); const account = accounts.find((a) => a.id === entry.account_id);
const jouranlEntry = new JournalEntry({ const jouranlEntry = new JournalEntry({
debit: entry.debit, debit: entry.debit,
credit: entry.credit, credit: entry.credit,
@@ -235,6 +237,7 @@ export default {
note: entry.note, note: entry.note,
date: formattedDate, date: formattedDate,
userId: user.id, userId: user.id,
draft: !form.status,
}); });
if (entry.debit) { if (entry.debit) {
journalPoster.debit(jouranlEntry); journalPoster.debit(jouranlEntry);
@@ -246,7 +249,7 @@ export default {
// Saves the journal entries and accounts balance changes. // Saves the journal entries and accounts balance changes.
await Promise.all([ await Promise.all([
journalPoster.saveEntries(), journalPoster.saveEntries(),
journalPoster.saveBalance(), (form.status) && journalPoster.saveBalance(),
]); ]);
return res.status(200).send({ id: manualJournal.id }); 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: { getManualJournal: {
validation: [ validation: [
param('id').exists().isNumeric().toInt(), 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() { 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 () => { it('Should response not found in case manual transaction id was not exists.', async () => {
const res = await request() const res = await request()
.delete('/api/accounting/manual-journals/100') .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 () => { describe('route: `/accounting/quick-journal-entries`', async () => {
it('Shoud `credit_account_id` be required', () => { it('Shoud `credit_account_id` be required', () => {