mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: Manual journals.
This commit is contained in:
@@ -66,9 +66,11 @@ factory.define('manual_journal', 'manual_journals', async () => {
|
||||
const user = await factory.create('user');
|
||||
|
||||
return {
|
||||
reference: faker.random.number(),
|
||||
journal_number: faker.random.number(),
|
||||
transaction_type: '',
|
||||
amount: faker.random.number(),
|
||||
// date: faker.random,
|
||||
date: faker.date.future,
|
||||
status: 1,
|
||||
user_id: user.id,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('manual_journals', (table) => {
|
||||
table.increments();
|
||||
table.string('reference');
|
||||
table.string('journal_number');
|
||||
table.string('transaction_type');
|
||||
table.decimal('amount');
|
||||
table.date('date');
|
||||
table.boolean('status').defaultTo(false);
|
||||
table.string('note');
|
||||
table.integer('user_id').unsigned();
|
||||
table.timestamps();
|
||||
|
||||
@@ -8,6 +8,7 @@ exports.seed = (knex) => {
|
||||
{ id: 1, name: 'accounts' },
|
||||
{ id: 2, name: 'items' },
|
||||
{ id: 3, name: 'expenses' },
|
||||
{ id: 4, name: 'manual_journals' },
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,6 +8,13 @@ import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
import JournalPoster from '@/services/Accounting/JournalPoster';
|
||||
import JournalEntry from '@/services/Accounting/JournalEntry';
|
||||
import ManualJournal from '@/models/JournalEntry';
|
||||
import Resource from '@/models/Resource';
|
||||
import View from '@/models/View';
|
||||
import {
|
||||
mapViewRolesToConditionals,
|
||||
validateViewRoles,
|
||||
} from '@/lib/ViewRolesBuilder';
|
||||
import FilterRoles from '@/lib/FilterRoles';
|
||||
|
||||
export default {
|
||||
/**
|
||||
@@ -17,6 +24,10 @@ export default {
|
||||
const router = express.Router();
|
||||
router.use(JWTAuth);
|
||||
|
||||
router.get('/manual-journals',
|
||||
this.manualJournals.validation,
|
||||
asyncMiddleware(this.manualJournals.handler));
|
||||
|
||||
router.post('/make-journal-entries',
|
||||
this.makeJournalEntries.validation,
|
||||
asyncMiddleware(this.makeJournalEntries.handler));
|
||||
@@ -32,6 +43,83 @@ export default {
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve manual journals,
|
||||
*/
|
||||
manualJournals: {
|
||||
validation: [
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const filter = {
|
||||
filter_roles: [],
|
||||
...req.query,
|
||||
};
|
||||
if (filter.stringified_filter_roles) {
|
||||
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
|
||||
}
|
||||
|
||||
const errorReasons = [];
|
||||
const viewConditionals = [];
|
||||
const manualJournalsResource = await Resource.query()
|
||||
.where('name', 'manual_journals')
|
||||
.withGraphFetched('fields')
|
||||
.first();
|
||||
|
||||
if (!manualJournalsResource) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'MANUAL_JOURNALS.RESOURCE.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
|
||||
const view = await View.query().onBuild((builder) => {
|
||||
if (filter.custom_view_id) {
|
||||
builder.where('id', filter.custom_view_id);
|
||||
} else {
|
||||
builder.where('favourite', true);
|
||||
}
|
||||
builder.where('resource_id', manualJournalsResource.id);
|
||||
builder.withGraphFetched('roles.field');
|
||||
builder.withGraphFetched('columns');
|
||||
builder.first();
|
||||
});
|
||||
|
||||
if (view && view.roles.length > 0) {
|
||||
viewConditionals.push(
|
||||
...mapViewRolesToConditionals(view.roles),
|
||||
);
|
||||
if (!validateViewRoles(viewConditionals, view.rolesLogicExpression)) {
|
||||
errorReasons.push({ type: 'VIEW.LOGIC.EXPRESSION.INVALID', code: 400 });
|
||||
}
|
||||
}
|
||||
// Validate the accounts resource fields.
|
||||
const filterRoles = new FilterRoles(Resource.tableName,
|
||||
filter.filter_roles.map((role) => ({ ...role, columnKey: role.fieldKey })),
|
||||
manualJournalsResource.fields);
|
||||
|
||||
if (filterRoles.validateFilterRoles().length > 0) {
|
||||
errorReasons.push({ type: 'ACCOUNTS.RESOURCE.HAS.NO.GIVEN.FIELDS', code: 500 });
|
||||
}
|
||||
if (errorReasons.length > 0) {
|
||||
return res.status(400).send({ errors: errorReasons });
|
||||
}
|
||||
// Manual journals.
|
||||
const manualJournals = await ManualJournal.query();
|
||||
|
||||
return res.status(200).send({
|
||||
manualJournals,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Make journal entrires.
|
||||
*/
|
||||
|
||||
@@ -245,6 +245,36 @@ describe('routes: `/accounting`', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe.only('route: `accounting/manual-journals`', async () => {
|
||||
|
||||
it('Should retrieve manual journals resource not found.', async () => {
|
||||
const res = await request()
|
||||
.get('/api/accounting/manual-journals')
|
||||
.set('x-access-token', loginRes.body.token)
|
||||
.send();
|
||||
|
||||
expect(res.status).equal(400);
|
||||
expect(res.body.errors[0].type).equals('MANUAL_JOURNALS.RESOURCE.NOT.FOUND');
|
||||
expect(res.body.errors[0].code).equals(200);
|
||||
});
|
||||
|
||||
it.only('Should retrieve all manual journals with pagination meta.', async () => {
|
||||
const resource = await create('resource', { name: 'manual_journals' });
|
||||
const manualJournal1 = await create('manual_journal');
|
||||
const manualJournal2 = await create('manual_journal');
|
||||
|
||||
const res = await request()
|
||||
.get('/api/accounting/manual-journals')
|
||||
.set('x-access-token', loginRes.body.token)
|
||||
.send();
|
||||
|
||||
expect(res.status).equals(200);
|
||||
expect(res.body.manualJournals).to.be.a('array');
|
||||
expect(res.body.manualJournals.length).equals(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('route: `/accounting/quick-journal-entries`', async () => {
|
||||
it('Shoud `credit_account_id` be required', () => {
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ describe('routes: `/financial_statements`', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.only('routes: `financial_statements/balance_sheet`', () => {
|
||||
describe('routes: `financial_statements/balance_sheet`', () => {
|
||||
it('Should response unauthorzied in case the user was not authorized.', async () => {
|
||||
const res = await request()
|
||||
.get('/api/financial_statements/balance_sheet')
|
||||
@@ -406,7 +406,7 @@ describe('routes: `/financial_statements`', () => {
|
||||
expect(res.body.balance_sheet.liabilities_equity.accounts).to.be.a('array');
|
||||
});
|
||||
|
||||
it.only('Should retrieve assets/liabilities total balance between the given date range.', async () => {
|
||||
it('Should retrieve assets/liabilities total balance between the given date range.', async () => {
|
||||
const res = await request()
|
||||
.get('/api/financial_statements/balance_sheet')
|
||||
.set('x-access-token', loginRes.body.token)
|
||||
|
||||
Reference in New Issue
Block a user