mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
feat: Publish manual journal api.
This commit is contained in:
@@ -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(),
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user