This commit is contained in:
Ahmed Bouhuolia
2020-03-21 23:32:04 +02:00
parent ace43ed830
commit b5f94e9a8b
17 changed files with 780 additions and 188 deletions

View File

@@ -214,6 +214,12 @@ export default {
query('account_types').optional().isArray(),
query('account_types.*').optional().isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(),
query('roles').optional().isArray({ min: 1 }),
query('roles.*.field_key').exists().escape().trim(),
query('roles.*.comparator').exists(),
query('roles.*.value').exists(),
query('roles.*.index').exists().isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);

View File

@@ -1,7 +1,7 @@
import express from 'express';
import { query, validationResult } from 'express-validator';
import { query, oneOf, validationResult } from 'express-validator';
import moment from 'moment';
import { pick } from 'lodash';
import { pick, difference, groupBy } from 'lodash';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import AccountTransaction from '@/models/AccountTransaction';
import jwtAuth from '@/http/middleware/jwtAuth';
@@ -30,9 +30,9 @@ export default {
const router = express.Router();
router.use(jwtAuth);
router.get('/ledger',
this.ledger.validation,
asyncMiddleware(this.ledger.handler));
router.get('/journal',
this.journal.validation,
asyncMiddleware(this.journal.handler));
router.get('/general_ledger',
this.generalLedger.validation,
@@ -60,13 +60,22 @@ export default {
/**
* Retrieve the ledger report of the given account.
*/
ledger: {
journal: {
validation: [
query('from_date').optional().isISO8601(),
query('to_date').optional().isISO8601(),
query('transaction_types').optional().isArray({ min: 1 }),
query('account_ids').optional().isArray({ min: 1 }),
query('account_ids.*').optional().isNumeric().toInt(),
oneOf([
query('transaction_types').optional().isArray({ min: 1 }),
query('transaction_types.*').optional().isNumeric().toInt(),
], [
query('transaction_types').optional().trim().escape(),
]),
oneOf([
query('account_ids').optional().isArray({ min: 1 }),
query('account_ids.*').optional().isNumeric().toInt(),
], [
query('account_ids').optional().isNumeric().toInt(),
]),
query('from_range').optional().isNumeric().toInt(),
query('to_range').optional().isNumeric().toInt(),
query('number_format.no_cents').optional().isBoolean().toBoolean(),
@@ -81,6 +90,8 @@ export default {
});
}
const filter = {
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
from_range: null,
to_range: null,
account_ids: [],
@@ -91,22 +102,45 @@ export default {
},
...req.query,
};
if (!Array.isArray(filter.transaction_types)) {
filter.transaction_types = [filter.transaction_types];
}
if (!Array.isArray(filter.account_ids)) {
filter.account_ids = [filter.account_ids];
}
filter.account_ids = filter.account_ids.map((id) => parseInt(id, 10));
const accountsJournalEntries = await AccountTransaction.query()
.modify('filterDateRange', filter.from_date, filter.to_date)
.modify('filterAccounts', filter.account_ids)
.modify('filterTransactionTypes', filter.transaction_types)
.modify('filterAmountRange', filter.from_range, filter.to_range)
.withGraphFetched('account');
.withGraphFetched('account.type');
const formatNumber = formatNumberClosure(filter.number_format);
const journalGrouped = groupBy(accountsJournalEntries, (entry) => {
return `${entry.id}-${entry.referenceType}`;
});
const journal = Object.keys(journalGrouped).map((key) => {
const transactionsGroup = journalGrouped[key];
const journalPoster = new JournalPoster();
journalPoster.loadEntries(transactionsGroup);
const trialBalance = journalPoster.getTrialBalance();
return {
id: key,
entries: transactionsGroup,
credit: formatNumber(trialBalance.credit),
debit: formatNumber(trialBalance.debit),
};
});
return res.status(200).send({
meta: { ...filter },
items: accountsJournalEntries.map((entry) => ({
...entry,
credit: formatNumber(entry.credit),
debit: formatNumber(entry.debit),
})),
query: { ...filter },
journal,
});
},
},
@@ -122,7 +156,10 @@ export default {
query('number_format.no_cents').optional().isBoolean().toBoolean(),
query('number_format.divide_1000').optional().isBoolean().toBoolean(),
query('none_zero').optional().isBoolean().toBoolean(),
query('accounts_ids').optional().trim().escape(),
query('accounts_ids').optional(),
query('accounts_ids.*').isNumeric().toInt(),
query('orderBy').optional().isIn(['created_at', 'name', 'code']),
query('order').optional().isIn(['desc', 'asc']),
],
async handler(req, res) {
const validationErrors = validationResult(req);
@@ -144,12 +181,29 @@ export default {
accounts_ids: [],
...req.query,
};
if (!Array.isArray(filter.accounts_ids)) {
filter.accounts_ids = [filter.accounts_ids];
}
filter.accounts_ids = filter.accounts_ids.map((id) => parseInt(id, 10));
const errorReasons = [];
if (filter.accounts_ids.length > 0) {
const accounts = await Account.query().whereIn('id', filter.accounts_ids);
const accountsIds = accounts.map((a) => a.id);
if (difference(filter.accounts_ids, accountsIds).length > 0) {
errorReasons.push({ type: 'FILTER.ACCOUNTS.IDS.NOT.FOUND', code: 200 });
}
}
if (errorReasons.length > 0) {
return res.status(400).send({ error: errorReasons });
}
const accounts = await Account.query()
.orderBy('index', 'DESC')
.modify('filterAccounts', filter.accounts_ids)
.withGraphFetched('transactions')
.withGraphFetched('type')
.withGraphFetched('transactions')
.modifyGraph('transactions', (builder) => {
builder.modify('filterDateRange', filter.from_date, filter.to_date);
});
@@ -175,7 +229,7 @@ export default {
const items = accounts
.filter((account) => (
account.transactions.length > 0 || !filter.none_zero
account.transactions.length > 0 || filter.none_zero
))
.map((account) => ({
...pick(account, ['id', 'name', 'code', 'index']),
@@ -184,12 +238,13 @@ export default {
let amount = 0;
if (account.type.normal === 'credit') {
amount += transaction.credit - transaction.credit;
amount += transaction.credit - transaction.debit;
} else if (account.type.normal === 'debit') {
amount += transaction.debit - transaction.credit;
}
return {
...transaction,
...pick(transaction, ['id', 'note', 'transactionType', 'referenceType',
'referenceId', 'date', 'createdAt']),
amount: formatNumber(amount),
};
}),
@@ -271,7 +326,6 @@ export default {
filter.to_date,
filterDateType,
);
// Retrieve the asset balance sheet.
const assets = accounts
.filter((account) => (
@@ -320,7 +374,6 @@ export default {
...(type !== 'total') ? {
periods_balance: dateRangeSet.map((date) => {
const balance = journalEntries.getClosingBalance(account.id, date, filterDateType);
return {
date,
formatted_amount: balanceFormatter(balance),
@@ -329,7 +382,7 @@ export default {
}),
} : {},
balance: {
formattedAmount: balanceFormatter(closingBalance),
formatted_amount: balanceFormatter(closingBalance),
amount: closingBalance,
date: filter.to_date,
},
@@ -460,7 +513,7 @@ export default {
basis: 'accural',
none_zero: false,
display_columns_type: 'total',
display_columns_by: 'total',
display_columns_by: 'month',
...req.query,
};
const incomeStatementTypes = await AccountType.query().where('income_sheet', true);
@@ -481,12 +534,14 @@ export default {
// Account balance formmatter based on the given query.
const numberFormatter = formatNumberClosure(filter.number_format);
const comparatorDateType = filter.display_columns_type === 'total'
? 'day' : filter.display_columns_by;
// Gets the date range set from start to end date.
const dateRangeSet = dateRangeCollection(
filter.from_date,
filter.to_date,
filter.display_columns_by,
comparatorDateType,
);
const accountsMapper = (incomeExpenseAccounts) => (
@@ -503,7 +558,7 @@ export default {
// Date periods when display columns type `periods`.
...(filter.display_columns_type === 'date_periods') && {
periods: dateRangeSet.map((date) => {
const type = filter.display_columns_by;
const type = comparatorDateType;
const amount = journalEntries.getClosingBalance(account.id, date, type);
return { date, amount, formatted_amount: numberFormatter(amount) };
@@ -551,23 +606,22 @@ export default {
// @return {Object}
const netIncomeTotal = (totalIncome, totalExpenses) => {
const netIncomeAmount = totalIncome.amount - totalExpenses.amount;
return { amount: netIncomeAmount, formatted_amount: netIncomeAmount };
return { amount: netIncomeAmount, formatted_amount: netIncomeAmount, date: filter.to_date };
};
const totalIncomeAccounts = totalAccountsReducer(accountsIncome);
const totalExpensesAccounts = totalAccountsReducer(accountsExpenses);
const incomeResponse = {
entry_normal: 'credit',
accounts: accountsIncome,
...(filter.display_columns_type === 'total') && {
total: {
amount: totalIncomeAccounts,
date: filter.to_date,
formatted_amount: numberFormatter(totalIncomeAccounts),
},
},
...(filter.display_columns_type === 'total') && (() => {
const totalIncomeAccounts = totalAccountsReducer(accountsIncome);
return {
total: {
amount: totalIncomeAccounts,
date: filter.to_date,
formatted_amount: numberFormatter(totalIncomeAccounts),
},
};
})(),
...(filter.display_columns_type === 'date_periods') && {
total_periods: [
...totalPeriodsMapper(accountsIncome),
@@ -577,14 +631,16 @@ export default {
const expenseResponse = {
entry_normal: 'debit',
accounts: accountsExpenses,
...(filter.display_columns_type === 'total') && {
total: {
amount: totalExpensesAccounts,
date: filter.to_date,
formatted_amount: numberFormatter(totalExpensesAccounts),
},
},
...(filter.display_columns_type === 'total') && (() => {
const totalExpensesAccounts = totalAccountsReducer(accountsExpenses);
return {
total: {
amount: totalExpensesAccounts,
date: filter.to_date,
formatted_amount: numberFormatter(totalExpensesAccounts),
},
};
})(),
...(filter.display_columns_type === 'date_periods') && {
total_periods: [
...totalPeriodsMapper(accountsExpenses),

View File

@@ -49,11 +49,14 @@ export default {
newItem: {
validation: [
check('name').exists(),
check('type').exists().trim().escape().isIn(['service', 'product']),
check('type').exists().trim().escape()
.isIn(['service', 'non-inventory', 'inventory']),
check('sku').optional().trim().escape(),
check('cost_price').exists().isNumeric(),
check('sell_price').exists().isNumeric(),
check('cost_account_id').exists().isInt().toInt(),
check('sell_account_id').exists().isInt().toInt(),
check('inventory_account_id').exists().isInt().toInt(),
check('category_id').optional().isInt().toInt(),
check('custom_fields').optional().isArray({ min: 1 }),
@@ -78,6 +81,9 @@ export default {
const costAccountPromise = Account.query().findById(form.cost_account_id);
const sellAccountPromise = Account.query().findById(form.sell_account_id);
const inventoryAccountPromise = (form.type === 'inventory') ?
Account.query().findByid(form.inventory_account_id) : null;
const itemCategoryPromise = (form.category_id)
? ItemCategory.query().findById(form.category_id) : null;
@@ -101,8 +107,14 @@ export default {
errorReasons.push({ type: 'FIELD_KEY_NOT_FOUND', code: 150, fields: notFoundFields });
}
}
const [costAccount, sellAccount, itemCategory] = await Promise.all([
costAccountPromise, sellAccountPromise, itemCategoryPromise,
const [
costAccount,
sellAccount,
itemCategory,
inventoryAccount,
] = await Promise.all([
costAccountPromise, sellAccountPromise,
itemCategoryPromise, inventoryAccountPromise,
]);
if (!costAccount) {
errorReasons.push({ type: 'COST_ACCOUNT_NOT_FOUND', code: 100 });
@@ -113,6 +125,9 @@ export default {
if (!itemCategory && form.category_id) {
errorReasons.push({ type: 'ITEM_CATEGORY_NOT_FOUND', code: 140 });
}
if (!inventoryAccount && form.type === 'inventory') {
errorReasons.push({ type: 'INVENTORY_ACCOUNT_NOT_FOUND', code: 150 });
}
if (errorReasons.length > 0) {
return res.boom.badRequest(null, { errors: errorReasons });
}

View File

@@ -300,6 +300,25 @@ describe('routes: /accounts/', () => {
expect(res.body.accounts[2].id).equals(account3.id);
expect(res.body.accounts[2].name).equals(`${account1.name}${account2.name}${account3.name}`);
});
it('Should retrieve filtered accounts according to the given filter roles.', async () => {
const account1 = await create('account', { name: 'ahmed' });
const account2 = await create('account');
const account3 = await create('account');
const res = await request()
.get('/api/accounts')
.set('x-access-token', loginRes.body.token)
.query({
filter_roles: [{
field_key: 'name',
comparator: 'equals',
value: 'ahmed',
}],
});
expect(res.body.accounts.length).equals(1);
});
});
describe('DELETE: `/accounts`', () => {

View File

@@ -1,10 +1,10 @@
import moment from 'moment';
import {
expect,
request,
login,
create,
} from '~/testInit';
import moment from 'moment';
let loginRes;
let creditAccount;
@@ -52,35 +52,36 @@ describe('routes: `/financial_statements`', () => {
afterEach(() => {
loginRes = null;
});
describe('routes: `/financial_statements/ledger`', () => {
describe('routes: `/financial_statements/journal`', () => {
it('Should response unauthorized in case the user was not authorized.', async () => {
const res = await request()
.get('/api/financial_statements/ledger')
.get('/api/financial_statements/journal')
.send();
expect(res.status).equals(400);
expect(res.status).equals(401);
});
it('Should retrieve ledger transactions grouped by accounts.', async () => {
it('Should retrieve ledger transactions grouped by reference type and id.', async () => {
const res = await request()
.get('/api/financial_statements/ledger')
.get('/api/financial_statements/journal')
.set('x-access-token', loginRes.body.token)
.send();
expect(res.status).equals(200);
expect(res.body.items.length).to.be.at.least(1);
expect(res.body.journal.length).to.be.at.least(1);
expect(res.body.items[0]).to.have.property('id');
expect(res.body.items[0]).to.have.property('referenceType');
expect(res.body.items[0]).to.have.property('referenceId');
expect(res.body.items[0]).to.have.property('date');
expect(res.body.items[0]).to.have.property('account');
expect(res.body.items[0]).to.have.property('note');
expect(res.body.journal[0].credit).to.be.a('number');
expect(res.body.journal[0].debit).to.be.a('number');
expect(res.body.journal[0].entries).to.be.a('array');
expect(res.body.journal[0].id).to.be.a('string');
expect(res.body.journal[0].entries[0].credit).to.be.a('number');
expect(res.body.journal[0].entries[0].debit).to.be.a('number');
});
it('Should retrieve transactions between date range.', async () => {
const res = await request()
.get('/api/financial_statements/ledger')
.get('/api/financial_statements/journal')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2018-01-01',
@@ -88,40 +89,47 @@ describe('routes: `/financial_statements`', () => {
})
.send();
expect(res.body.items.length).equals(0);
expect(res.body.journal.length).equals(0);
});
it('Should retrieve transactions that associated to the queried accounts.', async () => {
const res = await request()
.get('/api/financial_statements/ledger')
.get('/api/financial_statements/journal')
.set('x-access-token', loginRes.body.token)
.query({
account_ids: [creditAccount.id, debitAccount.id],
account_ids: [creditAccount.id],
})
.send();
expect(res.body.items.length).equals(4);
expect(res.body.journal.length).equals(2);
});
it('Should retrieve tranasactions with the given types.', async () => {
const res = await request()
.get('/api/financial_statements/ledger')
.get('/api/financial_statements/journal')
.set('x-access-token', loginRes.body.token)
.query({
transaction_types: ['Expense'],
});
expect(res.body.items.length).equals(1);
expect(res.body.journal.length).equals(1);
});
it('Should retrieve transactions with range amount.', async () => {
const res = await request()
.get('/api/financial_statements/ledger')
.get('/api/financial_statements/journal')
.set('x-access-token', loginRes.body.token)
.query({
from_range: 1000,
from_range: 2000,
to_range: 2000,
});
expect(res.body.journal[0].credit).satisfy((credit) => {
return credit === 0 || credit === 2000;
});
expect(res.body.journal[0].debit).satisfy((debit) => {
return debit === 0 || debit === 2000;
});
});
it('Should format credit and debit to no cents of retrieved transactions.', async () => {
@@ -130,7 +138,7 @@ describe('routes: `/financial_statements`', () => {
it('Should divide credit/debit amount on 1000', async () => {
const res = await request()
.get('/api/financial_statements/ledger')
.get('/api/financial_statements/journal')
.set('x-access-token', loginRes.body.token)
.query({
number_format: {
@@ -139,14 +147,14 @@ describe('routes: `/financial_statements`', () => {
})
.send();
res.body.items.forEach((item) => {
expect(item.credit).to.be.at.most(100);
expect(item.debit).to.be.at.most(100);
});
const journal = res.body.journal.find((j) => j.id === '1-Expense');
expect(journal.credit).equals(1);
expect(journal.debit).equals(0);
});
});
describe.only('routes: `/financial_statements/general_ledger`', () => {
describe('routes: `/financial_statements/general_ledger`', () => {
it('Should response unauthorized in case the user was not authorized.', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
@@ -206,36 +214,152 @@ describe('routes: `/financial_statements`', () => {
});
});
it('Should retrieve opening and closing balance between the given date range.', () => {
it('Should retrieve opening and closing balance between the given date range.', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-20',
to_date: '2020-03-30',
none_zero: true,
})
.send();
const targetAccount = res.body.accounts.find((a) => a.id === creditAccount.id);
expect(targetAccount).to.be.an('object');
expect(targetAccount.opening).to.deep.equal({
balance: 2000, date: '2020-01-20',
});
expect(targetAccount.closing).to.deep.equal({
balance: 2000, date: '2020-03-30',
});
});
it('Should retrieve transactions of accounts that has transactions between date range.', () => {
it('Should retrieve accounts with associated transactions.', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
none_zero: true,
})
.send();
const targetAccount = res.body.accounts.find((a) => a.id === creditAccount.id);
expect(targetAccount.transactions[0].amount).equals(1000);
expect(targetAccount.transactions[1].amount).equals(1000);
expect(targetAccount.transactions[1].id).to.be.an('number');
// expect(targetAccount.transactions[1].note).to.be.an('string');
// expect(targetAccount.transactions[1].transactionType).to.be.an('string');
// expect(targetAccount.transactions[1].referenceType).to.be.an('string');
// expect(targetAccount.transactions[1].referenceId).to.be.an('number');
expect(targetAccount.transactions[1].date).to.be.an('string');
})
it('Should retrieve accounts transactions only that between date range.', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-20',
to_date: '2020-03-30',
none_zero: true,
})
.send();
const targetAccount = res.body.accounts.find((a) => a.id === creditAccount.id);
expect(targetAccount.transactions.length).equals(0);
});
it('Should retrieve accounts transactions only that between date range.', () => {
it('Should not retrieve all accounts that have no transactions in the given date range when `none_zero` is `false`.', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-20',
to_date: '2020-03-30',
none_zero: false,
})
.send();
res.body.accounts.forEach((account) => {
expect(account.transactions.length).not.equals(0);
});
});
it('Should not retrieve all accounts that have no transactions in the given date range when `none_zero` is `false`.', () => {
it('Should retrieve all accounts even it have no transactions in the given date range when `none_zero` is `true`', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2020-03-30',
none_zero: true,
})
.send();
const accountsNoTransactions = res.body.accounts.filter(a => a.transactions.length === 0);
const accountsWithTransactions = res.body.accounts.filter(a => a.transactions.length > 0);
expect(accountsNoTransactions.length).not.equals(0);
expect(accountsWithTransactions.length).not.equals(0);
});
it('Should retrieve all accounts even it have no transactions in the given date range when `none_zero` is `true`', () => {
it('Should amount transactions divided on `1000` when `number_format.none_zero` is `true`.', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2020-03-30',
number_format: {
divide_1000: true,
},
})
.send();
const targetAccount = res.body.accounts.find((a) => a.id === creditAccount.id);
expect(targetAccount.transactions[0].amount).equals(1);
expect(targetAccount.transactions[1].amount).equals(1);
});
it('Should amount transactions divided on 1000 when `number_format.none_zero` is `true`.', () => {
it('Should amount transactions rounded with no decimals when `number_format.no_cents` is `true`.', async () => {
await create('account_transaction', {
debit: 0.25, credit: 0, account_id: debitAccount.id, date: '2020-1-10',
});
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2020-03-30',
number_format: {
divide_1000: true,
no_cents: true,
},
accounts_ids: [debitAccount.id]
})
.send();
expect(res.body.accounts[0].transactions[2].amount).equal(0);
});
it('Should amount transactions rounded with no decimals when `number_format.no_cents` is `true`.', () => {
});
it('Should retrieve only accounts that given in the query.', () => {
it('Should retrieve only accounts that given in the query.', async () => {
const res = await request()
.get('/api/financial_statements/general_ledger')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2020-03-30',
none_zero: true,
accounts_ids: [creditAccount.id],
})
.send();
expect(res.body.accounts.length).equals(1);
});
});
@@ -294,10 +418,10 @@ describe('routes: `/financial_statements`', () => {
.send();
expect(res.body.balance_sheet.assets.accounts[0].balance).deep.equals({
amount: 4000, formattedAmount: 4000, date: '2032-02-02',
amount: 4000, formatted_amount: 4000, date: '2032-02-02',
});
expect(res.body.balance_sheet.liabilities_equity.accounts[0].balance).deep.equals({
amount: 2000, formattedAmount: 2000, date: '2032-02-02',
amount: 2000, formatted_amount: 2000, date: '2032-02-02',
});
});
@@ -633,11 +757,11 @@ describe('routes: `/financial_statements`', () => {
from_date: moment().startOf('year').format('YYYY-MM-DD'),
to_date: moment().endOf('year').format('YYYY-MM-DD'),
display_columns_type: 'date_periods',
display_columns_by: 'month',
display_columns_by: 'quarter',
})
.send();
expect(res.body.columns.length).equals(12);
expect(res.body.columns.length).equals(4);
expect(res.body.columns).deep.equals([
'2020-03', '2020-06', '2020-09', '2020-12',
]);
@@ -679,31 +803,33 @@ describe('routes: `/financial_statements`', () => {
from_date: moment('2020-01-01').startOf('month').format('YYYY-MM-DD'),
to_date: moment('2020-01-01').endOf('month').format('YYYY-MM-DD'),
display_columns_type: 'total',
display_columns_by: 'day',
display_columns_by: 'month',
})
.send();
console.log(res.body);
// expect(res.body.income.accounts.length).equals(2);
// expect(res.body.income.accounts[0].name).to.be.an('string');
// expect(res.body.income.accounts[0].code).to.be.an('string');
// expect(res.body.income.accounts[0].periods).to.be.an('array');
// expect(res.body.income.accounts[0].periods.length).equals(31);
const zeroAccount = res.body.income.accounts.filter((a) => a.total.amount === 0);
expect(zeroAccount.length).not.equals(0);
});
it('Should retrieve total of each income account when display columns by `total`.', async () => {
const toDate = moment('2020-01-01').endOf('month').format('YYYY-MM-DD');
const res = await request()
.get('/api/financial_statements/profit_loss_sheet')
.set('x-access-token', loginRes.body.token)
.query({
from_date: moment('2020-01-01').startOf('month').format('YYYY-MM-DD'),
to_date: moment('2020-01-01').endOf('month').format('YYYY-MM-DD'),
display_columns_by: 'day',
to_date: toDate,
})
.send();
expect(res.body.income).deep.equals();
expect(res.body.income.accounts).to.be.an('array');
expect(res.body.income.accounts.length).not.equals(0);
expect(res.body.income.accounts[0].id).to.be.an('number');
expect(res.body.income.accounts[0].name).to.be.an('string');
expect(res.body.income.accounts[0].total).to.be.an('object');
expect(res.body.income.accounts[0].total.amount).to.be.an('number');
expect(res.body.income.accounts[0].total.formatted_amount).to.be.an('number');
expect(res.body.income.accounts[0].total.date).equals(toDate);
});
it('Should retrieve credit sumation of income accounts.', async () => {
@@ -713,17 +839,13 @@ describe('routes: `/financial_statements`', () => {
.query({
from_date: '2020-01-01',
to_date: '2021-01-01',
number_format: {
divide_1000: true,
},
})
.send();
console.log(res.body);
res.body.income.accounts[0].dates.forEach((item) => {
expect(item.rawAmount).equals(2000);
});
expect(res.body.income.total).to.be.an('object');
expect(res.body.income.total.amount).equals(2000);
expect(res.body.income.total.formatted_amount).equals(2000);
expect(res.body.income.total.date).equals('2021-01-01');
});
it('Should retrieve debit sumation of expenses accounts.', async () => {
@@ -733,91 +855,85 @@ describe('routes: `/financial_statements`', () => {
.query({
from_date: '2020-01-01',
to_date: '2021-01-01',
number_format: {
divide_1000: true,
},
})
.send();
res.body.expenses.accounts[0].dates.forEach((item) => {
expect(item.rawAmount).equals(4000);
});
expect(res.body.expenses.total).to.be.an('object');
expect(res.body.expenses.total.amount).equals(6000);
expect(res.body.expenses.total.formatted_amount).equals(6000);
expect(res.body.expenses.total.date).equals('2021-01-01');
});
it('Should retrieve credit sumation of income accounts between the given date range.', async () => {
it('Should retrieve credit total of income accounts with `date_periods` columns between the given date range.', async () => {
const res = await request()
.get('/api/financial_statements/profit_loss_sheet')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2021-01-01',
from_date: '2019-12-01',
to_date: '2020-12-01',
display_columns_type: 'date_periods',
display_columns_by: 'month',
number_format: {
divide_1000: true,
},
})
.send();
expect(res.body.income.accounts[0].dates.length).equals(12);
expect(res.body.income.total_periods[0].amount).equals(0);
expect(res.body.income.total_periods[1].amount).equals(2000);
expect(res.body.income.total_periods[2].amount).equals(2000);
});
it('Should retrieve debit sumation of expenses accounts between the given date range.', async () => {
it('Should retrieve debit total of expenses accounts with `date_periods` columns between the given date range.', async () => {
const res = await request()
.get('/api/financial_statements/profit_loss_sheet')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2021-01-01',
from_date: '2019-12-01',
to_date: '2020-12-01',
display_columns_type: 'date_periods',
display_columns_by: 'month',
number_format: {
divide_1000: true,
},
})
.send();
expect(res.body.expenses.accounts[0].dates.length).equals(12);
expect(res.body.expenses.total_periods[0].amount).equals(0);
expect(res.body.expenses.total_periods[1].amount).equals(6000);
expect(res.body.expenses.total_periods[2].amount).equals(6000);
});
it('Should retrieve total income of income accounts between the given date range.', async () => {
it('Should retrieve total net income with `total column display between the given date range.', async () => {
const res = await request()
.get('/api/financial_statements/profit_loss_sheet')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2021-01-01',
display_columns_by: 'month',
from_date: '2019-12-01',
to_date: '2020-12-01',
display_columns_type: 'total',
})
.send();
expect(res.body.total_income[0].rawAmount).equals(2000);
expect(res.body.net_income.total.amount).equals(-4000);
expect(res.body.net_income.total.formatted_amount).equals(-4000);
expect(res.body.net_income.total.date).equals('2020-12-01');
});
it('Should retrieve total expenses of expenses accounts between the given date range.', async () => {
it('Should retrieve total net income with `date_periods` columns between the given date range.', async () => {
const res = await request()
.get('/api/financial_statements/profit_loss_sheet')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2021-01-01',
display_columns_by: 'month',
from_date: '2019-12-01',
to_date: '2020-12-01',
display_columns_type: 'date_periods',
display_columns_by: 'quarter',
})
.send();
expect(res.body.total_expenses[0].rawAmount).equals(6000);
});
expect(res.body.net_income.total_periods.length).equals(5);
expect(res.body.net_income.total_periods[0].amount).equals(0);
expect(res.body.net_income.total_periods[0].formatted_amount).equal(0);
expect(res.body.net_income.total_periods[0].date).equals('2019-12');
it('Should retrieve total net income between the given date range.', async () => {
const res = await request()
.get('/api/financial_statements/profit_loss_sheet')
.set('x-access-token', loginRes.body.token)
.query({
from_date: '2020-01-01',
to_date: '2021-01-01',
display_columns_by: 'month',
})
.send();
expect(res.body.total_net_income[0].rawAmount).equals(-4000);
expect(res.body.net_income.total_periods[1].amount).equals(-4000);
expect(res.body.net_income.total_periods[1].formatted_amount).equal(-4000);
expect(res.body.net_income.total_periods[1].date).equals('2020-03');
});
it('Should not retrieve income or expenses accounts that has no transactions between the given date range in case none_zero equals true.', async () => {
@@ -828,7 +944,7 @@ describe('routes: `/financial_statements`', () => {
from_date: '2020-01-01',
to_date: '2021-01-01',
display_columns_by: 'month',
none_zero: true
none_zero: true,
})
.send();

View File

@@ -6,16 +6,29 @@ import knex from '@/database/knex';
import '@/models';
import app from '@/app';
import factory from '@/database/factories';
import knexConfig from '@/../knexfile';
import dbManager from '@/database/manager';
// import { hashPassword } from '@/utils';
const request = () => chai.request(app);
const { expect } = chai;
const login = async (givenUser) => {
const user = !givenUser ? await factory.create('user') : givenUser;
const response = request()
.post('/api/auth/login')
.send({
crediential: user.email,
password: 'admin',
});
return response;
};
before(async () => {
await dbManager.dropDb();
await dbManager.createDb('ratteb');
await dbManager.closeKnex();
await dbManager.close();
// await dbManager.dropDb();
// await dbManager.createDb();
});
beforeEach(async () => {
@@ -23,25 +36,12 @@ beforeEach(async () => {
await knex.migrate.latest();
});
afterEach(async () => {
after(async () => {
});
chai.use(chaiHttp);
chai.use(chaiThings);
const login = async (givenUser) => {
const user = !givenUser ? await factory.create('user') : givenUser;
const response = await request()
.post('/api/auth/login')
.send({
crediential: user.email,
password: 'admin',
});
return response;
};
const create = async (name, data) => factory.create(name, data);
const make = async (name, data) => factory.build(name, data);