mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 16:19:49 +00:00
feat: optimize dynamic list service.
feat: inactive mode for accounts, items, customers and vendors services.
This commit is contained in:
@@ -29,11 +29,9 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/transactions',
|
'/transactions',
|
||||||
[
|
[query('account_id').optional().isInt().toInt()],
|
||||||
query('account_id').optional().isInt().toInt(),
|
|
||||||
],
|
|
||||||
this.asyncMiddleware(this.accountTransactions.bind(this)),
|
this.asyncMiddleware(this.accountTransactions.bind(this)),
|
||||||
this.catchServiceErrors,
|
this.catchServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/activate',
|
'/:id/activate',
|
||||||
@@ -136,6 +134,8 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
query('column_sort_by').optional(),
|
query('column_sort_by').optional(),
|
||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
|
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +213,9 @@ export default class AccountsController extends BaseController {
|
|||||||
tenantId,
|
tenantId,
|
||||||
accountId
|
accountId
|
||||||
);
|
);
|
||||||
return res.status(200).send({ account: this.transfromToResponse(account) });
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send({ account: this.transfromToResponse(account) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -256,7 +258,7 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: accountId,
|
id: accountId,
|
||||||
message: 'The account has been activated successfully.'
|
message: 'The account has been activated successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -291,22 +293,24 @@ export default class AccountsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {Response}
|
* @param {Response}
|
||||||
*/
|
*/
|
||||||
async getAccountsList(req: Request, res: Response, next: NextFunction) {
|
public async getAccountsList(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter: IAccountsFilter = {
|
|
||||||
filterRoles: [],
|
// Filter query.
|
||||||
|
const filter = {
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'name',
|
columnSortBy: 'name',
|
||||||
|
inactiveMode: false,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { accounts, filterMeta } =
|
||||||
accounts,
|
await this.accountsService.getAccountsList(tenantId, filter);
|
||||||
filterMeta,
|
|
||||||
} = await this.accountsService.getAccountsList(tenantId, filter);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
accounts: this.transfromToResponse(accounts, 'accountTypeLabel', req),
|
accounts: this.transfromToResponse(accounts, 'accountTypeLabel', req),
|
||||||
@@ -353,7 +357,8 @@ export default class AccountsController extends BaseController {
|
|||||||
const transactionsFilter = this.matchedQueryData(req);
|
const transactionsFilter = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { transactions } = await this.accountsService.getAccountsTransactions(
|
const { transactions } =
|
||||||
|
await this.accountsService.getAccountsTransactions(
|
||||||
tenantId,
|
tenantId,
|
||||||
transactionsFilter
|
transactionsFilter
|
||||||
);
|
);
|
||||||
@@ -372,7 +377,12 @@ export default class AccountsController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {ServiceError} error
|
* @param {ServiceError} error
|
||||||
*/
|
*/
|
||||||
catchServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
private catchServiceErrors(
|
||||||
|
error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'account_not_found') {
|
if (error.errorType === 'account_not_found') {
|
||||||
return res.boom.notFound('The given account not found.', {
|
return res.boom.notFound('The given account not found.', {
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ export default class CustomersController extends ContactsController {
|
|||||||
|
|
||||||
query('custom_view_id').optional().isNumeric().toInt(),
|
query('custom_view_id').optional().isNumeric().toInt(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
|
|
||||||
|
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,17 +266,15 @@ export default class CustomersController extends ContactsController {
|
|||||||
*/
|
*/
|
||||||
async getCustomersList(req: Request, res: Response, next: NextFunction) {
|
async getCustomersList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
inactiveMode: false,
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ export default class VendorsController extends ContactsController {
|
|||||||
|
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
|
|
||||||
|
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,8 +229,13 @@ export default class VendorsController extends ContactsController {
|
|||||||
*/
|
*/
|
||||||
async getVendorsList(req: Request, res: Response, next: NextFunction) {
|
async getVendorsList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
const vendorsFilter: IVendorsFilter = {
|
const vendorsFilter: IVendorsFilter = {
|
||||||
filterRoles: [],
|
inactiveMode: false,
|
||||||
|
sortOrder: 'asc',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
page: 1,
|
||||||
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -290,16 +290,12 @@ export default class ExpensesController extends BaseController {
|
|||||||
async getExpensesList(req: Request, res: Response, next: NextFunction) {
|
async getExpensesList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { expenses, pagination, filterMeta } =
|
const { expenses, pagination, filterMeta } =
|
||||||
|
|||||||
@@ -32,13 +32,8 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
|||||||
return [
|
return [
|
||||||
query('from_date').optional().isISO8601(),
|
query('from_date').optional().isISO8601(),
|
||||||
query('to_date').optional().isISO8601(),
|
query('to_date').optional().isISO8601(),
|
||||||
oneOf(
|
query('transaction_type').optional().trim().escape(),
|
||||||
[
|
query('transaction_id').optional().isInt().toInt(),
|
||||||
query('transaction_types').optional().isArray({ min: 1 }),
|
|
||||||
query('transaction_types.*').optional().isNumeric().toInt(),
|
|
||||||
],
|
|
||||||
[query('transaction_types').optional().trim().escape()]
|
|
||||||
),
|
|
||||||
oneOf(
|
oneOf(
|
||||||
[
|
[
|
||||||
query('account_ids').optional().isArray({ min: 1 }),
|
query('account_ids').optional().isArray({ min: 1 }),
|
||||||
|
|||||||
@@ -199,8 +199,8 @@ export default class ItemsCategoriesController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
async getList(req: Request, res: Response, next: NextFunction) {
|
async getList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId, user } = req;
|
const { tenantId, user } = req;
|
||||||
|
|
||||||
const itemCategoriesFilter = {
|
const itemCategoriesFilter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ export default class ItemsController extends BaseController {
|
|||||||
|
|
||||||
query('custom_view_id').optional().isNumeric().toInt(),
|
query('custom_view_id').optional().isNumeric().toInt(),
|
||||||
query('stringified_filter_roles').optional().isJSON(),
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
|
|
||||||
|
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,17 +341,16 @@ export default class ItemsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
async getItemsList(req: Request, res: Response, next: NextFunction) {
|
async getItemsList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
|
inactiveMode: false,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
|
|||||||
@@ -288,14 +288,10 @@ export default class ManualJournalsController extends BaseController {
|
|||||||
const filter = {
|
const filter = {
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
filterRoles: [],
|
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
manualJournals,
|
manualJournals,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query } from 'express-validator';
|
import { check, param, query } from 'express-validator';
|
||||||
|
import * as R from 'ramda';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { IBillDTO, IBillEditDTO } from 'interfaces';
|
import { IBillDTO, IBillEditDTO } from 'interfaces';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
@@ -300,14 +301,11 @@ export default class BillsController extends BaseController {
|
|||||||
const filter = {
|
const filter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const { bills, pagination, filterMeta } =
|
const { bills, pagination, filterMeta } =
|
||||||
await this.billsService.getBills(tenantId, filter);
|
await this.billsService.getBills(tenantId, filter);
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import {
|
import { param, query } from 'express-validator';
|
||||||
param,
|
|
||||||
query,
|
|
||||||
} from 'express-validator';
|
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import BaseController from './BaseController';
|
import BaseController from './BaseController';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import ResourceService from 'services/Resource/ResourceService';
|
import ResourceService from 'services/Resource/ResourceService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ResourceController extends BaseController{
|
export default class ResourceController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
resourcesService: ResourceService;
|
resourcesService: ResourceService;
|
||||||
|
|
||||||
@@ -21,30 +18,61 @@ export default class ResourceController extends BaseController{
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:resource_model/fields', [
|
'/:resource_model/meta',
|
||||||
...this.resourceModelParamSchema,
|
[...this.resourceModelParamSchema],
|
||||||
],
|
this.asyncMiddleware(this.resourceMeta.bind(this)),
|
||||||
|
this.handleServiceErrors
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:resource_model/fields',
|
||||||
|
[...this.resourceModelParamSchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.resourceFields.bind(this)),
|
asyncMiddleware(this.resourceFields.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:resource_model/data', [
|
'/:resource_model/data',
|
||||||
...this.resourceModelParamSchema,
|
[...this.resourceModelParamSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.resourceData.bind(this)),
|
asyncMiddleware(this.resourceData.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
)
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
get resourceModelParamSchema() {
|
get resourceModelParamSchema() {
|
||||||
return [
|
return [param('resource_model').exists().trim().escape()];
|
||||||
param('resource_model').exists().trim().escape(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve resource model meta.
|
||||||
|
* @param {Request} req -
|
||||||
|
* @param {Response} res -
|
||||||
|
* @param {NextFunction} next -
|
||||||
|
* @returns {Response}
|
||||||
|
*/
|
||||||
|
private resourceMeta = (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Response => {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { resource_model: resourceModel } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resourceMeta = this.resourcesService.getResourceMeta(
|
||||||
|
tenantId,
|
||||||
|
resourceModel
|
||||||
|
);
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.send({ resource_meta: this.transfromToResponse(resourceMeta) });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve resource fields of the given resource.
|
* Retrieve resource fields of the given resource.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -56,7 +84,10 @@ export default class ResourceController extends BaseController{
|
|||||||
const { resource_model: resourceModel } = req.params;
|
const { resource_model: resourceModel } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resourceFields = this.resourcesService.getResourceFields(tenantId, resourceModel);
|
const resourceFields = this.resourcesService.getResourceFields(
|
||||||
|
tenantId,
|
||||||
|
resourceModel
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
resource_fields: this.transfromToResponse(resourceFields),
|
resource_fields: this.transfromToResponse(resourceFields),
|
||||||
@@ -78,7 +109,11 @@ export default class ResourceController extends BaseController{
|
|||||||
const filter = req.query;
|
const filter = req.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resourceData = await this.resourcesService.getResourceData(tenantId, resourceModel, filter);
|
const resourceData = await this.resourcesService.getResourceData(
|
||||||
|
tenantId,
|
||||||
|
resourceModel,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
resource_data: this.transfromToResponse(resourceData),
|
resource_data: this.transfromToResponse(resourceData),
|
||||||
@@ -95,7 +130,12 @@ export default class ResourceController extends BaseController{
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
private handleServiceErrors(
|
||||||
|
error: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'RESOURCE_MODEL_NOT_FOUND') {
|
if (error.errorType === 'RESOURCE_MODEL_NOT_FOUND') {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
@@ -105,4 +145,4 @@ export default class ResourceController extends BaseController{
|
|||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -261,16 +261,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { Router, Request, Response, NextFunction } from 'express';
|
|||||||
import { check, param, query, matchedData } from 'express-validator';
|
import { check, param, query, matchedData } from 'express-validator';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { ISaleEstimateDTO } from 'interfaces';
|
import { ISaleEstimateDTO } from 'interfaces';
|
||||||
import BaseController from 'api/controllers/BaseController'
|
import BaseController from 'api/controllers/BaseController';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import { ServiceError } from "exceptions";
|
import { ServiceError } from 'exceptions';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SalesEstimatesController extends BaseController {
|
export default class SalesEstimatesController extends BaseController {
|
||||||
@@ -23,63 +23,56 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/', [
|
'/',
|
||||||
...this.estimateValidationSchema,
|
[...this.estimateValidationSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.newEstimate.bind(this)),
|
asyncMiddleware(this.newEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/deliver',
|
'/:id/deliver',
|
||||||
[
|
[...this.validateSpecificEstimateSchema],
|
||||||
...this.validateSpecificEstimateSchema,
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deliverSaleEstimate.bind(this)),
|
asyncMiddleware(this.deliverSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/approve',
|
'/:id/approve',
|
||||||
[
|
[this.validateSpecificEstimateSchema],
|
||||||
this.validateSpecificEstimateSchema,
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id/reject',
|
'/:id/reject',
|
||||||
[
|
[this.validateSpecificEstimateSchema],
|
||||||
this.validateSpecificEstimateSchema,
|
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
)
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/:id', [
|
'/:id',
|
||||||
|
[
|
||||||
...this.validateSpecificEstimateSchema,
|
...this.validateSpecificEstimateSchema,
|
||||||
...this.estimateValidationSchema,
|
...this.estimateValidationSchema,
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.editEstimate.bind(this)),
|
asyncMiddleware(this.editEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:id', [
|
'/:id',
|
||||||
this.validateSpecificEstimateSchema,
|
[this.validateSpecificEstimateSchema],
|
||||||
],
|
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.deleteEstimate.bind(this)),
|
asyncMiddleware(this.deleteEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
this.validateSpecificEstimateSchema,
|
this.validateSpecificEstimateSchema,
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getEstimate.bind(this)),
|
asyncMiddleware(this.getEstimate.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
@@ -87,7 +80,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getEstimates.bind(this)),
|
asyncMiddleware(this.getEstimates.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
this.dynamicListService.handlerErrorsToResponse,
|
this.dynamicListService.handlerErrorsToResponse
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -109,8 +102,14 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.description').optional({ nullable: true }).trim().escape(),
|
check('entries.*.description')
|
||||||
check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
|
.optional({ nullable: true })
|
||||||
|
.trim()
|
||||||
|
.escape(),
|
||||||
|
check('entries.*.discount')
|
||||||
|
.optional({ nullable: true })
|
||||||
|
.isNumeric()
|
||||||
|
.toFloat(),
|
||||||
|
|
||||||
check('note').optional().trim().escape(),
|
check('note').optional().trim().escape(),
|
||||||
check('terms_conditions').optional().trim().escape(),
|
check('terms_conditions').optional().trim().escape(),
|
||||||
@@ -122,9 +121,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* Specific sale estimate validation schema.
|
* Specific sale estimate validation schema.
|
||||||
*/
|
*/
|
||||||
get validateSpecificEstimateSchema() {
|
get validateSpecificEstimateSchema() {
|
||||||
return [
|
return [param('id').exists().isNumeric().toInt()];
|
||||||
param('id').exists().isNumeric().toInt(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,7 +135,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
query('page').optional().isNumeric().toInt(),
|
query('page').optional().isNumeric().toInt(),
|
||||||
query('page_size').optional().isNumeric().toInt(),
|
query('page_size').optional().isNumeric().toInt(),
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,7 +149,10 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
const estimateDTO: ISaleEstimateDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const storedEstimate = await this.saleEstimateService.createEstimate(tenantId, estimateDTO);
|
const storedEstimate = await this.saleEstimateService.createEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateDTO
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: storedEstimate.id,
|
id: storedEstimate.id,
|
||||||
@@ -175,7 +175,11 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Update estimate with associated estimate entries.
|
// Update estimate with associated estimate entries.
|
||||||
await this.saleEstimateService.editEstimate(tenantId, estimateId, estimateDTO);
|
await this.saleEstimateService.editEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateId,
|
||||||
|
estimateDTO
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
@@ -200,7 +204,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: estimateId,
|
id: estimateId,
|
||||||
message: 'The sale estimate has been deleted successfully.'
|
message: 'The sale estimate has been deleted successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -283,7 +287,10 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const estimate = await this.saleEstimateService.getEstimate(tenantId, estimateId);
|
const estimate = await this.saleEstimateService.getEstimate(
|
||||||
|
tenantId,
|
||||||
|
estimateId
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).send({ estimate });
|
return res.status(200).send({ estimate });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -299,29 +306,22 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { salesEstimates, pagination, filterMeta } =
|
||||||
salesEstimates,
|
await this.saleEstimateService.estimatesList(tenantId, filter);
|
||||||
pagination,
|
|
||||||
filterMeta
|
|
||||||
} = await this.saleEstimateService.estimatesList(tenantId, filter);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_estimates: this.transfromToResponse(salesEstimates),
|
sales_estimates: this.transfromToResponse(salesEstimates),
|
||||||
pagination,
|
pagination,
|
||||||
filter_meta: this.transfromToResponse(filterMeta),
|
filter_meta: this.transfromToResponse(filterMeta),
|
||||||
})
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,12 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
handleServiceErrors(error: Error, req: Request, res: Response, next: NextFunction) {
|
private handleServiceErrors(
|
||||||
|
error: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'ITEMS_NOT_FOUND') {
|
if (error.errorType === 'ITEMS_NOT_FOUND') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
@@ -409,4 +414,4 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
}
|
}
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -284,23 +284,15 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
) {
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const { salesInvoices, filterMeta, pagination } =
|
||||||
salesInvoices,
|
await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
||||||
filterMeta,
|
|
||||||
pagination,
|
|
||||||
} = await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_invoices: salesInvoices,
|
sales_invoices: salesInvoices,
|
||||||
|
|||||||
@@ -230,16 +230,12 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const filter = {
|
const filter = {
|
||||||
filterRoles: [],
|
|
||||||
sortOrder: 'asc',
|
sortOrder: 'asc',
|
||||||
columnSortBy: 'created_at',
|
columnSortBy: 'created_at',
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
if (filter.stringifiedFilterRoles) {
|
|
||||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export interface IAccountResponse extends IAccount {
|
|||||||
|
|
||||||
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string,
|
stringifiedFilterRoles?: string,
|
||||||
|
onlyInactive: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IAccountType {
|
export interface IAccountType {
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ export interface IBill {
|
|||||||
|
|
||||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||||
stringifiedFilterRoles?: string;
|
stringifiedFilterRoles?: string;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBillsService {
|
export interface IBillsService {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { IModel, ISortOrder } from "./Model";
|
||||||
|
|
||||||
export interface IDynamicFilter {
|
export interface IDynamicFilter {
|
||||||
setTableName(tableName: string): void;
|
setModel(model: IModel): void;
|
||||||
buildQuery(): void;
|
buildQuery(): void;
|
||||||
|
getResponseMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFilterRole {
|
export interface IFilterRole {
|
||||||
@@ -10,19 +13,19 @@ export interface IFilterRole {
|
|||||||
index?: number;
|
index?: number;
|
||||||
comparator?: string;
|
comparator?: string;
|
||||||
}
|
}
|
||||||
|
export interface IDynamicListFilter {
|
||||||
export interface IDynamicListFilterDTO {
|
|
||||||
customViewId?: number;
|
customViewId?: number;
|
||||||
filterRoles?: IFilterRole[];
|
filterRoles?: IFilterRole[];
|
||||||
columnSortBy: string;
|
columnSortBy: ISortOrder;
|
||||||
sortOrder: string;
|
sortOrder: string;
|
||||||
|
stringifiedFilterRoles: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDynamicListService {
|
export interface IDynamicListService {
|
||||||
dynamicList(
|
dynamicList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
model: any,
|
model: any,
|
||||||
filter: IDynamicListFilterDTO
|
filter: IDynamicListFilter
|
||||||
): Promise<any>;
|
): Promise<any>;
|
||||||
handlerErrorsToResponse(error, req, res, next): void;
|
handlerErrorsToResponse(error, req, res, next): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ export interface IItemsFilter extends IDynamicListFilterDTO {
|
|||||||
stringifiedFilterRoles?: string,
|
stringifiedFilterRoles?: string,
|
||||||
page: number,
|
page: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
|
inactiveMode: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IItemsAutoCompleteFilter {
|
export interface IItemsAutoCompleteFilter {
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ export interface IJournalReportQuery {
|
|||||||
noCents: boolean,
|
noCents: boolean,
|
||||||
divideOn1000: boolean,
|
divideOn1000: boolean,
|
||||||
},
|
},
|
||||||
transactionTypes: string | string[],
|
transactionType: string,
|
||||||
|
transactionId: string,
|
||||||
|
|
||||||
accountsIds: number | number[],
|
accountsIds: number | number[],
|
||||||
fromRange: number,
|
fromRange: number,
|
||||||
toRange: number,
|
toRange: number,
|
||||||
|
|||||||
@@ -1,17 +1,72 @@
|
|||||||
|
|
||||||
|
|
||||||
export interface IModel {
|
export interface IModel {
|
||||||
name: string,
|
name: string;
|
||||||
tableName: string,
|
tableName: string;
|
||||||
fields: { [key: string]: any, },
|
fields: { [key: string]: any };
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IFilterMeta {
|
export interface IFilterMeta {
|
||||||
sortOrder: string,
|
sortOrder: string;
|
||||||
sortBy: string,
|
sortBy: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IPaginationMeta {
|
export interface IPaginationMeta {
|
||||||
pageSize: number,
|
pageSize: number;
|
||||||
page: number,
|
page: number;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaDefaultSort {
|
||||||
|
sortOrder: ISortOrder;
|
||||||
|
sortField: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelColumnType =
|
||||||
|
| 'text'
|
||||||
|
| 'number'
|
||||||
|
| 'enumeration'
|
||||||
|
| 'boolean'
|
||||||
|
| 'relation';
|
||||||
|
|
||||||
|
export type ISortOrder = 'DESC' | 'ASC';
|
||||||
|
|
||||||
|
export interface IModelMetaFieldCommon {
|
||||||
|
name: string;
|
||||||
|
column: string;
|
||||||
|
columnable?: boolean;
|
||||||
|
fieldType: IModelColumnType;
|
||||||
|
customQuery?: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaFieldNumber {
|
||||||
|
fieldType: 'number';
|
||||||
|
minLength?: number;
|
||||||
|
maxLength?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaFieldOther {
|
||||||
|
fieldType: 'text' | 'boolean';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IModelMetaField = IModelMetaFieldCommon &
|
||||||
|
(IModelMetaFieldOther | IModelMetaEnumerationField | IModelMetaRelationField);
|
||||||
|
|
||||||
|
export interface IModelMetaEnumerationOption {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaEnumerationField {
|
||||||
|
fieldType: 'enumeration';
|
||||||
|
options: IModelMetaEnumerationOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMetaRelationField {
|
||||||
|
fieldType: 'relation';
|
||||||
|
relationToModel: IModel;
|
||||||
|
relationToField: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelMeta {
|
||||||
|
defaultFilterField: string;
|
||||||
|
defaultSort: IModelMetaDefaultSort;
|
||||||
|
fields: { [key: string]: IModelMetaField };
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,52 +1,73 @@
|
|||||||
import { forEach, uniqBy } from 'lodash';
|
import { forEach, uniqBy } from 'lodash';
|
||||||
import { buildFilterRolesJoins } from 'lib/ViewRolesBuilder';
|
import DynamicFilterAbstructor from './DynamicFilterAbstructor';
|
||||||
import { IModel } from 'interfaces';
|
import { IDynamicFilter, IFilterRole, IModel } from 'interfaces';
|
||||||
|
|
||||||
export default class DynamicFilter {
|
export default class DynamicFilter extends DynamicFilterAbstructor{
|
||||||
model: IModel;
|
private model: IModel;
|
||||||
tableName: string;
|
private tableName: string;
|
||||||
|
private dynamicFilters: IDynamicFilter[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
* @param {String} tableName -
|
* @param {String} tableName -
|
||||||
*/
|
*/
|
||||||
constructor(model) {
|
constructor(model) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.tableName = model.tableName;
|
this.tableName = model.tableName;
|
||||||
this.filters = [];
|
this.dynamicFilters = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set filter.
|
* Registers the given dynamic filter.
|
||||||
* @param {*} filterRole - Filter role.
|
* @param {IDynamicFilter} filterRole - Filter role.
|
||||||
*/
|
*/
|
||||||
setFilter(filterRole) {
|
public setFilter = (dynamicFilter: IDynamicFilter) => {
|
||||||
filterRole.setModel(this.model);
|
dynamicFilter.setModel(this.model);
|
||||||
this.filters.push(filterRole);
|
this.dynamicFilters.push(dynamicFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve dynamic filter build queries.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private dynamicFiltersBuildQuery = () => {
|
||||||
|
return this.dynamicFilters.map((filter) => {
|
||||||
|
return filter.buildQuery()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve dynamic filter roles.
|
||||||
|
* @returns {IFilterRole[]}
|
||||||
|
*/
|
||||||
|
private dynamicFilterTableColumns = (): IFilterRole[] => {
|
||||||
|
const localFilterRoles = [];
|
||||||
|
|
||||||
|
this.dynamicFilters.forEach((dynamicFilter) => {
|
||||||
|
const { filterRoles } = dynamicFilter;
|
||||||
|
|
||||||
|
localFilterRoles.push(
|
||||||
|
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return localFilterRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds queries of filter roles.
|
* Builds queries of filter roles.
|
||||||
*/
|
*/
|
||||||
buildQuery() {
|
public buildQuery = () => {
|
||||||
const buildersCallbacks = [];
|
const buildersCallbacks = this.dynamicFiltersBuildQuery();
|
||||||
const tableColumns = [];
|
const tableColumns = this.dynamicFilterTableColumns();
|
||||||
|
|
||||||
this.filters.forEach((filter) => {
|
|
||||||
const { filterRoles } = filter;
|
|
||||||
|
|
||||||
buildersCallbacks.push(filter.buildQuery());
|
|
||||||
tableColumns.push(
|
|
||||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
buildersCallbacks.forEach((builderCallback) => {
|
buildersCallbacks.forEach((builderCallback) => {
|
||||||
builderCallback(builder);
|
builderCallback(builder);
|
||||||
});
|
});
|
||||||
|
|
||||||
buildFilterRolesJoins(
|
this.buildFilterRolesJoins(
|
||||||
this.model,
|
this.model,
|
||||||
uniqBy(tableColumns, 'columnKey')
|
uniqBy(tableColumns, 'columnKey')
|
||||||
)(builder);
|
)(builder);
|
||||||
@@ -56,10 +77,10 @@ export default class DynamicFilter {
|
|||||||
/**
|
/**
|
||||||
* Retrieve response metadata from all filters adapters.
|
* Retrieve response metadata from all filters adapters.
|
||||||
*/
|
*/
|
||||||
getResponseMeta() {
|
public getResponseMeta = () => {
|
||||||
const responseMeta = {};
|
const responseMeta = {};
|
||||||
|
|
||||||
this.filters.forEach((filter) => {
|
this.dynamicFilters.forEach((filter) => {
|
||||||
const { responseMeta: filterMeta } = filter;
|
const { responseMeta: filterMeta } = filter;
|
||||||
|
|
||||||
forEach(filterMeta, (value, key) => {
|
forEach(filterMeta, (value, key) => {
|
||||||
|
|||||||
37
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
37
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { IModel, IFilterRole } from 'interfaces';
|
||||||
|
|
||||||
|
export default class DynamicFilterAbstructor {
|
||||||
|
/**
|
||||||
|
* Extract relation table name from relation.
|
||||||
|
* @param {String} column -
|
||||||
|
* @return {String} - join relation table.
|
||||||
|
*/
|
||||||
|
protected getTableFromRelationColumn = (column: string) => {
|
||||||
|
const splitedColumn = column.split('.');
|
||||||
|
return splitedColumn.length > 0 ? splitedColumn[0] : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds view roles join queries.
|
||||||
|
* @param {String} tableName - Table name.
|
||||||
|
* @param {Array} roles - Roles.
|
||||||
|
*/
|
||||||
|
protected buildFilterRolesJoins = (model: IModel, roles: IFilterRole[]) => {
|
||||||
|
return (builder) => {
|
||||||
|
roles.forEach((role) => {
|
||||||
|
const field = model.getField(role.fieldKey);
|
||||||
|
|
||||||
|
if (field.relation) {
|
||||||
|
const joinTable = this.getTableFromRelationColumn(field.relation);
|
||||||
|
|
||||||
|
builder.join(
|
||||||
|
joinTable,
|
||||||
|
`${model.tableName}.${field.column}`,
|
||||||
|
'=',
|
||||||
|
field.relation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import { difference } from 'lodash';
|
import DynamicFilterRoleAbstructor from './DynamicFilterRoleAbstructor';
|
||||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
|
||||||
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
|
||||||
import { IFilterRole } from 'interfaces';
|
import { IFilterRole } from 'interfaces';
|
||||||
|
|
||||||
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||||
filterRoles: IFilterRole[];
|
private filterRoles: IFilterRole[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -35,17 +33,22 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
|||||||
/**
|
/**
|
||||||
* Builds database query of view roles.
|
* Builds database query of view roles.
|
||||||
*/
|
*/
|
||||||
buildQuery() {
|
protected buildQuery() {
|
||||||
return (builder) => {
|
|
||||||
const logicExpression = this.buildLogicExpression();
|
const logicExpression = this.buildLogicExpression();
|
||||||
buildFilterQuery(this.model, this.filterRoles, logicExpression)(builder);
|
|
||||||
|
return (builder) => {
|
||||||
|
this.buildFilterQuery(
|
||||||
|
this.model,
|
||||||
|
this.filterRoles,
|
||||||
|
logicExpression
|
||||||
|
)(builder);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets response meta.
|
* Sets response meta.
|
||||||
*/
|
*/
|
||||||
setResponseMeta() {
|
private setResponseMeta() {
|
||||||
this.responseMeta = {
|
this.responseMeta = {
|
||||||
filterRoles: this.filterRoles,
|
filterRoles: this.filterRoles,
|
||||||
};
|
};
|
||||||
|
|||||||
61
server/src/lib/DynamicFilter/DynamicFilterQueryParser.ts
Normal file
61
server/src/lib/DynamicFilter/DynamicFilterQueryParser.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { OPERATION } from 'lib/LogicEvaluation/Parser';
|
||||||
|
|
||||||
|
export default class QueryParser {
|
||||||
|
|
||||||
|
constructor(tree, queries) {
|
||||||
|
this.tree = tree;
|
||||||
|
this.queries = queries;
|
||||||
|
this.query = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setQuery(query) {
|
||||||
|
this.query = query.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
parse() {
|
||||||
|
return this.parseNode(this.tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseNode(node) {
|
||||||
|
if (typeof node === 'string') {
|
||||||
|
const nodeQuery = this.getQuery(node);
|
||||||
|
return (query) => { nodeQuery(query); };
|
||||||
|
}
|
||||||
|
if (OPERATION[node.operation] === undefined) {
|
||||||
|
throw new Error(`unknow expression ${node.operation}`);
|
||||||
|
}
|
||||||
|
const leftQuery = this.getQuery(node.left);
|
||||||
|
const rightQuery = this.getQuery(node.right);
|
||||||
|
|
||||||
|
switch (node.operation) {
|
||||||
|
case '&&':
|
||||||
|
case 'AND':
|
||||||
|
default:
|
||||||
|
return (nodeQuery) => nodeQuery.where((query) => {
|
||||||
|
query.where((q) => { leftQuery(q); });
|
||||||
|
query.andWhere((q) => { rightQuery(q); });
|
||||||
|
});
|
||||||
|
case '||':
|
||||||
|
case 'OR':
|
||||||
|
return (nodeQuery) => nodeQuery.where((query) => {
|
||||||
|
query.where((q) => { leftQuery(q); });
|
||||||
|
query.orWhere((q) => { rightQuery(q); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getQuery(node) {
|
||||||
|
if (typeof node !== 'string' && node !== null) {
|
||||||
|
return this.parseNode(node);
|
||||||
|
}
|
||||||
|
const value = parseFloat(node);
|
||||||
|
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
if (typeof this.queries[node] === 'undefined') {
|
||||||
|
throw new Error(`unknow query under index ${node}`);
|
||||||
|
}
|
||||||
|
return this.queries[node];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,300 @@
|
|||||||
import { IFilterRole, IDynamicFilter, IModel } from "interfaces";
|
import moment from 'moment';
|
||||||
|
import { IFilterRole, IDynamicFilter, IModel } from 'interfaces';
|
||||||
|
import { Lexer } from 'lib/LogicEvaluation/Lexer';
|
||||||
|
import Parser from 'lib/LogicEvaluation/Parser';
|
||||||
|
import DynamicFilterQueryParser from './DynamicFilterQueryParser';
|
||||||
|
import { COMPARATOR_TYPE, FIELD_TYPE } from './constants';
|
||||||
|
|
||||||
export default class DynamicFilterAbstructor implements IDynamicFilter {
|
export default abstract class DynamicFilterAbstructor
|
||||||
filterRoles: IFilterRole[] = [];
|
implements IDynamicFilter
|
||||||
tableName: string;
|
{
|
||||||
model: IModel;
|
protected filterRoles: IFilterRole[] = [];
|
||||||
responseMeta: { [key: string]: any } = {};
|
protected tableName: string;
|
||||||
|
protected model: IModel;
|
||||||
|
protected responseMeta: { [key: string]: any } = {};
|
||||||
|
|
||||||
setModel(model: IModel) {
|
/**
|
||||||
|
* Sets model the dynamic filter service.
|
||||||
|
* @param {IModel} model
|
||||||
|
*/
|
||||||
|
public setModel(model: IModel) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.tableName = model.tableName;
|
this.tableName = model.tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformes filter roles to map by index.
|
||||||
|
* @param {IModel} model
|
||||||
|
* @param {IFilterRole[]} roles
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected convertRolesMapByIndex = (model, roles) => {
|
||||||
|
const rolesIndexSet = {};
|
||||||
|
|
||||||
|
roles.forEach((role) => {
|
||||||
|
rolesIndexSet[role.index] = this.buildRoleQuery(model, role);
|
||||||
|
});
|
||||||
|
return rolesIndexSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds database query from stored view roles.
|
||||||
|
* @param {Array} roles -
|
||||||
|
* @return {Function}
|
||||||
|
*/
|
||||||
|
protected buildFilterRolesQuery = (
|
||||||
|
model: IModel,
|
||||||
|
roles: IFilterRole[],
|
||||||
|
logicExpression: string = ''
|
||||||
|
) => {
|
||||||
|
const rolesIndexSet = this.convertRolesMapByIndex(model, roles);
|
||||||
|
|
||||||
|
// 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 DynamicFilterQueryParser(parsedTree, rolesIndexSet);
|
||||||
|
|
||||||
|
return queryParser.parse();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds filter query for query builder.
|
||||||
|
* @param {String} tableName - Table name.
|
||||||
|
* @param {Array} roles - Filter roles.
|
||||||
|
* @param {String} logicExpression - Logic expression.
|
||||||
|
*/
|
||||||
|
protected buildFilterQuery = (
|
||||||
|
model: IModel,
|
||||||
|
roles: IFilterRole[],
|
||||||
|
logicExpression: string
|
||||||
|
) => {
|
||||||
|
return (builder) => {
|
||||||
|
this.buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds roles queries.
|
||||||
|
* @param {IModel} model -
|
||||||
|
* @param {Object} role -
|
||||||
|
*/
|
||||||
|
protected buildRoleQuery = (model: IModel, role: IFilterRole) => {
|
||||||
|
const fieldRelation = model.getField(role.fieldKey);
|
||||||
|
const comparatorColumn = `${model.tableName}.${fieldRelation.column}`;
|
||||||
|
|
||||||
|
// Field relation custom query.
|
||||||
|
if (typeof fieldRelation.customQuery !== 'undefined') {
|
||||||
|
return (builder) => {
|
||||||
|
fieldRelation.customQuery(builder, role);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
switch (fieldRelation.fieldType) {
|
||||||
|
case FIELD_TYPE.BOOLEAN:
|
||||||
|
case FIELD_TYPE.ENUMERATION:
|
||||||
|
return this.booleanRoleQueryBuilder(role, comparatorColumn);
|
||||||
|
case FIELD_TYPE.NUMBER:
|
||||||
|
return this.numberRoleQueryBuilder(role, comparatorColumn);
|
||||||
|
case FIELD_TYPE.DATE:
|
||||||
|
return this.dateQueryBuilder(role, comparatorColumn);
|
||||||
|
case FIELD_TYPE.TEXT:
|
||||||
|
default:
|
||||||
|
return this.textRoleQueryBuilder(role, comparatorColumn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean column query builder.
|
||||||
|
* @param {IFilterRole} role
|
||||||
|
* @param {string} comparatorColumn
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected booleanRoleQueryBuilder = (
|
||||||
|
role: IFilterRole,
|
||||||
|
comparatorColumn: string
|
||||||
|
) => {
|
||||||
|
switch (role.comparator) {
|
||||||
|
case COMPARATOR_TYPE.EQUALS:
|
||||||
|
case COMPARATOR_TYPE.EQUAL:
|
||||||
|
case COMPARATOR_TYPE.IS:
|
||||||
|
default:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, '=', role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||||
|
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||||
|
case COMPARATOR_TYPE.IS_NOT:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, '<>', role.value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Numeric column query builder.
|
||||||
|
* @param {IFilterRole} role
|
||||||
|
* @param {string} comparatorColumn
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
protected numberRoleQueryBuilder = (
|
||||||
|
role: IFilterRole,
|
||||||
|
comparatorColumn: string
|
||||||
|
) => {
|
||||||
|
switch (role.comparator) {
|
||||||
|
case COMPARATOR_TYPE.EQUALS:
|
||||||
|
case COMPARATOR_TYPE.EQUAL:
|
||||||
|
default:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, '=', role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||||
|
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||||
|
return (builder) => {
|
||||||
|
builder.whereNot(comparatorColumn, role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.BIGGER_THAN:
|
||||||
|
case COMPARATOR_TYPE.BIGGER:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, '>', role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.BIGGER_OR_EQUALS:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, '>=', role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.SMALLER_THAN:
|
||||||
|
case COMPARATOR_TYPE.SMALLER:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, '<', role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.SMALLER_OR_EQUALS:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, '<=', role.value);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text column query builder.
|
||||||
|
* @param {IFilterRole} role
|
||||||
|
* @param {string} comparatorColumn
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
protected textRoleQueryBuilder = (
|
||||||
|
role: IFilterRole,
|
||||||
|
comparatorColumn: string
|
||||||
|
) => {
|
||||||
|
switch (role.comparator) {
|
||||||
|
case COMPARATOR_TYPE.EQUAL:
|
||||||
|
case COMPARATOR_TYPE.EQUALS:
|
||||||
|
case COMPARATOR_TYPE.IS:
|
||||||
|
default:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||||
|
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||||
|
case COMPARATOR_TYPE.IS_NOT:
|
||||||
|
return (builder) => {
|
||||||
|
builder.whereNot(comparatorColumn, role.value);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.CONTAIN:
|
||||||
|
case COMPARATOR_TYPE.CONTAINS:
|
||||||
|
return (builder) => {
|
||||||
|
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.NOT_CONTAIN:
|
||||||
|
case COMPARATOR_TYPE.NOT_CONTAINS:
|
||||||
|
return (builder) => {
|
||||||
|
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date column query builder.
|
||||||
|
* @param {IFilterRole} role
|
||||||
|
* @param {string} comparatorColumn
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
protected dateQueryBuilder = (
|
||||||
|
role: IFilterRole,
|
||||||
|
comparatorColumn: string
|
||||||
|
) => {
|
||||||
|
switch (role.comparator) {
|
||||||
|
case COMPARATOR_TYPE.AFTER:
|
||||||
|
case COMPARATOR_TYPE.BEFORE:
|
||||||
|
return (builder) => {
|
||||||
|
this.dateQueryAfterBeforeComparator(role, comparatorColumn, builder);
|
||||||
|
};
|
||||||
|
case COMPARATOR_TYPE.IN:
|
||||||
|
return (builder) => {
|
||||||
|
this.dateQueryInComparator(role, comparatorColumn, builder);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date query 'IN' comparator type.
|
||||||
|
* @param {IFilterRole} role
|
||||||
|
* @param {string} comparatorColumn
|
||||||
|
* @param builder
|
||||||
|
*/
|
||||||
|
protected dateQueryInComparator = (
|
||||||
|
role: IFilterRole,
|
||||||
|
comparatorColumn: string,
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date query after/before comparator type.
|
||||||
|
* @param {IFilterRole} role
|
||||||
|
* @param {string} comparatorColumn - Column.
|
||||||
|
* @param builder
|
||||||
|
*/
|
||||||
|
protected dateQueryAfterBeforeComparator = (
|
||||||
|
role: IFilterRole,
|
||||||
|
comparatorColumn: string,
|
||||||
|
builder
|
||||||
|
) => {
|
||||||
|
const comparator = role.comparator === COMPARATOR_TYPE.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 === COMPARATOR_TYPE.BEFORE) {
|
||||||
|
targetDate.startOf('day');
|
||||||
|
} else {
|
||||||
|
targetDate.endOf('day');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const comparatorValue = targetDate.format(dateFormat);
|
||||||
|
builder.where(comparatorColumn, comparator, comparatorValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,13 @@ import {
|
|||||||
getTableFromRelationColumn,
|
getTableFromRelationColumn,
|
||||||
} from 'lib/ViewRolesBuilder';
|
} from 'lib/ViewRolesBuilder';
|
||||||
|
|
||||||
|
interface ISortRole {
|
||||||
|
fieldKey: string;
|
||||||
|
order: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||||
sortRole: { fieldKey: string; order: string } = {};
|
private sortRole: ISortRole = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -23,39 +28,28 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
|||||||
this.setResponseMeta();
|
this.setResponseMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the given field key with the model.
|
|
||||||
*/
|
|
||||||
validate() {
|
|
||||||
validateFieldKeyExistance(this.model, this.sortRole.fieldKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds database query of sort by column on the given direction.
|
* Builds database query of sort by column on the given direction.
|
||||||
*/
|
*/
|
||||||
buildQuery() {
|
public buildQuery() {
|
||||||
const fieldRelation = getRoleFieldColumn(
|
const field = this.model.getField(this.sortRole.fieldKey);
|
||||||
this.model,
|
const comparatorColumn = `${this.tableName}.${field.column}`;
|
||||||
this.sortRole.fieldKey
|
|
||||||
);
|
|
||||||
const comparatorColumn =
|
|
||||||
fieldRelation.relationColumn ||
|
|
||||||
`${this.tableName}.${fieldRelation.column}`;
|
|
||||||
|
|
||||||
if (typeof fieldRelation.sortQuery !== 'undefined') {
|
if (typeof field.customSortQuery !== 'undefined') {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
fieldRelation.sortQuery(builder, this.sortRole);
|
field.customSortQuery(builder, this.sortRole);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
if (this.sortRole.fieldKey) {
|
if (this.sortRole.fieldKey) {
|
||||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
||||||
}
|
}
|
||||||
this.joinBuildQuery()(builder);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
joinBuildQuery() {
|
private joinBuildQuery() {
|
||||||
const fieldColumn = getRoleFieldColumn(this.model, this.sortRole.fieldKey);
|
const fieldColumn = getRoleFieldColumn(this.model, this.sortRole.fieldKey);
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
@@ -75,7 +69,7 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
|||||||
/**
|
/**
|
||||||
* Sets response meta.
|
* Sets response meta.
|
||||||
*/
|
*/
|
||||||
setResponseMeta() {
|
public setResponseMeta() {
|
||||||
this.responseMeta = {
|
this.responseMeta = {
|
||||||
sortOrder: this.sortRole.fieldKey,
|
sortOrder: this.sortRole.fieldKey,
|
||||||
sortBy: this.sortRole.order,
|
sortBy: this.sortRole.order,
|
||||||
|
|||||||
36
server/src/lib/DynamicFilter/constants.ts
Normal file
36
server/src/lib/DynamicFilter/constants.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export const COMPARATOR_TYPE = {
|
||||||
|
EQUAL: 'equal',
|
||||||
|
EQUALS: 'equals',
|
||||||
|
|
||||||
|
NOT_EQUAL: 'not_equal',
|
||||||
|
NOT_EQUALS: 'not_equals',
|
||||||
|
|
||||||
|
BIGGER_THAN: 'bigger_than',
|
||||||
|
BIGGER: 'bigger',
|
||||||
|
BIGGER_OR_EQUALS: 'bigger_or_equals',
|
||||||
|
|
||||||
|
SMALLER_THAN: 'smaller_than',
|
||||||
|
SMALLER: 'smaller',
|
||||||
|
SMALLER_OR_EQUALS: 'smaller_or_equals',
|
||||||
|
|
||||||
|
IS: 'is',
|
||||||
|
IS_NOT: 'is_not',
|
||||||
|
|
||||||
|
CONTAINS: 'contains',
|
||||||
|
CONTAIN: 'contain',
|
||||||
|
NOT_CONTAINS: 'contains',
|
||||||
|
NOT_CONTAIN: 'contain',
|
||||||
|
|
||||||
|
AFTER: 'after',
|
||||||
|
BEFORE: 'before',
|
||||||
|
IN: 'in',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FIELD_TYPE = {
|
||||||
|
TEXT: 'text',
|
||||||
|
NUMBER: 'number',
|
||||||
|
ENUMERATION: 'enumeration',
|
||||||
|
BOOLEAN: 'boolean',
|
||||||
|
RELATION: 'relation',
|
||||||
|
DATE: 'date',
|
||||||
|
};
|
||||||
@@ -1,121 +1,7 @@
|
|||||||
import { difference } from 'lodash';
|
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';
|
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':
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
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.
|
* Get field column metadata and its relation with other tables.
|
||||||
* @param {String} tableName - Table name of target column.
|
* @param {String} tableName - Table name of target column.
|
||||||
@@ -126,68 +12,6 @@ export function getRoleFieldColumn(model: IModel, fieldKey: string) {
|
|||||||
return tableFields[fieldKey] ? tableFields[fieldKey] : null;
|
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}`;
|
|
||||||
|
|
||||||
if (typeof fieldRelation.query !== 'undefined') {
|
|
||||||
return (builder) => {
|
|
||||||
fieldRelation.query(builder, role);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
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 - Table name.
|
|
||||||
* @param {Array} roles - 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) {
|
export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
const fieldColumn = getRoleFieldColumn(model, sortColumnKey);
|
const fieldColumn = getRoleFieldColumn(model, sortColumnKey);
|
||||||
@@ -204,50 +28,6 @@ export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* Mapes the view roles to view conditionals.
|
||||||
* @param {Array} viewRoles -
|
* @param {Array} viewRoles -
|
||||||
@@ -316,14 +96,6 @@ export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
|||||||
return model?.fields?.[fieldKey] || false;
|
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.
|
* Retrieve model fields keys.
|
||||||
|
|||||||
63
server/src/models/Account.Settings.ts
Normal file
63
server/src/models/Account.Settings.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { IModelMeta } from 'interfaces';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
defaultFilterField: 'name',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'name',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
name: 'Account name',
|
||||||
|
column: 'name',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
column: 'description',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'Account code',
|
||||||
|
column: 'code',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
root_type: {
|
||||||
|
name: 'Root type',
|
||||||
|
column: 'root_type',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'asset', label: 'Asset' },
|
||||||
|
{ key: 'liability', label: 'Liability' },
|
||||||
|
{ key: 'equity', label: 'Equity' },
|
||||||
|
{ key: 'Income', label: 'Income' },
|
||||||
|
{ key: 'expense', label: 'Expense' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'Active',
|
||||||
|
column: 'active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Account balance',
|
||||||
|
column: 'amount',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
currency: {
|
||||||
|
name: 'Currency',
|
||||||
|
column: 'currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as IModelMeta;
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
/* eslint-disable global-require */
|
/* eslint-disable global-require */
|
||||||
import { Model } from 'objection';
|
import { mixin, Model } from 'objection';
|
||||||
import { flatten, castArray } from 'lodash';
|
import { castArray } from 'lodash';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import {
|
import { buildFilterQuery, buildSortColumnQuery } from 'lib/ViewRolesBuilder';
|
||||||
buildFilterQuery,
|
|
||||||
buildSortColumnQuery,
|
|
||||||
} from 'lib/ViewRolesBuilder';
|
|
||||||
import { flatToNestedArray } from 'utils';
|
import { flatToNestedArray } from 'utils';
|
||||||
import DependencyGraph from 'lib/DependencyGraph';
|
import DependencyGraph from 'lib/DependencyGraph';
|
||||||
import AccountTypesUtils from 'lib/AccountTypes'
|
import AccountTypesUtils from 'lib/AccountTypes';
|
||||||
|
import AccountSettings from './Account.Settings';
|
||||||
|
import ModelSettings from './ModelSetting';
|
||||||
|
|
||||||
export default class Account extends TenantModel {
|
export default class Account extends mixin(TenantModel, [ModelSettings]) {
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -21,7 +20,7 @@ export default class Account extends TenantModel {
|
|||||||
/**
|
/**
|
||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
*/
|
*/
|
||||||
get timestamps() {
|
static get timestamps() {
|
||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ export default class Account extends TenantModel {
|
|||||||
'accountRootType',
|
'accountRootType',
|
||||||
'accountNormal',
|
'accountNormal',
|
||||||
'isBalanceSheetAccount',
|
'isBalanceSheetAccount',
|
||||||
'isPLSheet'
|
'isPLSheet',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +94,13 @@ export default class Account extends TenantModel {
|
|||||||
const TABLE_NAME = Account.tableName;
|
const TABLE_NAME = Account.tableName;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('active', !active);
|
||||||
|
},
|
||||||
|
|
||||||
filterAccounts(query, accountIds) {
|
filterAccounts(query, accountIds) {
|
||||||
if (accountIds.length > 0) {
|
if (accountIds.length > 0) {
|
||||||
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
||||||
@@ -160,7 +166,10 @@ export default class Account extends TenantModel {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
isParentType(parentType) {
|
isParentType(parentType) {
|
||||||
return AccountTypesUtils.isParentTypeEqualsKey(this.accountType, parentType);
|
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||||
|
this.accountType,
|
||||||
|
parentType
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,7 +202,10 @@ export default class Account extends TenantModel {
|
|||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
*/
|
*/
|
||||||
static toNestedArray(accounts, options = { children: 'children' }) {
|
static toNestedArray(accounts, options = { children: 'children' }) {
|
||||||
return flatToNestedArray(accounts, { id: 'id', parentId: 'parentAccountId' })
|
return flatToNestedArray(accounts, {
|
||||||
|
id: 'id',
|
||||||
|
parentId: 'parentAccountId',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,92 +213,16 @@ export default class Account extends TenantModel {
|
|||||||
* @param {IAccount[]} accounts
|
* @param {IAccount[]} accounts
|
||||||
*/
|
*/
|
||||||
static toDependencyGraph(accounts) {
|
static toDependencyGraph(accounts) {
|
||||||
return DependencyGraph.fromArray(
|
return DependencyGraph.fromArray(accounts, {
|
||||||
accounts, { itemId: 'id', parentItemId: 'parentAccountId' }
|
itemId: 'id',
|
||||||
);
|
parentItemId: 'parentAccountId',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return AccountSettings;
|
||||||
name: {
|
|
||||||
label: 'Account name',
|
|
||||||
column: 'name',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
label: 'Account type',
|
|
||||||
column: 'account_type',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description',
|
|
||||||
column: 'description',
|
|
||||||
columnType: 'string',
|
|
||||||
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
label: 'Account code',
|
|
||||||
column: 'code',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
root_type: {
|
|
||||||
label: 'Root type',
|
|
||||||
options: [
|
|
||||||
{ key: 'asset', label: 'Asset', },
|
|
||||||
{ key: 'liability', label: 'Liability' },
|
|
||||||
{ key: 'equity', label: 'Equity' },
|
|
||||||
{ key: 'Income', label: 'Income' },
|
|
||||||
{ key: 'expense', label: 'Expense' },
|
|
||||||
],
|
|
||||||
query: (query, role) => {
|
|
||||||
const accountsTypes = AccountTypesUtils.getTypesByRootType(role.value);
|
|
||||||
const accountsTypesKeys = accountsTypes.map(type => type.key);
|
|
||||||
|
|
||||||
query.whereIn('account_type', accountsTypesKeys);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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',
|
|
||||||
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',
|
|
||||||
options: [
|
|
||||||
{ key: 'credit', label: 'Credit' },
|
|
||||||
{ key: 'debit', label: 'Debit' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
server/src/models/Bill.Settings.ts
Normal file
79
server/src/models/Bill.Settings.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { IModelMeta } from 'interfaces';
|
||||||
|
import Bill from './Bill';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
defaultFilterField: 'vendor',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'bill_date',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
// vendor: {
|
||||||
|
// name: 'Vendor',
|
||||||
|
// column: 'vendor_id',
|
||||||
|
// },
|
||||||
|
'bill_number': {
|
||||||
|
name: 'Bill number',
|
||||||
|
column: 'bill_number',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'bill_date': {
|
||||||
|
name: 'Bill date',
|
||||||
|
column: 'bill_date',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'due_date': {
|
||||||
|
name: 'Due date',
|
||||||
|
column: 'due_date',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'reference_no': {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference_no',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
name: 'Status',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
columnable: true,
|
||||||
|
options: [
|
||||||
|
{ name: 'Paid', key: 'paid' },
|
||||||
|
{ name: 'Partially paid', key: 'partially-paid' },
|
||||||
|
{ name: 'Overdue', key: 'overdue' },
|
||||||
|
{ name: 'Unpaid', key: 'unpaid' },
|
||||||
|
{ name: 'Opened', key: 'opened' },
|
||||||
|
{ name: 'Draft', key: 'draft' },
|
||||||
|
],
|
||||||
|
// filterQuery: Bill.statusFieldFilterQuery,
|
||||||
|
// sortQuery: Bill.statusFieldSortQuery,
|
||||||
|
},
|
||||||
|
'amount': {
|
||||||
|
name: 'Amount',
|
||||||
|
column: 'amount',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'payment_amount': {
|
||||||
|
name: 'Payment amount',
|
||||||
|
column: 'payment_amount',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'note': {
|
||||||
|
name: 'Note',
|
||||||
|
column: 'note',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as IModelMeta;
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Model, raw } from 'objection';
|
import { Model, raw, mixin } from 'objection';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { difference } from 'lodash';
|
import { difference } from 'lodash';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import { query } from 'winston';
|
import BillSettings from './Bill.Settings';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
|
||||||
export default class Bill extends TenantModel {
|
export default class Bill extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -12,10 +13,9 @@ export default class Bill extends TenantModel {
|
|||||||
return 'bills';
|
return 'bills';
|
||||||
}
|
}
|
||||||
|
|
||||||
static get resourceable() {
|
/**
|
||||||
return true;
|
* Model modifiers.
|
||||||
}
|
*/
|
||||||
|
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -198,6 +198,13 @@ export default class Bill extends TenantModel {
|
|||||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill model settings.
|
||||||
|
*/
|
||||||
|
static get meta() {
|
||||||
|
return BillSettings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
@@ -270,40 +277,8 @@ export default class Bill extends TenantModel {
|
|||||||
[changeMethod]('payment_amount', Math.abs(amount));
|
[changeMethod]('payment_amount', Math.abs(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
static get fields() {
|
|
||||||
return {
|
static statusFieldFilterQuery(query, role) {
|
||||||
vendor: {
|
|
||||||
label: 'Vendor',
|
|
||||||
column: 'vendor_id',
|
|
||||||
relation: 'contacts.id',
|
|
||||||
relationColumn: 'contacts.display_name',
|
|
||||||
},
|
|
||||||
bill_number: {
|
|
||||||
label: 'Bill number',
|
|
||||||
column: 'bill_number',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
bill_date: {
|
|
||||||
label: 'Bill date',
|
|
||||||
column: 'bill_date',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
due_date: {
|
|
||||||
label: 'Due date',
|
|
||||||
column: 'due_date',
|
|
||||||
},
|
|
||||||
reference_no: {
|
|
||||||
label: 'Reference No.',
|
|
||||||
column: 'reference_no',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
options: [],
|
|
||||||
query: (query, role) => {
|
|
||||||
switch (role.value) {
|
switch (role.value) {
|
||||||
case 'draft':
|
case 'draft':
|
||||||
query.modify('draft');
|
query.modify('draft');
|
||||||
@@ -324,33 +299,9 @@ export default class Bill extends TenantModel {
|
|||||||
query.modify('paid');
|
query.modify('paid');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
sortQuery(query, role) {
|
|
||||||
query.modify('sortByStatus', role.order);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
amount: {
|
|
||||||
label: 'Amount',
|
|
||||||
column: 'amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
payment_amount: {
|
|
||||||
label: 'Payment amount',
|
|
||||||
column: 'payment_amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
note: {
|
|
||||||
label: 'Note',
|
|
||||||
column: 'note',
|
|
||||||
},
|
|
||||||
user: {},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
static statusFieldSortQuery(query, role) {
|
||||||
|
return query.modify('sortByStatus', role.order);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
61
server/src/models/BillPayment.Settings.ts
Normal file
61
server/src/models/BillPayment.Settings.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { IModelMeta } from 'interfaces';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
defaultFilterField: 'vendor',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'bill_date',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'vendor': {
|
||||||
|
name: 'Vendor name',
|
||||||
|
column: 'vendor_id',
|
||||||
|
},
|
||||||
|
'amount': {
|
||||||
|
name: 'Amount',
|
||||||
|
column: 'amount',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'due_amount': {
|
||||||
|
name: 'Due amount',
|
||||||
|
column: 'due_amount',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'payment_account': {
|
||||||
|
name: 'Payment account',
|
||||||
|
column: 'payment_account_id',
|
||||||
|
},
|
||||||
|
'payment_number': {
|
||||||
|
name: 'Payment number',
|
||||||
|
column: 'payment_number',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'payment_date': {
|
||||||
|
name: 'Payment date',
|
||||||
|
column: 'payment_date',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'reference_no': {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
name: 'Description',
|
||||||
|
column: 'description',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Model } from "objection";
|
import { Model, mixin } from "objection";
|
||||||
import TenantModel from "models/TenantModel";
|
import TenantModel from "models/TenantModel";
|
||||||
|
import ModelSetting from "./ModelSetting";
|
||||||
|
import BillPaymentSettings from "./BillPayment.Settings";
|
||||||
|
|
||||||
export default class BillPayment extends TenantModel {
|
export default class BillPayment extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -16,8 +18,11 @@ export default class BillPayment extends TenantModel {
|
|||||||
return ["createdAt", "updatedAt"];
|
return ["createdAt", "updatedAt"];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get resourceable() {
|
/**
|
||||||
return true;
|
* Model settings.
|
||||||
|
*/
|
||||||
|
static get meta() {
|
||||||
|
return BillPaymentSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,70 +78,4 @@ export default class BillPayment extends TenantModel {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Resource fields.
|
|
||||||
*/
|
|
||||||
static get fields() {
|
|
||||||
return {
|
|
||||||
vendor: {
|
|
||||||
label: "Vendor name",
|
|
||||||
column: "vendor_id",
|
|
||||||
relation: "contacts.id",
|
|
||||||
relationColumn: "contacts.display_name",
|
|
||||||
},
|
|
||||||
amount: {
|
|
||||||
label: "Amount",
|
|
||||||
column: "amount",
|
|
||||||
columnType: "number",
|
|
||||||
fieldType: "number",
|
|
||||||
},
|
|
||||||
due_amount: {
|
|
||||||
label: "Due amount",
|
|
||||||
column: "due_amount",
|
|
||||||
columnType: "number",
|
|
||||||
fieldType: "number",
|
|
||||||
},
|
|
||||||
payment_account: {
|
|
||||||
label: "Payment account",
|
|
||||||
column: "payment_account_id",
|
|
||||||
relation: "accounts.id",
|
|
||||||
relationColumn: "accounts.name",
|
|
||||||
|
|
||||||
fieldType: "options",
|
|
||||||
optionsResource: "Account",
|
|
||||||
optionsKey: "id",
|
|
||||||
optionsLabel: "name",
|
|
||||||
},
|
|
||||||
payment_number: {
|
|
||||||
label: "Payment number",
|
|
||||||
column: "payment_number",
|
|
||||||
columnType: "string",
|
|
||||||
fieldType: "text",
|
|
||||||
},
|
|
||||||
payment_date: {
|
|
||||||
label: "Payment date",
|
|
||||||
column: "payment_date",
|
|
||||||
columnType: "date",
|
|
||||||
fieldType: "date",
|
|
||||||
},
|
|
||||||
reference_no: {
|
|
||||||
label: "Reference No.",
|
|
||||||
column: "reference",
|
|
||||||
columnType: "string",
|
|
||||||
fieldType: "text",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: "Description",
|
|
||||||
column: "description",
|
|
||||||
columnType: "string",
|
|
||||||
fieldType: "text",
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: "Created at",
|
|
||||||
column: "created_at",
|
|
||||||
columnType: "date",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
98
server/src/models/Customer.Settings.ts
Normal file
98
server/src/models/Customer.Settings.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
export default {
|
||||||
|
fields: {
|
||||||
|
display_name: {
|
||||||
|
name: 'Display name',
|
||||||
|
column: 'display_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
name: 'Email',
|
||||||
|
column: 'email',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
work_phone: {
|
||||||
|
name: 'Work phone',
|
||||||
|
column: 'work_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
personal_phone: {
|
||||||
|
name: 'Personal phone',
|
||||||
|
column: 'personal_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
company_name: {
|
||||||
|
name: 'Company name',
|
||||||
|
column: 'company_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
website: {
|
||||||
|
name: 'Website',
|
||||||
|
column: 'website',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
balance: {
|
||||||
|
name: 'Balance',
|
||||||
|
column: 'balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
opening_balance: {
|
||||||
|
name: 'Opening balance',
|
||||||
|
column: 'opening_balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
opening_balance_at: {
|
||||||
|
name: 'Opening balance at',
|
||||||
|
column: 'opening_balance_at',
|
||||||
|
filterable: false,
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
currency_code: {
|
||||||
|
column: 'currency_code',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: 'Status',
|
||||||
|
options: [
|
||||||
|
{ key: 'active', label: 'Active' },
|
||||||
|
{ key: 'inactive', label: 'Inactive' },
|
||||||
|
{ key: 'overdue', label: 'Overdue' },
|
||||||
|
{ key: 'unpaid', label: 'Unpaid' },
|
||||||
|
],
|
||||||
|
columnable: true,
|
||||||
|
filterQuery: statusFieldFilterQuery,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function statusFieldFilterQuery(query, role) {
|
||||||
|
switch (role.value) {
|
||||||
|
case 'active':
|
||||||
|
query.modify('active');
|
||||||
|
break;
|
||||||
|
case 'inactive':
|
||||||
|
query.modify('inactive');
|
||||||
|
break;
|
||||||
|
case 'overdue':
|
||||||
|
query.modify('overdue');
|
||||||
|
break;
|
||||||
|
case 'unpaid':
|
||||||
|
query.modify('unpaid');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Model } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import PaginationQueryBuilder from './Pagination';
|
import PaginationQueryBuilder from './Pagination';
|
||||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import CustomerSettings from './Customer.Settings';
|
||||||
|
|
||||||
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
@@ -15,7 +17,7 @@ class CustomerQueryBuilder extends PaginationQueryBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Customer extends TenantModel {
|
export default class Customer extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Query builder.
|
* Query builder.
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +65,13 @@ export default class Customer extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('active', !active);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the active customers.
|
* Filters the active customers.
|
||||||
*/
|
*/
|
||||||
@@ -81,10 +90,9 @@ export default class Customer extends TenantModel {
|
|||||||
overdue(query) {
|
overdue(query) {
|
||||||
query.select(
|
query.select(
|
||||||
'*',
|
'*',
|
||||||
Customer
|
Customer.relatedQuery('overDueInvoices', query.knex())
|
||||||
.relatedQuery('overDueInvoices', query.knex())
|
|
||||||
.count()
|
.count()
|
||||||
.as('countOverdue'),
|
.as('countOverdue')
|
||||||
);
|
);
|
||||||
query.having('countOverdue', '>', 0);
|
query.having('countOverdue', '>', 0);
|
||||||
},
|
},
|
||||||
@@ -93,7 +101,7 @@ export default class Customer extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
unpaid(query) {
|
unpaid(query) {
|
||||||
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,77 +130,12 @@ export default class Customer extends TenantModel {
|
|||||||
},
|
},
|
||||||
filter: (query) => {
|
filter: (query) => {
|
||||||
query.modify('overdue');
|
query.modify('overdue');
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return CustomerSettings;
|
||||||
contact_service: {
|
|
||||||
column: 'contact_service',
|
|
||||||
},
|
|
||||||
display_name: {
|
|
||||||
column: 'display_name',
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
column: 'email',
|
|
||||||
},
|
|
||||||
work_phone: {
|
|
||||||
column: 'work_phone',
|
|
||||||
},
|
|
||||||
personal_phone: {
|
|
||||||
column: 'personal_phone',
|
|
||||||
},
|
|
||||||
company_name: {
|
|
||||||
column: 'company_name',
|
|
||||||
},
|
|
||||||
website: {
|
|
||||||
column: 'website'
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
column: 'created_at',
|
|
||||||
},
|
|
||||||
balance: {
|
|
||||||
column: 'balance',
|
|
||||||
},
|
|
||||||
opening_balance: {
|
|
||||||
column: 'opening_balance',
|
|
||||||
},
|
|
||||||
opening_balance_at: {
|
|
||||||
column: 'opening_balance_at',
|
|
||||||
},
|
|
||||||
currency_code: {
|
|
||||||
column: 'currency_code',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
options: [
|
|
||||||
{ key: 'active', label: 'Active' },
|
|
||||||
{ key: 'inactive', label: 'Inactive' },
|
|
||||||
{ key: 'overdue', label: 'Overdue' },
|
|
||||||
{ key: 'unpaid', label: 'Unpaid' },
|
|
||||||
],
|
|
||||||
query: (query, role) => {
|
|
||||||
switch(role.value) {
|
|
||||||
case 'active':
|
|
||||||
query.modify('active');
|
|
||||||
break;
|
|
||||||
case 'inactive':
|
|
||||||
query.modify('inactive');
|
|
||||||
break;
|
|
||||||
case 'overdue':
|
|
||||||
query.modify('overdue');
|
|
||||||
break;
|
|
||||||
case 'unpaid':
|
|
||||||
query.modify('unpaid');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
column: 'created_at',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
server/src/models/Expense.Settings.ts
Normal file
82
server/src/models/Expense.Settings.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Expense - Settings.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
defaultFilterField: 'description',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'name',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'payment_date': {
|
||||||
|
name: 'Payment date',
|
||||||
|
column: 'payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'payment_account': {
|
||||||
|
name: 'Payment account',
|
||||||
|
column: 'payment_account_id',
|
||||||
|
|
||||||
|
fieldType: 'relation',
|
||||||
|
fieldRelation: 'paymentAccount',
|
||||||
|
|
||||||
|
fieldRelationType: 'enumeration',
|
||||||
|
relationLabelField: 'name',
|
||||||
|
relationKeyField: 'slug',
|
||||||
|
},
|
||||||
|
'amount': {
|
||||||
|
name: 'Amount',
|
||||||
|
column: 'total_amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
// currency_code: {
|
||||||
|
// name: 'Currency',
|
||||||
|
// column: 'currency_code',
|
||||||
|
// },
|
||||||
|
'reference_no': {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
name: 'Description',
|
||||||
|
column: 'description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'published': {
|
||||||
|
name: 'Published',
|
||||||
|
column: 'published_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
name: 'Status',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'draft', name: 'Draft' },
|
||||||
|
{ key: 'published', name: 'Published' },
|
||||||
|
],
|
||||||
|
filterQuery: statusFieldFilterQuery,
|
||||||
|
sortQuery: statusFieldSortQuery,
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function statusFieldFilterQuery(query, role) {
|
||||||
|
switch (role.value) {
|
||||||
|
case 'draft':
|
||||||
|
query.modify('filterByDraft');
|
||||||
|
break;
|
||||||
|
case 'published':
|
||||||
|
query.modify('filterByPublished');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusFieldSortQuery(query, role) {
|
||||||
|
return query.modify('sortByStatus', role.order);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Model } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import { viewRolesBuilder } from 'lib/ViewRolesBuilder';
|
import { viewRolesBuilder } from 'lib/ViewRolesBuilder';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import ExpenseSettings from './Expense.Settings';
|
||||||
|
|
||||||
export default class Expense extends TenantModel {
|
export default class Expense extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -31,12 +33,6 @@ export default class Expense extends TenantModel {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
static get media() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return ['isPublished', 'unallocatedCostAmount'];
|
return ['isPublished', 'unallocatedCostAmount'];
|
||||||
@@ -142,71 +138,7 @@ export default class Expense extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static get meta() {
|
||||||
* Model defined fields.
|
return ExpenseSettings;
|
||||||
*/
|
|
||||||
static get fields() {
|
|
||||||
return {
|
|
||||||
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',
|
|
||||||
columnType: 'string',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description',
|
|
||||||
column: 'description',
|
|
||||||
columnType: 'string',
|
|
||||||
},
|
|
||||||
published: {
|
|
||||||
label: 'Published',
|
|
||||||
column: 'published_at',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
options: [
|
|
||||||
{ key: 'draft', label: 'Draft' },
|
|
||||||
{ key: 'published', label: 'Published' },
|
|
||||||
],
|
|
||||||
query: (query, role) => {
|
|
||||||
switch (role.value) {
|
|
||||||
case 'draft':
|
|
||||||
query.modify('filterByDraft');
|
|
||||||
break;
|
|
||||||
case 'published':
|
|
||||||
query.modify('filterByPublished');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sortQuery(query, role) {
|
|
||||||
query.modify('sortByStatus', role.order);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
server/src/models/InventoryAdjustment.Settings.ts
Normal file
60
server/src/models/InventoryAdjustment.Settings.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'date',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'date',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'date': {
|
||||||
|
name: 'Date',
|
||||||
|
column: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'type': {
|
||||||
|
name: 'Adjustment type',
|
||||||
|
column: 'type',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'increment', name: 'Increment' },
|
||||||
|
{ key: 'decrement', name: 'Decrement' },
|
||||||
|
],
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'adjustment_account': {
|
||||||
|
name: 'Adjustment account',
|
||||||
|
column: 'adjustment_account_id',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'reason': {
|
||||||
|
name: 'Reason',
|
||||||
|
column: 'reason',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'reference_no': {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
name: 'Description',
|
||||||
|
column: 'description',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'published_at': {
|
||||||
|
name: 'Published at',
|
||||||
|
column: 'published_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
import InventoryAdjustmentSettings from './InventoryAdjustment.Settings';
|
||||||
|
|
||||||
export default class InventoryAdjustment extends TenantModel {
|
export default class InventoryAdjustment extends TenantModel {
|
||||||
/**
|
/**
|
||||||
@@ -40,8 +41,8 @@ export default class InventoryAdjustment extends TenantModel {
|
|||||||
|
|
||||||
static getInventoryDirection(type) {
|
static getInventoryDirection(type) {
|
||||||
const directions = {
|
const directions = {
|
||||||
'increment': 'IN',
|
increment: 'IN',
|
||||||
'decrement': 'OUT',
|
decrement: 'OUT',
|
||||||
};
|
};
|
||||||
return directions[type] || '';
|
return directions[type] || '';
|
||||||
}
|
}
|
||||||
@@ -81,52 +82,9 @@ export default class InventoryAdjustment extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return InventoryAdjustmentSettings;
|
||||||
date: {
|
|
||||||
label: 'Date',
|
|
||||||
column: 'date',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
label: 'Adjustment type',
|
|
||||||
column: 'type',
|
|
||||||
options: [
|
|
||||||
{ key: 'increment', label: 'Increment', },
|
|
||||||
{ key: 'decrement', label: 'Decrement' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
adjustment_account: {
|
|
||||||
column: 'adjustment_account_id',
|
|
||||||
},
|
|
||||||
reason: {
|
|
||||||
label: 'Reason',
|
|
||||||
column: 'reason',
|
|
||||||
},
|
|
||||||
reference_no: {
|
|
||||||
label: 'Reference No.',
|
|
||||||
column: 'reference_no',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description',
|
|
||||||
column: 'description',
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
label: 'User',
|
|
||||||
column: 'user_id',
|
|
||||||
},
|
|
||||||
published_at: {
|
|
||||||
label: 'Published at',
|
|
||||||
column: 'published_at'
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
server/src/models/Item.Settings.ts
Normal file
102
server/src/models/Item.Settings.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'name',
|
||||||
|
defaultSort: {
|
||||||
|
sortField: 'name',
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'type': {
|
||||||
|
name: 'Item type',
|
||||||
|
column: 'type',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'inventory', label: 'Inventory', },
|
||||||
|
{ key: 'service', label: 'Service' },
|
||||||
|
{ key: 'non-inventory', label: 'Non Inventory', },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'name': {
|
||||||
|
name: 'Name',
|
||||||
|
column: 'name',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'code': {
|
||||||
|
name: 'Code',
|
||||||
|
column: 'code',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'sellable': {
|
||||||
|
name: 'Sellable',
|
||||||
|
column: 'sellable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
'purchasable': {
|
||||||
|
name: 'Purchasable',
|
||||||
|
column: 'purchasable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
'sell_price': {
|
||||||
|
name: 'Sell price',
|
||||||
|
column: 'sell_price',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'cost_price': {
|
||||||
|
name: 'Cost price',
|
||||||
|
column: 'cost_price',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'cost_account': {
|
||||||
|
name: 'Cost account',
|
||||||
|
column: 'cost_account_id',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'sell_account': {
|
||||||
|
name: 'Sell account',
|
||||||
|
column: 'sell_account_id',
|
||||||
|
},
|
||||||
|
'inventory_account': {
|
||||||
|
name: 'Inventory account',
|
||||||
|
column: 'inventory_account_id',
|
||||||
|
},
|
||||||
|
'sell_description': {
|
||||||
|
name: 'Sell description',
|
||||||
|
column: 'sell_description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'purchase_description': {
|
||||||
|
name: 'Purchase description',
|
||||||
|
column: 'purchase_description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'quantity_on_hand': {
|
||||||
|
name: 'Quantity on hand',
|
||||||
|
column: 'quantity_on_hand',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'note': {
|
||||||
|
name: 'Note',
|
||||||
|
column: 'note',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'category': {
|
||||||
|
name: 'Category',
|
||||||
|
column: 'category_id',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
'active': {
|
||||||
|
name: 'Active',
|
||||||
|
column: 'active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
import { Model } from "objection";
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from "models/TenantModel";
|
import TenantModel from 'models/TenantModel';
|
||||||
import { buildFilterQuery } from "lib/ViewRolesBuilder";
|
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
||||||
|
import ItemSettings from './Item.Settings';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
|
||||||
export default class Item extends TenantModel {
|
export default class Item extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
static get tableName() {
|
static get tableName() {
|
||||||
return "items";
|
return 'items';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model timestamps.
|
* Model timestamps.
|
||||||
*/
|
*/
|
||||||
get timestamps() {
|
get timestamps() {
|
||||||
return ["createdAt", "updatedAt"];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +37,13 @@ export default class Item extends TenantModel {
|
|||||||
viewRolesBuilder(query, conditions, logicExpression) {
|
viewRolesBuilder(query, conditions, logicExpression) {
|
||||||
buildFilterQuery(Item.tableName, conditions, logicExpression)(query);
|
buildFilterQuery(Item.tableName, conditions, logicExpression)(query);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('active', !active);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +51,9 @@ export default class Item extends TenantModel {
|
|||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
const Media = require("models/Media");
|
const Media = require('models/Media');
|
||||||
const Account = require("models/Account");
|
const Account = require('models/Account');
|
||||||
const ItemCategory = require("models/ItemCategory");
|
const ItemCategory = require('models/ItemCategory');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@@ -54,8 +63,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: ItemCategory.default,
|
modelClass: ItemCategory.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.categoryId",
|
from: 'items.categoryId',
|
||||||
to: "items_categories.id",
|
to: 'items_categories.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -63,8 +72,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Account.default,
|
modelClass: Account.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.costAccountId",
|
from: 'items.costAccountId',
|
||||||
to: "accounts.id",
|
to: 'accounts.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -72,8 +81,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Account.default,
|
modelClass: Account.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.sellAccountId",
|
from: 'items.sellAccountId',
|
||||||
to: "accounts.id",
|
to: 'accounts.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -81,8 +90,8 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Account.default,
|
modelClass: Account.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.inventoryAccountId",
|
from: 'items.inventoryAccountId',
|
||||||
to: "accounts.id",
|
to: 'accounts.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -90,110 +99,21 @@ export default class Item extends TenantModel {
|
|||||||
relation: Model.ManyToManyRelation,
|
relation: Model.ManyToManyRelation,
|
||||||
modelClass: Media.default,
|
modelClass: Media.default,
|
||||||
join: {
|
join: {
|
||||||
from: "items.id",
|
from: 'items.id',
|
||||||
through: {
|
through: {
|
||||||
from: "media_links.model_id",
|
from: 'media_links.model_id',
|
||||||
to: "media_links.media_id",
|
to: 'media_links.media_id',
|
||||||
},
|
},
|
||||||
to: "media.id",
|
to: 'media.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return ItemSettings;
|
||||||
type: {
|
|
||||||
label: "Type",
|
|
||||||
column: "type",
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
label: "Name",
|
|
||||||
column: "name",
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
label: "Code",
|
|
||||||
column: "code",
|
|
||||||
},
|
|
||||||
sellable: {
|
|
||||||
label: "Sellable",
|
|
||||||
column: "sellable",
|
|
||||||
},
|
|
||||||
purchasable: {
|
|
||||||
label: "Purchasable",
|
|
||||||
column: "purchasable",
|
|
||||||
},
|
|
||||||
sell_price: {
|
|
||||||
label: "Sell price",
|
|
||||||
column: "sell_price",
|
|
||||||
},
|
|
||||||
cost_price: {
|
|
||||||
label: "Cost price",
|
|
||||||
column: "cost_price",
|
|
||||||
},
|
|
||||||
currency_code: {
|
|
||||||
label: "Currency",
|
|
||||||
column: "currency_code",
|
|
||||||
},
|
|
||||||
cost_account: {
|
|
||||||
label: "Cost account",
|
|
||||||
column: "cost_account_id",
|
|
||||||
relation: "accounts.id",
|
|
||||||
relationColumn: "accounts.name",
|
|
||||||
},
|
|
||||||
sell_account: {
|
|
||||||
label: "Sell account",
|
|
||||||
column: "sell_account_id",
|
|
||||||
relation: "accounts.id",
|
|
||||||
relationColumn: "accounts.name",
|
|
||||||
},
|
|
||||||
inventory_account: {
|
|
||||||
label: "Inventory account",
|
|
||||||
column: "inventory_account_id",
|
|
||||||
relation: "accounts.id",
|
|
||||||
relationColumn: "accounts.name",
|
|
||||||
},
|
|
||||||
sell_description: {
|
|
||||||
label: "Sell description",
|
|
||||||
column: "sell_description",
|
|
||||||
},
|
|
||||||
purchase_description: {
|
|
||||||
label: "Purchase description",
|
|
||||||
column: "purchase_description",
|
|
||||||
},
|
|
||||||
quantity_on_hand: {
|
|
||||||
label: "Quantity on hand",
|
|
||||||
column: "quantity_on_hand",
|
|
||||||
},
|
|
||||||
note: {
|
|
||||||
label: "Note",
|
|
||||||
column: "note",
|
|
||||||
},
|
|
||||||
category: {
|
|
||||||
label: "Category",
|
|
||||||
column: "category_id",
|
|
||||||
relation: "items_categories.id",
|
|
||||||
relationColumn: "items_categories.name",
|
|
||||||
},
|
|
||||||
active: {
|
|
||||||
label: "Active",
|
|
||||||
column: "active",
|
|
||||||
},
|
|
||||||
// user: {
|
|
||||||
// label: 'User',
|
|
||||||
// column: 'user_id',
|
|
||||||
// relation: 'users.id',
|
|
||||||
// relationColumn: 'users.',
|
|
||||||
// },
|
|
||||||
created_at: {
|
|
||||||
label: "Created at",
|
|
||||||
column: "created_at",
|
|
||||||
columnType: "date",
|
|
||||||
fieldType: "date",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
41
server/src/models/ItemCategory.Settings.ts
Normal file
41
server/src/models/ItemCategory.Settings.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'name',
|
||||||
|
defaultSort: {
|
||||||
|
sortField: 'name',
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
label: 'Name',
|
||||||
|
column: 'name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
label: 'Description',
|
||||||
|
column: 'description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
cost_account: {
|
||||||
|
label: 'Cost account',
|
||||||
|
column: 'cost_account_id',
|
||||||
|
},
|
||||||
|
sell_account: {
|
||||||
|
label: 'Sell account',
|
||||||
|
column: 'sell_account_id',
|
||||||
|
},
|
||||||
|
inventory_account: {
|
||||||
|
label: 'Inventory account',
|
||||||
|
column: 'inventory_account_id',
|
||||||
|
},
|
||||||
|
count: {
|
||||||
|
label: 'Count',
|
||||||
|
column: 'count',
|
||||||
|
sortQuery: this.sortCountQuery,
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
label: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -42,68 +42,6 @@ export default class ItemCategory extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Item category fields.
|
|
||||||
*/
|
|
||||||
static get fields() {
|
|
||||||
return {
|
|
||||||
name: {
|
|
||||||
label: 'Name',
|
|
||||||
column: 'name',
|
|
||||||
columnType: 'string'
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description',
|
|
||||||
column: 'description',
|
|
||||||
columnType: 'string'
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
label: 'User',
|
|
||||||
column: 'user_id',
|
|
||||||
relation: 'users.id',
|
|
||||||
relationColumn: 'users.id',
|
|
||||||
},
|
|
||||||
cost_account: {
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
count: {
|
|
||||||
label: 'Count',
|
|
||||||
column: 'count',
|
|
||||||
sortQuery: this.sortCountQuery
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static sortCountQuery(query, role) {
|
static sortCountQuery(query, role) {
|
||||||
query.orderBy('count', role.order);
|
query.orderBy('count', role.order);
|
||||||
}
|
}
|
||||||
|
|||||||
54
server/src/models/ManualJournal.Settings.ts
Normal file
54
server/src/models/ManualJournal.Settings.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'date',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'name',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'date': {
|
||||||
|
label: 'Date',
|
||||||
|
column: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'journal_number': {
|
||||||
|
label: 'Journal number',
|
||||||
|
column: 'journal_number',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'reference': {
|
||||||
|
label: 'Reference No.',
|
||||||
|
column: 'reference',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'journal_type': {
|
||||||
|
label: 'Journal type',
|
||||||
|
column: 'journal_type',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'amount': {
|
||||||
|
label: 'Amount',
|
||||||
|
column: 'amount',
|
||||||
|
columnType: 'number',
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
label: 'Description',
|
||||||
|
column: 'description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
label: 'Status',
|
||||||
|
column: 'status',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
sortQuery: statusFieldSortQuery,
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
label: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function statusFieldSortQuery(query, role) {
|
||||||
|
return query.modify('sortByStatus', role.order);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Model } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import { formatNumber } from 'utils';
|
import { formatNumber } from 'utils';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import ManualJournalSettings from './ManualJournal.Settings';
|
||||||
|
|
||||||
export default class ManualJournal extends TenantModel {
|
export default class ManualJournal extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -99,52 +101,7 @@ export default class ManualJournal extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static get meta() {
|
||||||
* Model defined fields.
|
return ManualJournalSettings;
|
||||||
*/
|
|
||||||
static get fields() {
|
|
||||||
return {
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
journal_type: {
|
|
||||||
label: 'Journal type',
|
|
||||||
column: 'journal_type',
|
|
||||||
columnType: 'string',
|
|
||||||
},
|
|
||||||
amount: {
|
|
||||||
label: 'Amount',
|
|
||||||
column: 'amount',
|
|
||||||
columnType: 'number',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description',
|
|
||||||
column: 'description',
|
|
||||||
columnType: 'string',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
column: 'status',
|
|
||||||
sortQuery(query, role) {
|
|
||||||
query.modify('sortByStatus', role.order);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
server/src/models/ModelSetting.ts
Normal file
54
server/src/models/ModelSetting.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { get } from 'lodash';
|
||||||
|
import { IModelMeta, IModelMetaField, IModelMetaDefaultSort } from 'interfaces';
|
||||||
|
|
||||||
|
export default (Model) =>
|
||||||
|
class ModelSettings extends Model {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static get meta(): IModelMeta {
|
||||||
|
throw new Error('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve specific model field meta of the given field key.
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {IModelMetaField}
|
||||||
|
*/
|
||||||
|
public static getField(key: string): IModelMetaField {
|
||||||
|
return get(this.meta.fields, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the specific model meta.
|
||||||
|
* @param {string} key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static getMeta(key: string) {
|
||||||
|
return get(this.meta, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the model meta fields.
|
||||||
|
* @return {{ [key: string]: IModelMetaField }}
|
||||||
|
*/
|
||||||
|
public static get fields(): { [key: string]: IModelMetaField } {
|
||||||
|
return this.getMeta('fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the model default sort settings.
|
||||||
|
* @return {IModelMetaDefaultSort}
|
||||||
|
*/
|
||||||
|
public static get defaultSort(): IModelMetaDefaultSort {
|
||||||
|
return this.getMeta('defaultSort');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the default filter field key.
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
public static get defaultFilterField(): string {
|
||||||
|
return this.getMeta('defaultFilterField');
|
||||||
|
}
|
||||||
|
};
|
||||||
43
server/src/models/PaymentReceive.Settings.ts
Normal file
43
server/src/models/PaymentReceive.Settings.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
export default {
|
||||||
|
fields: {
|
||||||
|
customer: {
|
||||||
|
name: 'Customer',
|
||||||
|
column: 'customer_id',
|
||||||
|
},
|
||||||
|
payment_date: {
|
||||||
|
name: 'Payment date',
|
||||||
|
column: 'payment_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Amount',
|
||||||
|
column: 'amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
reference_no: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
deposit_account: {
|
||||||
|
name: 'Deposit account',
|
||||||
|
column: 'deposit_account_id',
|
||||||
|
},
|
||||||
|
payment_receive_no: {
|
||||||
|
name: 'Payment receive No.',
|
||||||
|
column: 'payment_receive_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
statement: {
|
||||||
|
name: 'Statement',
|
||||||
|
column: 'statement',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldDate: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Model } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import PaymentReceiveSettings from './PaymentReceive.Settings';
|
||||||
|
|
||||||
export default class PaymentReceive extends TenantModel {
|
export default class PaymentReceive extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
@@ -75,63 +77,9 @@ export default class PaymentReceive extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
*
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return PaymentReceiveSettings;
|
||||||
customer: {
|
|
||||||
label: 'Customer',
|
|
||||||
column: 'customer_id',
|
|
||||||
relation: 'contacts.id',
|
|
||||||
relationColumn: 'contacts.displayName',
|
|
||||||
|
|
||||||
fieldType: 'options',
|
|
||||||
optionsResource: 'customers',
|
|
||||||
optionsKey: 'id',
|
|
||||||
optionsLable: 'displayName',
|
|
||||||
},
|
|
||||||
payment_date: {
|
|
||||||
label: 'Payment date',
|
|
||||||
column: 'payment_date',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
amount: {
|
|
||||||
label: 'Amount',
|
|
||||||
column: 'amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
reference_no: {
|
|
||||||
label: 'Reference No.',
|
|
||||||
column: 'reference_no',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
deposit_account: {
|
|
||||||
column: 'deposit_account_id',
|
|
||||||
lable: 'Deposit account',
|
|
||||||
relation: "accounts.id",
|
|
||||||
relationColumn: 'accounts.name',
|
|
||||||
optionsResource: "account",
|
|
||||||
},
|
|
||||||
payment_receive_no: {
|
|
||||||
label: 'Payment receive No.',
|
|
||||||
column: 'payment_receive_no',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'description',
|
|
||||||
column: 'description',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
85
server/src/models/SaleEstimate.Settings.ts
Normal file
85
server/src/models/SaleEstimate.Settings.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'estimate_date',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'estimate_date',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'amount': {
|
||||||
|
name: 'Amount',
|
||||||
|
column: 'amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'estimate_number': {
|
||||||
|
name: 'Estimate number',
|
||||||
|
column: 'estimate_number',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'customer': {
|
||||||
|
name: 'Customer',
|
||||||
|
column: 'customer_id',
|
||||||
|
},
|
||||||
|
'estimate_date': {
|
||||||
|
name: 'Estimate date',
|
||||||
|
column: 'estimate_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'expiration_date': {
|
||||||
|
name: 'Expiration date',
|
||||||
|
column: 'expiration_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'reference_no': {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'note': {
|
||||||
|
name: 'Note',
|
||||||
|
column: 'note',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'terms_conditions': {
|
||||||
|
name: 'Terms & conditions',
|
||||||
|
column: 'terms_conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
name: 'Status',
|
||||||
|
filterQuery: statusFieldFilterQuery,
|
||||||
|
sortQuery: statusFieldSortQuery,
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function statusFieldSortQuery(query, role) {
|
||||||
|
return query.modify('orderByDraft', role.order);
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusFieldFilterQuery(query, role) {
|
||||||
|
switch (role.value) {
|
||||||
|
case 'draft':
|
||||||
|
query.modify('draft');
|
||||||
|
break;
|
||||||
|
case 'delivered':
|
||||||
|
query.modify('delivered');
|
||||||
|
break;
|
||||||
|
case 'approved':
|
||||||
|
query.modify('approved');
|
||||||
|
break;
|
||||||
|
case 'rejected':
|
||||||
|
query.modify('rejected');
|
||||||
|
break;
|
||||||
|
case 'invoiced':
|
||||||
|
query.modify('invoiced');
|
||||||
|
break;
|
||||||
|
case 'expired':
|
||||||
|
query.modify('expired');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Model } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import { defaultToTransform } from 'utils';
|
import { defaultToTransform } from 'utils';
|
||||||
import HasItemEntries from 'services/Sales/HasItemsEntries';
|
import SaleEstimateSettings from './SaleEstimate.Settings';
|
||||||
import { query } from 'winston';
|
import ModelSetting from './ModelSetting';
|
||||||
|
|
||||||
export default class SaleEstimate extends TenantModel {
|
export default class SaleEstimate extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -29,7 +29,7 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
'isExpired',
|
'isExpired',
|
||||||
'isConvertedToInvoice',
|
'isConvertedToInvoice',
|
||||||
'isApproved',
|
'isApproved',
|
||||||
'isRejected'
|
'isRejected',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
return defaultToTransform(
|
return defaultToTransform(
|
||||||
this.expirationDate,
|
this.expirationDate,
|
||||||
moment().isAfter(this.expirationDate, 'day'),
|
moment().isAfter(this.expirationDate, 'day'),
|
||||||
false,
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,14 +123,14 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
* Filters the approved estimates transactions.
|
* Filters the approved estimates transactions.
|
||||||
*/
|
*/
|
||||||
approved(query) {
|
approved(query) {
|
||||||
query.whereNot('approved_at', null)
|
query.whereNot('approved_at', null);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sorting the estimates orders by delivery status.
|
* Sorting the estimates orders by delivery status.
|
||||||
*/
|
*/
|
||||||
orderByDraft(query, order) {
|
orderByDraft(query, order) {
|
||||||
query.orderByRaw(`delivered_at is null ${order}`)
|
query.orderByRaw(`delivered_at is null ${order}`);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
},
|
},
|
||||||
filter(query) {
|
filter(query) {
|
||||||
query.where('contact_service', 'customer');
|
query.where('contact_service', 'customer');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
entries: {
|
entries: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
@@ -168,91 +168,9 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Model settings.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return SaleEstimateSettings;
|
||||||
amount: {
|
|
||||||
label: 'Amount',
|
|
||||||
column: 'amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
estimate_number: {
|
|
||||||
label: 'Estimate number',
|
|
||||||
column: 'estimate_number',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
customer: {
|
|
||||||
label: 'Customer',
|
|
||||||
column: 'customer_id',
|
|
||||||
relation: 'contacts.id',
|
|
||||||
relationColumn: 'contacts.displayName',
|
|
||||||
|
|
||||||
fieldType: 'options',
|
|
||||||
optionsResource: 'customers',
|
|
||||||
optionsKey: 'id',
|
|
||||||
optionsLable: 'displayName',
|
|
||||||
},
|
|
||||||
estimate_date: {
|
|
||||||
label: 'Estimate date',
|
|
||||||
column: 'estimate_date',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
expiration_date: {
|
|
||||||
label: 'Expiration date',
|
|
||||||
column: 'expiration_date',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
reference_no: {
|
|
||||||
label: "Reference No.",
|
|
||||||
column: "reference",
|
|
||||||
columnType: "number",
|
|
||||||
fieldType: "number",
|
|
||||||
},
|
|
||||||
note: {
|
|
||||||
label: 'Note',
|
|
||||||
column: 'note',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
terms_conditions: {
|
|
||||||
label: 'Terms & conditions',
|
|
||||||
column: 'terms_conditions',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
query: (query, role) => {
|
|
||||||
switch(role.value) {
|
|
||||||
case 'draft':
|
|
||||||
query.modify('draft'); break;
|
|
||||||
case 'delivered':
|
|
||||||
query.modify('delivered'); break;
|
|
||||||
case 'approved':
|
|
||||||
query.modify('approved'); break;
|
|
||||||
case 'rejected':
|
|
||||||
query.modify('rejected'); break;
|
|
||||||
case 'invoiced':
|
|
||||||
query.modify('invoiced');
|
|
||||||
break;
|
|
||||||
case 'expired':
|
|
||||||
query.modify('expired'); break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sortQuery: (query, role) => {
|
|
||||||
query.modify('orderByDraft', role.order);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
server/src/models/SaleInvoice.Settings.ts
Normal file
86
server/src/models/SaleInvoice.Settings.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'customer',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'created_at',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
// customer: {
|
||||||
|
// name: 'Customer',
|
||||||
|
// column: 'customer_id',
|
||||||
|
// },
|
||||||
|
invoice_date: {
|
||||||
|
name: 'Invoice date',
|
||||||
|
column: 'invoice_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
due_date: {
|
||||||
|
name: 'Due date',
|
||||||
|
column: 'due_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
invoice_no: {
|
||||||
|
name: 'Invoice No.',
|
||||||
|
column: 'invoice_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
reference_no: {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
invoice_message: {
|
||||||
|
name: 'Invoice message',
|
||||||
|
column: 'invoice_message',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
terms_conditions: {
|
||||||
|
name: 'Terms & conditions',
|
||||||
|
column: 'terms_conditions',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true,
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
name: 'Invoice amount',
|
||||||
|
column: 'balance',
|
||||||
|
columnable: true,
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
payment_amount: {
|
||||||
|
name: 'Payment amount',
|
||||||
|
column: 'payment_amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
due_amount: {
|
||||||
|
name: 'Due amount',
|
||||||
|
column: 'due_amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
// sortQuery: SaleInvoice.dueAmountFieldSortQuery,
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
// status: {
|
||||||
|
// name: 'Status',
|
||||||
|
// columnable: true,
|
||||||
|
// fieldType: 'enumeration',
|
||||||
|
// options: [
|
||||||
|
// { key: 'draft', name: 'Draft' },
|
||||||
|
// { key: 'delivered', name: 'Delivered' },
|
||||||
|
// { key: 'unpaid', name: 'Unpaid' },
|
||||||
|
// { key: 'overdue', name: 'Overdue' },
|
||||||
|
// { key: 'partially-paid', name: 'Partially paid' },
|
||||||
|
// { key: 'paid', name: 'Paid' },
|
||||||
|
// ],
|
||||||
|
// // filterQuery: SaleInvoice.statusFieldFilterQuery,
|
||||||
|
// // sortQuery: SaleInvoice.statusFieldSortQuery,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Model, raw } from 'objection';
|
import { mixin, Model, raw } from 'objection';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import SaleInvoiceMeta from './SaleInvoice.Settings';
|
||||||
|
|
||||||
export default class SaleInvoice extends TenantModel {
|
export default class SaleInvoice extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -243,6 +245,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Sale invoice associated entries.
|
||||||
|
*/
|
||||||
entries: {
|
entries: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: ItemEntry.default,
|
modelClass: ItemEntry.default,
|
||||||
@@ -255,6 +260,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Belongs to customer model.
|
||||||
|
*/
|
||||||
customer: {
|
customer: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Contact.default,
|
modelClass: Contact.default,
|
||||||
@@ -267,6 +275,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice has associated account transactions.
|
||||||
|
*/
|
||||||
transactions: {
|
transactions: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
modelClass: AccountTransaction.default,
|
modelClass: AccountTransaction.default,
|
||||||
@@ -316,100 +327,17 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Sale invoice meta.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return SaleInvoiceMeta;
|
||||||
customer: {
|
}
|
||||||
label: 'Customer',
|
|
||||||
column: 'customer_id',
|
|
||||||
relation: 'contacts.id',
|
|
||||||
relationColumn: 'contacts.displayName',
|
|
||||||
|
|
||||||
fieldType: 'options',
|
static dueAmountFieldSortQuery(query, role) {
|
||||||
optionsResource: 'customers',
|
|
||||||
optionsKey: 'id',
|
|
||||||
optionsLable: 'displayName',
|
|
||||||
},
|
|
||||||
invoice_date: {
|
|
||||||
label: 'Invoice date',
|
|
||||||
column: 'invoice_date',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
due_date: {
|
|
||||||
label: 'Due date',
|
|
||||||
column: 'due_date',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
invoice_no: {
|
|
||||||
label: 'Invoice No.',
|
|
||||||
column: 'invoice_no',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
reference_no: {
|
|
||||||
label: 'Reference No.',
|
|
||||||
column: 'reference_no',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
invoice_message: {
|
|
||||||
label: 'Invoice message',
|
|
||||||
column: 'invoice_message',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
terms_conditions: {
|
|
||||||
label: 'Terms & conditions',
|
|
||||||
column: 'terms_conditions',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
invoice_amount: {
|
|
||||||
label: 'Invoice amount',
|
|
||||||
column: 'invoice_amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
payment_amount: {
|
|
||||||
label: 'Payment amount',
|
|
||||||
column: 'payment_amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
balance: {
|
|
||||||
label: 'Balance',
|
|
||||||
column: 'balance',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
due_amount: {
|
|
||||||
label: 'Due amount',
|
|
||||||
column: 'due_amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
sortQuery(query, role) {
|
|
||||||
query.modify('sortByDueAmount', role.order);
|
query.modify('sortByDueAmount', role.order);
|
||||||
},
|
}
|
||||||
},
|
|
||||||
created_at: {
|
static statusFieldFilterQuery(query, role) {
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
options: [
|
|
||||||
{ key: 'draft', label: 'Draft' },
|
|
||||||
{ key: 'delivered', label: 'Delivered' },
|
|
||||||
{ key: 'unpaid', label: 'Unpaid' },
|
|
||||||
{ key: 'overdue', label: 'Overdue' },
|
|
||||||
{ key: 'partially-paid', label: 'Partially paid' },
|
|
||||||
{ key: 'paid', label: 'Paid' },
|
|
||||||
],
|
|
||||||
query: (query, role) => {
|
|
||||||
switch (role.value) {
|
switch (role.value) {
|
||||||
case 'draft':
|
case 'draft':
|
||||||
query.modify('draft');
|
query.modify('draft');
|
||||||
@@ -430,11 +358,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
query.modify('paid');
|
query.modify('paid');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
sortQuery(query, role) {
|
|
||||||
|
static statusFieldSortQuery(query, role) {
|
||||||
query.modify('sortByStatus', role.order);
|
query.modify('sortByStatus', role.order);
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
87
server/src/models/SaleReceipt.Settings.ts
Normal file
87
server/src/models/SaleReceipt.Settings.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'receipt_date',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'created_at',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'amount': {
|
||||||
|
name: 'Amount',
|
||||||
|
column: 'amount',
|
||||||
|
fieldType: 'number',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'deposit_account': {
|
||||||
|
column: 'deposit_account_id',
|
||||||
|
name: 'Deposit account',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'customer': {
|
||||||
|
name: 'Customer',
|
||||||
|
column: 'customer_id',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'receipt_date': {
|
||||||
|
name: 'Receipt date',
|
||||||
|
column: 'receipt_date',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'receipt_number': {
|
||||||
|
name: 'Receipt No.',
|
||||||
|
column: 'receipt_number',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'reference_no': {
|
||||||
|
name: 'Reference No.',
|
||||||
|
column: 'reference_no',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'receipt_message': {
|
||||||
|
name: 'Receipt message',
|
||||||
|
column: 'receipt_message',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'statement': {
|
||||||
|
name: 'Statement',
|
||||||
|
column: 'statement',
|
||||||
|
fieldType: 'text',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
name: 'Status',
|
||||||
|
fieldType: 'enumeration',
|
||||||
|
options: [
|
||||||
|
{ key: 'draft', name: 'Draft' },
|
||||||
|
{ key: 'closed', name: 'Closed' },
|
||||||
|
],
|
||||||
|
query: statusFieldFilterQuery,
|
||||||
|
sortQuery: statusFieldSortQuery,
|
||||||
|
columnable: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function statusFieldFilterQuery(query, role) {
|
||||||
|
switch (role.value) {
|
||||||
|
case 'draft':
|
||||||
|
query.modify('draft');
|
||||||
|
break;
|
||||||
|
case 'closed':
|
||||||
|
query.modify('closed');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusFieldSortQuery(query, role) {
|
||||||
|
query.modify('sortByStatus', role.order);
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Model, mixin } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import SaleReceiptSettings from './SaleReceipt.Settings';
|
||||||
|
|
||||||
export default class SaleReceipt extends TenantModel {
|
export default class SaleReceipt extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -20,10 +22,7 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return [
|
return ['isClosed', 'isDraft'];
|
||||||
'isClosed',
|
|
||||||
'isDraft',
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +65,7 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
sortByStatus(query, order) {
|
sortByStatus(query, order) {
|
||||||
query.orderByRaw(`CLOSED_AT IS NULL ${order}`);
|
query.orderByRaw(`CLOSED_AT IS NULL ${order}`);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +88,7 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
},
|
},
|
||||||
filter(query) {
|
filter(query) {
|
||||||
query.where('contact_service', 'customer');
|
query.where('contact_service', 'customer');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
depositAccount: {
|
depositAccount: {
|
||||||
@@ -118,95 +117,19 @@ export default class SaleReceipt extends TenantModel {
|
|||||||
modelClass: AccountTransaction.default,
|
modelClass: AccountTransaction.default,
|
||||||
join: {
|
join: {
|
||||||
from: 'sales_receipts.id',
|
from: 'sales_receipts.id',
|
||||||
to: 'accounts_transactions.referenceId'
|
to: 'accounts_transactions.referenceId',
|
||||||
},
|
},
|
||||||
filter(builder) {
|
filter(builder) {
|
||||||
builder.where('reference_type', 'SaleReceipt');
|
builder.where('reference_type', 'SaleReceipt');
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model defined fields.
|
* Sale invoice meta.
|
||||||
*/
|
*/
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return SaleReceiptSettings;
|
||||||
amount: {
|
|
||||||
label: 'Amount',
|
|
||||||
column: 'amount',
|
|
||||||
columnType: 'number',
|
|
||||||
fieldType: 'number',
|
|
||||||
},
|
|
||||||
deposit_account: {
|
|
||||||
column: 'deposit_account_id',
|
|
||||||
label: 'Deposit account',
|
|
||||||
relation: "accounts.id",
|
|
||||||
optionsResource: "account",
|
|
||||||
},
|
|
||||||
customer: {
|
|
||||||
label: 'Customer',
|
|
||||||
column: 'customer_id',
|
|
||||||
fieldType: 'options',
|
|
||||||
optionsResource: 'customers',
|
|
||||||
optionsKey: 'id',
|
|
||||||
optionsLable: 'displayName',
|
|
||||||
},
|
|
||||||
receipt_date: {
|
|
||||||
label: 'Receipt date',
|
|
||||||
column: 'receipt_date',
|
|
||||||
columnType: 'date',
|
|
||||||
fieldType: 'date',
|
|
||||||
},
|
|
||||||
receipt_number: {
|
|
||||||
label: 'Receipt No.',
|
|
||||||
column: 'receipt_number',
|
|
||||||
columnType: 'string',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
reference_no: {
|
|
||||||
label: 'Reference No.',
|
|
||||||
column: 'reference_no',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
receipt_message: {
|
|
||||||
label: 'Receipt message',
|
|
||||||
column: 'receipt_message',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
statement: {
|
|
||||||
label: 'Statement',
|
|
||||||
column: 'statement',
|
|
||||||
columnType: 'text',
|
|
||||||
fieldType: 'text',
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
options: [
|
|
||||||
{ key: 'draft', label: 'Draft', },
|
|
||||||
{ key: 'closed', label: 'Closed' },
|
|
||||||
],
|
|
||||||
query: (query, role) => {
|
|
||||||
switch(role.value) {
|
|
||||||
case 'draft':
|
|
||||||
query.modify('draft');
|
|
||||||
break;
|
|
||||||
case 'closed':
|
|
||||||
query.modify('closed');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sortQuery(query, role) {
|
|
||||||
query.modify('sortByStatus', role.order);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
89
server/src/models/Vendor.Settings.ts
Normal file
89
server/src/models/Vendor.Settings.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
export default {
|
||||||
|
defaultFilterField: 'display_name',
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'created_at',
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
'display_name': {
|
||||||
|
name: 'Display name',
|
||||||
|
column: 'display_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'email': {
|
||||||
|
name: 'Email',
|
||||||
|
column: 'email',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'work_phone': {
|
||||||
|
name: 'Work phone',
|
||||||
|
column: 'work_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'personal_phone': {
|
||||||
|
name: 'Personal phone',
|
||||||
|
column: 'personal_phone',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'company_name': {
|
||||||
|
name: 'Company name',
|
||||||
|
column: 'company_name',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'website': {
|
||||||
|
name: 'Website',
|
||||||
|
column: 'website',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'created_at': {
|
||||||
|
name: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'balance': {
|
||||||
|
name: 'Balance',
|
||||||
|
column: 'balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'opening_balance': {
|
||||||
|
name: 'Opening balance',
|
||||||
|
column: 'opening_balance',
|
||||||
|
fieldType: 'number',
|
||||||
|
},
|
||||||
|
'opening_balance_at': {
|
||||||
|
name: 'Opening balance at',
|
||||||
|
column: 'opening_balance_at',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
|
'currency_code': {
|
||||||
|
name: 'Currency code',
|
||||||
|
column: 'currency_code',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
label: 'Status',
|
||||||
|
options: [
|
||||||
|
{ key: 'active', label: 'Active' },
|
||||||
|
{ key: 'inactive', label: 'Inactive' },
|
||||||
|
{ key: 'overdue', label: 'Overdue' },
|
||||||
|
{ key: 'unpaid', label: 'Unpaid' },
|
||||||
|
],
|
||||||
|
query: (query, role) => {
|
||||||
|
switch (role.value) {
|
||||||
|
case 'active':
|
||||||
|
query.modify('active');
|
||||||
|
break;
|
||||||
|
case 'inactive':
|
||||||
|
query.modify('inactive');
|
||||||
|
break;
|
||||||
|
case 'overdue':
|
||||||
|
query.modify('overdue');
|
||||||
|
break;
|
||||||
|
case 'unpaid':
|
||||||
|
query.modify('unpaid');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Model, QueryBuilder } from 'objection';
|
import { Model, mixin } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import PaginationQueryBuilder from './Pagination';
|
import PaginationQueryBuilder from './Pagination';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
import VendorSettings from './Vendor.Settings';
|
||||||
|
|
||||||
class VendorQueryBuilder extends PaginationQueryBuilder {
|
class VendorQueryBuilder extends PaginationQueryBuilder {
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
@@ -14,7 +16,7 @@ class VendorQueryBuilder extends PaginationQueryBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Vendor extends TenantModel {
|
export default class Vendor extends mixin(TenantModel, [ModelSetting]) {
|
||||||
/**
|
/**
|
||||||
* Query builder.
|
* Query builder.
|
||||||
*/
|
*/
|
||||||
@@ -62,6 +64,13 @@ export default class Vendor extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Inactive/Active mode.
|
||||||
|
*/
|
||||||
|
inactiveMode(query, active = false) {
|
||||||
|
query.where('active', !active);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the active customers.
|
* Filters the active customers.
|
||||||
*/
|
*/
|
||||||
@@ -125,72 +134,7 @@ export default class Vendor extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get fields() {
|
static get meta() {
|
||||||
return {
|
return VendorSettings;
|
||||||
contact_service: {
|
|
||||||
column: 'contact_service',
|
|
||||||
},
|
|
||||||
display_name: {
|
|
||||||
column: 'display_name',
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
column: 'email',
|
|
||||||
},
|
|
||||||
work_phone: {
|
|
||||||
column: 'work_phone',
|
|
||||||
},
|
|
||||||
personal_phone: {
|
|
||||||
column: 'personal_phone',
|
|
||||||
},
|
|
||||||
company_name: {
|
|
||||||
column: 'company_name',
|
|
||||||
},
|
|
||||||
website: {
|
|
||||||
column: 'website'
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
column: 'created_at',
|
|
||||||
},
|
|
||||||
balance: {
|
|
||||||
column: 'balance',
|
|
||||||
},
|
|
||||||
opening_balance: {
|
|
||||||
column: 'opening_balance',
|
|
||||||
},
|
|
||||||
opening_balance_at: {
|
|
||||||
column: 'opening_balance_at',
|
|
||||||
},
|
|
||||||
currency_code: {
|
|
||||||
column: 'currency_code',
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
label: 'Status',
|
|
||||||
options: [
|
|
||||||
{ key: 'active', label: 'Active' },
|
|
||||||
{ key: 'inactive', label: 'Inactive' },
|
|
||||||
{ key: 'overdue', label: 'Overdue' },
|
|
||||||
{ key: 'unpaid', label: 'Unpaid' },
|
|
||||||
],
|
|
||||||
query: (query, role) => {
|
|
||||||
switch(role.value) {
|
|
||||||
case 'active':
|
|
||||||
query.modify('active');
|
|
||||||
break;
|
|
||||||
case 'inactive':
|
|
||||||
query.modify('inactive');
|
|
||||||
break;
|
|
||||||
case 'overdue':
|
|
||||||
query.modify('overdue');
|
|
||||||
break;
|
|
||||||
case 'unpaid':
|
|
||||||
query.modify('unpaid');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
column: 'created_at',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { difference, chain, uniq } from 'lodash';
|
import { difference, chain, uniq } from 'lodash';
|
||||||
import { kebabCase } from 'lodash';
|
import { kebabCase } from 'lodash';
|
||||||
|
import R from 'ramda';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import {
|
import {
|
||||||
@@ -606,6 +607,17 @@ export default class AccountsService {
|
|||||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
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.
|
* Retrieve accounts datatable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -613,21 +625,26 @@ export default class AccountsService {
|
|||||||
*/
|
*/
|
||||||
public async getAccountsList(
|
public async getAccountsList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: IAccountsFilter
|
filterDTO: IAccountsFilter
|
||||||
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
||||||
const { Account } = this.tenancy.models(tenantId);
|
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(
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
Account,
|
Account,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info('[accounts] trying to get accounts datatable list.', {
|
this.logger.info('[accounts] trying to get accounts datatable list.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
filter,
|
filter,
|
||||||
});
|
});
|
||||||
const accounts = await Account.query().onBuild((builder) => {
|
const accounts = await Account.query().onBuild((builder) => {
|
||||||
dynamicList.buildQuery()(builder);
|
dynamicList.buildQuery()(builder);
|
||||||
|
builder.modify('inactiveMode', filter.inactiveMode);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -730,7 +747,8 @@ export default class AccountsService {
|
|||||||
{
|
{
|
||||||
id: 'id',
|
id: 'id',
|
||||||
parentId: 'parent_account_id',
|
parentId: 'parent_account_id',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { omit, defaultTo } from 'lodash';
|
import { omit, defaultTo } from 'lodash';
|
||||||
import async from 'async';
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -265,6 +265,16 @@ export default class CustomersService {
|
|||||||
return this.transformContactToCustomer(contact);
|
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.
|
* Retrieve customers paginated list.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
@@ -272,7 +282,7 @@ export default class CustomersService {
|
|||||||
*/
|
*/
|
||||||
public async getCustomersList(
|
public async getCustomersList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
customersFilter: ICustomersFilter
|
filterDTO: ICustomersFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
customers: ICustomer[];
|
customers: ICustomer[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
@@ -280,17 +290,23 @@ export default class CustomersService {
|
|||||||
}> {
|
}> {
|
||||||
const { Customer } = this.tenancy.models(tenantId);
|
const { Customer } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Parses customers list filter DTO.
|
||||||
|
const filter = this.parseCustomersListFilterDTO(filterDTO);
|
||||||
|
|
||||||
// Dynamic list.
|
// Dynamic list.
|
||||||
const dynamicList = await this.dynamicListService.dynamicList(
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
Customer,
|
Customer,
|
||||||
customersFilter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Customers.
|
||||||
const { results, pagination } = await Customer.query()
|
const { results, pagination } = await Customer.query()
|
||||||
.onBuild((query) => {
|
.onBuild((builder) => {
|
||||||
dynamicList.buildQuery()(query);
|
dynamicList.buildQuery()(builder);
|
||||||
|
builder.modify('inactiveMode', filter.inactiveMode);
|
||||||
})
|
})
|
||||||
.pagination(customersFilter.page - 1, customersFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customers: results.map(this.transformContactToCustomer),
|
customers: results.map(this.transformContactToCustomer),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { intersection, defaultTo } from 'lodash';
|
import { defaultTo } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -255,6 +256,12 @@ export default class VendorsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseVendorsListFilterDTO(filterDTO) {
|
||||||
|
return R.compose(
|
||||||
|
this.dynamicListService.parseStringifiedFilter
|
||||||
|
)(filterDTO);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve vendors datatable list.
|
* Retrieve vendors datatable list.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
@@ -262,7 +269,7 @@ export default class VendorsService {
|
|||||||
*/
|
*/
|
||||||
public async getVendorsList(
|
public async getVendorsList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
vendorsFilter: IVendorsFilter
|
filterDTO: IVendorsFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
vendors: IVendor[];
|
vendors: IVendor[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
@@ -270,21 +277,28 @@ export default class VendorsService {
|
|||||||
}> {
|
}> {
|
||||||
const { Vendor } = this.tenancy.models(tenantId);
|
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,
|
tenantId,
|
||||||
Vendor,
|
Vendor,
|
||||||
vendorsFilter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Vendors list.
|
||||||
const { results, pagination } = await Vendor.query()
|
const { results, pagination } = await Vendor.query()
|
||||||
.onBuild((builder) => {
|
.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 {
|
return {
|
||||||
vendors: results,
|
vendors: results,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicFilter.getResponseMeta(),
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default class DynamicListAbstruct {
|
||||||
|
|
||||||
|
}
|
||||||
52
server/src/services/DynamicListing/DynamicListCustomView.ts
Normal file
52
server/src/services/DynamicListing/DynamicListCustomView.ts
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
103
server/src/services/DynamicListing/DynamicListFilterRoles.ts
Normal file
103
server/src/services/DynamicListing/DynamicListFilterRoles.ts
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,161 +1,88 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import validator from 'is-my-json-valid';
|
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { castArray, isEmpty } from 'lodash';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
|
import { DynamicFilter } from 'lib/DynamicFilter';
|
||||||
import {
|
import {
|
||||||
DynamicFilter,
|
IDynamicListFilter,
|
||||||
DynamicFilterSortBy,
|
|
||||||
DynamicFilterViews,
|
|
||||||
DynamicFilterFilterRoles,
|
|
||||||
} from 'lib/DynamicFilter';
|
|
||||||
import {
|
|
||||||
validateFieldKeyExistance,
|
|
||||||
validateFilterRolesFieldsExistance,
|
|
||||||
} from 'lib/ViewRolesBuilder';
|
|
||||||
import {
|
|
||||||
IDynamicListFilterDTO,
|
|
||||||
IFilterRole,
|
|
||||||
IDynamicListService,
|
IDynamicListService,
|
||||||
|
IFilterRole,
|
||||||
IModel,
|
IModel,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
|
import DynamicListFilterRoles from './DynamicListFilterRoles';
|
||||||
const ERRORS = {
|
import DynamicListSortBy from './DynamicListSortBy';
|
||||||
VIEW_NOT_FOUND: 'view_not_found',
|
import DynamicListCustomView from './DynamicListCustomView';
|
||||||
SORT_COLUMN_NOT_FOUND: 'sort_column_not_found',
|
|
||||||
FILTER_ROLES_FIELDS_NOT_FOUND: 'filter_roles_fields_not_found',
|
|
||||||
};
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class DynamicListService implements IDynamicListService {
|
export default class DynamicListService implements IDynamicListService {
|
||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
/**
|
@Inject()
|
||||||
* Retreive custom view or throws error not found.
|
dynamicListFilterRoles: DynamicListFilterRoles;
|
||||||
* @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');
|
|
||||||
|
|
||||||
if (!view || view.resourceModel !== model.name) {
|
@Inject()
|
||||||
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
|
dynamicListSortBy: DynamicListSortBy;
|
||||||
}
|
|
||||||
return view;
|
@Inject()
|
||||||
}
|
dynamicListView: DynamicListCustomView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the sort column whether exists.
|
* Parses filter DTO.
|
||||||
* @param {IModel} model
|
|
||||||
* @param {string} columnSortBy - Sort column
|
|
||||||
* @throws {ServiceError}
|
|
||||||
*/
|
*/
|
||||||
private validateSortColumnExistance(model: any, columnSortBy: string) {
|
private parseFilterObject = (model, filterDTO) => {
|
||||||
const notExistsField = validateFieldKeyExistance(model, columnSortBy);
|
return {
|
||||||
|
// Merges the default properties with filter object.
|
||||||
if (!notExistsField) {
|
...model.defaultSort ? {
|
||||||
throw new ServiceError(ERRORS.SORT_COLUMN_NOT_FOUND);
|
sortOrder: model.defaultSort.sortOrder,
|
||||||
}
|
columnSortBy: model.defaultSort.sortOrder,
|
||||||
}
|
} : {},
|
||||||
|
...filterDTO,
|
||||||
/**
|
};
|
||||||
* 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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamic listing.
|
* Dynamic listing.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {IModel} model - Model.
|
* @param {IModel} model - Model.
|
||||||
* @param {IDynamicListFilterDTO} filter - Dynamic filter DTO.
|
* @param {IDynamicListFilter} filter - Dynamic filter DTO.
|
||||||
*/
|
*/
|
||||||
public async dynamicList(
|
public dynamicList = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
model: IModel,
|
model: IModel,
|
||||||
filter: IDynamicListFilterDTO
|
filter: IDynamicListFilter
|
||||||
) {
|
) => {
|
||||||
const dynamicFilter = new DynamicFilter(model);
|
const dynamicFilter = new DynamicFilter(model);
|
||||||
|
|
||||||
// Custom view filter roles.
|
// Parses the filter object.
|
||||||
if (filter.customViewId) {
|
const parsedFilter = this.parseFilterObject(model, filter);
|
||||||
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);
|
|
||||||
|
|
||||||
const sortByFilter = new DynamicFilterSortBy(
|
// Custom view filter roles.
|
||||||
filter.columnSortBy,
|
// if (filter.customViewId) {
|
||||||
filter.sortOrder
|
// 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.
|
// Filter roles.
|
||||||
if (filter.filterRoles.length > 0) {
|
if (!isEmpty(parsedFilter.filterRoles)) {
|
||||||
const filterRoles = filter.filterRoles.map((filterRole, index) => ({
|
const dynamicFilterRoles = this.dynamicListFilterRoles.dynamicList(
|
||||||
...filterRole,
|
model,
|
||||||
index: index + 1,
|
parsedFilter.filterRoles
|
||||||
}));
|
);
|
||||||
this.validateFilterRolesSchema(filterRoles);
|
|
||||||
this.validateRolesFieldsExistance(model, filterRoles);
|
|
||||||
|
|
||||||
// Validate the model resource fields.
|
|
||||||
const dynamicFilterRoles = new DynamicFilterFilterRoles(filterRoles);
|
|
||||||
dynamicFilter.setFilter(dynamicFilterRoles);
|
dynamicFilter.setFilter(dynamicFilterRoles);
|
||||||
}
|
}
|
||||||
return dynamicFilter;
|
return dynamicFilter;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware to catch services errors
|
* Middleware to catch services errors
|
||||||
@@ -173,25 +100,62 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'sort_column_not_found') {
|
if (error.errorType === 'sort_column_not_found') {
|
||||||
return res.boom.badRequest(null, {
|
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') {
|
if (error.errorType === 'view_not_found') {
|
||||||
return res.boom.badRequest(null, {
|
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') {
|
if (error.errorType === 'filter_roles_fields_not_found') {
|
||||||
return res.boom.badRequest(null, {
|
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') {
|
if (error.errorType === 'stringified_filter_roles_invalid') {
|
||||||
return res.boom.badRequest(null, {
|
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);
|
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))
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
40
server/src/services/DynamicListing/DynamicListSortBy.ts
Normal file
40
server/src/services/DynamicListing/DynamicListSortBy.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
server/src/services/DynamicListing/constants.ts
Normal file
6
server/src/services/DynamicListing/constants.ts
Normal 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',
|
||||||
|
};
|
||||||
0
server/src/services/DynamicListing/validators.ts
Normal file
0
server/src/services/DynamicListing/validators.ts
Normal file
@@ -1,6 +1,7 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { difference, sumBy, omit, map } from 'lodash';
|
import { difference, sumBy, omit } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -630,6 +631,16 @@ export default class ExpensesService implements IExpensesService {
|
|||||||
return expenses.filter((expense) => expense.publishedAt);
|
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.
|
* Retrieve expenses datatable lsit.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -638,35 +649,41 @@ export default class ExpensesService implements IExpensesService {
|
|||||||
*/
|
*/
|
||||||
public async getExpensesList(
|
public async getExpensesList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
expensesFilter: IExpensesFilter
|
filterDTO: IExpensesFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
expenses: IExpense[];
|
expenses: IExpense[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { Expense } = this.tenancy.models(tenantId);
|
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,
|
tenantId,
|
||||||
Expense,
|
Expense,
|
||||||
expensesFilter
|
filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info('[expense] trying to get expenses datatable list.', {
|
this.logger.info('[expense] trying to get expenses datatable list.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
expensesFilter,
|
filter,
|
||||||
});
|
});
|
||||||
const { results, pagination } = await Expense.query()
|
const { results, pagination } = await Expense.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('paymentAccount');
|
builder.withGraphFetched('paymentAccount');
|
||||||
builder.withGraphFetched('categories.expenseAccount');
|
builder.withGraphFetched('categories.expenseAccount');
|
||||||
dynamicFilter.buildQuery()(builder);
|
|
||||||
|
dynamicList.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(expensesFilter.page - 1, expensesFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expenses: results,
|
expenses: results,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicFilter.getResponseMeta(),
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export default class JournalSheetService {
|
|||||||
fromRange: null,
|
fromRange: null,
|
||||||
toRange: null,
|
toRange: null,
|
||||||
accountsIds: [],
|
accountsIds: [],
|
||||||
transactionTypes: [],
|
|
||||||
numberFormat: {
|
numberFormat: {
|
||||||
noCents: false,
|
noCents: false,
|
||||||
divideOn1000: false,
|
divideOn1000: false,
|
||||||
@@ -107,6 +106,13 @@ export default class JournalSheetService {
|
|||||||
}
|
}
|
||||||
query.modify('filterDateRange', filter.fromDate, filter.toDate);
|
query.modify('filterDateRange', filter.fromDate, filter.toDate);
|
||||||
query.orderBy(['date', 'createdAt', 'indexGroup', 'index']);
|
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.
|
// Transform the transactions array to journal collection.
|
||||||
const transactionsJournal = Journal.fromTransactions(
|
const transactionsJournal = Journal.fromTransactions(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -225,8 +226,8 @@ export default class InventoryAdjustmentService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish the inventory adjustment transaction.
|
* Publish the inventory adjustment transaction.
|
||||||
* @param tenantId
|
* @param {number} tenantId
|
||||||
* @param inventoryAdjustmentId
|
* @param {number} inventoryAdjustmentId
|
||||||
*/
|
*/
|
||||||
async publishInventoryAdjustment(
|
async publishInventoryAdjustment(
|
||||||
tenantId: number,
|
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.
|
* Retrieve the inventory adjustments paginated list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -272,17 +284,21 @@ export default class InventoryAdjustmentService {
|
|||||||
*/
|
*/
|
||||||
async getInventoryAdjustments(
|
async getInventoryAdjustments(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
adjustmentsFilter: IInventoryAdjustmentsFilter
|
filterDTO: IInventoryAdjustmentsFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
inventoryAdjustments: IInventoryAdjustment[];
|
inventoryAdjustments: IInventoryAdjustment[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
}> {
|
}> {
|
||||||
const { InventoryAdjustment } = this.tenancy.models(tenantId);
|
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(
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
InventoryAdjustment,
|
InventoryAdjustment,
|
||||||
adjustmentsFilter
|
filter,
|
||||||
);
|
);
|
||||||
const { results, pagination } = await InventoryAdjustment.query()
|
const { results, pagination } = await InventoryAdjustment.query()
|
||||||
.onBuild((query) => {
|
.onBuild((query) => {
|
||||||
@@ -291,7 +307,7 @@ export default class InventoryAdjustmentService {
|
|||||||
|
|
||||||
dynamicFilter.buildQuery()(query);
|
dynamicFilter.buildQuery()(query);
|
||||||
})
|
})
|
||||||
.pagination(adjustmentsFilter.page - 1, adjustmentsFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inventoryAdjustments: results,
|
inventoryAdjustments: results,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from 'typedi';
|
import { Inject } from 'typedi';
|
||||||
import { difference } from 'lodash';
|
import { difference } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
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.
|
* Retrieve item categories list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -384,10 +397,15 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
|||||||
*/
|
*/
|
||||||
public async getItemCategoriesList(
|
public async getItemCategoriesList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: IItemCategoriesFilter,
|
filterDTO: IItemCategoriesFilter,
|
||||||
authorizedUser: ISystemUser
|
authorizedUser: ISystemUser
|
||||||
): Promise<{ itemCategories: IItemCategory[]; filterMeta: IFilterMeta }> {
|
): Promise<{ itemCategories: IItemCategory[]; filterMeta: IFilterMeta }> {
|
||||||
const { ItemCategory } = this.tenancy.models(tenantId);
|
const { ItemCategory } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Parses list filter DTO.
|
||||||
|
const filter = this.parsesListFilterDTO(filterDTO);
|
||||||
|
|
||||||
|
// Dynamic list service.
|
||||||
const dynamicList = await this.dynamicListService.dynamicList(
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
ItemCategory,
|
ItemCategory,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defaultTo, difference } from 'lodash';
|
import { defaultTo } from 'lodash';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -525,20 +526,37 @@ export default class ItemsService implements IItemsService {
|
|||||||
return this.transformItemToResponse(tenantId, item);
|
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.
|
* Retrieve items datatable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {IItemsFilter} itemsFilter
|
* @param {IItemsFilter} itemsFilter
|
||||||
*/
|
*/
|
||||||
public async itemsList(tenantId: number, itemsFilter: IItemsFilter) {
|
public async itemsList(tenantId: number, filterDTO: IItemsFilter) {
|
||||||
const { Item } = this.tenancy.models(tenantId);
|
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(
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
Item,
|
Item,
|
||||||
itemsFilter
|
filter
|
||||||
);
|
);
|
||||||
const { results: items, pagination } = await Item.query()
|
const { results: items, pagination } = await Item.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
|
builder.modify('inactiveMode', filter.inactiveMode);
|
||||||
|
|
||||||
builder.withGraphFetched('inventoryAccount');
|
builder.withGraphFetched('inventoryAccount');
|
||||||
builder.withGraphFetched('sellAccount');
|
builder.withGraphFetched('sellAccount');
|
||||||
builder.withGraphFetched('costAccount');
|
builder.withGraphFetched('costAccount');
|
||||||
@@ -546,7 +564,7 @@ export default class ItemsService implements IItemsService {
|
|||||||
|
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(itemsFilter.page - 1, itemsFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
const results = items.map((item) =>
|
const results = items.map((item) =>
|
||||||
this.transformItemToResponse(tenantId, item)
|
this.transformItemToResponse(tenantId, item)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { difference, sumBy, omit, map } from 'lodash';
|
import { difference, sumBy, omit, map } from 'lodash';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ServiceError, ServiceErrors } from 'exceptions';
|
import * as R from 'ramda';
|
||||||
|
import { ServiceError } from 'exceptions';
|
||||||
import {
|
import {
|
||||||
IManualJournalDTO,
|
IManualJournalDTO,
|
||||||
IManualJournalsService,
|
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.
|
* Retrieve manual journals datatable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId -
|
||||||
* @param {IManualJournalsFilter} filter
|
* @param {IManualJournalsFilter} filter -
|
||||||
*/
|
*/
|
||||||
public async getManualJournals(
|
public async getManualJournals(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
filter: IManualJournalsFilter
|
filterDTO: IManualJournalsFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
manualJournals: IManualJournal;
|
manualJournals: IManualJournal;
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
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,
|
tenantId,
|
||||||
ManualJournal,
|
ManualJournal,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info('[manual_journals] trying to get manual journals list.', {
|
this.logger.info('[manual_journals] trying to get manual journals list.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
filter,
|
filter,
|
||||||
});
|
});
|
||||||
const { results, pagination } = await ManualJournal.query()
|
const { results, pagination } = await ManualJournal.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
dynamicList.buildQuery()(builder);
|
dynamicService.buildQuery()(builder);
|
||||||
builder.withGraphFetched('entries.account');
|
builder.withGraphFetched('entries.account');
|
||||||
})
|
})
|
||||||
.pagination(filter.page - 1, filter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
@@ -802,7 +817,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
|||||||
return {
|
return {
|
||||||
manualJournals: results,
|
manualJournals: results,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicList.getResponseMeta(),
|
filterMeta: dynamicService.getResponseMeta(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { omit, sumBy, difference } from 'lodash';
|
import { sumBy, difference } from 'lodash';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -25,7 +26,7 @@ import TenancyService from 'services/Tenancy/TenancyService';
|
|||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import { entriesAmountDiff, formatDateFields } from 'utils';
|
import { entriesAmountDiff, formatDateFields } from 'utils';
|
||||||
import { ServiceError } from 'exceptions';
|
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 VendorsService from 'services/Contacts/VendorsService';
|
||||||
import { ERRORS } from './constants';
|
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.
|
* Retrieve bill payment paginted and filterable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -642,34 +649,40 @@ export default class BillPaymentsService implements IBillPaymentsService {
|
|||||||
*/
|
*/
|
||||||
public async listBillPayments(
|
public async listBillPayments(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
billPaymentsFilter: IBillPaymentsFilter
|
filterDTO: IBillPaymentsFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
billPayments: IBillPayment;
|
billPayments: IBillPayment;
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { BillPayment } = this.tenancy.models(tenantId);
|
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,
|
tenantId,
|
||||||
BillPayment,
|
BillPayment,
|
||||||
billPaymentsFilter
|
filter
|
||||||
);
|
);
|
||||||
this.logger.info('[bill_payment] try to get bill payments list.', {
|
this.logger.info('[bill_payment] try to get bill payments list.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { results, pagination } = await BillPayment.query()
|
const { results, pagination } = await BillPayment.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('vendor');
|
builder.withGraphFetched('vendor');
|
||||||
builder.withGraphFetched('paymentAccount');
|
builder.withGraphFetched('paymentAccount');
|
||||||
|
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicList.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(billPaymentsFilter.page - 1, billPaymentsFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
billPayments: results,
|
billPayments: results,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicFilter.getResponseMeta(),
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { omit, runInContext, sumBy } from 'lodash';
|
import { omit, runInContext, sumBy } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
import composeAsync from 'async/compose';
|
import composeAsync from 'async/compose';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
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.
|
* Retrieve bills data table list.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -528,28 +539,33 @@ export default class BillsService
|
|||||||
*/
|
*/
|
||||||
public async getBills(
|
public async getBills(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
billsFilter: IBillsFilter
|
filterDTO: IBillsFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
bills: IBill;
|
bills: IBill;
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { Bill } = this.tenancy.models(tenantId);
|
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(
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
Bill,
|
Bill,
|
||||||
billsFilter
|
filter,
|
||||||
);
|
);
|
||||||
this.logger.info('[bills] trying to get bills data table.', {
|
this.logger.info('[bills] trying to get bills data table.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
billsFilter,
|
filter,
|
||||||
});
|
});
|
||||||
const { results, pagination } = await Bill.query()
|
const { results, pagination } = await Bill.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('vendor');
|
builder.withGraphFetched('vendor');
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(billsFilter.page - 1, billsFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bills: results,
|
bills: results,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Service, Inject } from 'typedi';
|
|||||||
import { camelCase, upperFirst } from 'lodash';
|
import { camelCase, upperFirst } from 'lodash';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { buildFilter } from 'objection-filter';
|
import { buildFilter } from 'objection-filter';
|
||||||
import { IModel } from 'interfaces';
|
import { IModel, IModelMeta } from 'interfaces';
|
||||||
import {
|
import {
|
||||||
getModelFields,
|
getModelFields,
|
||||||
} from 'lib/ViewRolesBuilder'
|
} from 'lib/ViewRolesBuilder'
|
||||||
@@ -102,4 +102,18 @@ export default class ResourceService {
|
|||||||
|
|
||||||
return buildFilter(resourceModel).build(filter);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { omit, sumBy, difference } from 'lodash';
|
import { omit, sumBy, difference } from 'lodash';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -613,6 +614,16 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
|
|||||||
return saleInvoices;
|
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.
|
* Retrieve payment receives paginated and filterable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -620,33 +631,39 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
|
|||||||
*/
|
*/
|
||||||
public async listPaymentReceives(
|
public async listPaymentReceives(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceivesFilter: IPaymentReceivesFilter
|
filterDTO: IPaymentReceivesFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
paymentReceives: IPaymentReceive[];
|
paymentReceives: IPaymentReceive[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
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,
|
tenantId,
|
||||||
PaymentReceive,
|
PaymentReceive,
|
||||||
paymentReceivesFilter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
const { results, pagination } = await PaymentReceive.query()
|
const { results, pagination } = await PaymentReceive.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('customer');
|
builder.withGraphFetched('customer');
|
||||||
builder.withGraphFetched('depositAccount');
|
builder.withGraphFetched('depositAccount');
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicList.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(
|
.pagination(
|
||||||
paymentReceivesFilter.page - 1,
|
filter.page - 1,
|
||||||
paymentReceivesFilter.pageSize
|
filter.pageSize
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paymentReceives: results,
|
paymentReceives: results,
|
||||||
pagination,
|
pagination,
|
||||||
filterMeta: dynamicFilter.getResponseMeta(),
|
filterMeta: dynamicList.getResponseMeta(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { omit, sumBy } from 'lodash';
|
import { filter, omit, sumBy } from 'lodash';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
IEstimatesFilter,
|
IEstimatesFilter,
|
||||||
IFilterMeta,
|
IFilterMeta,
|
||||||
@@ -412,6 +413,16 @@ export default class SaleEstimateService implements ISalesEstimatesService{
|
|||||||
return estimate;
|
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.
|
* Retrieves estimates filterable and paginated list.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
@@ -419,17 +430,22 @@ export default class SaleEstimateService implements ISalesEstimatesService{
|
|||||||
*/
|
*/
|
||||||
public async estimatesList(
|
public async estimatesList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
estimatesFilter: IEstimatesFilter
|
filterDTO: IEstimatesFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
salesEstimates: ISaleEstimate[];
|
salesEstimates: ISaleEstimate[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Parses filter DTO.
|
||||||
|
const filter = this.parseListFilterDTO(filterDTO);
|
||||||
|
|
||||||
|
// Dynamic list service.
|
||||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
SaleEstimate,
|
SaleEstimate,
|
||||||
estimatesFilter
|
filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { results, pagination } = await SaleEstimate.query()
|
const { results, pagination } = await SaleEstimate.query()
|
||||||
@@ -438,7 +454,7 @@ export default class SaleEstimateService implements ISalesEstimatesService{
|
|||||||
builder.withGraphFetched('entries');
|
builder.withGraphFetched('entries');
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(estimatesFilter.page - 1, estimatesFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
salesEstimates: results,
|
salesEstimates: results,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Service, Inject } from 'typedi';
|
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 moment from 'moment';
|
||||||
import composeAsync from 'async/compose';
|
import composeAsync from 'async/compose';
|
||||||
import {
|
import {
|
||||||
@@ -647,6 +648,17 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
|||||||
return saleInvoice;
|
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.
|
* Retrieve sales invoices filterable and paginated list.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -655,22 +667,27 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
|||||||
*/
|
*/
|
||||||
public async salesInvoicesList(
|
public async salesInvoicesList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
salesInvoicesFilter: ISalesInvoicesFilter
|
filterDTO: ISalesInvoicesFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
salesInvoices: ISaleInvoice[];
|
salesInvoices: ISaleInvoice[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Parses stringified filter roles.
|
||||||
|
const filter = this.parseListFilterDTO(filterDTO);
|
||||||
|
|
||||||
|
// Dynamic list service.
|
||||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
SaleInvoice,
|
SaleInvoice,
|
||||||
salesInvoicesFilter
|
filter
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info('[sale_invoice] try to get sales invoices list.', {
|
this.logger.info('[sale_invoice] try to get sales invoices list.', {
|
||||||
tenantId,
|
tenantId,
|
||||||
salesInvoicesFilter,
|
filter,
|
||||||
});
|
});
|
||||||
const { results, pagination } = await SaleInvoice.query()
|
const { results, pagination } = await SaleInvoice.query()
|
||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
@@ -678,7 +695,7 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
|||||||
builder.withGraphFetched('customer');
|
builder.withGraphFetched('customer');
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(salesInvoicesFilter.page - 1, salesInvoicesFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
salesInvoices: results,
|
salesInvoices: results,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { omit, sumBy } from 'lodash';
|
import { omit, sumBy } from 'lodash';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import * as R from 'ramda';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -406,6 +407,16 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
|||||||
return saleReceipt;
|
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.
|
* Retrieve sales receipts paginated and filterable list.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -413,17 +424,22 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
|||||||
*/
|
*/
|
||||||
public async salesReceiptsList(
|
public async salesReceiptsList(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
salesReceiptsFilter: ISaleReceiptFilter
|
filterDTO: ISaleReceiptFilter
|
||||||
): Promise<{
|
): Promise<{
|
||||||
salesReceipts: ISaleReceipt[];
|
salesReceipts: ISaleReceipt[];
|
||||||
pagination: IPaginationMeta;
|
pagination: IPaginationMeta;
|
||||||
filterMeta: IFilterMeta;
|
filterMeta: IFilterMeta;
|
||||||
}> {
|
}> {
|
||||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
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(
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
tenantId,
|
tenantId,
|
||||||
SaleReceipt,
|
SaleReceipt,
|
||||||
salesReceiptsFilter
|
filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.info('[sale_receipt] try to get sales receipts list.', {
|
this.logger.info('[sale_receipt] try to get sales receipts list.', {
|
||||||
@@ -437,7 +453,7 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
|||||||
|
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(salesReceiptsFilter.page - 1, salesReceiptsFilter.pageSize);
|
.pagination(filter.page - 1, filter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
salesReceipts: results,
|
salesReceipts: results,
|
||||||
|
|||||||
Reference in New Issue
Block a user