mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
Custom fields feature.
This commit is contained in:
@@ -120,4 +120,51 @@ factory.define('resource', 'resources', () => ({
|
||||
name: faker.lorem.word(),
|
||||
}));
|
||||
|
||||
factory.define('view', 'views', async () => {
|
||||
const resource = await factory.create('resource');
|
||||
return {
|
||||
name: faker.lorem.word(),
|
||||
resource_id: resource.id,
|
||||
predefined: false,
|
||||
};
|
||||
});
|
||||
|
||||
factory.define('resource_field', 'resource_fields', async () => {
|
||||
const resource = await factory.create('resource');
|
||||
const dataTypes = ['select', 'date', 'text'];
|
||||
|
||||
return {
|
||||
label_name: faker.lorem.words(),
|
||||
data_type: dataTypes[Math.floor(Math.random() * dataTypes.length)],
|
||||
help_text: faker.lorem.words(),
|
||||
default: faker.lorem.word(),
|
||||
resource_id: resource.id,
|
||||
active: true,
|
||||
predefined: false,
|
||||
};
|
||||
});
|
||||
|
||||
factory.define('view_role', 'view_roles', async () => {
|
||||
const view = await factory.create('view');
|
||||
const field = await factory.create('resource_field');
|
||||
|
||||
return {
|
||||
view_id: view.id,
|
||||
index: faker.random.number(),
|
||||
field_id: field.id,
|
||||
value: '',
|
||||
comparator: '',
|
||||
};
|
||||
});
|
||||
|
||||
factory.define('view_has_columns', 'view_has_columns', async () => {
|
||||
const view = await factory.create('view');
|
||||
const field = await factory.create('resource_field');
|
||||
|
||||
return {
|
||||
field_id: field.id,
|
||||
view_id: view.id,
|
||||
};
|
||||
});
|
||||
|
||||
export default factory;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('resource_fields', (table) => {
|
||||
table.increments();
|
||||
table.string('label_name');
|
||||
table.string('data_type');
|
||||
table.string('help_text');
|
||||
table.string('default');
|
||||
table.boolean('active');
|
||||
table.boolean('predefined');
|
||||
table.json('options');
|
||||
table.integer('resource_id').unsigned().references('id').inTable('resources');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('resource_fields');
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('view_has_columns', (table) => {
|
||||
table.increments();
|
||||
table.integer('view_id').unsigned();
|
||||
table.integer('field_id').unsigned();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('view_has_columns');
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('view_roles', (table) => {
|
||||
table.increments();
|
||||
table.integer('index');
|
||||
table.integer('field_id').unsigned().references('id').inTable('resource_fields');
|
||||
table.string('comparator');
|
||||
table.string('value');
|
||||
table.integer('view_id').unsigned();
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('view_roles');
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
exports.up = function (knex) {
|
||||
return knex.schema.createTable('views', (table) => {
|
||||
table.increments();
|
||||
table.string('name');
|
||||
table.boolean('predefined');
|
||||
table.integer('resource_id').unsigned().references('id').inTable('resources');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => knex.schema.dropTableIfExists('views');
|
||||
@@ -6,7 +6,9 @@ import Account from '@/models/Account';
|
||||
// import AccountBalance from '@/models/AccountBalance';
|
||||
|
||||
export default {
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
@@ -17,6 +19,11 @@ export default {
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opening balance to the given account.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
openingBalnace: {
|
||||
validation: [
|
||||
check('accounts').isArray({ min: 1 }),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import express from 'express';
|
||||
import { check, validationResult } from 'express-validator';
|
||||
import { check, validationResult, param } from 'express-validator';
|
||||
import asyncMiddleware from '../middleware/asyncMiddleware';
|
||||
import Account from '@/models/Account';
|
||||
import AccountBalance from '@/models/AccountBalance';
|
||||
// import AccountBalance from '@/models/AccountBalance';
|
||||
import AccountType from '@/models/AccountType';
|
||||
// import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
|
||||
@@ -22,13 +22,12 @@ export default {
|
||||
this.editAccount.validation,
|
||||
asyncMiddleware(this.editAccount.handler));
|
||||
|
||||
// router.get('/:id',
|
||||
// this.getAccount.validation,
|
||||
// asyncMiddleware(this.getAccount.handler));
|
||||
router.get('/:id',
|
||||
asyncMiddleware(this.getAccount.handler));
|
||||
|
||||
// router.delete('/:id',
|
||||
// this.deleteAccount.validation,
|
||||
// asyncMiddleware(this.deleteAccount.handler));
|
||||
router.delete('/:id',
|
||||
this.deleteAccount.validation,
|
||||
asyncMiddleware(this.deleteAccount.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
@@ -87,6 +86,7 @@ export default {
|
||||
*/
|
||||
editAccount: {
|
||||
validation: [
|
||||
param('id').toInt(),
|
||||
check('name').isLength({ min: 3 }).trim().escape(),
|
||||
check('code').isLength({ max: 10 }).trim().escape(),
|
||||
check('account_type_id').isNumeric().toInt(),
|
||||
@@ -142,7 +142,9 @@ export default {
|
||||
* Get details of the given account.
|
||||
*/
|
||||
getAccount: {
|
||||
valiation: [],
|
||||
valiation: [
|
||||
param('id').toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const account = await Account.where('id', id).fetch();
|
||||
@@ -159,7 +161,9 @@ export default {
|
||||
* Delete the given account.
|
||||
*/
|
||||
deleteAccount: {
|
||||
validation: [],
|
||||
validation: [
|
||||
param('id').toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const account = await Account.where('id', id).fetch();
|
||||
@@ -168,7 +172,6 @@ export default {
|
||||
return res.boom.notFound();
|
||||
}
|
||||
await account.destroy();
|
||||
await AccountBalance.where('account_id', id).destroy({ require: false });
|
||||
|
||||
return res.status(200).send({ id: account.previous('id') });
|
||||
},
|
||||
|
||||
@@ -92,7 +92,7 @@ export default {
|
||||
});
|
||||
}
|
||||
const { email } = req.body;
|
||||
const user = User.where('email', email).fetch();
|
||||
const user = await User.where('email', email).fetch();
|
||||
|
||||
if (!user) {
|
||||
return res.status(422).send();
|
||||
|
||||
246
server/src/http/controllers/Fields.js
Normal file
246
server/src/http/controllers/Fields.js
Normal file
@@ -0,0 +1,246 @@
|
||||
import express from 'express';
|
||||
import { check, param, validationResult } from 'express-validator';
|
||||
import ResourceField from '@/models/ResourceField';
|
||||
import Resource from '@/models/Resource';
|
||||
import asyncMiddleware from '../middleware/asyncMiddleware';
|
||||
|
||||
/**
|
||||
* Types of the custom fields.
|
||||
*/
|
||||
const TYPES = ['text', 'email', 'number', 'url', 'percentage', 'checkbox', 'radio', 'textarea'];
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Router constructor method.
|
||||
*/
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/resource/:resource_id',
|
||||
this.addNewField.validation,
|
||||
asyncMiddleware(this.addNewField.handler));
|
||||
|
||||
router.post('/:field_id',
|
||||
this.editField.validation,
|
||||
asyncMiddleware(this.editField.handler));
|
||||
|
||||
router.post('/status/:field_id',
|
||||
this.changeStatus.validation,
|
||||
asyncMiddleware(this.changeStatus.handler));
|
||||
|
||||
router.get('/:field_id',
|
||||
asyncMiddleware(this.getField.handler));
|
||||
|
||||
router.delete('/:field_id',
|
||||
asyncMiddleware(this.deleteField.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new field control to the given resource.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
addNewField: {
|
||||
validation: [
|
||||
param('resource_id').toInt(),
|
||||
check('label').exists().escape().trim(),
|
||||
check('data_type').exists().isIn(TYPES),
|
||||
check('help_text').optional(),
|
||||
check('default').optional(),
|
||||
check('options').optional().isArray(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { resource_id: resourceId } = req.params;
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'VALIDATION_ERROR', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const resource = await Resource.where('id', resourceId).fetch();
|
||||
|
||||
if (!resource) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'RESOURCE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
const { label, data_type: dataType, help_text: helpText } = req.body;
|
||||
const { default: defaultValue, options } = req.body;
|
||||
|
||||
const choices = options.map((option, index) => ({ key: index + 1, value: option }));
|
||||
|
||||
const field = ResourceField.forge({
|
||||
data_type: dataType,
|
||||
label_name: label,
|
||||
help_text: helpText,
|
||||
default: defaultValue,
|
||||
resource_id: resource.id,
|
||||
options: choices,
|
||||
});
|
||||
|
||||
await field.save();
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit details of the given field.
|
||||
*/
|
||||
editField: {
|
||||
validation: [
|
||||
param('field_id').toInt(),
|
||||
check('label').exists().escape().trim(),
|
||||
check('data_type').exists(),
|
||||
check('help_text').optional(),
|
||||
check('default').optional(),
|
||||
check('options').optional().isArray(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { field_id: fieldId } = req.params;
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'VALIDATION_ERROR', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const field = await ResourceField.where('id', fieldId).fetch();
|
||||
|
||||
if (!field) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'FIELD_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
// Sets the default value of optional fields.
|
||||
const form = { options: [], ...req.body };
|
||||
|
||||
const { label, data_type: dataType, help_text: helpText } = form;
|
||||
const { default: defaultValue, options } = form;
|
||||
|
||||
const storedFieldOptions = field.attributes.options || [];
|
||||
let lastChoiceIndex = 0;
|
||||
storedFieldOptions.forEach((option) => {
|
||||
const key = parseInt(option.key, 10);
|
||||
if (key > lastChoiceIndex) {
|
||||
lastChoiceIndex = key;
|
||||
}
|
||||
});
|
||||
const savedOptionKeys = options.filter((op) => typeof op === 'object');
|
||||
const notSavedOptionsKeys = options.filter((op) => typeof op !== 'object');
|
||||
|
||||
const choices = [
|
||||
...savedOptionKeys,
|
||||
...notSavedOptionsKeys.map((option) => {
|
||||
lastChoiceIndex += 1;
|
||||
return { key: lastChoiceIndex, value: option };
|
||||
}),
|
||||
];
|
||||
|
||||
await field.save({
|
||||
data_type: dataType,
|
||||
label_name: label,
|
||||
help_text: helpText,
|
||||
default: defaultValue,
|
||||
options: choices,
|
||||
});
|
||||
|
||||
return res.status(200).send({ id: field.get('id') });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the fields list of the given resource.
|
||||
* @param {Request} req -
|
||||
* @param {Response} res -
|
||||
*/
|
||||
fieldsList: {
|
||||
validation: [
|
||||
param('resource_id').toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { resource_id: resourceId } = req.params;
|
||||
const fields = await ResourceField.where('resource_id', resourceId).fetchAll();
|
||||
|
||||
return res.status(200).send({ fields: fields.toJSON() });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Change status of the given field.
|
||||
*/
|
||||
changeStatus: {
|
||||
validation: [
|
||||
param('field_id').toInt(),
|
||||
check('active').isBoolean().toBoolean(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { field_id: fieldId } = req.params;
|
||||
const field = await ResourceField.where('id', fieldId).fetch();
|
||||
|
||||
if (!field) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'NOT_FOUND_FIELD', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
const { active } = req.body;
|
||||
await field.save({ active });
|
||||
|
||||
return res.status(200).send({ id: field.get('id') });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve details of the given field.
|
||||
*/
|
||||
getField: {
|
||||
validation: [
|
||||
param('field_id').toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { field_id: id } = req.params;
|
||||
const field = await ResourceField.where('id', id).fetch();
|
||||
|
||||
if (!field) {
|
||||
return res.boom.notFound();
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
field: field.toJSON(),
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the given field.
|
||||
*/
|
||||
deleteField: {
|
||||
validation: [
|
||||
param('field_id').toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { field_id: id } = req.params;
|
||||
const field = await ResourceField.where('id', id).fetch();
|
||||
|
||||
if (!field) {
|
||||
return res.boom.notFound();
|
||||
}
|
||||
if (field.attributes.predefined) {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'PREDEFINED_FIELD', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
await field.destroy();
|
||||
|
||||
return res.status(200).send({ id: field.get('id') });
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import express from 'express';
|
||||
import { check, validationResult } from 'express-validator';
|
||||
import { check, param, validationResult } from 'express-validator';
|
||||
import asyncMiddleware from '../middleware/asyncMiddleware';
|
||||
import ItemCategory from '@/models/ItemCategory';
|
||||
// import JWTAuth from '@/http/middleware/jwtAuth';
|
||||
@@ -79,6 +79,7 @@ export default {
|
||||
*/
|
||||
editCategory: {
|
||||
validation: [
|
||||
param('id').toInt(),
|
||||
check('name').exists({ checkFalsy: true }).trim().escape(),
|
||||
check('parent_category_id').optional().isNumeric().toInt(),
|
||||
check('description').optional().trim().escape(),
|
||||
@@ -93,13 +94,11 @@ export default {
|
||||
});
|
||||
}
|
||||
const { name, parent_category_id: parentCategoryId, description } = req.body;
|
||||
|
||||
const itemCategory = await ItemCategory.where('id', id).fetch();
|
||||
|
||||
if (!itemCategory) {
|
||||
return res.boom.notFound();
|
||||
}
|
||||
|
||||
if (parentCategoryId && parentCategoryId !== itemCategory.attributes.parent_category_id) {
|
||||
const foundParentCategory = await ItemCategory.where('id', parentCategoryId).fetch();
|
||||
|
||||
@@ -109,7 +108,6 @@ export default {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await itemCategory.save({
|
||||
label: name,
|
||||
description,
|
||||
@@ -124,7 +122,9 @@ export default {
|
||||
* Delete the give item category.
|
||||
*/
|
||||
deleteItem: {
|
||||
validation: [],
|
||||
validation: [
|
||||
param('id').toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { id } = req.params;
|
||||
const itemCategory = await ItemCategory.where('id', id).fetch();
|
||||
@@ -151,4 +151,22 @@ export default {
|
||||
return res.status(200).send({ items: items.toJSON() });
|
||||
},
|
||||
},
|
||||
|
||||
getCategory: {
|
||||
validation: [
|
||||
param('category_id').toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { category_id: categoryId } = req.params;
|
||||
const item = await ItemCategory.where('id', categoryId).fetch();
|
||||
|
||||
if (!item) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'CATEGORY_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({ category: item.toJSON() });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
194
server/src/http/controllers/Views.js
Normal file
194
server/src/http/controllers/Views.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import { difference } from 'lodash';
|
||||
import express from 'express';
|
||||
import { check, validationResult } from 'express-validator';
|
||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||
import Resource from '@/models/Resource';
|
||||
import View from '../../models/View';
|
||||
|
||||
export default {
|
||||
resource: 'items',
|
||||
|
||||
router() {
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/resource/:resource_id',
|
||||
this.createView.validation,
|
||||
asyncMiddleware(this.createView.handler));
|
||||
|
||||
router.post('/:view_id',
|
||||
this.editView.validation,
|
||||
asyncMiddleware(this.editView.handler));
|
||||
|
||||
router.delete('/:view_id',
|
||||
this.deleteView.validation,
|
||||
asyncMiddleware(this.deleteView.handler));
|
||||
|
||||
router.get('/:view_id',
|
||||
asyncMiddleware(this.getView.handler));
|
||||
|
||||
return router;
|
||||
},
|
||||
|
||||
/**
|
||||
* List all views that associated with the given resource.
|
||||
*/
|
||||
listViews: {
|
||||
validation: [],
|
||||
async handler(req, res) {
|
||||
const { resource_id: resourceId } = req.params;
|
||||
const views = await View.where('resource_id', resourceId).fetchAll();
|
||||
|
||||
return res.status(200).send({ views: views.toJSON() });
|
||||
},
|
||||
},
|
||||
|
||||
getView: {
|
||||
async handler(req, res) {
|
||||
const { view_id: viewId } = req.params;
|
||||
const view = await View.where('id', viewId).fetch({
|
||||
withRelated: ['resource', 'columns', 'viewRoles'],
|
||||
});
|
||||
|
||||
if (!view) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'ROLE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({ ...view.toJSON() });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the given view of the resource.
|
||||
*/
|
||||
deleteView: {
|
||||
validation: [],
|
||||
async handler(req, res) {
|
||||
const { view_id: viewId } = req.params;
|
||||
const view = await View.where('id', viewId).fetch({
|
||||
withRelated: ['viewRoles', 'columns'],
|
||||
});
|
||||
|
||||
if (!view) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'VIEW_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
if (view.attributes.predefined) {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'PREDEFINED_VIEW', code: 200 }],
|
||||
});
|
||||
}
|
||||
// console.log(view);
|
||||
await view.destroy();
|
||||
|
||||
// await view.columns().destroy({ require: false });
|
||||
|
||||
return res.status(200).send({ id: view.get('id') });
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new view.
|
||||
*/
|
||||
createView: {
|
||||
validation: [
|
||||
check('label').exists().escape().trim(),
|
||||
check('columns').isArray({ min: 3 }),
|
||||
check('roles').isArray(),
|
||||
check('roles.*.field').exists().escape().trim(),
|
||||
check('roles.*.comparator').exists(),
|
||||
check('roles.*.value').exists(),
|
||||
check('roles.*.index').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { resource_id: resourceId } = req.params;
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
|
||||
const resource = await Resource.where('id', resourceId).fetch();
|
||||
|
||||
if (!resource) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'RESOURCE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
const errorReasons = [];
|
||||
const { label, roles, columns } = req.body;
|
||||
|
||||
const fieldsSlugs = roles.map((role) => role.field);
|
||||
|
||||
const resourceFields = await resource.fields().fetch();
|
||||
const resourceFieldsKeys = resourceFields.map((f) => f.get('key'));
|
||||
const notFoundFields = difference(fieldsSlugs, resourceFieldsKeys);
|
||||
|
||||
if (notFoundFields.length > 0) {
|
||||
errorReasons.push({ type: 'RESOURCE_FIELDS_NOT_EXIST', code: 100, fields: notFoundFields });
|
||||
}
|
||||
|
||||
const notFoundColumns = difference(columns, resourceFieldsKeys);
|
||||
|
||||
if (notFoundColumns.length > 0) {
|
||||
errorReasons.push({ type: 'COLUMNS_NOT_EXIST', code: 200, fields: notFoundColumns });
|
||||
}
|
||||
|
||||
if (errorReasons.length > 0) {
|
||||
return res.boom.badRequest(null, { errors: errorReasons });
|
||||
}
|
||||
|
||||
const view = await View.forge({
|
||||
name: label,
|
||||
predefined: false,
|
||||
});
|
||||
|
||||
// Save view details.
|
||||
await view.save();
|
||||
|
||||
// Save view columns.
|
||||
|
||||
// Save view roles.
|
||||
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
editView: {
|
||||
validation: [
|
||||
check('label').exists().escape().trim(),
|
||||
check('columns').isArray({ min: 3 }),
|
||||
check('roles').isArray(),
|
||||
check('roles.*.field').exists().escape().trim(),
|
||||
check('roles.*.comparator').exists(),
|
||||
check('roles.*.value').exists(),
|
||||
check('roles.*.index').exists().isNumeric().toInt(),
|
||||
],
|
||||
async handler(req, res) {
|
||||
const { view_id: viewId } = req.params;
|
||||
const validationErrors = validationResult(req);
|
||||
|
||||
if (!validationErrors.isEmpty()) {
|
||||
return res.boom.badData(null, {
|
||||
code: 'validation_error', ...validationErrors,
|
||||
});
|
||||
}
|
||||
const view = await View.where('id', viewId).fetch();
|
||||
|
||||
if (!view) {
|
||||
return res.boom.notFound(null, {
|
||||
errors: [{ type: 'ROLE_NOT_FOUND', code: 100 }],
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send();
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -6,6 +6,8 @@ import Items from '@/http/controllers/Items';
|
||||
import ItemCategories from '@/http/controllers/ItemCategories';
|
||||
import Accounts from '@/http/controllers/Accounts';
|
||||
import AccountOpeningBalance from '@/http/controllers/AccountOpeningBalance';
|
||||
import Views from '@/http/controllers/Views';
|
||||
import CustomFields from '@/http/controllers/Fields';
|
||||
|
||||
export default (app) => {
|
||||
// app.use('/api/oauth2', OAuth2.router());
|
||||
@@ -14,6 +16,8 @@ export default (app) => {
|
||||
app.use('/api/roles', Roles.router());
|
||||
app.use('/api/accounts', Accounts.router());
|
||||
app.use('/api/accountOpeningBalance', AccountOpeningBalance.router());
|
||||
app.use('/api/views', Views.router());
|
||||
app.use('/api/fields', CustomFields.router());
|
||||
app.use('/api/items', Items.router());
|
||||
app.use('/api/item_categories', ItemCategories.router());
|
||||
};
|
||||
|
||||
@@ -21,6 +21,11 @@ const Account = bookshelf.Model.extend({
|
||||
balances() {
|
||||
return this.hasMany('AccountBalance', 'accounnt_id');
|
||||
},
|
||||
}, {
|
||||
/**
|
||||
* Cascade delete dependents.
|
||||
*/
|
||||
dependents: ['balances'],
|
||||
});
|
||||
|
||||
export default bookshelf.model('Account', Account);
|
||||
|
||||
@@ -11,10 +11,18 @@ const Resource = bookshelf.Model.extend({
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
permissions() {
|
||||
/**
|
||||
* Resource model may has many views.
|
||||
*/
|
||||
views() {
|
||||
return this.hasMany('View', 'resource_id');
|
||||
},
|
||||
|
||||
roles() {
|
||||
/**
|
||||
* Resource model may has many fields.
|
||||
*/
|
||||
fields() {
|
||||
return this.hasMany('ResourceField', 'resource_id');
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
37
server/src/models/ResourceField.js
Normal file
37
server/src/models/ResourceField.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { snakeCase } from 'lodash';
|
||||
import bookshelf from './bookshelf';
|
||||
|
||||
const ResourceField = bookshelf.Model.extend({
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
tableName: 'resource_fields',
|
||||
|
||||
/**
|
||||
* Timestamp columns.
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
virtuals: {
|
||||
/**
|
||||
* Resource field key.
|
||||
*/
|
||||
key() {
|
||||
return snakeCase(this.attributes.label_name);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Resource field may belongs to resource model.
|
||||
*/
|
||||
resource() {
|
||||
return this.belongsTo('Resource', 'resource_id');
|
||||
},
|
||||
}, {
|
||||
/**
|
||||
* JSON Columns.
|
||||
*/
|
||||
jsonColumns: ['options'],
|
||||
});
|
||||
|
||||
export default bookshelf.model('ResourceField', ResourceField);
|
||||
38
server/src/models/View.js
Normal file
38
server/src/models/View.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import bookshelf from './bookshelf';
|
||||
|
||||
const View = bookshelf.Model.extend({
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
tableName: 'views',
|
||||
|
||||
/**
|
||||
* Timestamp columns.
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
/**
|
||||
* View model belongs to resource model.
|
||||
*/
|
||||
resource() {
|
||||
return this.belongsTo('Resource', 'resource_id');
|
||||
},
|
||||
|
||||
/**
|
||||
* View model may has many columns.
|
||||
*/
|
||||
columns() {
|
||||
return this.belongsToMany('ResourceField', 'view_has_columns', 'view_id', 'field_id');
|
||||
},
|
||||
|
||||
/**
|
||||
* View model may has many view roles.
|
||||
*/
|
||||
viewRoles() {
|
||||
return this.hasMany('ViewRole', 'view_id');
|
||||
},
|
||||
}, {
|
||||
dependents: ['columns', 'viewRoles'],
|
||||
});
|
||||
|
||||
export default bookshelf.model('View', View);
|
||||
19
server/src/models/ViewColumn.js
Normal file
19
server/src/models/ViewColumn.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import bookshelf from './bookshelf';
|
||||
|
||||
const ViewColumn = bookshelf.Model.extend({
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
tableName: 'view_columns',
|
||||
|
||||
/**
|
||||
* Timestamp columns.
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
view() {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export default bookshelf.model('ViewColumn', ViewColumn);
|
||||
22
server/src/models/ViewRole.js
Normal file
22
server/src/models/ViewRole.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import bookshelf from './bookshelf';
|
||||
|
||||
const ViewRole = bookshelf.Model.extend({
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
tableName: 'view_roles',
|
||||
|
||||
/**
|
||||
* Timestamp columns.
|
||||
*/
|
||||
hasTimestamps: false,
|
||||
|
||||
/**
|
||||
* View role model may belongs to view model.
|
||||
*/
|
||||
view() {
|
||||
return this.belongsTo('View', 'view_id');
|
||||
},
|
||||
});
|
||||
|
||||
export default bookshelf.model('ViewRole', ViewRole);
|
||||
@@ -2,6 +2,7 @@ import Bookshelf from 'bookshelf';
|
||||
import jsonColumns from 'bookshelf-json-columns';
|
||||
import bookshelfParanoia from 'bookshelf-paranoia';
|
||||
import bookshelfModelBase from 'bookshelf-modelbase';
|
||||
import cascadeDelete from 'bookshelf-cascade-delete';
|
||||
import knex from '../database/knex';
|
||||
|
||||
const bookshelf = Bookshelf(knex);
|
||||
@@ -13,5 +14,6 @@ bookshelf.plugin('virtuals');
|
||||
bookshelf.plugin(jsonColumns);
|
||||
bookshelf.plugin(bookshelfParanoia);
|
||||
bookshelf.plugin(bookshelfModelBase.pluggable);
|
||||
bookshelf.plugin(cascadeDelete);
|
||||
|
||||
export default bookshelf;
|
||||
|
||||
Reference in New Issue
Block a user