mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
299 lines
8.7 KiB
TypeScript
299 lines
8.7 KiB
TypeScript
import { difference } from 'lodash';
|
|
import moment from 'moment';
|
|
import { Lexer } from 'lib/LogicEvaluation/Lexer';
|
|
import Parser from 'lib/LogicEvaluation/Parser';
|
|
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
|
import { IFilterRole, IModel } from 'interfaces';
|
|
|
|
const numberRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
|
switch (role.comparator) {
|
|
case 'equals':
|
|
case 'equal':
|
|
default:
|
|
return (builder) => {
|
|
builder.where(comparatorColumn, '=', role.value);
|
|
};
|
|
case 'not_equals':
|
|
case 'not_equal':
|
|
return (builder) => {
|
|
builder.whereNot(comparatorColumn, role.value);
|
|
};
|
|
case 'bigger_than':
|
|
case 'bigger':
|
|
return (builder) => {
|
|
builder.where(comparatorColumn, '>', role.value);
|
|
};
|
|
case 'bigger_or_equals':
|
|
return (builder) => {
|
|
builder.where(comparatorColumn, '>=', role.value);
|
|
};
|
|
case 'smaller_than':
|
|
case 'smaller':
|
|
return (builder) => {
|
|
builder.where(comparatorColumn, '<', role.value);
|
|
};
|
|
case 'smaller_or_equals':
|
|
return (builder) => {
|
|
builder.where(comparatorColumn, '<=', role.value);
|
|
};
|
|
}
|
|
};
|
|
|
|
const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
|
switch (role.comparator) {
|
|
case 'equals':
|
|
default:
|
|
return (builder) => {
|
|
builder.where(comparatorColumn, role.value);
|
|
};
|
|
case 'not_equal':
|
|
case 'not_equals':
|
|
return (builder) => {
|
|
builder.whereNot(comparatorColumn, role.value);
|
|
};
|
|
case 'contain':
|
|
case 'contains':
|
|
return (builder) => {
|
|
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
|
};
|
|
case 'not_contain':
|
|
case 'not_contains':
|
|
return (builder) => {
|
|
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
|
};
|
|
}
|
|
};
|
|
|
|
const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
|
switch(role.comparator) {
|
|
case 'after':
|
|
case 'before':
|
|
return (builder) => {
|
|
const comparator = role.comparator === 'before' ? '<' : '>';
|
|
const hasTimeFormat = moment(role.value, 'YYYY-MM-DD HH:MM', true).isValid();
|
|
const targetDate = moment(role.value);
|
|
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
|
|
|
if (!hasTimeFormat) {
|
|
if (role.comparator === 'before') {
|
|
targetDate.startOf('day');
|
|
} else {
|
|
targetDate.endOf('day');
|
|
}
|
|
}
|
|
const comparatorValue = targetDate.format(dateFormat);
|
|
builder.where(comparatorColumn, comparator, comparatorValue);
|
|
};
|
|
case 'in':
|
|
return (builder) => {
|
|
const hasTimeFormat = moment(role.value, 'YYYY-MM-DD HH:MM', true).isValid();
|
|
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
|
|
|
if (hasTimeFormat) {
|
|
const targetDateTime = moment(role.value).format(dateFormat);
|
|
builder.where(comparatorColumn, '=', targetDateTime);
|
|
} else {
|
|
const startDate = moment(role.value).startOf('day');
|
|
const endDate = moment(role.value).endOf('day');
|
|
|
|
builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
|
|
builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get field column metadata and its relation with other tables.
|
|
* @param {String} tableName - Table name of target column.
|
|
* @param {String} fieldKey - Target column key that stored in resource field.
|
|
*/
|
|
export function getRoleFieldColumn(model: IModel, fieldKey: string) {
|
|
const tableFields = model.fields;
|
|
return (tableFields[fieldKey]) ? tableFields[fieldKey] : null;
|
|
}
|
|
|
|
/**
|
|
* Builds roles queries.
|
|
* @param {IModel} model -
|
|
* @param {Object} role -
|
|
*/
|
|
export function buildRoleQuery(model: IModel, role: IFilterRole) {
|
|
const fieldRelation = getRoleFieldColumn(model, role.fieldKey);
|
|
const comparatorColumn = fieldRelation.relationColumn || `${model.tableName}.${fieldRelation.column}`;
|
|
|
|
switch (fieldRelation.columnType) {
|
|
case 'number':
|
|
return numberRoleQueryBuilder(role, comparatorColumn);
|
|
case 'date':
|
|
return dateQueryBuilder(role, comparatorColumn);
|
|
case 'text':
|
|
case 'varchar':
|
|
default:
|
|
return textRoleQueryBuilder(role, comparatorColumn);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract relation table name from relation.
|
|
* @param {String} column -
|
|
* @return {String} - join relation table.
|
|
*/
|
|
export const getTableFromRelationColumn = (column: string) => {
|
|
const splitedColumn = column.split('.');
|
|
return (splitedColumn.length > 0) ? splitedColumn[0] : '';
|
|
};
|
|
|
|
/**
|
|
* Builds view roles join queries.
|
|
* @param {String} tableName -
|
|
* @param {Array} roles -
|
|
*/
|
|
export function buildFilterRolesJoins(model: IModel, roles: IFilterRole[]) {
|
|
return (builder) => {
|
|
roles.forEach((role) => {
|
|
const fieldColumn = getRoleFieldColumn(model, role.fieldKey);
|
|
|
|
if (fieldColumn.relation) {
|
|
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
|
builder.join(joinTable, `${model.tableName}.${fieldColumn.column}`, '=', fieldColumn.relation);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
|
return (builder) => {
|
|
const fieldColumn = getRoleFieldColumn(model, sortColumnKey);
|
|
|
|
if (fieldColumn.relation) {
|
|
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
|
builder.join(joinTable, `${model.tableName}.${fieldColumn.column}`, '=', fieldColumn.relation);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Builds database query from stored view roles.
|
|
*
|
|
* @param {Array} roles -
|
|
* @return {Function}
|
|
*/
|
|
export function buildFilterRolesQuery(model: IModel, roles: IFilterRole[], logicExpression: string = '') {
|
|
const rolesIndexSet = {};
|
|
|
|
roles.forEach((role) => {
|
|
rolesIndexSet[role.index] = buildRoleQuery(model, role);
|
|
});
|
|
// Lexer for logic expression.
|
|
const lexer = new Lexer(logicExpression);
|
|
const tokens = lexer.getTokens();
|
|
|
|
// Parse the logic expression.
|
|
const parser = new Parser(tokens);
|
|
const parsedTree = parser.parse();
|
|
|
|
const queryParser = new QueryParser(parsedTree, rolesIndexSet);
|
|
return queryParser.parse();
|
|
}
|
|
|
|
/**
|
|
* Builds filter query for query builder.
|
|
* @param {String} tableName -
|
|
* @param {Array} roles -
|
|
* @param {String} logicExpression -
|
|
*/
|
|
export const buildFilterQuery = (model: IModel, roles: IFilterRole[], logicExpression: string) => {
|
|
return (builder) => {
|
|
buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Mapes the view roles to view conditionals.
|
|
* @param {Array} viewRoles -
|
|
* @return {Array}
|
|
*/
|
|
export function mapViewRolesToConditionals(viewRoles) {
|
|
return viewRoles.map((viewRole) => ({
|
|
comparator: viewRole.comparator,
|
|
value: viewRole.value,
|
|
index: viewRole.index,
|
|
|
|
columnKey: viewRole.field.key,
|
|
slug: viewRole.field.slug,
|
|
}));
|
|
}
|
|
|
|
|
|
export function mapFilterRolesToDynamicFilter(roles) {
|
|
return roles.map((role) => ({
|
|
...role,
|
|
columnKey: role.fieldKey,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Builds sort column query.
|
|
* @param {String} tableName -
|
|
* @param {String} columnKey -
|
|
* @param {String} sortDirection -
|
|
*/
|
|
export function buildSortColumnQuery(model: IModel, columnKey: string, sortDirection: string) {
|
|
const fieldRelation = getRoleFieldColumn(model, columnKey);
|
|
const sortColumn = fieldRelation.relation || `${model.tableName}.${fieldRelation.column}`;
|
|
|
|
return (builder) => {
|
|
builder.orderBy(sortColumn, sortDirection);
|
|
buildSortColumnJoin(model, columnKey)(builder);
|
|
};
|
|
}
|
|
|
|
export function validateFilterLogicExpression(logicExpression: string, indexes) {
|
|
const logicExpIndexes = logicExpression.match(/\d+/g) || [];
|
|
const diff = difference(logicExpIndexes.map(Number), indexes);
|
|
|
|
return (diff.length > 0) ? false : true;
|
|
}
|
|
|
|
export function validateRolesLogicExpression(logicExpression: string, roles: IFilterRole[]) {
|
|
return validateFilterLogicExpression(logicExpression, roles.map((r) => r.index));
|
|
}
|
|
|
|
export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
|
return model?.fields?.[fieldKey] || false;
|
|
}
|
|
|
|
export function validateFilterRolesFieldsExistance(model, filterRoles: IFilterRole[]) {
|
|
return filterRoles.filter((filterRole: IFilterRole) => {
|
|
return !validateFieldKeyExistance(model, filterRole.fieldKey);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieve model fields keys.
|
|
* @param {IModel} Model
|
|
* @return {string[]}
|
|
*/
|
|
export function getModelFieldsKeys(Model: IModel) {
|
|
const fields = Object.keys(Model.fields);
|
|
|
|
return fields.sort((a, b) => {
|
|
if (a < b) { return -1; }
|
|
if (a > b) { return 1; }
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
export function getModelFields(Model: IModel) {
|
|
const fieldsKey = this.getModelFieldsKeys(Model);
|
|
|
|
return fieldsKey.map((fieldKey) => {
|
|
const field = Model.fields[fieldKey];
|
|
return {
|
|
...field,
|
|
key: fieldKey,
|
|
};
|
|
})
|
|
}
|