This commit is contained in:
Ahmed Bouhuolia
2020-03-16 00:06:15 +02:00
parent 56701951b7
commit 73711384f6
7925 changed files with 18478 additions and 959 deletions

View File

@@ -0,0 +1,34 @@
import express from 'express';
import JWTAuth from '@/http/middleware/jwtAuth';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import AccountType from '@/models/AccountType';
export default {
/**
* Router constructor.
*/
router() {
const router = express.Router();
router.use(JWTAuth);
router.get('/',
this.getAccountTypesList.validation,
asyncMiddleware(this.getAccountTypesList.handler));
return router;
},
/**
* Retrieve accounts types list.
*/
getAccountTypesList: {
validation: [],
async handler(req, res) {
const accountTypes = await AccountType.query();
return res.status(200).send({
account_types: accountTypes,
});
},
},
};

View File

@@ -52,7 +52,10 @@ export default {
code: 'validation_error', ...validationErrors,
});
}
const form = { ...req.body };
const form = {
date: new Date(),
...req.body,
};
const errorReasons = [];
let totalCredit = 0;
let totalDebit = 0;
@@ -98,6 +101,7 @@ export default {
const account = accounts.find((a) => a.id === entry.account_id);
const jouranlEntry = new JournalEntry({
date: entry.date,
debit: entry.debit,
credit: entry.credit,
account: account.id,

View File

@@ -6,8 +6,14 @@ import AccountType from '@/models/AccountType';
import AccountTransaction from '@/models/AccountTransaction';
import JournalPoster from '@/services/Accounting/JournalPoster';
import AccountBalance from '@/models/AccountBalance';
import Resource from '@/models/Resource';
import View from '@/models/View';
import JWTAuth from '@/http/middleware/jwtAuth';
import NestedSet from '../../collection/NestedSet';
import {
mapViewRolesToConditionals,
validateViewRoles,
} from '@/lib/ViewRolesBuilder';
export default {
/**
@@ -162,12 +168,12 @@ export default {
],
async handler(req, res) {
const { id } = req.params;
const account = await Account.where('id', id).fetch();
const account = await Account.query().where('id', id).first();
if (!account) {
return res.boom.notFound();
}
return res.status(200).send({ item: { ...account.attributes } });
return res.status(200).send({ account: { ...account } });
},
},
@@ -204,8 +210,10 @@ export default {
*/
getAccountsList: {
validation: [
query('display_type').optional().isIn(['tree', 'flat']),
query('account_types').optional().isArray(),
query('account_types.*').optional().isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
@@ -216,19 +224,72 @@ export default {
});
}
const form = {
const filter = {
account_types: [],
...req.body,
display_type: 'tree',
...req.query,
};
const accounts = await Account.query()
.modify('filterAccountTypes', form.account_types);
const errorReasons = [];
const viewConditionals = [];
const accountsResource = await Resource.query().where('name', 'accounts').first();
const accountsNestedSet = new NestedSet(accounts, {
parentId: 'parentAccountId',
if (!accountsResource) {
return res.status(400).send({
errors: [{ type: 'ACCOUNTS_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', accountsResource.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 });
}
}
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
const accounts = await Account.query().onBuild((builder) => {
builder.modify('filterAccountTypes', filter.account_types);
builder.withGraphFetched('type');
if (viewConditionals.length) {
builder.modify('viewRolesBuilder', viewConditionals, view.rolesLogicExpression);
}
});
const nestedAccounts = new NestedSet(accounts, { parentId: 'parentAccountId' });
const groupsAccounts = nestedAccounts.toTree();
const accountsList = [];
if (filter.display_type === 'tree') {
accountsList.push(...groupsAccounts);
} else if (filter.display_type === 'flat') {
const flattenAccounts = nestedAccounts.flattenTree((account, parentAccount) => {
if (parentAccount) {
account.name = `${parentAccount.name}${account.name}`;
}
return account;
});
accountsList.push(...flattenAccounts);
}
return res.status(200).send({
// ...accountsNestedSet.toArray(),
accounts: accountsList,
...(view) ? {
customViewId: view.id,
} : {},
});
},
},

View File

@@ -39,7 +39,7 @@ export default {
login: {
validation: [
check('crediential').exists().isEmail(),
check('password').exists().isLength({ min: 5 }),
check('password').exists().isLength({ min: 4 }),
],
async handler(req, res) {
const validationErrors = validationResult(req);
@@ -64,12 +64,12 @@ export default {
}
if (!user.verifyPassword(password)) {
return res.boom.badRequest(null, {
errors: [{ type: 'INCORRECT_PASSWORD', code: 110 }],
errors: [{ type: 'INVALID_DETAILS', code: 100 }],
});
}
if (!user.active) {
return res.boom.badRequest(null, {
errors: [{ type: 'USER_INACTIVE', code: 120 }],
errors: [{ type: 'USER_INACTIVE', code: 110 }],
});
}
// user.update({ last_login_at: new Date() });
@@ -80,7 +80,7 @@ export default {
}, JWT_SECRET_KEY, {
expiresIn: '1d',
});
return res.status(200).send({ token });
return res.status(200).send({ token, user });
},
},

View File

@@ -0,0 +1,46 @@
import express from 'express';
import { check, validationResult } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
export default {
router() {
const router = express.Router();
router.get('/all',
this.all.validation,
asyncMiddleware(this.all.handler));
router.get('/registered',
this.registered.validation,
asyncMiddleware(this.registered.handler));
return router;
},
all: {
validation: [],
async handler(req, res) {
return res.status(200).send({
currencies: [
{ currency_code: 'USD', currency_sign: '$' },
{ currency_code: 'LYD', currency_sign: '' },
],
});
},
},
registered: {
validation: [],
async handler(req, res) {
return res.status(200).send({
currencies: [
{ currency_code: 'USD', currency_sign: '$' },
{ currency_code: 'LYD', currency_sign: '' },
],
});
},
},
};

View File

@@ -17,6 +17,10 @@ import AccountTransaction from '@/models/AccountTransaction';
import View from '@/models/View';
import Resource from '../../models/Resource';
import ResourceCustomFieldRepository from '@/services/CustomFields/ResourceCustomFieldRepository';
import {
validateViewRoles,
mapViewRolesToConditionals,
} from '@/lib/ViewRolesBuilder';
export default {
/**
@@ -50,9 +54,9 @@ export default {
this.listExpenses.validation,
asyncMiddleware(this.listExpenses.handler));
router.get('/:id',
this.getExpense.validation,
asyncMiddleware(this.getExpense.handler));
// router.get('/:id',
// this.getExpense.validation,
// asyncMiddleware(this.getExpense.handler));
return router;
},
@@ -62,7 +66,7 @@ export default {
*/
newExpense: {
validation: [
check('date').optional().isISO8601(),
check('date').optional(),
check('payment_account_id').exists().isNumeric().toInt(),
check('expense_account_id').exists().isNumeric().toInt(),
check('description').optional(),
@@ -90,7 +94,7 @@ export default {
};
// Convert the date to the general format.
form.date = moment(form.date).format('YYYY-MM-DD');
s
const errorReasons = [];
const paymentAccount = await Account.query()
.findById(form.payment_account_id).first();
@@ -103,19 +107,19 @@ s
if (!expenseAccount) {
errorReasons.push({ type: 'EXPENSE.ACCOUNT.NOT.FOUND', code: 200 });
}
const customFields = new ResourceCustomFieldRepository(Expense);
await customFields.load();
// const customFields = new ResourceCustomFieldRepository(Expense);
// await customFields.load();
if (customFields.validateExistCustomFields()) {
errorReasons.push({ type: 'CUSTOM.FIELDS.SLUGS.NOT.EXISTS', code: 400 });
}
// if (customFields.validateExistCustomFields()) {
// errorReasons.push({ type: 'CUSTOM.FIELDS.SLUGS.NOT.EXISTS', code: 400 });
// }
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
const expenseTransaction = await Expense.query().insertAndFetch({
...omit(form, ['custom_fields']),
});
customFields.fillCustomFields(expenseTransaction.id, form.custom_fields);
// customFields.fillCustomFields(expenseTransaction.id, form.custom_fields);
const journalEntries = new JournalPoster();
const creditEntry = new JournalEntry({
@@ -140,7 +144,7 @@ s
journalEntries.debit(debitEntry);
await Promise.all([
customFields.saveCustomFields(expenseTransaction.id),
// customFields.saveCustomFields(expenseTransaction.id),
journalEntries.saveEntries(),
journalEntries.saveBalance(),
]);
@@ -331,38 +335,61 @@ s
const expenseResource = await Resource.query().where('name', 'expenses').first();
if (!expenseResource) {
errorReasons.push({ type: 'EXPENSE_NOT_FOUND', code: 300 });
errorReasons.push({ type: 'EXPENSE_RESOURCE_NOT_FOUND', code: 300 });
}
const view = await View.query().runBefore((result, q) => {
if (filter.customer_view_id) {
q.where('id', filter.customer_view_id);
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
const view = await View.query().onBuild((builder) => {
if (filter.custom_view_id) {
builder.where('id', filter.custom_view_id);
} else {
q.where('favorite', true);
builder.where('favourite', true);
}
q.where('resource_id', expenseResource.id);
q.withGraphFetched('viewRoles');
q.withGraphFetched('columns');
q.first();
return result;
});
builder.where('resource_id', expenseResource.id);
builder.withGraphFetched('viewRoles.field');
builder.withGraphFetched('columns');
if (!view) {
builder.first();
});
let viewConditionals = [];
if (view && view.viewRoles.length > 0) {
viewConditionals = mapViewRolesToConditionals(view.viewRoles);
if (!validateViewRoles(viewConditionals, view.rolesLogicExpression)) {
errorReasons.push({ type: 'VIEW.LOGIC.EXPRESSION.INVALID', code: 400 })
}
}
if (!view && filter.custom_view_id) {
errorReasons.push({ type: 'VIEW_NOT_FOUND', code: 100 });
}
if (errorReasons.length > 0) {
return res.boom.badRequest(null, { errors: errorReasons });
}
const expenses = await Expense.query()
.modify('filterByAmountRange', filter.range_from, filter.to_range)
.modify('filterByDateRange', filter.date_from, filter.date_to)
.modify('filterByExpenseAccount', filter.expense_account_id)
.modify('filterByPaymentAccount', filter.payment_account_id)
.modify('orderBy', filter.column_sort_order, filter.sort_order)
.page(filter.page, filter.page_size);
const expenses = await Expense.query().onBuild((builder) => {
builder.withGraphFetched('paymentAccount');
builder.withGraphFetched('expenseAccount');
builder.withGraphFetched('user');
if (viewConditionals.length) {
builder.modify('viewRolesBuilder', viewConditionals, view.rolesLogicExpression);
}
builder.modify('filterByAmountRange', filter.range_from, filter.to_range);
builder.modify('filterByDateRange', filter.date_from, filter.date_to);
builder.modify('filterByExpenseAccount', filter.expense_account_id);
builder.modify('filterByPaymentAccount', filter.payment_account_id);
builder.modify('orderBy', filter.column_sort_order, filter.sort_order);
}).page(filter.page - 1, filter.page_size);
return res.status(200).send({
columns: view.columns,
viewRoles: view.viewRoles,
...(view) ? {
customViewId: view.id,
viewColumns: view.columns,
viewConditionals,
} : {},
expenses,
});
},
},

View File

@@ -226,7 +226,6 @@ export default {
errors: [{ type: 'PREDEFINED_FIELD', code: 100 }],
});
}
await field.destroy();
return res.status(200).send({ id: field.get('id') });

View File

@@ -289,6 +289,7 @@ export default {
})),
];
return res.status(200).send({
query: { ...filter },
columns: { ...dateRangeSet },
balance_sheet: {
assets,

View File

@@ -68,9 +68,7 @@ export default {
}
const options = await Option.query();
return res.status(200).sends({
options: options.toArray(),
});
return res.status(200).sends({ options });
},
},
};

View File

@@ -0,0 +1,94 @@
import express from 'express';
import {
param,
query,
validationResult,
} from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import jwtAuth from '@/http/middleware/jwtAuth';
import Resource from '@/models/Resource';
export default {
/**
* Router constructor.
*/
router() {
const router = express.Router();
router.use(jwtAuth);
router.get('/:resource_slug/columns',
this.resourceColumns.validation,
asyncMiddleware(this.resourceColumns.handler));
router.get('/:resource_slug/fields',
this.resourceFields.validation,
asyncMiddleware(this.resourceFields.handler));
return router;
},
/**
* Retrieve resource columns of the given resource.
*/
resourceColumns: {
validation: [
param('resource_slug').trim().escape().exists(),
],
async handler(req, res) {
const { resource_slug: resourceSlug } = req.params;
const resource = await Resource.query()
.where('name', resourceSlug)
.withGraphFetched('fields')
.first();
if (!resource) {
return res.status(400).send({
errors: [{ type: 'RESOURCE.SLUG.NOT.FOUND', code: 200 }],
});
}
const resourceFields = resource.fields
.filter((field) => field.columnable)
.map((field) => ({
id: field.id,
label: field.labelName,
key: field.key,
}));
return res.status(200).send({
resource_columns: resourceFields,
resource_slug: resourceSlug,
});
},
},
/**
* Retrieve resource fields of the given resource.
*/
resourceFields: {
validation: [
param('resource_slug').trim().escape().exists(),
query('predefined').optional().isBoolean().toBoolean(),
query('builtin').optional().isBoolean().toBoolean(),
],
async handler(req, res) {
const { resource_slug: resourceSlug } = req.params;
const resource = await Resource.query()
.where('name', resourceSlug)
.withGraphFetched('fields')
.first();
if (!resource) {
return res.status(400).send({
errors: [{ type: 'RESOURCE.SLUG.NOT.FOUND', code: 200 }],
});
}
return res.status(200).send({
resource_fields: resource.fields,
resource_slug: resourceSlug,
});
},
},
};

View File

@@ -1,5 +1,10 @@
import express from 'express';
import { check, validationResult } from 'express-validator';
import {
check,
query,
param,
validationResult,
} from 'express-validator';
import User from '@/models/User';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import jwtAuth from '@/http/middleware/jwtAuth';
@@ -12,32 +17,32 @@ export default {
*/
router() {
const router = express.Router();
const permit = Authorization('users');
// const permit = Authorization('users');
router.use(jwtAuth);
router.post('/',
permit('create'),
// permit('create'),
this.newUser.validation,
asyncMiddleware(this.newUser.handler));
router.post('/:id',
permit('create', 'edit'),
// permit('create', 'edit'),
this.editUser.validation,
asyncMiddleware(this.editUser.handler));
router.get('/',
permit('view'),
// permit('view'),
this.listUsers.validation,
asyncMiddleware(this.listUsers.handler));
router.get('/:id',
permit('view'),
// permit('view'),
this.getUser.validation,
asyncMiddleware(this.getUser.handler));
router.delete('/:id',
permit('create', 'edit', 'delete'),
// permit('create', 'edit', 'delete'),
this.deleteUser.validation,
asyncMiddleware(this.deleteUser.handler));
@@ -49,8 +54,8 @@ export default {
*/
newUser: {
validation: [
check('first_name').exists(),
check('last_name').exists(),
check('first_name').trim().escape().exists(),
check('last_name').trim().escape().exists(),
check('email').exists().isEmail(),
check('phone_number').optional().isMobilePhone(),
check('password').isLength({ min: 4 }).exists().custom((value, { req }) => {
@@ -72,13 +77,12 @@ export default {
}
const { email, phone_number: phoneNumber } = req.body;
const foundUsers = await User.query((query) => {
query.where('email', email);
query.orWhere('phone_number', phoneNumber);
}).fetchAll();
const foundUsers = await User.query()
.where('email', email)
.orWhere('phone_number', phoneNumber);
const foundUserEmail = foundUsers.find((u) => u.attributes.email === email);
const foundUserPhone = foundUsers.find((u) => u.attributes.phone_number === phoneNumber);
const foundUserEmail = foundUsers.find((u) => u.email === email);
const foundUserPhone = foundUsers.find((u) => u.phoneNumber === phoneNumber);
const errorReasons = [];
@@ -92,7 +96,7 @@ export default {
return res.boom.badRequest(null, { errors: errorReasons });
}
const user = User.forge({
const user = await User.query().insert({
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
@@ -100,9 +104,7 @@ export default {
active: req.body.status,
});
await user.save();
return res.status(200).send({ id: user.get('id') });
return res.status(200).send({ user });
},
},
@@ -111,6 +113,7 @@ export default {
*/
editUser: {
validation: [
param('id').exists().isNumeric().toInt(),
check('first_name').exists(),
check('last_name').exists(),
check('email').exists().isEmail(),
@@ -133,21 +136,22 @@ export default {
code: 'validation_error', ...validationErrors,
});
}
const user = await User.where('id', id).fetch();
const user = await User.query().where('id', id).first();
if (!user) {
return res.boom.notFound();
}
const { email, phone_number: phoneNumber } = req.body;
const foundUsers = await User.query((query) => {
query.whereNot('id', id);
query.where('email', email);
query.orWhere('phone_number', phoneNumber);
}).fetchAll();
const foundUsers = await User.query()
.whereNot('id', id)
.andWhere((q) => {
q.where('email', email);
q.orWhere('phone_number', phoneNumber);
});
const foundUserEmail = foundUsers.find((u) => u.attribues.email === email);
const foundUserPhone = foundUsers.find((u) => u.attribues.phone_number === phoneNumber);
const foundUserEmail = foundUsers.find((u) => u.email === email);
const foundUserPhone = foundUsers.find((u) => u.phoneNumber === phoneNumber);
const errorReasons = [];
@@ -158,17 +162,16 @@ export default {
errorReasons.push({ type: 'PHONE_NUMBER_ALREADY_EXIST', code: 120 });
}
if (errorReasons.length > 0) {
return res.badRequest(null, { errors: errorReasons });
return res.boom.badRequest(null, { errors: errorReasons });
}
await user.save({
await User.query().where('id', id).update({
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
phone_number: req.body.phone_number,
status: req.body.status,
active: req.body.status,
});
return res.status(200).send();
},
},
@@ -180,30 +183,34 @@ export default {
validation: [],
async handler(req, res) {
const { id } = req.params;
const user = await User.where('id', id).fetch();
const user = await User.query().where('id', id).first();
if (!user) {
return res.boom.notFound(null, {
errors: [{ type: 'USER_NOT_FOUND', code: 100 }],
});
}
await User.query().where('id', id).delete();
await user.destroy();
return res.status(200).send();
},
},
/**
* Retrieve user details of the given user id.
*/
getUser: {
validation: [],
validation: [
param('id').exists().isNumeric().toInt(),
],
async handler(req, res) {
const { id } = req.params;
const user = await User.where('id', id).fetch();
const user = await User.query().where('id', id).first();
if (!user) {
return res.boom.notFound();
}
return res.status(200).send({ item: user.toJSON() });
return res.status(200).send({ user });
},
},
@@ -211,8 +218,11 @@ export default {
* Retrieve the list of users.
*/
listUsers: {
validation: [],
handler(req, res) {
validation: [
query('page_size').optional().isNumeric().toInt(),
query('page').optional().isNumeric().toInt(),
],
async handler(req, res) {
const filter = {
first_name: '',
last_name: '',
@@ -224,28 +234,10 @@ export default {
...req.query,
};
const users = User.query((query) => {
if (filter.first_name) {
query.where('first_name', filter.first_name);
}
if (filter.last_name) {
query.where('last_name', filter.last_name);
}
if (filter.email) {
query.where('email', filter.email);
}
if (filter.phone_number) {
query.where('phone_number', filter.phone_number);
}
}).fetchPage({
page_size: filter.page_size,
page: filter.page,
});
const users = await User.query()
.page(filter.page - 1, filter.page_size);
return res.status(200).send({
items: users.toJSON(),
pagination: users.pagination,
});
return res.status(200).send({ users });
},
},
};

View File

@@ -1,11 +1,21 @@
import { difference, pick } from 'lodash';
import express from 'express';
import { check, query, validationResult } from 'express-validator';
import {
check,
query,
param,
oneOf,
validationResult,
} from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import jwtAuth from '@/http/middleware/jwtAuth';
import Resource from '@/models/Resource';
import View from '@/models/View';
import ViewRole from '@/models/ViewRole';
import ViewColumn from '@/models/ViewColumn';
import {
validateViewLogicExpression,
} from '@/lib/ViewRolesBuilder';
export default {
resource: 'items',
@@ -18,6 +28,10 @@ export default {
router.use(jwtAuth);
router.get('/',
this.listViews.validation,
asyncMiddleware(this.listViews.handler));
router.post('/',
this.createView.validation,
asyncMiddleware(this.createView.handler));
@@ -33,10 +47,6 @@ export default {
router.get('/:view_id',
asyncMiddleware(this.getView.handler));
router.get('/resource/:resource_name',
this.getResourceViews.validation,
asyncMiddleware(this.getResourceViews.handler));
return router;
},
@@ -45,29 +55,53 @@ export default {
*/
listViews: {
validation: [
query('resource_name').optional().trim().escape(),
oneOf([
query('resource_name').exists().trim().escape(),
], [
query('resource_id').exists().isNumeric().toInt(),
]),
],
async handler(req, res) {
const { resource_id: resourceId } = req.params;
const views = await View.where('resource_id', resourceId).fetchAll();
const filter = { ...req.query };
return res.status(200).send({ views: views.toJSON() });
const resource = await Resource.query().onBuild((builder) => {
if (filter.resource_id) {
builder.where('id', filter.resource_id);
}
if (filter.resource_name) {
builder.where('name', filter.resource_name);
}
builder.first();
});
const views = await View.query().where('resource_id', resource.id);
return res.status(200).send({ views });
},
},
/**
* Retrieve view details of the given view id.
*/
getView: {
validation: [
param('view_id').exists().isNumeric().toInt(),
],
async handler(req, res) {
const { view_id: viewId } = req.params;
const view = await View.where('id', viewId).fetch({
withRelated: ['resource', 'columns', 'viewRoles'],
});
const view = await View.query()
.where('id', viewId)
.withGraphFetched('resource')
.withGraphFetched('columns')
.withGraphFetched('roles.field')
.first();
if (!view) {
return res.boom.notFound(null, {
errors: [{ type: 'ROLE_NOT_FOUND', code: 100 }],
errors: [{ type: 'VIEW_NOT_FOUND', code: 100 }],
});
}
return res.status(200).send({ ...view.toJSON() });
return res.status(200).send({ view: view.toJSON() });
},
},
@@ -75,7 +109,9 @@ export default {
* Delete the given view of the resource.
*/
deleteView: {
validation: [],
validation: [
param('view_id').exists().isNumeric().toInt(),
],
async handler(req, res) {
const { view_id: viewId } = req.params;
const view = await View.query().findById(viewId);
@@ -91,12 +127,12 @@ export default {
});
}
await Promise.all([
view.$relatedQuery('viewRoles').delete(),
view.$relatedQuery('roles').delete(),
view.$relatedQuery('columns').delete(),
]);
await view.delete();
await View.query().where('id', view.id).delete();
return res.status(200).send({ id: view.get('id') });
return res.status(200).send({ id: view.id });
},
},
@@ -106,15 +142,15 @@ export default {
createView: {
validation: [
check('resource_name').exists().escape().trim(),
check('label').exists().escape().trim(),
check('columns').exists().isArray({ min: 1 }),
check('roles').isArray(),
check('roles.*.field').exists().escape().trim(),
check('name').exists().escape().trim(),
check('logic_expression').exists().trim().escape(),
check('roles').isArray({ min: 1 }),
check('roles.*.field_key').exists().escape().trim(),
check('roles.*.comparator').exists(),
check('roles.*.value').exists(),
check('roles.*.index').exists().isNumeric().toInt(),
check('columns').exists().isArray(),
check('columns.*.field').exists().escape().trim(),
check('columns').exists().isArray({ min: 1 }),
check('columns.*.key').exists().escape().trim(),
check('columns.*.index').exists().isNumeric().toInt(),
],
async handler(req, res) {
@@ -134,10 +170,12 @@ export default {
});
}
const errorReasons = [];
const fieldsSlugs = form.roles.map((role) => role.field);
const fieldsSlugs = form.roles.map((role) => role.field_key);
const resourceFields = await resource.$relatedQuery('fields');
const resourceFieldsKeys = resourceFields.map((f) => f.slug);
const resourceFieldsKeys = resourceFields.map((f) => f.key);
const resourceFieldsKeysMap = new Map(resourceFields.map((field) => [field.key, field]));
const columnsKeys = form.columns.map((c) => c.key);
// The difference between the stored resource fields and submit fields keys.
const notFoundFields = difference(fieldsSlugs, resourceFieldsKeys);
@@ -146,34 +184,49 @@ export default {
errorReasons.push({ type: 'RESOURCE_FIELDS_NOT_EXIST', code: 100, fields: notFoundFields });
}
// The difference between the stored resource fields and the submit columns keys.
const notFoundColumns = difference(form.columns, resourceFieldsKeys);
const notFoundColumns = difference(columnsKeys, resourceFieldsKeys);
if (notFoundColumns.length > 0) {
errorReasons.push({ type: 'COLUMNS_NOT_EXIST', code: 200, columns: notFoundColumns });
}
// Validates the view conditional logic expression.
if (!validateViewLogicExpression(form.logic_expression, form.roles.map((r) => r.index))) {
errorReasons.push({ type: 'VIEW.ROLES.LOGIC.EXPRESSION.INVALID', code: 400 });
}
if (errorReasons.length > 0) {
return res.boom.badRequest(null, { errors: errorReasons });
}
// Save view details.
const view = await View.query().insert({
name: form.label,
name: form.name,
predefined: false,
resource_id: resource.id,
roles_logic_expression: form.logic_expression,
});
// Save view roles async operations.
const saveViewRolesOpers = [];
form.roles.forEach((role) => {
const fieldModel = resourceFields.find((f) => f.slug === role.field);
const oper = ViewRole.query().insert({
const fieldModel = resourceFieldsKeysMap.get(role.field_key);
const saveViewRoleOper = ViewRole.query().insert({
...pick(role, ['comparator', 'value', 'index']),
field_id: fieldModel.id,
view_id: view.id,
});
saveViewRolesOpers.push(oper);
saveViewRolesOpers.push(saveViewRoleOper);
});
form.columns.forEach((column) => {
const fieldModel = resourceFieldsKeysMap.get(column.key);
const saveViewColumnOper = ViewColumn.query().insert({
field_id: fieldModel.id,
view_id: view.id,
index: column.index,
});
saveViewRolesOpers.push(saveViewColumnOper);
});
await Promise.all(saveViewRolesOpers);
@@ -183,6 +236,7 @@ export default {
editView: {
validation: [
param('view_id').exists().isNumeric().toInt(),
check('label').exists().escape().trim(),
check('columns').isArray({ min: 3 }),
check('roles').isArray(),
@@ -207,17 +261,7 @@ export default {
errors: [{ type: 'ROLE_NOT_FOUND', code: 100 }],
});
}
return res.status(200).send();
},
},
getResourceViews: {
validation: [
],
async handler(req, res) {
},
},
};

View File

@@ -5,6 +5,7 @@ import Roles from '@/http/controllers/Roles';
import Items from '@/http/controllers/Items';
import ItemCategories from '@/http/controllers/ItemCategories';
import Accounts from '@/http/controllers/Accounts';
import AccountTypes from '@/http/controllers/AccountTypes';
import AccountOpeningBalance from '@/http/controllers/AccountOpeningBalance';
import Views from '@/http/controllers/Views';
import CustomFields from '@/http/controllers/Fields';
@@ -14,19 +15,23 @@ import Expenses from '@/http/controllers/Expenses';
import Options from '@/http/controllers/Options';
import Budget from '@/http/controllers/Budget';
import BudgetReports from '@/http/controllers/BudgetReports';
import Currencies from '@/http/controllers/Currencies';
import Customers from '@/http/controllers/Customers';
import Suppliers from '@/http/controllers/Suppliers';
import Bills from '@/http/controllers/Bills';
import CurrencyAdjustment from './controllers/CurrencyAdjustment';
import Resources from './controllers/Resources';
// import SalesReports from '@/http/controllers/SalesReports';
// import PurchasesReports from '@/http/controllers/PurchasesReports';
export default (app) => {
// app.use('/api/oauth2', OAuth2.router());
app.use('/api/auth', Authentication.router());
app.use('/api/currencies', Currencies.router());
app.use('/api/users', Users.router());
app.use('/api/roles', Roles.router());
app.use('/api/accounts', Accounts.router());
app.use('/api/account_types', AccountTypes.router());
app.use('/api/accounting', Accounting.router());
app.use('/api/accounts_opening_balances', AccountOpeningBalance.router());
app.use('/api/views', Views.router());
@@ -41,6 +46,7 @@ export default (app) => {
// app.use('/api/suppliers', Suppliers.router());
// app.use('/api/bills', Bills.router());
app.use('/api/budget', Budget.router());
app.use('/api/resources', Resources.router());
// app.use('/api/currency_adjustment', CurrencyAdjustment.router());
// app.use('/api/reports/sales', SalesReports.router());
// app.use('/api/reports/purchases', PurchasesReports.router());