feat: remove SET_DASHBOARD_REQUEST_LOADING reducer.

feat: fix dropdown filter.
feat: fix fetch resource data.
This commit is contained in:
Ahmed Bouhuolia
2020-10-20 19:58:24 +02:00
parent 00ba1bb75e
commit 322af97d77
51 changed files with 1160 additions and 1009 deletions

View File

@@ -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);
}
};

View File

@@ -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);
}

View File

@@ -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) => {

View File

@@ -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);
};

View File

@@ -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"
}

View File

@@ -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' },
],
},
};
}

View File

@@ -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.
*/

View File

@@ -14,4 +14,8 @@ export default class Currency extends TenantModel {
get timestamps() {
return ['createdAt', 'updatedAt'];
}
static get resourceable() {
return true;
}
}

View File

@@ -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',
},
};
}

View File

@@ -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',
},
};
}

View File

@@ -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',

View File

@@ -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;
}

View File

@@ -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);
}
}