mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
feat: remove SET_DASHBOARD_REQUEST_LOADING reducer.
feat: fix dropdown filter. feat: fix fetch resource data.
This commit is contained in:
@@ -24,9 +24,18 @@ export default class ResourceController extends BaseController{
|
||||
'/:resource_model/fields', [
|
||||
...this.resourceModelParamSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.resourceFields.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:resource_model/data', [
|
||||
...this.resourceModelParamSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.resourceData.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
)
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -57,6 +66,28 @@ export default class ResourceController extends BaseController{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve resource data of the give resource based on the given query.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async resourceData(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { resource_model: resourceModel } = req.params;
|
||||
const filter = req.query;
|
||||
|
||||
try {
|
||||
const resourceData = await this.resourcesService.getResourceData(tenantId, resourceModel, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
resource_data: this.transfromToResponse(resourceData),
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles service errors.
|
||||
* @param {Error} error
|
||||
@@ -72,5 +103,6 @@ export default class ResourceController extends BaseController{
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,121 +113,6 @@ export default class SalesReceiptsController extends BaseController{
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether sale receipt exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async validateSaleReceiptExistance(req: Request, res: Response, next: Function) {
|
||||
const { tenantId } = req;
|
||||
const { id: saleReceiptId } = req.params;
|
||||
|
||||
const isSaleReceiptExists = await this.saleReceiptService
|
||||
.isSaleReceiptExists(
|
||||
tenantId,
|
||||
saleReceiptId,
|
||||
);
|
||||
if (!isSaleReceiptExists) {
|
||||
return res.status(404).send({
|
||||
errors: [{ type: 'SALE.RECEIPT.NOT.FOUND', code: 200 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether sale receipt customer exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateReceiptCustomerExistance(req: Request, res: Response, next: Function) {
|
||||
const saleReceipt = { ...req.body };
|
||||
const { Customer } = req.models;
|
||||
|
||||
const foundCustomer = await Customer.query().findById(saleReceipt.customer_id);
|
||||
|
||||
if (!foundCustomer) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'CUSTOMER.ID.NOT.EXISTS', code: 200 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether sale receipt deposit account exists on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateReceiptDepositAccountExistance(req: Request, res: Response, next: Function) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const saleReceipt = { ...req.body };
|
||||
const isDepositAccountExists = await this.accountsService.isAccountExists(
|
||||
tenantId,
|
||||
saleReceipt.deposit_account_id
|
||||
);
|
||||
if (!isDepositAccountExists) {
|
||||
return res.status(400).send({
|
||||
errors: [{ type: 'DEPOSIT.ACCOUNT.NOT.EXISTS', code: 300 }],
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether receipt items ids exist on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateReceiptItemsIdsExistance(req: Request, res: Response, next: Function) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const saleReceipt = { ...req.body };
|
||||
const estimateItemsIds = saleReceipt.entries.map((e) => e.item_id);
|
||||
|
||||
const notFoundItemsIds = await this.itemsService.isItemsIdsExists(
|
||||
tenantId,
|
||||
estimateItemsIds
|
||||
);
|
||||
if (notFoundItemsIds.length > 0) {
|
||||
return res.status(400).send({ errors: [{ type: 'ITEMS.IDS.NOT.EXISTS', code: 400 }] });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate receipt entries ids existance on the storage.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Function} next
|
||||
*/
|
||||
async validateReceiptEntriesIds(req: Request, res: Response, next: Function) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const saleReceipt = { ...req.body };
|
||||
const { id: saleReceiptId } = req.params;
|
||||
|
||||
// Validate the entries IDs that not stored or associated to the sale receipt.
|
||||
const notExistsEntriesIds = await this.saleReceiptService
|
||||
.isSaleReceiptEntriesIDsExists(
|
||||
tenantId,
|
||||
saleReceiptId,
|
||||
saleReceipt,
|
||||
);
|
||||
if (notExistsEntriesIds.length > 0) {
|
||||
return res.status(400).send({ errors: [{
|
||||
type: 'ENTRIES.IDS.NOT.FOUND',
|
||||
code: 500,
|
||||
}]
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new receipt.
|
||||
* @param {Request} req
|
||||
@@ -244,7 +129,10 @@ export default class SalesReceiptsController extends BaseController{
|
||||
tenantId,
|
||||
saleReceiptDTO,
|
||||
);
|
||||
return res.status(200).send({ id: storedSaleReceipt.id });
|
||||
return res.status(200).send({
|
||||
id: storedSaleReceipt.id,
|
||||
message: 'Sale receipt has been created successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -263,7 +151,10 @@ export default class SalesReceiptsController extends BaseController{
|
||||
// Deletes the sale receipt.
|
||||
await this.saleReceiptService.deleteSaleReceipt(tenantId, saleReceiptId);
|
||||
|
||||
return res.status(200).send({ id: saleReceiptId });
|
||||
return res.status(200).send({
|
||||
id: saleReceiptId,
|
||||
message: 'Sale receipt has been deleted successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -287,7 +178,9 @@ export default class SalesReceiptsController extends BaseController{
|
||||
saleReceiptId,
|
||||
saleReceipt,
|
||||
);
|
||||
return res.status(200).send();
|
||||
return res.status(200).send({
|
||||
message: 'Sale receipt has been edited successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
this.setResponseMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filter roles logic expression.
|
||||
* @return {string}
|
||||
*/
|
||||
private buildLogicExpression(): string {
|
||||
let expression = '';
|
||||
this.filterRoles.forEach((role, index) => {
|
||||
|
||||
@@ -42,12 +42,14 @@ const numberRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) =>
|
||||
const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||
switch (role.comparator) {
|
||||
case 'equals':
|
||||
case 'is':
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, role.value);
|
||||
};
|
||||
case 'not_equal':
|
||||
case 'not_equals':
|
||||
case 'is_not':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"Journal": "Journal",
|
||||
"Reconciliation": "Reconciliation",
|
||||
"Credit": "Credit",
|
||||
"Debit": "Debit",
|
||||
"Interest": "Interest",
|
||||
"Depreciation": "Depreciation",
|
||||
"Payroll": "Payroll",
|
||||
@@ -67,5 +68,10 @@
|
||||
"Journal number": "Journal number",
|
||||
"Status": "Status",
|
||||
"Journal type": "Journal type",
|
||||
"Date": "Date"
|
||||
"Date": "Date",
|
||||
"Asset": "Asset",
|
||||
"Liability": "Liability",
|
||||
"First-in first-out (FIFO)": "First-in first-out (FIFO)",
|
||||
"Last-in first-out (LIFO)": "Last-in first-out (LIFO)",
|
||||
"Average rate": "Average rate"
|
||||
}
|
||||
@@ -123,50 +123,84 @@ export default class Account extends TenantModel {
|
||||
name: {
|
||||
label: 'Account name',
|
||||
column: 'name',
|
||||
columnType: 'string',
|
||||
|
||||
fieldType: 'text',
|
||||
},
|
||||
type: {
|
||||
label: 'Account type',
|
||||
column: 'account_type_id',
|
||||
relation: 'account_types.id',
|
||||
relationColumn: 'account_types.key',
|
||||
|
||||
fieldType: 'options',
|
||||
optionsResource: 'AccountType',
|
||||
optionsKey: 'key',
|
||||
optionsLabel: 'label',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string',
|
||||
|
||||
fieldType: 'text',
|
||||
},
|
||||
code: {
|
||||
label: 'Account code',
|
||||
column: 'code',
|
||||
columnType: 'string',
|
||||
fieldType: 'text',
|
||||
},
|
||||
root_type: {
|
||||
label: 'Type',
|
||||
label: 'Root type',
|
||||
column: 'account_type_id',
|
||||
relation: 'account_types.id',
|
||||
relationColumn: 'account_types.root_type',
|
||||
options: [
|
||||
{ key: 'asset', label: 'Asset', },
|
||||
{ key: 'liability', label: 'Liability' },
|
||||
{ key: 'equity', label: 'Equity' },
|
||||
{ key: 'Income', label: 'Income' },
|
||||
{ key: 'expense', label: 'Expense' },
|
||||
],
|
||||
fieldType: 'options',
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
active: {
|
||||
label: 'Active',
|
||||
column: 'active',
|
||||
columnType: 'boolean',
|
||||
fieldType: 'checkbox',
|
||||
},
|
||||
balance: {
|
||||
label: 'Balance',
|
||||
column: 'amount',
|
||||
columnType: 'number'
|
||||
columnType: 'number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency: {
|
||||
label: 'Currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'options',
|
||||
optionsResource: 'currency',
|
||||
optionsKey: 'currency_code',
|
||||
optionsLabel: 'currency_name',
|
||||
},
|
||||
normal: {
|
||||
label: 'Account normal',
|
||||
column: 'account_type_id',
|
||||
fieldType: 'options',
|
||||
relation: 'account_types.id',
|
||||
relationColumn: 'account_types.normal'
|
||||
relationColumn: 'account_types.normal',
|
||||
options: [
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
{ key: 'debit', label: 'Debit' },
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,13 @@ export default class AccountType extends TenantModel {
|
||||
return ['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translatable lable.
|
||||
*/
|
||||
|
||||
@@ -14,4 +14,8 @@ export default class Currency extends TenantModel {
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,31 +127,38 @@ export default class Expense extends TenantModel {
|
||||
payment_date: {
|
||||
label: 'Payment date',
|
||||
column: 'payment_date',
|
||||
columnType: 'date',
|
||||
},
|
||||
payment_account: {
|
||||
label: 'Payment account',
|
||||
column: 'payment_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account',
|
||||
},
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'total_amount',
|
||||
columnType: 'number'
|
||||
},
|
||||
currency_code: {
|
||||
label: 'Currency',
|
||||
column: 'currency_code',
|
||||
optionsResource: 'currency',
|
||||
},
|
||||
reference_no: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference_no'
|
||||
column: 'reference_no',
|
||||
columnType: 'string',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string',
|
||||
},
|
||||
published: {
|
||||
label: 'Published',
|
||||
column: 'published',
|
||||
|
||||
},
|
||||
user: {
|
||||
label: 'User',
|
||||
@@ -162,6 +169,7 @@ export default class Expense extends TenantModel {
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,16 +50,19 @@ export default class ItemCategory extends TenantModel {
|
||||
name: {
|
||||
label: 'Name',
|
||||
column: 'name',
|
||||
columnType: 'string'
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string'
|
||||
},
|
||||
parent_category_id: {
|
||||
label: 'Parent category',
|
||||
column: 'parent_category_id',
|
||||
relation: 'items_categories.id',
|
||||
relationColumn: 'items_categories.id',
|
||||
optionsResource: 'item_category',
|
||||
},
|
||||
user: {
|
||||
label: 'User',
|
||||
@@ -71,24 +74,34 @@ export default class ItemCategory extends TenantModel {
|
||||
label: 'Cost account',
|
||||
column: 'cost_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account'
|
||||
},
|
||||
sell_account: {
|
||||
label: 'Sell account',
|
||||
column: 'sell_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account'
|
||||
},
|
||||
inventory_account: {
|
||||
label: 'Inventory account',
|
||||
column: 'inventory_account_id',
|
||||
relation: 'accounts.id',
|
||||
optionsResource: 'account'
|
||||
},
|
||||
cost_method: {
|
||||
label: 'Cost method',
|
||||
column: 'cost_method',
|
||||
options: [{
|
||||
key: 'FIFO', label: 'First-in first-out (FIFO)',
|
||||
key: 'LIFO', label: 'Last-in first-out (LIFO)',
|
||||
key: 'average', label: 'Average rate',
|
||||
}],
|
||||
columnType: 'string',
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,14 +66,17 @@ export default class ManualJournal extends TenantModel {
|
||||
date: {
|
||||
label: 'Date',
|
||||
column: 'date',
|
||||
columnType: 'date',
|
||||
},
|
||||
journal_number: {
|
||||
label: 'Journal number',
|
||||
column: 'journal_number',
|
||||
columnType: 'string',
|
||||
},
|
||||
reference: {
|
||||
label: 'Reference No.',
|
||||
column: 'reference',
|
||||
columnType: 'string',
|
||||
},
|
||||
status: {
|
||||
label: 'Status',
|
||||
@@ -82,10 +85,12 @@ export default class ManualJournal extends TenantModel {
|
||||
amount: {
|
||||
label: 'Amount',
|
||||
column: 'amount',
|
||||
columnType: 'number',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
columnType: 'string',
|
||||
},
|
||||
user: {
|
||||
label: 'User',
|
||||
|
||||
@@ -70,8 +70,6 @@ export default class DynamicListService implements IDynamicListService {
|
||||
private validateRolesFieldsExistance(model: IModel, filterRoles: IFilterRole[]) {
|
||||
const invalidFieldsKeys = validateFilterRolesFieldsExistance(model, filterRoles);
|
||||
|
||||
console.log(invalidFieldsKeys);
|
||||
|
||||
if (invalidFieldsKeys.length > 0) {
|
||||
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
|
||||
}
|
||||
@@ -86,8 +84,9 @@ export default class DynamicListService implements IDynamicListService {
|
||||
required: true,
|
||||
type: 'object',
|
||||
properties: {
|
||||
condition: { type: 'string' },
|
||||
fieldKey: { required: true, type: 'string' },
|
||||
value: { required: true, type: 'string' },
|
||||
value: { required: true },
|
||||
},
|
||||
});
|
||||
const invalidFields = filterRoles.filter((filterRole) => {
|
||||
@@ -126,12 +125,16 @@ export default class DynamicListService implements IDynamicListService {
|
||||
}
|
||||
// Filter roles.
|
||||
if (filter.filterRoles.length > 0) {
|
||||
this.validateFilterRolesSchema(filter.filterRoles);
|
||||
this.validateRolesFieldsExistance(model, filter.filterRoles);
|
||||
const filterRoles = filter.filterRoles.map((filterRole, index) => ({
|
||||
...filterRole,
|
||||
index: index + 1,
|
||||
}));
|
||||
this.validateFilterRolesSchema(filterRoles);
|
||||
this.validateRolesFieldsExistance(model, filterRoles);
|
||||
|
||||
// Validate the model resource fields.
|
||||
const filterRoles = new DynamicFilterFilterRoles(filter.filterRoles);
|
||||
dynamicFilter.setFilter(filterRoles);
|
||||
const dynamicFilterRoles = new DynamicFilterFilterRoles(filterRoles);
|
||||
dynamicFilter.setFilter(dynamicFilterRoles);
|
||||
}
|
||||
return dynamicFilter;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { camelCase, upperFirst } from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
import { buildFilter } from 'objection-filter';
|
||||
import { IModel } from 'interfaces';
|
||||
import {
|
||||
getModelFields,
|
||||
@@ -35,18 +36,42 @@ export default class ResourceService {
|
||||
const fields = getModelFields(Model);
|
||||
|
||||
return fields.map((field) => ({
|
||||
label: __(field.label, field.label),
|
||||
label: __(field.label),
|
||||
key: field.key,
|
||||
dataType: field.columnType,
|
||||
fieldType: field.fieldType,
|
||||
|
||||
...(field.options) ? {
|
||||
options: field.options.map((option) => ({
|
||||
...option, label: __(option.label),
|
||||
})),
|
||||
} : {},
|
||||
|
||||
...(field.optionsResource) ? {
|
||||
optionsResource: field.optionsResource,
|
||||
optionsKey: field.optionsKey,
|
||||
optionsLabel: field.optionsLabel,
|
||||
} : {},
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should model be resource-able or throw service error.
|
||||
* @param {IModel} model
|
||||
*/
|
||||
private shouldModelBeResourceable(model: IModel) {
|
||||
if (!model.resourceable) {
|
||||
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve resource fields from resource model name.
|
||||
* @param {string} resourceName
|
||||
*/
|
||||
public getResourceFields(tenantId: number, modelName: string) {
|
||||
const resourceModel = this.getResourceModel(tenantId, modelName);
|
||||
this.shouldModelBeResourceable(resourceModel);
|
||||
|
||||
return this.getModelFields(tenantId, resourceModel);
|
||||
}
|
||||
@@ -63,9 +88,18 @@ export default class ResourceService {
|
||||
if (!Models[modelName]) {
|
||||
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
||||
}
|
||||
if (!Models[modelName].resourceable) {
|
||||
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
||||
}
|
||||
return Models[modelName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve resource data from the storage based on the given query.
|
||||
* @param {number} tenantId
|
||||
* @param {string} modelName
|
||||
*/
|
||||
public async getResourceData(tenantId: number, modelName: string, filter: any) {
|
||||
const resourceModel = this.getResourceModel(tenantId, modelName);
|
||||
this.shouldModelBeResourceable(resourceModel);
|
||||
|
||||
return buildFilter(resourceModel).build(filter);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user