mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
WIP
This commit is contained in:
34
server/src/http/controllers/AccountTypes.js
Normal file
34
server/src/http/controllers/AccountTypes.js
Normal 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,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
} : {},
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
46
server/src/http/controllers/Currencies.js
Normal file
46
server/src/http/controllers/Currencies.js
Normal 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: '' },
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -226,7 +226,6 @@ export default {
|
||||
errors: [{ type: 'PREDEFINED_FIELD', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
await field.destroy();
|
||||
|
||||
return res.status(200).send({ id: field.get('id') });
|
||||
|
||||
@@ -289,6 +289,7 @@ export default {
|
||||
})),
|
||||
];
|
||||
return res.status(200).send({
|
||||
query: { ...filter },
|
||||
columns: { ...dateRangeSet },
|
||||
balance_sheet: {
|
||||
assets,
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
94
server/src/http/controllers/Resources.js
Normal file
94
server/src/http/controllers/Resources.js
Normal 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,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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 });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user