feat: optimize dynamic list service.

feat: inactive mode for accounts, items, customers and vendors services.
This commit is contained in:
a.bouhuolia
2021-07-29 08:46:41 +02:00
parent 720dc5b7d7
commit 9186076676
80 changed files with 2748 additions and 1806 deletions

View File

@@ -1,6 +1,7 @@
import { Inject, Service } from 'typedi';
import { difference, chain, uniq } from 'lodash';
import { kebabCase } from 'lodash';
import R from 'ramda';
import TenancyService from 'services/Tenancy/TenancyService';
import { ServiceError } from 'exceptions';
import {
@@ -606,6 +607,17 @@ export default class AccountsService {
this.eventDispatcher.dispatch(events.accounts.onActivated);
}
/**
* Parsees accounts list filter DTO.
* @param filterDTO
* @returns
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter
)(filterDTO);
}
/**
* Retrieve accounts datatable list.
* @param {number} tenantId
@@ -613,21 +625,26 @@ export default class AccountsService {
*/
public async getAccountsList(
tenantId: number,
filter: IAccountsFilter
filterDTO: IAccountsFilter
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
const { Account } = this.tenancy.models(tenantId);
// Parses the stringified filter roles.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
Account,
filter
);
this.logger.info('[accounts] trying to get accounts datatable list.', {
tenantId,
filter,
});
const accounts = await Account.query().onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
});
return {
@@ -727,10 +744,11 @@ export default class AccountsService {
}));
return flatToNestedArray(
this.i18nService.i18nMapper(_accounts, ['account_type_label'], tenantId),
{
id: 'id',
parentId: 'parent_account_id',
});
{
id: 'id',
parentId: 'parent_account_id',
}
);
}
/**

View File

@@ -1,6 +1,6 @@
import { Inject, Service } from 'typedi';
import { omit, defaultTo } from 'lodash';
import async from 'async';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -265,6 +265,16 @@ export default class CustomersService {
return this.transformContactToCustomer(contact);
}
/**
* Parses customers list filter DTO.
* @param filterDTO -
*/
private parseCustomersListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter
)(filterDTO);
}
/**
* Retrieve customers paginated list.
* @param {number} tenantId - Tenant id.
@@ -272,7 +282,7 @@ export default class CustomersService {
*/
public async getCustomersList(
tenantId: number,
customersFilter: ICustomersFilter
filterDTO: ICustomersFilter
): Promise<{
customers: ICustomer[];
pagination: IPaginationMeta;
@@ -280,17 +290,23 @@ export default class CustomersService {
}> {
const { Customer } = this.tenancy.models(tenantId);
// Parses customers list filter DTO.
const filter = this.parseCustomersListFilterDTO(filterDTO);
// Dynamic list.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
Customer,
customersFilter
filter
);
// Customers.
const { results, pagination } = await Customer.query()
.onBuild((query) => {
dynamicList.buildQuery()(query);
.onBuild((builder) => {
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
})
.pagination(customersFilter.page - 1, customersFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
customers: results.map(this.transformContactToCustomer),

View File

@@ -1,5 +1,6 @@
import { Inject, Service } from 'typedi';
import { intersection, defaultTo } from 'lodash';
import { defaultTo } from 'lodash';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -255,6 +256,12 @@ export default class VendorsService {
);
}
private parseVendorsListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter
)(filterDTO);
}
/**
* Retrieve vendors datatable list.
* @param {number} tenantId - Tenant id.
@@ -262,7 +269,7 @@ export default class VendorsService {
*/
public async getVendorsList(
tenantId: number,
vendorsFilter: IVendorsFilter
filterDTO: IVendorsFilter
): Promise<{
vendors: IVendor[];
pagination: IPaginationMeta;
@@ -270,21 +277,28 @@ export default class VendorsService {
}> {
const { Vendor } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(
// Parses vendors list filter DTO.
const filter = this.parseVendorsListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
Vendor,
vendorsFilter
filter
);
// Vendors list.
const { results, pagination } = await Vendor.query()
.onBuild((builder) => {
dynamicFilter.buildQuery()(builder);
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
})
.pagination(vendorsFilter.page - 1, vendorsFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
vendors: results,
pagination,
filterMeta: dynamicFilter.getResponseMeta(),
filterMeta: dynamicList.getResponseMeta(),
};
}

View File

@@ -0,0 +1,6 @@
export default class DynamicListAbstruct {
}

View File

@@ -0,0 +1,52 @@
import { Inject, Service } from 'typedi';
import DynamicListAbstruct from './DynamicListAbstruct';
import DynamicFilterViews from 'lib/DynamicFilter/DynamicFilterViews';
import { ServiceError } from 'exceptions';
import HasTenancyService from 'services/Tenancy/TenancyService';
import {ERRORS } from './constants';
import { IModel }from 'interfaces';
@Service()
export default class DynamicListCustomView extends DynamicListAbstruct {
@Inject()
tenancy: HasTenancyService;
/**
* Retreive custom view or throws error not found.
* @param {number} tenantId
* @param {number} viewId
* @return {Promise<IView>}
*/
private getCustomViewOrThrowError = async (
tenantId: number,
viewId: number,
model: IModel
) => {
const { viewRepository } = this.tenancy.repositories(tenantId);
const view = await viewRepository.findOneById(viewId, 'roles');
if (!view || view.resourceModel !== model.name) {
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
}
return view;
};
/**
* Dynamic list custom view.
* @param {IModel} model
* @param {number} customViewId
* @returns
*/
public dynamicListCustomView = async (
tenantId: number,
model,
customViewId: number
) => {
const view = await this.getCustomViewOrThrowError(
tenantId,
customViewId,
model
);
return new DynamicFilterViews(view);
};
}

View File

@@ -0,0 +1,103 @@
import { Service } from 'typedi';
import * as R from 'ramda';
import validator from 'is-my-json-valid';
import { IFilterRole, IModel } from 'interfaces';
import DynamicListAbstruct from './DynamicListAbstruct';
import DynamicFilterFilterRoles from 'lib/DynamicFilter/DynamicFilterFilterRoles';
import { ERRORS } from './constants';
import { ServiceError } from 'exceptions';
@Service()
export default class DynamicListFilterRoles extends DynamicListAbstruct {
/**
* Validates filter roles schema.
* @param {IFilterRole[]} filterRoles - Filter roles.
*/
private validateFilterRolesSchema = (filterRoles: IFilterRole[]) => {
const validate = validator({
required: true,
type: 'object',
properties: {
condition: { type: 'string' },
fieldKey: { required: true, type: 'string' },
value: { required: true },
},
});
const invalidFields = filterRoles.filter((filterRole) => {
return !validate(filterRole);
});
if (invalidFields.length > 0) {
throw new ServiceError(ERRORS.STRINGIFIED_FILTER_ROLES_INVALID);
}
};
/**
* Retrieve filter roles fields key that not exists on the given model.
* @param {IModel} model
* @param {IFilterRole} filterRoles
* @returns {string[]}
*/
private getFilterRolesFieldsNotExist = (
model,
filterRoles: IFilterRole[]
): string[] => {
return filterRoles
.filter((filterRole) => !model.getField(filterRole.fieldKey))
.map((filterRole) => filterRole.fieldKey);
};
/**
* Validates existance the fields of filter roles.
* @param {IModel} model
* @param {IFilterRole[]} filterRoles
* @throws {ServiceError}
*/
private validateFilterRolesFieldsExistance = (
model: IModel,
filterRoles: IFilterRole[]
) => {
const invalidFieldsKeys = this.getFilterRolesFieldsNotExist(
model,
filterRoles
);
if (invalidFieldsKeys.length > 0) {
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
}
};
/**
* Associate index to filter roles.
* @param {IFilterRole[]} filterRoles
* @returns {IFilterRole[]}
*/
private incrementFilterRolesIndex = (
filterRoles: IFilterRole[]
): IFilterRole[] => {
return filterRoles.map((filterRole, index) => ({
...filterRole,
index: index + 1,
}));
};
/**
* Dynamic list filter roles.
* @param {IModel} model
* @param {IFilterRole[]} filterRoles
* @returns {DynamicFilterFilterRoles}
*/
public dynamicList = (
model: IModel,
filterRoles: IFilterRole[]
): DynamicFilterFilterRoles => {
const filterRolesParsed = R.compose(this.incrementFilterRolesIndex)(
filterRoles
);
// Validate filter roles json schema.
this.validateFilterRolesSchema(filterRolesParsed);
// Validate the model resource fields.
this.validateFilterRolesFieldsExistance(model, filterRoles);
return new DynamicFilterFilterRoles(filterRolesParsed);
};
}

View File

@@ -1,161 +1,88 @@
import { Service, Inject } from 'typedi';
import validator from 'is-my-json-valid';
import { Request, Response, NextFunction } from 'express';
import { castArray, isEmpty } from 'lodash';
import { ServiceError } from 'exceptions';
import { DynamicFilter } from 'lib/DynamicFilter';
import {
DynamicFilter,
DynamicFilterSortBy,
DynamicFilterViews,
DynamicFilterFilterRoles,
} from 'lib/DynamicFilter';
import {
validateFieldKeyExistance,
validateFilterRolesFieldsExistance,
} from 'lib/ViewRolesBuilder';
import {
IDynamicListFilterDTO,
IFilterRole,
IDynamicListFilter,
IDynamicListService,
IFilterRole,
IModel,
} from 'interfaces';
import TenancyService from 'services/Tenancy/TenancyService';
const ERRORS = {
VIEW_NOT_FOUND: 'view_not_found',
SORT_COLUMN_NOT_FOUND: 'sort_column_not_found',
FILTER_ROLES_FIELDS_NOT_FOUND: 'filter_roles_fields_not_found',
};
import DynamicListFilterRoles from './DynamicListFilterRoles';
import DynamicListSortBy from './DynamicListSortBy';
import DynamicListCustomView from './DynamicListCustomView';
@Service()
export default class DynamicListService implements IDynamicListService {
@Inject()
tenancy: TenancyService;
/**
* Retreive custom view or throws error not found.
* @param {number} tenantId
* @param {number} viewId
* @return {Promise<IView>}
*/
private async getCustomViewOrThrowError(
tenantId: number,
viewId: number,
model: IModel
) {
const { viewRepository } = this.tenancy.repositories(tenantId);
const view = await viewRepository.findOneById(viewId, 'roles');
@Inject()
dynamicListFilterRoles: DynamicListFilterRoles;
if (!view || view.resourceModel !== model.name) {
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
}
return view;
}
@Inject()
dynamicListSortBy: DynamicListSortBy;
@Inject()
dynamicListView: DynamicListCustomView;
/**
* Validates the sort column whether exists.
* @param {IModel} model
* @param {string} columnSortBy - Sort column
* @throws {ServiceError}
* Parses filter DTO.
*/
private validateSortColumnExistance(model: any, columnSortBy: string) {
const notExistsField = validateFieldKeyExistance(model, columnSortBy);
if (!notExistsField) {
throw new ServiceError(ERRORS.SORT_COLUMN_NOT_FOUND);
}
}
/**
* Validates existance the fields of filter roles.
* @param {IModel} model
* @param {IFilterRole[]} filterRoles
* @throws {ServiceError}
*/
private validateRolesFieldsExistance(
model: IModel,
filterRoles: IFilterRole[]
) {
const invalidFieldsKeys = validateFilterRolesFieldsExistance(
model,
filterRoles
);
if (invalidFieldsKeys.length > 0) {
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
}
}
/**
* Validates filter roles schema.
* @param {IFilterRole[]} filterRoles
*/
private validateFilterRolesSchema(filterRoles: IFilterRole[]) {
const validate = validator({
required: true,
type: 'object',
properties: {
condition: { type: 'string' },
fieldKey: { required: true, type: 'string' },
value: { required: true },
},
});
const invalidFields = filterRoles.filter((filterRole) => {
const isValid = validate(filterRole);
return isValid ? false : true;
});
if (invalidFields.length > 0) {
throw new ServiceError('stringified_filter_roles_invalid');
}
}
private parseFilterObject = (model, filterDTO) => {
return {
// Merges the default properties with filter object.
...model.defaultSort ? {
sortOrder: model.defaultSort.sortOrder,
columnSortBy: model.defaultSort.sortOrder,
} : {},
...filterDTO,
};
};
/**
* Dynamic listing.
* @param {number} tenantId - Tenant id.
* @param {IModel} model - Model.
* @param {IDynamicListFilterDTO} filter - Dynamic filter DTO.
* @param {IDynamicListFilter} filter - Dynamic filter DTO.
*/
public async dynamicList(
public dynamicList = async (
tenantId: number,
model: IModel,
filter: IDynamicListFilterDTO
) {
filter: IDynamicListFilter
) => {
const dynamicFilter = new DynamicFilter(model);
// Custom view filter roles.
if (filter.customViewId) {
const view = await this.getCustomViewOrThrowError(
tenantId,
filter.customViewId,
model
);
const viewFilter = new DynamicFilterViews(view);
dynamicFilter.setFilter(viewFilter);
}
// Sort by the given column.
if (filter.columnSortBy) {
this.validateSortColumnExistance(model, filter.columnSortBy);
// Parses the filter object.
const parsedFilter = this.parseFilterObject(model, filter);
const sortByFilter = new DynamicFilterSortBy(
filter.columnSortBy,
filter.sortOrder
// Custom view filter roles.
// if (filter.customViewId) {
// const dynamicListCustomView = this.dynamicListView.dynamicListCustomView();
// dynamicFilter.setFilter(dynamicListCustomView);
// }
// Sort by the given column.
if (parsedFilter.columnSortBy) {
const dynmaicListSortBy = this.dynamicListSortBy.dynamicSortBy(
model,
parsedFilter.columnSortBy,
parsedFilter.sortOrder,
);
dynamicFilter.setFilter(sortByFilter);
dynamicFilter.setFilter(dynmaicListSortBy);
}
// Filter roles.
if (filter.filterRoles.length > 0) {
const filterRoles = filter.filterRoles.map((filterRole, index) => ({
...filterRole,
index: index + 1,
}));
this.validateFilterRolesSchema(filterRoles);
this.validateRolesFieldsExistance(model, filterRoles);
// Validate the model resource fields.
const dynamicFilterRoles = new DynamicFilterFilterRoles(filterRoles);
if (!isEmpty(parsedFilter.filterRoles)) {
const dynamicFilterRoles = this.dynamicListFilterRoles.dynamicList(
model,
parsedFilter.filterRoles
);
dynamicFilter.setFilter(dynamicFilterRoles);
}
return dynamicFilter;
}
};
/**
* Middleware to catch services errors
@@ -173,25 +100,62 @@ export default class DynamicListService implements IDynamicListService {
if (error instanceof ServiceError) {
if (error.errorType === 'sort_column_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'SORT.COLUMN.NOT.FOUND', code: 200 }],
errors: [
{
type: 'SORT.COLUMN.NOT.FOUND',
message: 'Sort column not found.',
code: 200,
},
],
});
}
if (error.errorType === 'view_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'CUSTOM.VIEW.NOT.FOUND', code: 100 }],
errors: [
{
type: 'CUSTOM.VIEW.NOT.FOUND',
message: 'Custom view not found.',
code: 100,
},
],
});
}
if (error.errorType === 'filter_roles_fields_not_found') {
return res.boom.badRequest(null, {
errors: [{ type: 'FILTER.ROLES.FIELDS.NOT.FOUND', code: 300 }],
errors: [
{
type: 'FILTER.ROLES.FIELDS.NOT.FOUND',
message: 'Filter roles fields not found.',
code: 300,
},
],
});
}
if (error.errorType === 'stringified_filter_roles_invalid') {
return res.boom.badRequest(null, {
errors: [{ type: 'STRINGIFIED_FILTER_ROLES_INVALID', code: 400 }],
errors: [
{
type: 'STRINGIFIED_FILTER_ROLES_INVALID',
message: 'Stringified filter roles json invalid.',
code: 400,
},
],
});
}
}
next(error);
}
/**
* Parses stringified filter roles.
* @param {string} stringifiedFilterRoles - Stringified filter roles.
*/
public parseStringifiedFilter = (filterRoles: IDynamicListFilter) => {
return {
...filterRoles,
filterRoles: filterRoles.stringifiedFilterRoles
? castArray(JSON.parse(filterRoles.stringifiedFilterRoles))
: [],
};
};
}

View File

@@ -0,0 +1,40 @@
import { Service } from 'typedi';
import DynamicListAbstruct from './DynamicListAbstruct';
import DynamicFilterSortBy from 'lib/DynamicFilter/DynamicFilterSortBy';
import { IModel, ISortOrder } from 'interfaces';
import { ServiceError } from 'exceptions';
import { ERRORS } from './constants';
@Service()
export default class DynamicListSortBy extends DynamicListAbstruct {
/**
* Dynamic list sort by.
* @param {IModel} model
* @param {string} columnSortBy
* @param {ISortOrder} sortOrder
* @returns {DynamicFilterSortBy}
*/
public dynamicSortBy(
model: IModel,
columnSortBy: string,
sortOrder: ISortOrder
) {
this.validateSortColumnExistance(model, columnSortBy);
return new DynamicFilterSortBy(columnSortBy, sortOrder);
}
/**
* Validates the sort column whether exists.
* @param {IModel} model - Model.
* @param {string} columnSortBy - Sort column
* @throws {ServiceError}
*/
private validateSortColumnExistance(model: any, columnSortBy: string) {
const field = model.getField(columnSortBy);
if (!field) {
throw new ServiceError(ERRORS.SORT_COLUMN_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,6 @@
export const ERRORS = {
STRINGIFIED_FILTER_ROLES_INVALID: 'stringified_filter_roles_invalid',
VIEW_NOT_FOUND: 'view_not_found',
SORT_COLUMN_NOT_FOUND: 'sort_column_not_found',
FILTER_ROLES_FIELDS_NOT_FOUND: 'filter_roles_fields_not_found',
};

View File

@@ -1,6 +1,7 @@
import { Service, Inject } from 'typedi';
import { difference, sumBy, omit, map } from 'lodash';
import { difference, sumBy, omit } from 'lodash';
import moment from 'moment';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -630,6 +631,16 @@ export default class ExpensesService implements IExpensesService {
return expenses.filter((expense) => expense.publishedAt);
}
/**
* Parses filter DTO of expenses list.
* @param filterDTO -
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter,
)(filterDTO);
}
/**
* Retrieve expenses datatable lsit.
* @param {number} tenantId
@@ -638,35 +649,41 @@ export default class ExpensesService implements IExpensesService {
*/
public async getExpensesList(
tenantId: number,
expensesFilter: IExpensesFilter
filterDTO: IExpensesFilter
): Promise<{
expenses: IExpense[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { Expense } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(
// Parses list filter DTO.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
Expense,
expensesFilter
filter,
);
this.logger.info('[expense] trying to get expenses datatable list.', {
tenantId,
expensesFilter,
filter,
});
const { results, pagination } = await Expense.query()
.onBuild((builder) => {
builder.withGraphFetched('paymentAccount');
builder.withGraphFetched('categories.expenseAccount');
dynamicFilter.buildQuery()(builder);
dynamicList.buildQuery()(builder);
})
.pagination(expensesFilter.page - 1, expensesFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
expenses: results,
pagination,
filterMeta: dynamicFilter.getResponseMeta(),
filterMeta: dynamicList.getResponseMeta(),
};
}

View File

@@ -29,7 +29,6 @@ export default class JournalSheetService {
fromRange: null,
toRange: null,
accountsIds: [],
transactionTypes: [],
numberFormat: {
noCents: false,
divideOn1000: false,
@@ -107,6 +106,13 @@ export default class JournalSheetService {
}
query.modify('filterDateRange', filter.fromDate, filter.toDate);
query.orderBy(['date', 'createdAt', 'indexGroup', 'index']);
if (filter.transactionType) {
return query.where('reference_type', filter.transactionType);
}
if (filter.transactionType && filter.transactionId) {
return query.where('reference_id', filter.transactionId);
}
});
// Transform the transactions array to journal collection.
const transactionsJournal = Journal.fromTransactions(

View File

@@ -1,6 +1,7 @@
import { Inject, Service } from 'typedi';
import { omit } from 'lodash';
import moment from 'moment';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -225,8 +226,8 @@ export default class InventoryAdjustmentService {
/**
* Publish the inventory adjustment transaction.
* @param tenantId
* @param inventoryAdjustmentId
* @param {number} tenantId
* @param {number} inventoryAdjustmentId
*/
async publishInventoryAdjustment(
tenantId: number,
@@ -265,6 +266,17 @@ export default class InventoryAdjustmentService {
);
}
/**
* Parses inventory adjustments list filter DTO.
* @param filterDTO
* @returns
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter,
)(filterDTO);
}
/**
* Retrieve the inventory adjustments paginated list.
* @param {number} tenantId
@@ -272,17 +284,21 @@ export default class InventoryAdjustmentService {
*/
async getInventoryAdjustments(
tenantId: number,
adjustmentsFilter: IInventoryAdjustmentsFilter
filterDTO: IInventoryAdjustmentsFilter
): Promise<{
inventoryAdjustments: IInventoryAdjustment[];
pagination: IPaginationMeta;
}> {
const { InventoryAdjustment } = this.tenancy.models(tenantId);
// Parses inventory adjustments list filter DTO.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
InventoryAdjustment,
adjustmentsFilter
filter,
);
const { results, pagination } = await InventoryAdjustment.query()
.onBuild((query) => {
@@ -291,7 +307,7 @@ export default class InventoryAdjustmentService {
dynamicFilter.buildQuery()(query);
})
.pagination(adjustmentsFilter.page - 1, adjustmentsFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
inventoryAdjustments: results,

View File

@@ -1,5 +1,6 @@
import { Inject } from 'typedi';
import { difference } from 'lodash';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -377,6 +378,18 @@ export default class ItemCategoriesService implements IItemCategoriesService {
}
}
/**
* Parses items categories filter DTO.
* @param {} filterDTO
* @returns
*/
private parsesListFilterDTO(filterDTO) {
return R.compose(
// Parses stringified filter roles.
this.dynamicListService.parseStringifiedFilter,
)(filterDTO);
}
/**
* Retrieve item categories list.
* @param {number} tenantId
@@ -384,10 +397,15 @@ export default class ItemCategoriesService implements IItemCategoriesService {
*/
public async getItemCategoriesList(
tenantId: number,
filter: IItemCategoriesFilter,
filterDTO: IItemCategoriesFilter,
authorizedUser: ISystemUser
): Promise<{ itemCategories: IItemCategory[]; filterMeta: IFilterMeta }> {
const { ItemCategory } = this.tenancy.models(tenantId);
// Parses list filter DTO.
const filter = this.parsesListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
ItemCategory,

View File

@@ -1,5 +1,6 @@
import { defaultTo, difference } from 'lodash';
import { defaultTo } from 'lodash';
import { Service, Inject } from 'typedi';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -525,20 +526,37 @@ export default class ItemsService implements IItemsService {
return this.transformItemToResponse(tenantId, item);
}
/**
* Parses items list filter DTO.
* @param {} filterDTO - Filter DTO.
*/
private parseItemsListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter,
)(filterDTO);
}
/**
* Retrieve items datatable list.
* @param {number} tenantId
* @param {IItemsFilter} itemsFilter
*/
public async itemsList(tenantId: number, itemsFilter: IItemsFilter) {
public async itemsList(tenantId: number, filterDTO: IItemsFilter) {
const { Item } = this.tenancy.models(tenantId);
// Parses items list filter DTO.
const filter = this.parseItemsListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
Item,
itemsFilter
filter
);
const { results: items, pagination } = await Item.query()
.onBuild((builder) => {
builder.modify('inactiveMode', filter.inactiveMode);
builder.withGraphFetched('inventoryAccount');
builder.withGraphFetched('sellAccount');
builder.withGraphFetched('costAccount');
@@ -546,7 +564,7 @@ export default class ItemsService implements IItemsService {
dynamicFilter.buildQuery()(builder);
})
.pagination(itemsFilter.page - 1, itemsFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
const results = items.map((item) =>
this.transformItemToResponse(tenantId, item)

View File

@@ -1,7 +1,8 @@
import { difference, sumBy, omit, map } from 'lodash';
import { Service, Inject } from 'typedi';
import moment from 'moment';
import { ServiceError, ServiceErrors } from 'exceptions';
import * as R from 'ramda';
import { ServiceError } from 'exceptions';
import {
IManualJournalDTO,
IManualJournalsService,
@@ -768,33 +769,47 @@ export default class ManualJournalsService implements IManualJournalsService {
);
}
/**
* Parses filter DTO of the manual journals list.
* @param filterDTO
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter
)(filterDTO);
}
/**
* Retrieve manual journals datatable list.
* @param {number} tenantId
* @param {IManualJournalsFilter} filter
* @param {number} tenantId -
* @param {IManualJournalsFilter} filter -
*/
public async getManualJournals(
tenantId: number,
filter: IManualJournalsFilter
filterDTO: IManualJournalsFilter
): Promise<{
manualJournals: IManualJournal;
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { ManualJournal } = this.tenancy.models(tenantId);
const dynamicList = await this.dynamicListService.dynamicList(
// Parses filter DTO.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic service.
const dynamicService = await this.dynamicListService.dynamicList(
tenantId,
ManualJournal,
filter
);
this.logger.info('[manual_journals] trying to get manual journals list.', {
tenantId,
filter,
});
const { results, pagination } = await ManualJournal.query()
.onBuild((builder) => {
dynamicList.buildQuery()(builder);
dynamicService.buildQuery()(builder);
builder.withGraphFetched('entries.account');
})
.pagination(filter.page - 1, filter.pageSize);
@@ -802,7 +817,7 @@ export default class ManualJournalsService implements IManualJournalsService {
return {
manualJournals: results,
pagination,
filterMeta: dynamicList.getResponseMeta(),
filterMeta: dynamicService.getResponseMeta(),
};
}

View File

@@ -1,5 +1,6 @@
import { Inject, Service } from 'typedi';
import { omit, sumBy, difference } from 'lodash';
import { sumBy, difference } from 'lodash';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -25,7 +26,7 @@ import TenancyService from 'services/Tenancy/TenancyService';
import DynamicListingService from 'services/DynamicListing/DynamicListService';
import { entriesAmountDiff, formatDateFields } from 'utils';
import { ServiceError } from 'exceptions';
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from 'data/AccountTypes';
import { ACCOUNT_TYPE } from 'data/AccountTypes';
import VendorsService from 'services/Contacts/VendorsService';
import { ERRORS } from './constants';
@@ -635,6 +636,12 @@ export default class BillPaymentsService implements IBillPaymentsService {
]);
}
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter
)(filterDTO);
}
/**
* Retrieve bill payment paginted and filterable list.
* @param {number} tenantId
@@ -642,34 +649,40 @@ export default class BillPaymentsService implements IBillPaymentsService {
*/
public async listBillPayments(
tenantId: number,
billPaymentsFilter: IBillPaymentsFilter
filterDTO: IBillPaymentsFilter
): Promise<{
billPayments: IBillPayment;
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { BillPayment } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(
// Parses filter DTO.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
BillPayment,
billPaymentsFilter
filter
);
this.logger.info('[bill_payment] try to get bill payments list.', {
tenantId,
});
const { results, pagination } = await BillPayment.query()
.onBuild((builder) => {
builder.withGraphFetched('vendor');
builder.withGraphFetched('paymentAccount');
dynamicFilter.buildQuery()(builder);
dynamicList.buildQuery()(builder);
})
.pagination(billPaymentsFilter.page - 1, billPaymentsFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
billPayments: results,
pagination,
filterMeta: dynamicFilter.getResponseMeta(),
filterMeta: dynamicList.getResponseMeta(),
};
}

View File

@@ -1,6 +1,7 @@
import { omit, runInContext, sumBy } from 'lodash';
import moment from 'moment';
import { Inject, Service } from 'typedi';
import * as R from 'ramda';
import composeAsync from 'async/compose';
import {
EventDispatcher,
@@ -521,6 +522,16 @@ export default class BillsService
});
}
/**
* Parses bills list filter DTO.
* @param filterDTO -
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter,
)(filterDTO);
}
/**
* Retrieve bills data table list.
* @param {number} tenantId -
@@ -528,28 +539,33 @@ export default class BillsService
*/
public async getBills(
tenantId: number,
billsFilter: IBillsFilter
filterDTO: IBillsFilter
): Promise<{
bills: IBill;
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { Bill } = this.tenancy.models(tenantId);
// Parses bills list filter DTO.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
Bill,
billsFilter
filter,
);
this.logger.info('[bills] trying to get bills data table.', {
tenantId,
billsFilter,
filter,
});
const { results, pagination } = await Bill.query()
.onBuild((builder) => {
builder.withGraphFetched('vendor');
dynamicFilter.buildQuery()(builder);
})
.pagination(billsFilter.page - 1, billsFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
bills: results,

View File

@@ -2,7 +2,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 { IModel, IModelMeta } from 'interfaces';
import {
getModelFields,
} from 'lib/ViewRolesBuilder'
@@ -102,4 +102,18 @@ export default class ResourceService {
return buildFilter(resourceModel).build(filter);
}
/**
* Retrieve the resource meta.
* @param {number} tenantId
* @param {string} modelName
* @returns {IModelMeta}
*/
public getResourceMeta(tenantId: number, modelName: string): IModelMeta {
const resourceModel = this.getResourceModel(tenantId, modelName);
const settings = resourceModel.meta();
return settings;
}
}

View File

@@ -1,5 +1,6 @@
import { omit, sumBy, difference } from 'lodash';
import { Service, Inject } from 'typedi';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -613,6 +614,16 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
return saleInvoices;
}
/**
* Parses payments receive list filter DTO.
* @param filterDTO
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter
)(filterDTO);
}
/**
* Retrieve payment receives paginated and filterable list.
* @param {number} tenantId
@@ -620,33 +631,39 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
*/
public async listPaymentReceives(
tenantId: number,
paymentReceivesFilter: IPaymentReceivesFilter
filterDTO: IPaymentReceivesFilter
): Promise<{
paymentReceives: IPaymentReceive[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { PaymentReceive } = this.tenancy.models(tenantId);
const dynamicFilter = await this.dynamicListService.dynamicList(
// Parses filter DTO.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
tenantId,
PaymentReceive,
paymentReceivesFilter
filter
);
const { results, pagination } = await PaymentReceive.query()
.onBuild((builder) => {
builder.withGraphFetched('customer');
builder.withGraphFetched('depositAccount');
dynamicFilter.buildQuery()(builder);
dynamicList.buildQuery()(builder);
})
.pagination(
paymentReceivesFilter.page - 1,
paymentReceivesFilter.pageSize
filter.page - 1,
filter.pageSize
);
return {
paymentReceives: results,
pagination,
filterMeta: dynamicFilter.getResponseMeta(),
filterMeta: dynamicList.getResponseMeta(),
};
}

View File

@@ -1,5 +1,6 @@
import { omit, sumBy } from 'lodash';
import { filter, omit, sumBy } from 'lodash';
import { Service, Inject } from 'typedi';
import * as R from 'ramda';
import {
IEstimatesFilter,
IFilterMeta,
@@ -412,6 +413,16 @@ export default class SaleEstimateService implements ISalesEstimatesService{
return estimate;
}
/**
* Parses estimates list filter DTO.
* @param filterDTO
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter
)(filterDTO);
}
/**
* Retrieves estimates filterable and paginated list.
* @param {number} tenantId -
@@ -419,17 +430,22 @@ export default class SaleEstimateService implements ISalesEstimatesService{
*/
public async estimatesList(
tenantId: number,
estimatesFilter: IEstimatesFilter
filterDTO: IEstimatesFilter
): Promise<{
salesEstimates: ISaleEstimate[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleEstimate } = this.tenancy.models(tenantId);
// Parses filter DTO.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleEstimate,
estimatesFilter
filter,
);
const { results, pagination } = await SaleEstimate.query()
@@ -438,7 +454,7 @@ export default class SaleEstimateService implements ISalesEstimatesService{
builder.withGraphFetched('entries');
dynamicFilter.buildQuery()(builder);
})
.pagination(estimatesFilter.page - 1, estimatesFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
salesEstimates: results,

View File

@@ -1,5 +1,6 @@
import { Service, Inject } from 'typedi';
import { omit, sumBy, join, entries } from 'lodash';
import { omit, sumBy } from 'lodash';
import * as R from 'ramda';
import moment from 'moment';
import composeAsync from 'async/compose';
import {
@@ -647,6 +648,17 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
return saleInvoice;
}
/**
* Parses the sale invoice list filter DTO.
* @param filterDTO
* @returns
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter,
)(filterDTO);
}
/**
* Retrieve sales invoices filterable and paginated list.
* @param {Request} req
@@ -655,22 +667,27 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
*/
public async salesInvoicesList(
tenantId: number,
salesInvoicesFilter: ISalesInvoicesFilter
filterDTO: ISalesInvoicesFilter
): Promise<{
salesInvoices: ISaleInvoice[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleInvoice } = this.tenancy.models(tenantId);
// Parses stringified filter roles.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleInvoice,
salesInvoicesFilter
filter
);
this.logger.info('[sale_invoice] try to get sales invoices list.', {
tenantId,
salesInvoicesFilter,
filter,
});
const { results, pagination } = await SaleInvoice.query()
.onBuild((builder) => {
@@ -678,7 +695,7 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder);
})
.pagination(salesInvoicesFilter.page - 1, salesInvoicesFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
salesInvoices: results,

View File

@@ -1,6 +1,7 @@
import { omit, sumBy } from 'lodash';
import { Service, Inject } from 'typedi';
import moment from 'moment';
import * as R from 'ramda';
import {
EventDispatcher,
EventDispatcherInterface,
@@ -406,6 +407,16 @@ export default class SalesReceiptService implements ISalesReceiptsService {
return saleReceipt;
}
/**
* Parses the sale receipts list filter DTO.
* @param filterDTO
*/
private parseListFilterDTO(filterDTO) {
return R.compose(
this.dynamicListService.parseStringifiedFilter,
)(filterDTO);
}
/**
* Retrieve sales receipts paginated and filterable list.
* @param {number} tenantId
@@ -413,17 +424,22 @@ export default class SalesReceiptService implements ISalesReceiptsService {
*/
public async salesReceiptsList(
tenantId: number,
salesReceiptsFilter: ISaleReceiptFilter
filterDTO: ISaleReceiptFilter
): Promise<{
salesReceipts: ISaleReceipt[];
pagination: IPaginationMeta;
filterMeta: IFilterMeta;
}> {
const { SaleReceipt } = this.tenancy.models(tenantId);
// Parses the stringified filter roles.
const filter = this.parseListFilterDTO(filterDTO);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
SaleReceipt,
salesReceiptsFilter
filter,
);
this.logger.info('[sale_receipt] try to get sales receipts list.', {
@@ -437,7 +453,7 @@ export default class SalesReceiptService implements ISalesReceiptsService {
dynamicFilter.buildQuery()(builder);
})
.pagination(salesReceiptsFilter.page - 1, salesReceiptsFilter.pageSize);
.pagination(filter.page - 1, filter.pageSize);
return {
salesReceipts: results,