mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital into feature/viewDetail
This commit is contained in:
@@ -29,11 +29,9 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
router.get(
|
||||
'/transactions',
|
||||
[
|
||||
query('account_id').optional().isInt().toInt(),
|
||||
],
|
||||
[query('account_id').optional().isInt().toInt()],
|
||||
this.asyncMiddleware(this.accountTransactions.bind(this)),
|
||||
this.catchServiceErrors,
|
||||
this.catchServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/activate',
|
||||
@@ -136,6 +134,8 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
query('column_sort_by').optional(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
|
||||
query('inactive_mode').optional().isBoolean().toBoolean(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -213,7 +213,9 @@ export default class AccountsController extends BaseController {
|
||||
tenantId,
|
||||
accountId
|
||||
);
|
||||
return res.status(200).send({ account: this.transfromToResponse(account) });
|
||||
return res
|
||||
.status(200)
|
||||
.send({ account: this.transfromToResponse(account) });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -256,7 +258,7 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
return res.status(200).send({
|
||||
id: accountId,
|
||||
message: 'The account has been activated successfully.'
|
||||
message: 'The account has been activated successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -291,22 +293,24 @@ export default class AccountsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @param {Response}
|
||||
*/
|
||||
async getAccountsList(req: Request, res: Response, next: NextFunction) {
|
||||
public async getAccountsList(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter: IAccountsFilter = {
|
||||
filterRoles: [],
|
||||
|
||||
// Filter query.
|
||||
const filter = {
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'name',
|
||||
inactiveMode: false,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
accounts,
|
||||
filterMeta,
|
||||
} = await this.accountsService.getAccountsList(tenantId, filter);
|
||||
const { accounts, filterMeta } =
|
||||
await this.accountsService.getAccountsList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
accounts: this.transfromToResponse(accounts, 'accountTypeLabel', req),
|
||||
@@ -343,9 +347,9 @@ export default class AccountsController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve accounts transactions list.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @returns {Response}
|
||||
*/
|
||||
async accountTransactions(req: Request, res: Response, next: NextFunction) {
|
||||
@@ -353,10 +357,11 @@ export default class AccountsController extends BaseController {
|
||||
const transactionsFilter = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const { transactions } = await this.accountsService.getAccountsTransactions(
|
||||
tenantId,
|
||||
transactionsFilter
|
||||
);
|
||||
const { transactions } =
|
||||
await this.accountsService.getAccountsTransactions(
|
||||
tenantId,
|
||||
transactionsFilter
|
||||
);
|
||||
return res.status(200).send({
|
||||
transactions: this.transfromToResponse(transactions),
|
||||
});
|
||||
@@ -372,7 +377,12 @@ export default class AccountsController extends BaseController {
|
||||
* @param {Response} res
|
||||
* @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.errorType === '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('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) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
inactiveMode: false,
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
|
||||
@@ -100,6 +100,8 @@ export default class VendorsController extends ContactsController {
|
||||
|
||||
query('page').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) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const vendorsFilter: IVendorsFilter = {
|
||||
filterRoles: [],
|
||||
inactiveMode: false,
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
|
||||
|
||||
@@ -290,16 +290,12 @@ export default class ExpensesController extends BaseController {
|
||||
async getExpensesList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const { expenses, pagination, filterMeta } =
|
||||
|
||||
@@ -17,6 +17,7 @@ import TransactionsByCustomers from './FinancialStatements/TransactionsByCustome
|
||||
import TransactionsByVendors from './FinancialStatements/TransactionsByVendors';
|
||||
import CashFlowStatementController from './FinancialStatements/CashFlow/CashFlow';
|
||||
import InventoryDetailsController from './FinancialStatements/InventoryDetails';
|
||||
import TransactionsByReferenceController from './FinancialStatements/TransactionsByReference';
|
||||
|
||||
@Service()
|
||||
export default class FinancialStatementsService {
|
||||
@@ -87,6 +88,10 @@ export default class FinancialStatementsService {
|
||||
'/inventory-item-details',
|
||||
Container.get(InventoryDetailsController).router(),
|
||||
);
|
||||
router.use(
|
||||
'/transactions-by-reference',
|
||||
Container.get(TransactionsByReferenceController).router(),
|
||||
)
|
||||
return router;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +32,8 @@ export default class JournalSheetController extends BaseFinancialReportControlle
|
||||
return [
|
||||
query('from_date').optional().isISO8601(),
|
||||
query('to_date').optional().isISO8601(),
|
||||
oneOf(
|
||||
[
|
||||
query('transaction_types').optional().isArray({ min: 1 }),
|
||||
query('transaction_types.*').optional().isNumeric().toInt(),
|
||||
],
|
||||
[query('transaction_types').optional().trim().escape()]
|
||||
),
|
||||
query('transaction_type').optional().trim().escape(),
|
||||
query('transaction_id').optional().isInt().toInt(),
|
||||
oneOf(
|
||||
[
|
||||
query('account_ids').optional().isArray({ min: 1 }),
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { query, ValidationChain } from 'express-validator';
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import TransactionsByReferenceService from 'services/FinancialStatements/TransactionsByReference';
|
||||
|
||||
@Service()
|
||||
export default class TransactionsByReferenceController extends BaseController {
|
||||
@Inject()
|
||||
private transactionsByReferenceService: TransactionsByReferenceService;
|
||||
|
||||
/**
|
||||
* Router constructor.
|
||||
*/
|
||||
router() {
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
this.validationSchema,
|
||||
this.validationResult,
|
||||
this.asyncMiddleware(this.transactionsByReference.bind(this))
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation schema.
|
||||
*/
|
||||
get validationSchema(): ValidationChain[] {
|
||||
return [
|
||||
query('reference_id').exists().isInt(),
|
||||
query('reference_type').exists().isString(),
|
||||
|
||||
query('number_format.precision')
|
||||
.optional()
|
||||
.isInt({ min: 0, max: 5 })
|
||||
.toInt(),
|
||||
query('number_format.divide_on_1000').optional().isBoolean().toBoolean(),
|
||||
query('number_format.negative_format')
|
||||
.optional()
|
||||
.isIn(['parentheses', 'mines'])
|
||||
.trim()
|
||||
.escape(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve transactions by the given reference type and id.
|
||||
* @param {Request} req - Request object.
|
||||
* @param {Response} res - Response.
|
||||
* @param {NextFunction} next
|
||||
* @returns
|
||||
*/
|
||||
public async transactionsByReference(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter = this.matchedQueryData(req);
|
||||
|
||||
try {
|
||||
const transactions =
|
||||
await this.transactionsByReferenceService.getTransactionsByReference(
|
||||
tenantId,
|
||||
filter
|
||||
);
|
||||
const accept = this.accepts(req);
|
||||
const acceptType = accept.types(['json']);
|
||||
|
||||
switch (acceptType) {
|
||||
case 'json':
|
||||
default:
|
||||
return res
|
||||
.status(200)
|
||||
.send(this.transformToJsonResponse(transactions));
|
||||
}
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
private transformToJsonResponse(transactions) {
|
||||
return transactions;
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,8 @@ export default class InventoryAdjustmentsController extends BaseController {
|
||||
|
||||
query('page').optional().isNumeric().toInt(),
|
||||
query('page_size').optional().isNumeric().toInt(),
|
||||
|
||||
query('stringified_filter_roles').optional().isJSON(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -199,8 +199,8 @@ export default class ItemsCategoriesController extends BaseController {
|
||||
*/
|
||||
async getList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId, user } = req;
|
||||
|
||||
const itemCategoriesFilter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
...this.matchedQueryData(req),
|
||||
|
||||
@@ -185,6 +185,8 @@ export default class ItemsController extends BaseController {
|
||||
|
||||
query('custom_view_id').optional().isNumeric().toInt(),
|
||||
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) {
|
||||
const { tenantId } = req;
|
||||
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
inactiveMode: false,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
items,
|
||||
|
||||
@@ -288,14 +288,10 @@ export default class ManualJournalsController extends BaseController {
|
||||
const filter = {
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
filterRoles: [],
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
try {
|
||||
const {
|
||||
manualJournals,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query } from 'express-validator';
|
||||
import * as R from 'ramda';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { IBillDTO, IBillEditDTO } from 'interfaces';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
@@ -300,14 +301,11 @@ export default class BillsController extends BaseController {
|
||||
const filter = {
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const { bills, pagination, filterMeta } =
|
||||
await this.billsService.getBills(tenantId, filter);
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
param,
|
||||
query,
|
||||
} from 'express-validator';
|
||||
import { param, query } from 'express-validator';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import BaseController from './BaseController';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import ResourceService from 'services/Resource/ResourceService';
|
||||
|
||||
@Service()
|
||||
export default class ResourceController extends BaseController{
|
||||
export default class ResourceController extends BaseController {
|
||||
@Inject()
|
||||
resourcesService: ResourceService;
|
||||
|
||||
@@ -21,42 +18,76 @@ export default class ResourceController extends BaseController{
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'/:resource_model/fields', [
|
||||
...this.resourceModelParamSchema,
|
||||
],
|
||||
'/:resource_model/meta',
|
||||
[...this.resourceModelParamSchema],
|
||||
this.asyncMiddleware(this.resourceMeta.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:resource_model/fields',
|
||||
[...this.resourceModelParamSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.resourceFields.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:resource_model/data', [
|
||||
...this.resourceModelParamSchema,
|
||||
],
|
||||
'/:resource_model/data',
|
||||
[...this.resourceModelParamSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.resourceData.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
)
|
||||
this.handleServiceErrors
|
||||
);
|
||||
return router;
|
||||
}
|
||||
|
||||
get resourceModelParamSchema() {
|
||||
return [
|
||||
param('resource_model').exists().trim().escape(),
|
||||
];
|
||||
return [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.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
resourceFields(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const { resource_model: resourceModel } = req.params;
|
||||
|
||||
try {
|
||||
const resourceFields = this.resourcesService.getResourceFields(tenantId, resourceModel);
|
||||
const resourceFields = this.resourcesService.getResourceFields(
|
||||
tenantId,
|
||||
resourceModel
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
resource_fields: this.transfromToResponse(resourceFields),
|
||||
@@ -68,9 +99,9 @@ export default class ResourceController extends BaseController{
|
||||
|
||||
/**
|
||||
* Retrieve resource data of the give resource based on the given query.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async resourceData(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
@@ -78,7 +109,11 @@ export default class ResourceController extends BaseController{
|
||||
const filter = req.query;
|
||||
|
||||
try {
|
||||
const resourceData = await this.resourcesService.getResourceData(tenantId, resourceModel, filter);
|
||||
const resourceData = await this.resourcesService.getResourceData(
|
||||
tenantId,
|
||||
resourceModel,
|
||||
filter
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
resource_data: this.transfromToResponse(resourceData),
|
||||
@@ -90,12 +125,17 @@ export default class ResourceController extends BaseController{
|
||||
|
||||
/**
|
||||
* Handles service errors.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @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.errorType === 'RESOURCE_MODEL_NOT_FOUND') {
|
||||
return res.status(400).send({
|
||||
@@ -105,4 +145,4 @@ export default class ResourceController extends BaseController{
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -261,16 +261,12 @@ export default class PaymentReceivesController extends BaseController {
|
||||
async getPaymentReceiveList(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { check, param, query, matchedData } from 'express-validator';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleEstimateDTO } from 'interfaces';
|
||||
import BaseController from 'api/controllers/BaseController'
|
||||
import BaseController from 'api/controllers/BaseController';
|
||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import { ServiceError } from "exceptions";
|
||||
import { ServiceError } from 'exceptions';
|
||||
|
||||
@Service()
|
||||
export default class SalesEstimatesController extends BaseController {
|
||||
@@ -23,63 +23,56 @@ export default class SalesEstimatesController extends BaseController {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/', [
|
||||
...this.estimateValidationSchema,
|
||||
],
|
||||
'/',
|
||||
[...this.estimateValidationSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.newEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/deliver',
|
||||
[
|
||||
...this.validateSpecificEstimateSchema,
|
||||
],
|
||||
[...this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deliverSaleEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/approve',
|
||||
[
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
[this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.approveSaleEstimate.bind(this)),
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id/reject',
|
||||
[
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
[this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.rejectSaleEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
)
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.post(
|
||||
'/:id', [
|
||||
'/:id',
|
||||
[
|
||||
...this.validateSpecificEstimateSchema,
|
||||
...this.estimateValidationSchema,
|
||||
],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.editEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.delete(
|
||||
'/:id', [
|
||||
this.validateSpecificEstimateSchema,
|
||||
],
|
||||
'/:id',
|
||||
[this.validateSpecificEstimateSchema],
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.deleteEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/:id',
|
||||
this.validateSpecificEstimateSchema,
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getEstimate.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.handleServiceErrors
|
||||
);
|
||||
router.get(
|
||||
'/',
|
||||
@@ -87,7 +80,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
this.validationResult,
|
||||
asyncMiddleware(this.getEstimates.bind(this)),
|
||||
this.handleServiceErrors,
|
||||
this.dynamicListService.handlerErrorsToResponse,
|
||||
this.dynamicListService.handlerErrorsToResponse
|
||||
);
|
||||
return router;
|
||||
}
|
||||
@@ -109,8 +102,14 @@ export default class SalesEstimatesController extends BaseController {
|
||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||
check('entries.*.description').optional({ nullable: true }).trim().escape(),
|
||||
check('entries.*.discount').optional({ nullable: true }).isNumeric().toFloat(),
|
||||
check('entries.*.description')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape(),
|
||||
check('entries.*.discount')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toFloat(),
|
||||
|
||||
check('note').optional().trim().escape(),
|
||||
check('terms_conditions').optional().trim().escape(),
|
||||
@@ -122,9 +121,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
* Specific sale estimate validation schema.
|
||||
*/
|
||||
get validateSpecificEstimateSchema() {
|
||||
return [
|
||||
param('id').exists().isNumeric().toInt(),
|
||||
];
|
||||
return [param('id').exists().isNumeric().toInt()];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,8 +134,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
query('column_sort_by').optional(),
|
||||
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||
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);
|
||||
|
||||
try {
|
||||
const storedEstimate = await this.saleEstimateService.createEstimate(tenantId, estimateDTO);
|
||||
const storedEstimate = await this.saleEstimateService.createEstimate(
|
||||
tenantId,
|
||||
estimateDTO
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
id: storedEstimate.id,
|
||||
@@ -165,8 +165,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Handle update estimate details with associated entries.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async editEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -175,12 +175,16 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
try {
|
||||
// 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({
|
||||
id: estimateId,
|
||||
message: 'The sale estimate has been created successfully.',
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -188,8 +192,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Deletes the given estimate with associated entries.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deleteEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -200,7 +204,7 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
return res.status(200).send({
|
||||
id: estimateId,
|
||||
message: 'The sale estimate has been deleted successfully.'
|
||||
message: 'The sale estimate has been deleted successfully.',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -209,8 +213,8 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Deliver the given sale estimate.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async deliverSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -230,9 +234,9 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Marks the sale estimate as approved.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async approveSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -252,9 +256,9 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Marks the sale estimate as rejected.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async rejectSaleEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
@@ -274,16 +278,19 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve the given estimate with associated entries.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async getEstimate(req: Request, res: Response, next: NextFunction) {
|
||||
const { id: estimateId } = req.params;
|
||||
const { tenantId } = req;
|
||||
|
||||
try {
|
||||
const estimate = await this.saleEstimateService.getEstimate(tenantId, estimateId);
|
||||
const estimate = await this.saleEstimateService.getEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
|
||||
return res.status(200).send({ estimate });
|
||||
} catch (error) {
|
||||
@@ -293,35 +300,28 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Retrieve estimates with pagination metadata.
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getEstimates(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
salesEstimates,
|
||||
pagination,
|
||||
filterMeta
|
||||
} = await this.saleEstimateService.estimatesList(tenantId, filter);
|
||||
const { salesEstimates, pagination, filterMeta } =
|
||||
await this.saleEstimateService.estimatesList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
sales_estimates: this.transfromToResponse(salesEstimates),
|
||||
pagination,
|
||||
filter_meta: this.transfromToResponse(filterMeta),
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -329,12 +329,17 @@ export default class SalesEstimatesController extends BaseController {
|
||||
|
||||
/**
|
||||
* Handles service errors.
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
* @param {Error} error
|
||||
* @param {Request} req
|
||||
* @param {Response} res
|
||||
* @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.errorType === 'ITEMS_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
@@ -409,4 +414,4 @@ export default class SalesEstimatesController extends BaseController {
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,23 +284,15 @@ export default class SaleInvoicesController extends BaseController {
|
||||
) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
salesInvoices,
|
||||
filterMeta,
|
||||
pagination,
|
||||
} = await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
||||
const { salesInvoices, filterMeta, pagination } =
|
||||
await this.saleInvoiceService.salesInvoicesList(tenantId, filter);
|
||||
|
||||
return res.status(200).send({
|
||||
sales_invoices: salesInvoices,
|
||||
|
||||
@@ -230,16 +230,12 @@ export default class SalesReceiptsController extends BaseController {
|
||||
async getSalesReceipts(req: Request, res: Response, next: NextFunction) {
|
||||
const { tenantId } = req;
|
||||
const filter = {
|
||||
filterRoles: [],
|
||||
sortOrder: 'asc',
|
||||
columnSortBy: 'created_at',
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
...this.matchedQueryData(req),
|
||||
};
|
||||
if (filter.stringifiedFilterRoles) {
|
||||
filter.filterRoles = JSON.parse(filter.stringifiedFilterRoles);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface IAccountResponse extends IAccount {
|
||||
|
||||
export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||
stringifiedFilterRoles?: string,
|
||||
onlyInactive: boolean;
|
||||
};
|
||||
|
||||
export interface IAccountType {
|
||||
|
||||
@@ -62,6 +62,8 @@ export interface IBill {
|
||||
|
||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||
stringifiedFilterRoles?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
export interface IBillsService {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { IModel, ISortOrder } from "./Model";
|
||||
|
||||
export interface IDynamicFilter {
|
||||
setTableName(tableName: string): void;
|
||||
setModel(model: IModel): void;
|
||||
buildQuery(): void;
|
||||
getResponseMeta();
|
||||
}
|
||||
|
||||
export interface IFilterRole {
|
||||
@@ -10,19 +13,19 @@ export interface IFilterRole {
|
||||
index?: number;
|
||||
comparator?: string;
|
||||
}
|
||||
|
||||
export interface IDynamicListFilterDTO {
|
||||
export interface IDynamicListFilter {
|
||||
customViewId?: number;
|
||||
filterRoles?: IFilterRole[];
|
||||
columnSortBy: string;
|
||||
columnSortBy: ISortOrder;
|
||||
sortOrder: string;
|
||||
stringifiedFilterRoles: string;
|
||||
}
|
||||
|
||||
export interface IDynamicListService {
|
||||
dynamicList(
|
||||
tenantId: number,
|
||||
model: any,
|
||||
filter: IDynamicListFilterDTO
|
||||
filter: IDynamicListFilter
|
||||
): Promise<any>;
|
||||
handlerErrorsToResponse(error, req, res, next): void;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface IItemsFilter extends IDynamicListFilterDTO {
|
||||
stringifiedFilterRoles?: string,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
inactiveMode: boolean,
|
||||
};
|
||||
|
||||
export interface IItemsAutoCompleteFilter {
|
||||
|
||||
@@ -7,7 +7,9 @@ export interface IJournalReportQuery {
|
||||
noCents: boolean,
|
||||
divideOn1000: boolean,
|
||||
},
|
||||
transactionTypes: string | string[],
|
||||
transactionType: string,
|
||||
transactionId: string,
|
||||
|
||||
accountsIds: number | number[],
|
||||
fromRange: number,
|
||||
toRange: number,
|
||||
|
||||
@@ -1,17 +1,81 @@
|
||||
|
||||
|
||||
export interface IModel {
|
||||
name: string,
|
||||
tableName: string,
|
||||
fields: { [key: string]: any, },
|
||||
};
|
||||
name: string;
|
||||
tableName: string;
|
||||
fields: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface IFilterMeta {
|
||||
sortOrder: string,
|
||||
sortBy: string,
|
||||
};
|
||||
sortOrder: string;
|
||||
sortBy: string;
|
||||
}
|
||||
|
||||
export interface IPaginationMeta {
|
||||
pageSize: number,
|
||||
page: number,
|
||||
};
|
||||
pageSize: 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 IModelMetaRelationFieldCommon {
|
||||
fieldType: 'relation';
|
||||
}
|
||||
|
||||
export interface IModelMetaRelationEnumerationField {
|
||||
relationType: 'enumeration';
|
||||
relationKey: string;
|
||||
relationEntityLabel: string;
|
||||
relationEntityKey: string;
|
||||
}
|
||||
|
||||
export type IModelMetaRelationField = IModelMetaRelationFieldCommon & (
|
||||
IModelMetaRelationEnumerationField
|
||||
);
|
||||
|
||||
export interface IModelMeta {
|
||||
defaultFilterField: string;
|
||||
defaultSort: IModelMetaDefaultSort;
|
||||
fields: { [key: string]: IModelMetaField };
|
||||
}
|
||||
|
||||
31
server/src/interfaces/TransactionsByReference.ts
Normal file
31
server/src/interfaces/TransactionsByReference.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
export interface ITransactionsByReferenceQuery {
|
||||
referenceType: string;
|
||||
referenceId: string;
|
||||
}
|
||||
|
||||
export interface ITransactionsByReferenceAmount {
|
||||
amount: number;
|
||||
formattedAmount: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ITransactionsByReferenceTransaction{
|
||||
credit: ITransactionsByReferenceAmount;
|
||||
debit: ITransactionsByReferenceAmount;
|
||||
|
||||
contactType: string;
|
||||
formattedContactType: string;
|
||||
|
||||
contactId: number;
|
||||
|
||||
referenceType: string;
|
||||
formattedReferenceType: string;
|
||||
|
||||
referenceId: number;
|
||||
|
||||
accountName: string;
|
||||
accountCode: string;
|
||||
accountId: number;
|
||||
}
|
||||
@@ -55,6 +55,7 @@ export * from './CashFlow';
|
||||
export * from './InventoryDetails';
|
||||
export * from './LandedCost';
|
||||
export * from './Entry';
|
||||
export * from './TransactionsByReference';
|
||||
|
||||
export interface I18nService {
|
||||
__: (input: string) => string;
|
||||
|
||||
@@ -1,65 +1,85 @@
|
||||
import { forEach, uniqBy } from 'lodash';
|
||||
import { buildFilterRolesJoins } from 'lib/ViewRolesBuilder';
|
||||
import { IModel } from 'interfaces';
|
||||
import DynamicFilterAbstructor from './DynamicFilterAbstructor';
|
||||
import { IDynamicFilter, IFilterRole, IModel } from 'interfaces';
|
||||
|
||||
export default class DynamicFilter {
|
||||
model: IModel;
|
||||
tableName: string;
|
||||
export default class DynamicFilter extends DynamicFilterAbstructor{
|
||||
private model: IModel;
|
||||
private tableName: string;
|
||||
private dynamicFilters: IDynamicFilter[];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param {String} tableName -
|
||||
*/
|
||||
constructor(model) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
this.tableName = model.tableName;
|
||||
this.filters = [];
|
||||
this.dynamicFilters = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter.
|
||||
* @param {*} filterRole - Filter role.
|
||||
* Registers the given dynamic filter.
|
||||
* @param {IDynamicFilter} filterRole - Filter role.
|
||||
*/
|
||||
setFilter(filterRole) {
|
||||
filterRole.setModel(this.model);
|
||||
this.filters.push(filterRole);
|
||||
public setFilter = (dynamicFilter: IDynamicFilter) => {
|
||||
dynamicFilter.setModel(this.model);
|
||||
|
||||
dynamicFilter.onInitialize();
|
||||
|
||||
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.
|
||||
*/
|
||||
buildQuery() {
|
||||
const buildersCallbacks = [];
|
||||
const tableColumns = [];
|
||||
|
||||
this.filters.forEach((filter) => {
|
||||
const { filterRoles } = filter;
|
||||
|
||||
buildersCallbacks.push(filter.buildQuery());
|
||||
tableColumns.push(
|
||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
||||
);
|
||||
});
|
||||
public buildQuery = () => {
|
||||
const buildersCallbacks = this.dynamicFiltersBuildQuery();
|
||||
const tableColumns = this.dynamicFilterTableColumns();
|
||||
|
||||
return (builder) => {
|
||||
buildersCallbacks.forEach((builderCallback) => {
|
||||
builderCallback(builder);
|
||||
});
|
||||
|
||||
buildFilterRolesJoins(
|
||||
this.model,
|
||||
uniqBy(tableColumns, 'columnKey')
|
||||
)(builder);
|
||||
this.buildFilterRolesJoins(builder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve response metadata from all filters adapters.
|
||||
*/
|
||||
getResponseMeta() {
|
||||
public getResponseMeta = () => {
|
||||
const responseMeta = {};
|
||||
|
||||
this.filters.forEach((filter) => {
|
||||
this.dynamicFilters.forEach((filter) => {
|
||||
const { responseMeta: filterMeta } = filter;
|
||||
|
||||
forEach(filterMeta, (value, key) => {
|
||||
|
||||
40
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
40
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { IModel, IFilterRole } from 'interfaces';
|
||||
import { FIELD_TYPE } from './constants';
|
||||
|
||||
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 = (builder) => {
|
||||
this.dynamicFilters.forEach((dynamicFilter) => {
|
||||
const relationsFields = dynamicFilter.relationFields;
|
||||
|
||||
this.buildFieldsJoinQueries(builder, relationsFields);
|
||||
});
|
||||
};
|
||||
|
||||
private buildFieldsJoinQueries = (builder, fieldsRelations: string[]) => {
|
||||
fieldsRelations.forEach((fieldRelation) => {
|
||||
const relation = this.model.relationMappings[fieldRelation];
|
||||
|
||||
if (relation) {
|
||||
const splitToRelation = relation.join.to.split('.');
|
||||
const relationTable = splitToRelation[0] || '';
|
||||
|
||||
builder.join(relationTable, relation.join.from, '=', relation.join.to);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { difference } from 'lodash';
|
||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
||||
import DynamicFilterRoleAbstructor from './DynamicFilterRoleAbstructor';
|
||||
import { IFilterRole } from 'interfaces';
|
||||
|
||||
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
filterRoles: IFilterRole[];
|
||||
private filterRoles: IFilterRole[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -13,18 +11,23 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
*/
|
||||
constructor(filterRoles: IFilterRole[]) {
|
||||
super();
|
||||
|
||||
|
||||
this.filterRoles = filterRoles;
|
||||
|
||||
this.setResponseMeta();
|
||||
}
|
||||
|
||||
public onInitialize() {
|
||||
this.setFilterRolesRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filter roles logic expression.
|
||||
* @return {string}
|
||||
*/
|
||||
private buildLogicExpression(): string {
|
||||
let expression = '';
|
||||
|
||||
|
||||
this.filterRoles.forEach((role, index) => {
|
||||
expression +=
|
||||
index === 0 ? `${role.index} ` : `${role.condition} ${role.index} `;
|
||||
@@ -35,19 +38,33 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
/**
|
||||
* Builds database query of view roles.
|
||||
*/
|
||||
buildQuery() {
|
||||
protected buildQuery() {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
|
||||
return (builder) => {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
buildFilterQuery(this.model, this.filterRoles, logicExpression)(builder);
|
||||
this.buildFilterQuery(
|
||||
this.model,
|
||||
this.filterRoles,
|
||||
logicExpression
|
||||
)(builder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
setResponseMeta() {
|
||||
private setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
filterRoles: this.filterRoles,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filter roles relations if field was relation type.
|
||||
*/
|
||||
private setFilterRolesRelations() {
|
||||
this.filterRoles.forEach((relationRole) => {
|
||||
this.setRelationIfRelationField(relationRole.fieldKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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,349 @@
|
||||
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 {
|
||||
filterRoles: IFilterRole[] = [];
|
||||
tableName: string;
|
||||
model: IModel;
|
||||
responseMeta: { [key: string]: any } = {};
|
||||
export default abstract class DynamicFilterAbstructor
|
||||
implements IDynamicFilter
|
||||
{
|
||||
protected filterRoles: IFilterRole[] = [];
|
||||
protected tableName: string;
|
||||
protected model: IModel;
|
||||
protected responseMeta: { [key: string]: any } = {};
|
||||
public relationFields = [];
|
||||
|
||||
setModel(model: IModel) {
|
||||
/**
|
||||
* Sets model the dynamic filter service.
|
||||
* @param {IModel} model
|
||||
*/
|
||||
public setModel(model: IModel) {
|
||||
this.model = model;
|
||||
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);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve relation column of comparator fieldز
|
||||
*/
|
||||
private getFieldComparatorRelationColumn(field) {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
const relationModel = relation.modelClass;
|
||||
const relationColumn =
|
||||
field.relationEntityKey === 'id'
|
||||
? 'id'
|
||||
: relationModel.getField(field.relationEntityKey, 'column');
|
||||
|
||||
return `${relationModel.tableName}.${relationColumn}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the comparator field column.
|
||||
* @param {IModel} model -
|
||||
* @param {} -
|
||||
*/
|
||||
private getFieldComparatorColumn = (field) => {
|
||||
return field.fieldType === FIELD_TYPE.RELATION
|
||||
? this.getFieldComparatorRelationColumn(field)
|
||||
: `${this.tableName}.${field.column}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds roles queries.
|
||||
* @param {IModel} model -
|
||||
* @param {Object} role -
|
||||
*/
|
||||
protected buildRoleQuery = (model: IModel, role: IFilterRole) => {
|
||||
const field = model.getField(role.fieldKey);
|
||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||
|
||||
// Field relation custom query.
|
||||
if (typeof field.filterCustomQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
field.filterCustomQuery(builder, role);
|
||||
};
|
||||
}
|
||||
switch (field.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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers relation field if the given field was relation type
|
||||
* and not registered.
|
||||
* @param {string} fieldKey - Field key.
|
||||
*/
|
||||
protected setRelationIfRelationField = (fieldKey: string): void => {
|
||||
const field = this.model.getField(fieldKey);
|
||||
const isAlreadyRegistered = this.relationFields.some(
|
||||
(field) => field === fieldKey
|
||||
);
|
||||
|
||||
if (
|
||||
!isAlreadyRegistered &&
|
||||
field &&
|
||||
field.fieldType === FIELD_TYPE.RELATION
|
||||
) {
|
||||
this.relationFields.push(field.relationKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||
import {
|
||||
getRoleFieldColumn,
|
||||
validateFieldKeyExistance,
|
||||
getTableFromRelationColumn,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
import { FIELD_TYPE } from './constants';
|
||||
|
||||
interface ISortRole {
|
||||
fieldKey: string;
|
||||
order: string;
|
||||
}
|
||||
|
||||
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||
sortRole: { fieldKey: string; order: string } = {};
|
||||
private sortRole: ISortRole = {};
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -24,58 +25,65 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given field key with the model.
|
||||
* On initialize the dyanmic sort by.
|
||||
*/
|
||||
validate() {
|
||||
validateFieldKeyExistance(this.model, this.sortRole.fieldKey);
|
||||
public onInitialize() {
|
||||
this.setRelationIfRelationField(this.sortRole.fieldKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve field comparator relatin column.
|
||||
* @param field
|
||||
* @returns {string}
|
||||
*/
|
||||
private getFieldComparatorRelationColumn = (field): string => {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
const relationModel = relation.modelClass;
|
||||
const relationField = relationModel.getField(field.relationEntityLabel);
|
||||
|
||||
return `${relationModel.tableName}.${relationField.column}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the comparator field column.
|
||||
* @param {IModel} field
|
||||
* @returns {string}
|
||||
*/
|
||||
private getFieldComparatorColumn = (field): string => {
|
||||
return field.fieldType === FIELD_TYPE.RELATION
|
||||
? this.getFieldComparatorRelationColumn(field)
|
||||
: `${this.tableName}.${field.column}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds database query of sort by column on the given direction.
|
||||
*/
|
||||
buildQuery() {
|
||||
const fieldRelation = getRoleFieldColumn(
|
||||
this.model,
|
||||
this.sortRole.fieldKey
|
||||
);
|
||||
const comparatorColumn =
|
||||
fieldRelation.relationColumn ||
|
||||
`${this.tableName}.${fieldRelation.column}`;
|
||||
public buildQuery = () => {
|
||||
const field = this.model.getField(this.sortRole.fieldKey);
|
||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||
|
||||
if (typeof fieldRelation.sortQuery !== 'undefined') {
|
||||
// Sort custom query.
|
||||
if (typeof field.sortCustomQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
fieldRelation.sortQuery(builder, this.sortRole);
|
||||
field.sortCustomQuery(builder, this.sortRole);
|
||||
};
|
||||
}
|
||||
|
||||
return (builder) => {
|
||||
if (this.sortRole.fieldKey) {
|
||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
||||
}
|
||||
this.joinBuildQuery()(builder);
|
||||
};
|
||||
}
|
||||
|
||||
joinBuildQuery() {
|
||||
const fieldColumn = getRoleFieldColumn(this.model, this.sortRole.fieldKey);
|
||||
|
||||
return (builder) => {
|
||||
if (fieldColumn.relation) {
|
||||
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
||||
|
||||
builder.join(
|
||||
joinTable,
|
||||
`${this.model.tableName}.${fieldColumn.column}`,
|
||||
'=',
|
||||
fieldColumn.relation
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
setResponseMeta() {
|
||||
public setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
sortOrder: this.sortRole.fieldKey,
|
||||
sortBy: this.sortRole.order,
|
||||
|
||||
37
server/src/lib/DynamicFilter/constants.ts
Normal file
37
server/src/lib/DynamicFilter/constants.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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',
|
||||
COMPUTED: 'computed'
|
||||
};
|
||||
@@ -1,121 +1,7 @@
|
||||
import { difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Lexer } from 'lib/LogicEvaluation/Lexer';
|
||||
import Parser from 'lib/LogicEvaluation/Parser';
|
||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||
|
||||
import { IFilterRole, IModel } from 'interfaces';
|
||||
|
||||
const numberRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case 'equals':
|
||||
case 'equal':
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case 'not_equals':
|
||||
case 'not_equal':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case 'bigger_than':
|
||||
case 'bigger':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>', role.value);
|
||||
};
|
||||
case 'bigger_or_equals':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>=', role.value);
|
||||
};
|
||||
case 'smaller_than':
|
||||
case 'smaller':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<', role.value);
|
||||
};
|
||||
case 'smaller_or_equals':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<=', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||
switch (role.comparator) {
|
||||
case 'equals':
|
||||
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.
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return (builder) => {
|
||||
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.
|
||||
* @param {Array} viewRoles -
|
||||
@@ -316,14 +96,6 @@ export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
||||
return model?.fields?.[fieldKey] || false;
|
||||
}
|
||||
|
||||
export function validateFilterRolesFieldsExistance(
|
||||
model,
|
||||
filterRoles: IFilterRole[]
|
||||
) {
|
||||
return filterRoles.filter((filterRole: IFilterRole) => {
|
||||
return !validateFieldKeyExistance(model, filterRole.fieldKey);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve model fields keys.
|
||||
|
||||
100
server/src/models/Account.Settings.ts
Normal file
100
server/src/models/Account.Settings.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { IModelMeta } from 'interfaces';
|
||||
import { ACCOUNT_TYPES } from 'data/AccountTypes';
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'name',
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
name: 'Account name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
slug: {
|
||||
name: 'Account slug',
|
||||
column: 'slug',
|
||||
fieldType: 'text',
|
||||
columnable: false,
|
||||
},
|
||||
code: {
|
||||
name: 'Account code',
|
||||
column: 'code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
root_type: {
|
||||
name: 'Root type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'asset', label: 'Asset' },
|
||||
{ key: 'liability', label: 'Liability' },
|
||||
{ key: 'equity', label: 'Equity' },
|
||||
{ key: 'Income', label: 'Income' },
|
||||
{ key: 'expense', label: 'Expense' },
|
||||
],
|
||||
filterCustomQuery: RootTypeFieldFilterQuery,
|
||||
sortable: false,
|
||||
},
|
||||
normal: {
|
||||
name: 'Account normal',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'debit', label: 'Debit' },
|
||||
{ key: 'credit', label: 'Credit' },
|
||||
],
|
||||
filterCustomQuery: NormalTypeFieldFilterQuery,
|
||||
sortable: false,
|
||||
},
|
||||
type: {
|
||||
name: 'Type',
|
||||
column: 'account_type',
|
||||
fieldType: 'enumeration',
|
||||
options: ACCOUNT_TYPES.map((accountType) => ({
|
||||
label: accountType.label,
|
||||
key: accountType.key
|
||||
})),
|
||||
},
|
||||
active: {
|
||||
name: 'Active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
filterable: false,
|
||||
},
|
||||
balance: {
|
||||
name: 'Account balance',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
currency: {
|
||||
name: 'Currency',
|
||||
column: 'currency_code',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter query of root type field .
|
||||
*/
|
||||
function RootTypeFieldFilterQuery(query, role) {
|
||||
query.modify('filterByRootType', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query of normal field .
|
||||
*/
|
||||
function NormalTypeFieldFilterQuery(query, role) {
|
||||
query.modify('filterByAccountNormal', role.value);
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
/* eslint-disable global-require */
|
||||
import { Model } from 'objection';
|
||||
import { flatten, castArray } from 'lodash';
|
||||
import { mixin, Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import {
|
||||
buildFilterQuery,
|
||||
buildSortColumnQuery,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
import { buildFilterQuery, buildSortColumnQuery } from 'lib/ViewRolesBuilder';
|
||||
import { flatToNestedArray } from 'utils';
|
||||
import DependencyGraph from 'lib/DependencyGraph';
|
||||
import AccountTypesUtils from 'lib/AccountTypes'
|
||||
import AccountTypesUtils from 'lib/AccountTypes';
|
||||
import AccountSettings from './Account.Settings';
|
||||
import ModelSettings from './ModelSetting';
|
||||
import { ACCOUNT_TYPES } from 'data/AccountTypes';
|
||||
|
||||
export default class Account extends TenantModel {
|
||||
export default class Account extends mixin(TenantModel, [ModelSettings]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ export default class Account extends TenantModel {
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default class Account extends TenantModel {
|
||||
'accountRootType',
|
||||
'accountNormal',
|
||||
'isBalanceSheetAccount',
|
||||
'isPLSheet'
|
||||
'isPLSheet',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -95,6 +95,13 @@ export default class Account extends TenantModel {
|
||||
const TABLE_NAME = Account.tableName;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('accounts.active', !active);
|
||||
},
|
||||
|
||||
filterAccounts(query, accountIds) {
|
||||
if (accountIds.length > 0) {
|
||||
query.whereIn(`${TABLE_NAME}.id`, accountIds);
|
||||
@@ -111,6 +118,28 @@ export default class Account extends TenantModel {
|
||||
sortColumnBuilder(query, columnKey, direction) {
|
||||
buildSortColumnQuery(Account.tableName, columnKey, direction)(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by root type.
|
||||
*/
|
||||
filterByRootType(query, rootType) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.rootType === rootType
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filter by account normal
|
||||
*/
|
||||
filterByAccountNormal(query, accountNormal) {
|
||||
const filterTypes = ACCOUNT_TYPES.filter(
|
||||
(accountType) => accountType.normal === accountNormal,
|
||||
).map((accountType) => accountType.key);
|
||||
|
||||
query.whereIn('account_type', filterTypes);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,10 +163,10 @@ export default class Account extends TenantModel {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detarmines whether the given type equals the account type.
|
||||
* @param {string} accountType
|
||||
* @param {string} accountType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAccountType(accountType) {
|
||||
@@ -147,7 +176,7 @@ export default class Account extends TenantModel {
|
||||
|
||||
/**
|
||||
* Detarmines whether the given root type equals the account type.
|
||||
* @param {string} rootType
|
||||
* @param {string} rootType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isRootType(rootType) {
|
||||
@@ -156,11 +185,14 @@ export default class Account extends TenantModel {
|
||||
|
||||
/**
|
||||
* Detarmine whether the given parent type equals the account type.
|
||||
* @param {string} parentType
|
||||
* @param {string} parentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isParentType(parentType) {
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(this.accountType, parentType);
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||
this.accountType,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,105 +220,32 @@ export default class Account extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts flatten accounts list to nested array.
|
||||
* @param {Array} accounts
|
||||
* @param {Object} options
|
||||
* Converts flatten accounts list to nested array.
|
||||
* @param {Array} accounts
|
||||
* @param {Object} options
|
||||
*/
|
||||
static toNestedArray(accounts, options = { children: 'children' }) {
|
||||
return flatToNestedArray(accounts, { id: 'id', parentId: 'parentAccountId' })
|
||||
return flatToNestedArray(accounts, {
|
||||
id: 'id',
|
||||
parentId: 'parentAccountId',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes the accounts list to depenedency graph structure.
|
||||
* @param {IAccount[]} accounts
|
||||
*/
|
||||
* @param {IAccount[]} accounts
|
||||
*/
|
||||
static toDependencyGraph(accounts) {
|
||||
return DependencyGraph.fromArray(
|
||||
accounts, { itemId: 'id', parentItemId: 'parentAccountId' }
|
||||
);
|
||||
return DependencyGraph.fromArray(accounts, {
|
||||
itemId: 'id',
|
||||
parentItemId: 'parentAccountId',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
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' },
|
||||
],
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return AccountSettings;
|
||||
}
|
||||
}
|
||||
|
||||
96
server/src/models/Bill.Settings.ts
Normal file
96
server/src/models/Bill.Settings.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
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',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'vendor',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: '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' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
amount: {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
name: 'Payment amount',
|
||||
column: 'payment_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
note: {
|
||||
name: 'Note',
|
||||
column: 'note',
|
||||
fieldType: 'text',
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Status field filter custom query.
|
||||
*/
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('statusFilter', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status field sort custom query.
|
||||
*/
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import { Model, raw, mixin } from 'objection';
|
||||
import moment from 'moment';
|
||||
import { difference } from 'lodash';
|
||||
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
|
||||
*/
|
||||
@@ -12,10 +13,9 @@ export default class Bill extends TenantModel {
|
||||
return 'bills';
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
@@ -71,15 +71,42 @@ export default class Bill extends TenantModel {
|
||||
* Filters the bills from the given date.
|
||||
*/
|
||||
fromDate(query, fromDate) {
|
||||
query.where('bill_date', '<=', fromDate)
|
||||
query.where('bill_date', '<=', fromDate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort the bills by full-payment bills.
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`PAYMENT_AMOUNT = AMOUNT ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
statusFilter(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
default:
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,7 +130,7 @@ export default class Bill extends TenantModel {
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
'isOverdue',
|
||||
'unallocatedCostAmount'
|
||||
'unallocatedCostAmount',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -198,6 +225,13 @@ export default class Bill extends TenantModel {
|
||||
return Math.max(date.diff(dueDate, 'days'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bill model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BillSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
@@ -269,88 +303,4 @@ export default class Bill extends TenantModel {
|
||||
.where('id', billId)
|
||||
[changeMethod]('payment_amount', Math.abs(amount));
|
||||
}
|
||||
|
||||
static get fields() {
|
||||
return {
|
||||
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) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'opened':
|
||||
query.modify('opened');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
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',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
67
server/src/models/BillPayment.Settings.ts
Normal file
67
server/src/models/BillPayment.Settings.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
export default {
|
||||
defaultFilterField: 'vendor',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'bill_date',
|
||||
},
|
||||
fields: {
|
||||
'vendor': {
|
||||
name: 'Vendor name',
|
||||
column: 'vendor_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'vendor',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
'amount': {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'due_amount': {
|
||||
name: 'Due amount',
|
||||
column: 'due_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'payment_account': {
|
||||
name: 'Payment account',
|
||||
column: 'payment_account_id',
|
||||
|
||||
fieldType: 'relation',
|
||||
fieldRelation: 'paymentAccount',
|
||||
|
||||
fieldRelationType: 'enumeration',
|
||||
relationLabelField: 'name',
|
||||
relationKeyField: 'slug',
|
||||
},
|
||||
'payment_number': {
|
||||
name: 'Payment number',
|
||||
column: 'payment_number',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'payment_date': {
|
||||
name: 'Payment date',
|
||||
column: 'payment_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'description': {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model } from "objection";
|
||||
import { Model, mixin } from "objection";
|
||||
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
|
||||
*/
|
||||
@@ -16,8 +18,11 @@ export default class BillPayment extends TenantModel {
|
||||
return ["createdAt", "updatedAt"];
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
/**
|
||||
* Model settings.
|
||||
*/
|
||||
static get meta() {
|
||||
return BillPaymentSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,7 +31,7 @@ export default class BillPayment extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const BillPaymentEntry = require("models/BillPaymentEntry");
|
||||
const AccountTransaction = require("models/AccountTransaction");
|
||||
const Contact = require("models/Contact");
|
||||
const Vendor = require("models/Vendor");
|
||||
const Account = require("models/Account");
|
||||
|
||||
return {
|
||||
@@ -41,7 +46,7 @@ export default class BillPayment extends TenantModel {
|
||||
|
||||
vendor: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
modelClass: Vendor.default,
|
||||
join: {
|
||||
from: "bills_payments.vendorId",
|
||||
to: "contacts.id",
|
||||
@@ -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 PaginationQueryBuilder from './Pagination';
|
||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import CustomerSettings from './Customer.Settings';
|
||||
|
||||
class CustomerQueryBuilder extends PaginationQueryBuilder {
|
||||
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.
|
||||
*/
|
||||
@@ -63,6 +65,13 @@ export default class Customer extends TenantModel {
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('active', !active);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the active customers.
|
||||
*/
|
||||
@@ -81,10 +90,9 @@ export default class Customer extends TenantModel {
|
||||
overdue(query) {
|
||||
query.select(
|
||||
'*',
|
||||
Customer
|
||||
.relatedQuery('overDueInvoices', query.knex())
|
||||
Customer.relatedQuery('overDueInvoices', query.knex())
|
||||
.count()
|
||||
.as('countOverdue'),
|
||||
.as('countOverdue')
|
||||
);
|
||||
query.having('countOverdue', '>', 0);
|
||||
},
|
||||
@@ -93,7 +101,7 @@ export default class Customer extends TenantModel {
|
||||
*/
|
||||
unpaid(query) {
|
||||
query.whereRaw('`BALANCE` + `OPENING_BALANCE` <> 0');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,77 +130,12 @@ export default class Customer extends TenantModel {
|
||||
},
|
||||
filter: (query) => {
|
||||
query.modify('overdue');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get fields() {
|
||||
return {
|
||||
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',
|
||||
}
|
||||
};
|
||||
static get meta() {
|
||||
return CustomerSettings;
|
||||
}
|
||||
}
|
||||
|
||||
71
server/src/models/Expense.Settings.ts
Normal file
71
server/src/models/Expense.Settings.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'paymentAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'amount': {
|
||||
name: 'Amount',
|
||||
column: 'total_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'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' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
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
|
||||
*/
|
||||
@@ -25,19 +27,8 @@ export default class Expense extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static get media() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static get virtualAttributes() {
|
||||
return ['isPublished', 'unallocatedCostAmount'];
|
||||
}
|
||||
@@ -96,6 +87,18 @@ export default class Expense extends TenantModel {
|
||||
filterByPublished(query) {
|
||||
query.whereNot('published_at', null);
|
||||
},
|
||||
|
||||
filterByStatus(query, status) {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
query.modify('filterByDraft');
|
||||
break;
|
||||
case 'published':
|
||||
default:
|
||||
query.modify('filterByPublished');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -142,71 +145,7 @@ export default class Expense extends TenantModel {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
*/
|
||||
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',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return ExpenseSettings;
|
||||
}
|
||||
}
|
||||
|
||||
59
server/src/models/InventoryAdjustment.Settings.ts
Normal file
59
server/src/models/InventoryAdjustment.Settings.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
defaultFilterField: 'date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'date',
|
||||
},
|
||||
fields: {
|
||||
'date': {
|
||||
name: 'Date',
|
||||
column: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'type': {
|
||||
name: 'Adjustment type',
|
||||
column: 'type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'increment', name: 'Increment' },
|
||||
{ key: 'decrement', name: 'Decrement' },
|
||||
],
|
||||
},
|
||||
'adjustment_account': {
|
||||
name: 'Adjustment account',
|
||||
column: 'adjustment_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'adjustmentAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'reason': {
|
||||
name: 'Reason',
|
||||
column: 'reason',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'description': {
|
||||
name: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'published_at': {
|
||||
name: 'Published at',
|
||||
column: 'published_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import InventoryAdjustmentSettings from './InventoryAdjustment.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class InventoryAdjustment extends TenantModel {
|
||||
export default class InventoryAdjustment extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -40,8 +42,8 @@ export default class InventoryAdjustment extends TenantModel {
|
||||
|
||||
static getInventoryDirection(type) {
|
||||
const directions = {
|
||||
'increment': 'IN',
|
||||
'decrement': 'OUT',
|
||||
increment: 'IN',
|
||||
decrement: 'OUT',
|
||||
};
|
||||
return directions[type] || '';
|
||||
}
|
||||
@@ -81,52 +83,9 @@ export default class InventoryAdjustment extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
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',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return InventoryAdjustmentSettings;
|
||||
}
|
||||
}
|
||||
|
||||
123
server/src/models/Item.Settings.ts
Normal file
123
server/src/models/Item.Settings.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
fields: {
|
||||
'type': {
|
||||
name: 'Item type',
|
||||
column: 'type',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'inventory', label: 'Inventory', },
|
||||
{ key: 'service', label: 'Service' },
|
||||
{ key: 'non-inventory', label: 'Non Inventory', },
|
||||
],
|
||||
},
|
||||
'name': {
|
||||
name: 'Name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'code': {
|
||||
name: 'Code',
|
||||
column: 'code',
|
||||
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',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'costAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'sell_account': {
|
||||
name: 'Sell account',
|
||||
column: 'sell_account_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'sellAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'inventory_account': {
|
||||
name: 'Inventory account',
|
||||
column: 'inventory_account_id',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'inventoryAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'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',
|
||||
},
|
||||
'category': {
|
||||
name: 'Category',
|
||||
column: 'category_id',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'category',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
'active': {
|
||||
name: 'Active',
|
||||
column: 'active',
|
||||
fieldType: 'boolean',
|
||||
filterable: false,
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,20 +1,22 @@
|
||||
import { Model } from "objection";
|
||||
import TenantModel from "models/TenantModel";
|
||||
import { buildFilterQuery } from "lib/ViewRolesBuilder";
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
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
|
||||
*/
|
||||
static get tableName() {
|
||||
return "items";
|
||||
return 'items';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ["createdAt", "updatedAt"];
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,13 @@ export default class Item extends TenantModel {
|
||||
viewRolesBuilder(query, conditions, logicExpression) {
|
||||
buildFilterQuery(Item.tableName, conditions, logicExpression)(query);
|
||||
},
|
||||
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('items.active', !active);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,9 +51,9 @@ export default class Item extends TenantModel {
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Media = require("models/Media");
|
||||
const Account = require("models/Account");
|
||||
const ItemCategory = require("models/ItemCategory");
|
||||
const Media = require('models/Media');
|
||||
const Account = require('models/Account');
|
||||
const ItemCategory = require('models/ItemCategory');
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -54,8 +63,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemCategory.default,
|
||||
join: {
|
||||
from: "items.categoryId",
|
||||
to: "items_categories.id",
|
||||
from: 'items.categoryId',
|
||||
to: 'items_categories.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -63,8 +72,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: "items.costAccountId",
|
||||
to: "accounts.id",
|
||||
from: 'items.costAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -72,8 +81,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: "items.sellAccountId",
|
||||
to: "accounts.id",
|
||||
from: 'items.sellAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -81,8 +90,8 @@ export default class Item extends TenantModel {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Account.default,
|
||||
join: {
|
||||
from: "items.inventoryAccountId",
|
||||
to: "accounts.id",
|
||||
from: 'items.inventoryAccountId',
|
||||
to: 'accounts.id',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -90,110 +99,21 @@ export default class Item extends TenantModel {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Media.default,
|
||||
join: {
|
||||
from: "items.id",
|
||||
from: 'items.id',
|
||||
through: {
|
||||
from: "media_links.model_id",
|
||||
to: "media_links.media_id",
|
||||
from: 'media_links.model_id',
|
||||
to: 'media_links.media_id',
|
||||
},
|
||||
to: "media.id",
|
||||
to: 'media.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Item fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
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",
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return ItemSettings;
|
||||
}
|
||||
}
|
||||
|
||||
30
server/src/models/ItemCategory.Settings.ts
Normal file
30
server/src/models/ItemCategory.Settings.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export default {
|
||||
defaultFilterField: 'name',
|
||||
defaultSort: {
|
||||
sortField: 'name',
|
||||
sortOrder: 'DESC',
|
||||
},
|
||||
fields: {
|
||||
name: {
|
||||
label: 'Name',
|
||||
column: 'name',
|
||||
fieldType: 'text',
|
||||
},
|
||||
description: {
|
||||
label: 'Description',
|
||||
column: 'description',
|
||||
fieldType: 'text',
|
||||
},
|
||||
count: {
|
||||
label: 'Count',
|
||||
column: 'count',
|
||||
fieldType: 'number',
|
||||
virtualColumn: true,
|
||||
},
|
||||
created_at: {
|
||||
label: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import path from 'path';
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import ItemCategorySettings from './ItemCategory.Settings';
|
||||
|
||||
export default class ItemCategory extends TenantModel {
|
||||
export default class ItemCategory extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -10,10 +11,6 @@ export default class ItemCategory extends TenantModel {
|
||||
return 'items_categories';
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
@@ -43,68 +40,23 @@ export default class ItemCategory extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Item category fields.
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get fields() {
|
||||
static get modifiers() {
|
||||
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',
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
sortByCount(query, order = 'asc') {
|
||||
query.orderBy('count', order);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static sortCountQuery(query, role) {
|
||||
query.orderBy('count', role.order);
|
||||
}
|
||||
/**
|
||||
* Model meta.
|
||||
*/
|
||||
static get meta() {
|
||||
return ItemCategorySettings;
|
||||
}
|
||||
}
|
||||
|
||||
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',
|
||||
sortCustomQuery: 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 { 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.
|
||||
*/
|
||||
@@ -99,52 +101,7 @@ export default class ManualJournal extends TenantModel {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
*/
|
||||
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',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return ManualJournalSettings;
|
||||
}
|
||||
}
|
||||
|
||||
56
server/src/models/ModelSetting.ts
Normal file
56
server/src/models/ModelSetting.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
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, attribute?:string): IModelMetaField {
|
||||
const field = get(this.meta.fields, key);
|
||||
|
||||
return attribute ? get(field, attribute) : field;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
};
|
||||
57
server/src/models/PaymentReceive.Settings.ts
Normal file
57
server/src/models/PaymentReceive.Settings.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
export default {
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: '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',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'depositAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
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 ModelSetting from './ModelSetting';
|
||||
import PaymentReceiveSettings from './PaymentReceive.Settings';
|
||||
|
||||
export default class PaymentReceive extends TenantModel {
|
||||
export default class PaymentReceive extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
@@ -75,63 +77,9 @@ export default class PaymentReceive extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
*
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
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',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return PaymentReceiveSettings;
|
||||
}
|
||||
}
|
||||
|
||||
81
server/src/models/SaleEstimate.Settings.ts
Normal file
81
server/src/models/SaleEstimate.Settings.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
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',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: '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',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ name: 'Delivered', key: 'delivered' },
|
||||
{ name: 'Rejected', key: 'rejected' },
|
||||
{ name: 'Approved', key: 'approved' },
|
||||
{ name: 'Delivered', key: 'delivered' },
|
||||
{ name: 'Draft', key: 'draft' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
columnType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('orderByStatus', role.order);
|
||||
}
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import moment from 'moment';
|
||||
import { Model } from 'objection';
|
||||
import { Model, mixin } from 'objection';
|
||||
import TenantModel from 'models/TenantModel';
|
||||
import { defaultToTransform } from 'utils';
|
||||
import HasItemEntries from 'services/Sales/HasItemsEntries';
|
||||
import { query } from 'winston';
|
||||
import SaleEstimateSettings from './SaleEstimate.Settings';
|
||||
import ModelSetting from './ModelSetting';
|
||||
|
||||
export default class SaleEstimate extends TenantModel {
|
||||
export default class SaleEstimate extends mixin(TenantModel, [ModelSetting]) {
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -29,9 +29,9 @@ export default class SaleEstimate extends TenantModel {
|
||||
'isExpired',
|
||||
'isConvertedToInvoice',
|
||||
'isApproved',
|
||||
'isRejected'
|
||||
'isRejected',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the sale estimate converted to sale invoice.
|
||||
@@ -57,7 +57,7 @@ export default class SaleEstimate extends TenantModel {
|
||||
return defaultToTransform(
|
||||
this.expirationDate,
|
||||
moment().isAfter(this.expirationDate, 'day'),
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,13 +123,38 @@ export default class SaleEstimate extends TenantModel {
|
||||
* Filters the approved estimates transactions.
|
||||
*/
|
||||
approved(query) {
|
||||
query.whereNot('approved_at', null)
|
||||
query.whereNot('approved_at', null);
|
||||
},
|
||||
/**
|
||||
* Sorting the estimates orders by delivery status.
|
||||
*/
|
||||
orderByDraft(query, order) {
|
||||
query.orderByRaw(`delivered_at is null ${order}`)
|
||||
orderByStatus(query, order) {
|
||||
query.orderByRaw(`delivered_at is null ${order}`);
|
||||
},
|
||||
/**
|
||||
* Filtering the estimates oreders by status field.
|
||||
*/
|
||||
filterByStatus(query, filterType) {
|
||||
switch (filterType) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -151,7 +176,7 @@ export default class SaleEstimate extends TenantModel {
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'customer');
|
||||
}
|
||||
},
|
||||
},
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
@@ -168,91 +193,9 @@ export default class SaleEstimate extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Model settings.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
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',
|
||||
},
|
||||
};
|
||||
static get meta() {
|
||||
return SaleEstimateSettings;
|
||||
}
|
||||
}
|
||||
|
||||
100
server/src/models/SaleInvoice.Settings.ts
Normal file
100
server/src/models/SaleInvoice.Settings.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
export default {
|
||||
defaultFilterField: 'customer',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
fields: {
|
||||
customer: {
|
||||
name: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
invoice_date: {
|
||||
name: 'Invoice date',
|
||||
column: 'invoice_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
due_date: {
|
||||
name: 'Due date',
|
||||
column: 'due_date',
|
||||
fieldType: 'date',
|
||||
},
|
||||
invoice_no: {
|
||||
name: 'Invoice No.',
|
||||
column: 'invoice_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
reference_no: {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
invoice_message: {
|
||||
name: 'Invoice message',
|
||||
column: 'invoice_message',
|
||||
fieldType: 'text',
|
||||
},
|
||||
terms_conditions: {
|
||||
name: 'Terms & conditions',
|
||||
column: 'terms_conditions',
|
||||
fieldType: 'text',
|
||||
},
|
||||
amount: {
|
||||
name: 'Invoice amount',
|
||||
column: 'balance',
|
||||
fieldType: 'number',
|
||||
},
|
||||
payment_amount: {
|
||||
name: 'Payment amount',
|
||||
column: 'payment_amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
due_amount: { // calculated.
|
||||
name: 'Due amount',
|
||||
column: 'due_amount',
|
||||
fieldType: 'number',
|
||||
virtualColumn: true,
|
||||
},
|
||||
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' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
created_at: {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Status field filter custom query.
|
||||
*/
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('statusFilter', role.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status field sort custom query.
|
||||
*/
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import { mixin, Model, raw } from 'objection';
|
||||
import moment from 'moment';
|
||||
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
|
||||
*/
|
||||
@@ -106,10 +108,6 @@ export default class SaleInvoice extends TenantModel {
|
||||
return this.getOverdueDays();
|
||||
}
|
||||
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} asDate
|
||||
@@ -229,6 +227,33 @@ export default class SaleInvoice extends TenantModel {
|
||||
byPrefixAndNumber(query, prefix, number) {
|
||||
query.where('invoice_no', `${prefix}${number}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Status filter.
|
||||
*/
|
||||
statusFilter(query, filterType) {
|
||||
switch (filterType) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
default:
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,11 +263,14 @@ export default class SaleInvoice extends TenantModel {
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const Contact = require('models/Contact');
|
||||
const Customer = require('models/Customer');
|
||||
const InventoryCostLotTracker = require('models/InventoryCostLotTracker');
|
||||
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Sale invoice associated entries.
|
||||
*/
|
||||
entries: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
@@ -255,9 +283,12 @@ export default class SaleInvoice extends TenantModel {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Belongs to customer model.
|
||||
*/
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'sales_invoices.customerId',
|
||||
to: 'contacts.id',
|
||||
@@ -267,6 +298,9 @@ export default class SaleInvoice extends TenantModel {
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoice has associated account transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
@@ -316,125 +350,13 @@ export default class SaleInvoice extends TenantModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Sale invoice meta.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
customer: {
|
||||
label: 'Customer',
|
||||
column: 'customer_id',
|
||||
relation: 'contacts.id',
|
||||
relationColumn: 'contacts.displayName',
|
||||
static get meta() {
|
||||
return SaleInvoiceMeta;
|
||||
}
|
||||
|
||||
fieldType: 'options',
|
||||
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);
|
||||
},
|
||||
},
|
||||
created_at: {
|
||||
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) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'delivered':
|
||||
query.modify('delivered');
|
||||
break;
|
||||
case 'unpaid':
|
||||
query.modify('unpaid');
|
||||
break;
|
||||
case 'overdue':
|
||||
query.modify('overdue');
|
||||
break;
|
||||
case 'partially-paid':
|
||||
query.modify('partiallyPaid');
|
||||
break;
|
||||
case 'paid':
|
||||
query.modify('paid');
|
||||
break;
|
||||
}
|
||||
},
|
||||
sortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
},
|
||||
},
|
||||
};
|
||||
static dueAmountFieldSortQuery(query, role) {
|
||||
query.modify('sortByDueAmount', role.order);
|
||||
}
|
||||
}
|
||||
|
||||
85
server/src/models/SaleReceipt.Settings.ts
Normal file
85
server/src/models/SaleReceipt.Settings.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
export default {
|
||||
defaultFilterField: 'receipt_date',
|
||||
defaultSort: {
|
||||
sortOrder: 'DESC',
|
||||
sortField: 'created_at',
|
||||
},
|
||||
fields: {
|
||||
'amount': {
|
||||
name: 'Amount',
|
||||
column: 'amount',
|
||||
fieldType: 'number',
|
||||
},
|
||||
'deposit_account': {
|
||||
column: 'deposit_account_id',
|
||||
name: 'Deposit account',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'depositAccount',
|
||||
|
||||
relationEntityLabel: 'name',
|
||||
relationEntityKey: 'slug',
|
||||
},
|
||||
'customer': {
|
||||
name: 'Customer',
|
||||
column: 'customer_id',
|
||||
fieldType: 'relation',
|
||||
|
||||
relationType: 'enumeration',
|
||||
relationKey: 'customer',
|
||||
|
||||
relationEntityLabel: 'display_name',
|
||||
relationEntityKey: 'id',
|
||||
},
|
||||
'receipt_date': {
|
||||
name: 'Receipt date',
|
||||
column: 'receipt_date',
|
||||
fieldType: 'date',
|
||||
|
||||
},
|
||||
'receipt_number': {
|
||||
name: 'Receipt No.',
|
||||
column: 'receipt_number',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'reference_no': {
|
||||
name: 'Reference No.',
|
||||
column: 'reference_no',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'receipt_message': {
|
||||
name: 'Receipt message',
|
||||
column: 'receipt_message',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'statement': {
|
||||
name: 'Statement',
|
||||
column: 'statement',
|
||||
fieldType: 'text',
|
||||
},
|
||||
'created_at': {
|
||||
name: 'Created at',
|
||||
column: 'created_at',
|
||||
fieldType: 'date',
|
||||
},
|
||||
'status': {
|
||||
name: 'Status',
|
||||
fieldType: 'enumeration',
|
||||
options: [
|
||||
{ key: 'draft', name: 'Draft' },
|
||||
{ key: 'closed', name: 'Closed' },
|
||||
],
|
||||
filterCustomQuery: StatusFieldFilterQuery,
|
||||
sortCustomQuery: StatusFieldSortQuery,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function StatusFieldFilterQuery(query, role) {
|
||||
query.modify('filterByStatus', role.value);
|
||||
}
|
||||
|
||||
function StatusFieldSortQuery(query, role) {
|
||||
query.modify('sortByStatus', role.order);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model, mixin } from 'objection';
|
||||
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
|
||||
*/
|
||||
@@ -16,14 +18,11 @@ export default class SaleReceipt extends TenantModel {
|
||||
return ['created_at', 'updated_at'];
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'isClosed',
|
||||
'isDraft',
|
||||
];
|
||||
return ['isClosed', 'isDraft'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +65,21 @@ export default class SaleReceipt extends TenantModel {
|
||||
*/
|
||||
sortByStatus(query, order) {
|
||||
query.orderByRaw(`CLOSED_AT IS NULL ${order}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filtering the receipts orders by status.
|
||||
*/
|
||||
filterByStatus(query, status) {
|
||||
switch (status) {
|
||||
case 'draft':
|
||||
query.modify('draft');
|
||||
break;
|
||||
case 'closed':
|
||||
default:
|
||||
query.modify('closed');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -74,7 +88,7 @@ export default class SaleReceipt extends TenantModel {
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Contact = require('models/Contact');
|
||||
const Customer = require('models/Customer');
|
||||
const Account = require('models/Account');
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
@@ -82,14 +96,14 @@ export default class SaleReceipt extends TenantModel {
|
||||
return {
|
||||
customer: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Contact.default,
|
||||
modelClass: Customer.default,
|
||||
join: {
|
||||
from: 'sales_receipts.customerId',
|
||||
to: 'contacts.id',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('contact_service', 'customer');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
depositAccount: {
|
||||
@@ -118,95 +132,19 @@ export default class SaleReceipt extends TenantModel {
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'sales_receipts.id',
|
||||
to: 'accounts_transactions.referenceId'
|
||||
to: 'accounts_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'SaleReceipt');
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Model defined fields.
|
||||
* Sale invoice meta.
|
||||
*/
|
||||
static get fields() {
|
||||
return {
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
static get meta() {
|
||||
return SaleReceiptSettings;
|
||||
}
|
||||
}
|
||||
|
||||
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 PaginationQueryBuilder from './Pagination';
|
||||
import ModelSetting from './ModelSetting';
|
||||
import VendorSettings from './Vendor.Settings';
|
||||
|
||||
class VendorQueryBuilder extends PaginationQueryBuilder {
|
||||
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.
|
||||
*/
|
||||
@@ -62,6 +64,13 @@ export default class Vendor extends TenantModel {
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('active', !active);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters the active customers.
|
||||
*/
|
||||
@@ -125,72 +134,7 @@ export default class Vendor extends TenantModel {
|
||||
};
|
||||
}
|
||||
|
||||
static get fields() {
|
||||
return {
|
||||
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',
|
||||
}
|
||||
};
|
||||
static get meta() {
|
||||
return VendorSettings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { difference, chain, uniq } from 'lodash';
|
||||
import { kebabCase } from 'lodash';
|
||||
import R from 'ramda';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import {
|
||||
@@ -606,6 +607,17 @@ export default class AccountsService {
|
||||
this.eventDispatcher.dispatch(events.accounts.onActivated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsees accounts list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts datatable list.
|
||||
* @param {number} tenantId
|
||||
@@ -613,21 +625,26 @@ export default class AccountsService {
|
||||
*/
|
||||
public async getAccountsList(
|
||||
tenantId: number,
|
||||
filter: IAccountsFilter
|
||||
filterDTO: IAccountsFilter
|
||||
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
||||
const { Account } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses the stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Account,
|
||||
filter
|
||||
);
|
||||
|
||||
this.logger.info('[accounts] trying to get accounts datatable list.', {
|
||||
tenantId,
|
||||
filter,
|
||||
});
|
||||
const accounts = await Account.query().onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -727,10 +744,11 @@ export default class AccountsService {
|
||||
}));
|
||||
return flatToNestedArray(
|
||||
this.i18nService.i18nMapper(_accounts, ['account_type_label'], tenantId),
|
||||
{
|
||||
id: 'id',
|
||||
parentId: 'parent_account_id',
|
||||
});
|
||||
{
|
||||
id: 'id',
|
||||
parentId: 'parent_account_id',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { omit, defaultTo } from 'lodash';
|
||||
import async from 'async';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -265,6 +265,16 @@ export default class CustomersService {
|
||||
return this.transformContactToCustomer(contact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses customers list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseCustomersListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve customers paginated list.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
@@ -272,7 +282,7 @@ export default class CustomersService {
|
||||
*/
|
||||
public async getCustomersList(
|
||||
tenantId: number,
|
||||
customersFilter: ICustomersFilter
|
||||
filterDTO: ICustomersFilter
|
||||
): Promise<{
|
||||
customers: ICustomer[];
|
||||
pagination: IPaginationMeta;
|
||||
@@ -280,17 +290,23 @@ export default class CustomersService {
|
||||
}> {
|
||||
const { Customer } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses customers list filter DTO.
|
||||
const filter = this.parseCustomersListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Customer,
|
||||
customersFilter
|
||||
filter
|
||||
);
|
||||
|
||||
// Customers.
|
||||
const { results, pagination } = await Customer.query()
|
||||
.onBuild((query) => {
|
||||
dynamicList.buildQuery()(query);
|
||||
.onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
})
|
||||
.pagination(customersFilter.page - 1, customersFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
customers: results.map(this.transformContactToCustomer),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { intersection, defaultTo } from 'lodash';
|
||||
import { defaultTo } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -255,6 +256,12 @@ export default class VendorsService {
|
||||
);
|
||||
}
|
||||
|
||||
private parseVendorsListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve vendors datatable list.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
@@ -262,7 +269,7 @@ export default class VendorsService {
|
||||
*/
|
||||
public async getVendorsList(
|
||||
tenantId: number,
|
||||
vendorsFilter: IVendorsFilter
|
||||
filterDTO: IVendorsFilter
|
||||
): Promise<{
|
||||
vendors: IVendor[];
|
||||
pagination: IPaginationMeta;
|
||||
@@ -270,21 +277,28 @@ export default class VendorsService {
|
||||
}> {
|
||||
const { Vendor } = this.tenancy.models(tenantId);
|
||||
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
// Parses vendors list filter DTO.
|
||||
const filter = this.parseVendorsListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Vendor,
|
||||
vendorsFilter
|
||||
filter
|
||||
);
|
||||
|
||||
// Vendors list.
|
||||
const { results, pagination } = await Vendor.query()
|
||||
.onBuild((builder) => {
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
dynamicList.buildQuery()(builder);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
})
|
||||
.pagination(vendorsFilter.page - 1, vendorsFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
vendors: results,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,90 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import validator from 'is-my-json-valid';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { castArray, isEmpty } from 'lodash';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import { DynamicFilter } from 'lib/DynamicFilter';
|
||||
import {
|
||||
DynamicFilter,
|
||||
DynamicFilterSortBy,
|
||||
DynamicFilterViews,
|
||||
DynamicFilterFilterRoles,
|
||||
} from 'lib/DynamicFilter';
|
||||
import {
|
||||
validateFieldKeyExistance,
|
||||
validateFilterRolesFieldsExistance,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
import {
|
||||
IDynamicListFilterDTO,
|
||||
IFilterRole,
|
||||
IDynamicListFilter,
|
||||
IDynamicListService,
|
||||
IFilterRole,
|
||||
IModel,
|
||||
} from 'interfaces';
|
||||
import TenancyService from 'services/Tenancy/TenancyService';
|
||||
|
||||
const ERRORS = {
|
||||
VIEW_NOT_FOUND: 'view_not_found',
|
||||
SORT_COLUMN_NOT_FOUND: 'sort_column_not_found',
|
||||
FILTER_ROLES_FIELDS_NOT_FOUND: 'filter_roles_fields_not_found',
|
||||
};
|
||||
import DynamicListFilterRoles from './DynamicListFilterRoles';
|
||||
import DynamicListSortBy from './DynamicListSortBy';
|
||||
import DynamicListCustomView from './DynamicListCustomView';
|
||||
|
||||
@Service()
|
||||
export default class DynamicListService implements IDynamicListService {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Retreive custom view or throws error not found.
|
||||
* @param {number} tenantId
|
||||
* @param {number} viewId
|
||||
* @return {Promise<IView>}
|
||||
*/
|
||||
private async getCustomViewOrThrowError(
|
||||
tenantId: number,
|
||||
viewId: number,
|
||||
model: IModel
|
||||
) {
|
||||
const { viewRepository } = this.tenancy.repositories(tenantId);
|
||||
const view = await viewRepository.findOneById(viewId, 'roles');
|
||||
@Inject()
|
||||
dynamicListFilterRoles: DynamicListFilterRoles;
|
||||
|
||||
if (!view || view.resourceModel !== model.name) {
|
||||
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
@Inject()
|
||||
dynamicListSortBy: DynamicListSortBy;
|
||||
|
||||
@Inject()
|
||||
dynamicListView: DynamicListCustomView;
|
||||
|
||||
/**
|
||||
* Validates the sort column whether exists.
|
||||
* @param {IModel} model
|
||||
* @param {string} columnSortBy - Sort column
|
||||
* @throws {ServiceError}
|
||||
* Parses filter DTO.
|
||||
* @param {IMode} model -
|
||||
* @param {} filterDTO -
|
||||
*/
|
||||
private validateSortColumnExistance(model: any, columnSortBy: string) {
|
||||
const notExistsField = validateFieldKeyExistance(model, columnSortBy);
|
||||
|
||||
if (!notExistsField) {
|
||||
throw new ServiceError(ERRORS.SORT_COLUMN_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates existance the fields of filter roles.
|
||||
* @param {IModel} model
|
||||
* @param {IFilterRole[]} filterRoles
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
private validateRolesFieldsExistance(
|
||||
model: IModel,
|
||||
filterRoles: IFilterRole[]
|
||||
) {
|
||||
const invalidFieldsKeys = validateFilterRolesFieldsExistance(
|
||||
model,
|
||||
filterRoles
|
||||
);
|
||||
|
||||
if (invalidFieldsKeys.length > 0) {
|
||||
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates filter roles schema.
|
||||
* @param {IFilterRole[]} filterRoles
|
||||
*/
|
||||
private validateFilterRolesSchema(filterRoles: IFilterRole[]) {
|
||||
const validate = validator({
|
||||
required: true,
|
||||
type: 'object',
|
||||
properties: {
|
||||
condition: { type: 'string' },
|
||||
fieldKey: { required: true, type: 'string' },
|
||||
value: { required: true },
|
||||
},
|
||||
});
|
||||
const invalidFields = filterRoles.filter((filterRole) => {
|
||||
const isValid = validate(filterRole);
|
||||
return isValid ? false : true;
|
||||
});
|
||||
if (invalidFields.length > 0) {
|
||||
throw new ServiceError('stringified_filter_roles_invalid');
|
||||
}
|
||||
}
|
||||
private parseFilterObject = (model, filterDTO) => {
|
||||
return {
|
||||
// Merges the default properties with filter object.
|
||||
...model.defaultSort ? {
|
||||
sortOrder: model.defaultSort.sortOrder,
|
||||
columnSortBy: model.defaultSort.sortOrder,
|
||||
} : {},
|
||||
...filterDTO,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamic listing.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {IModel} model - Model.
|
||||
* @param {IDynamicListFilterDTO} filter - Dynamic filter DTO.
|
||||
* @param {IDynamicListFilter} filter - Dynamic filter DTO.
|
||||
*/
|
||||
public async dynamicList(
|
||||
public dynamicList = async (
|
||||
tenantId: number,
|
||||
model: IModel,
|
||||
filter: IDynamicListFilterDTO
|
||||
) {
|
||||
filter: IDynamicListFilter
|
||||
) => {
|
||||
const dynamicFilter = new DynamicFilter(model);
|
||||
|
||||
// Custom view filter roles.
|
||||
if (filter.customViewId) {
|
||||
const view = await this.getCustomViewOrThrowError(
|
||||
tenantId,
|
||||
filter.customViewId,
|
||||
model
|
||||
);
|
||||
const viewFilter = new DynamicFilterViews(view);
|
||||
dynamicFilter.setFilter(viewFilter);
|
||||
}
|
||||
// Sort by the given column.
|
||||
if (filter.columnSortBy) {
|
||||
this.validateSortColumnExistance(model, filter.columnSortBy);
|
||||
// Parses the filter object.
|
||||
const parsedFilter = this.parseFilterObject(model, filter);
|
||||
|
||||
const sortByFilter = new DynamicFilterSortBy(
|
||||
filter.columnSortBy,
|
||||
filter.sortOrder
|
||||
// Custom view filter roles.
|
||||
// if (filter.customViewId) {
|
||||
// const dynamicListCustomView = this.dynamicListView.dynamicListCustomView();
|
||||
|
||||
// dynamicFilter.setFilter(dynamicListCustomView);
|
||||
// }
|
||||
// Sort by the given column.
|
||||
if (parsedFilter.columnSortBy) {
|
||||
const dynmaicListSortBy = this.dynamicListSortBy.dynamicSortBy(
|
||||
model,
|
||||
parsedFilter.columnSortBy,
|
||||
parsedFilter.sortOrder,
|
||||
);
|
||||
dynamicFilter.setFilter(sortByFilter);
|
||||
dynamicFilter.setFilter(dynmaicListSortBy);
|
||||
}
|
||||
// Filter roles.
|
||||
if (filter.filterRoles.length > 0) {
|
||||
const filterRoles = filter.filterRoles.map((filterRole, index) => ({
|
||||
...filterRole,
|
||||
index: index + 1,
|
||||
}));
|
||||
this.validateFilterRolesSchema(filterRoles);
|
||||
this.validateRolesFieldsExistance(model, filterRoles);
|
||||
|
||||
// Validate the model resource fields.
|
||||
const dynamicFilterRoles = new DynamicFilterFilterRoles(filterRoles);
|
||||
if (!isEmpty(parsedFilter.filterRoles)) {
|
||||
const dynamicFilterRoles = this.dynamicListFilterRoles.dynamicList(
|
||||
model,
|
||||
parsedFilter.filterRoles
|
||||
);
|
||||
dynamicFilter.setFilter(dynamicFilterRoles);
|
||||
}
|
||||
return dynamicFilter;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware to catch services errors
|
||||
@@ -173,25 +102,62 @@ export default class DynamicListService implements IDynamicListService {
|
||||
if (error instanceof ServiceError) {
|
||||
if (error.errorType === 'sort_column_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'SORT.COLUMN.NOT.FOUND', code: 200 }],
|
||||
errors: [
|
||||
{
|
||||
type: 'SORT.COLUMN.NOT.FOUND',
|
||||
message: 'Sort column not found.',
|
||||
code: 200,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'view_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'CUSTOM.VIEW.NOT.FOUND', code: 100 }],
|
||||
errors: [
|
||||
{
|
||||
type: 'CUSTOM.VIEW.NOT.FOUND',
|
||||
message: 'Custom view not found.',
|
||||
code: 100,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'filter_roles_fields_not_found') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'FILTER.ROLES.FIELDS.NOT.FOUND', code: 300 }],
|
||||
errors: [
|
||||
{
|
||||
type: 'FILTER.ROLES.FIELDS.NOT.FOUND',
|
||||
message: 'Filter roles fields not found.',
|
||||
code: 300,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'stringified_filter_roles_invalid') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'STRINGIFIED_FILTER_ROLES_INVALID', code: 400 }],
|
||||
errors: [
|
||||
{
|
||||
type: 'STRINGIFIED_FILTER_ROLES_INVALID',
|
||||
message: 'Stringified filter roles json invalid.',
|
||||
code: 400,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses stringified filter roles.
|
||||
* @param {string} stringifiedFilterRoles - Stringified filter roles.
|
||||
*/
|
||||
public parseStringifiedFilter = (filterRoles: IDynamicListFilter) => {
|
||||
return {
|
||||
...filterRoles,
|
||||
filterRoles: filterRoles.stringifiedFilterRoles
|
||||
? castArray(JSON.parse(filterRoles.stringifiedFilterRoles))
|
||||
: [],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
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 { difference, sumBy, omit, map } from 'lodash';
|
||||
import { difference, sumBy, omit } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -630,6 +631,16 @@ export default class ExpensesService implements IExpensesService {
|
||||
return expenses.filter((expense) => expense.publishedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses filter DTO of expenses list.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve expenses datatable lsit.
|
||||
* @param {number} tenantId
|
||||
@@ -638,35 +649,41 @@ export default class ExpensesService implements IExpensesService {
|
||||
*/
|
||||
public async getExpensesList(
|
||||
tenantId: number,
|
||||
expensesFilter: IExpensesFilter
|
||||
filterDTO: IExpensesFilter
|
||||
): Promise<{
|
||||
expenses: IExpense[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
|
||||
// Parses list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Expense,
|
||||
expensesFilter
|
||||
filter,
|
||||
);
|
||||
|
||||
this.logger.info('[expense] trying to get expenses datatable list.', {
|
||||
tenantId,
|
||||
expensesFilter,
|
||||
filter,
|
||||
});
|
||||
const { results, pagination } = await Expense.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('paymentAccount');
|
||||
builder.withGraphFetched('categories.expenseAccount');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
|
||||
dynamicList.buildQuery()(builder);
|
||||
})
|
||||
.pagination(expensesFilter.page - 1, expensesFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
expenses: results,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ export default class JournalSheetService {
|
||||
fromRange: null,
|
||||
toRange: null,
|
||||
accountsIds: [],
|
||||
transactionTypes: [],
|
||||
numberFormat: {
|
||||
noCents: false,
|
||||
divideOn1000: false,
|
||||
@@ -107,6 +106,13 @@ export default class JournalSheetService {
|
||||
}
|
||||
query.modify('filterDateRange', filter.fromDate, filter.toDate);
|
||||
query.orderBy(['date', 'createdAt', 'indexGroup', 'index']);
|
||||
|
||||
if (filter.transactionType) {
|
||||
return query.where('reference_type', filter.transactionType);
|
||||
}
|
||||
if (filter.transactionType && filter.transactionId) {
|
||||
return query.where('reference_id', filter.transactionId);
|
||||
}
|
||||
});
|
||||
// Transform the transactions array to journal collection.
|
||||
const transactionsJournal = Journal.fromTransactions(
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
IAccount,
|
||||
IAccountTransaction,
|
||||
INumberFormatQuery,
|
||||
ITransactionsByReferenceQuery,
|
||||
ITransactionsByReferenceTransaction,
|
||||
} from 'interfaces';
|
||||
import FinancialSheet from '../FinancialSheet';
|
||||
|
||||
export default class TransactionsByReference extends FinancialSheet {
|
||||
readonly transactions: IAccountTransaction[];
|
||||
readonly query: ITransactionsByReferenceQuery;
|
||||
readonly baseCurrency: string;
|
||||
readonly numberFormat: INumberFormatQuery;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {IAccountTransaction[]} transactions
|
||||
* @param {ITransactionsByReferenceQuery} query
|
||||
* @param {string} baseCurrency
|
||||
*/
|
||||
constructor(
|
||||
transactions: (IAccountTransaction & { account: IAccount }) [],
|
||||
query: ITransactionsByReferenceQuery,
|
||||
baseCurrency: string
|
||||
) {
|
||||
super();
|
||||
|
||||
this.transactions = transactions;
|
||||
this.query = query;
|
||||
this.baseCurrency = baseCurrency;
|
||||
this.numberFormat = this.query.numberFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mappes the given account transaction to report transaction.
|
||||
* @param {IAccountTransaction} transaction
|
||||
* @returns {ITransactionsByReferenceTransaction}
|
||||
*/
|
||||
private transactionMapper = (
|
||||
transaction: IAccountTransaction
|
||||
): ITransactionsByReferenceTransaction => {
|
||||
return {
|
||||
credit: this.getAmountMeta(transaction.credit, { money: true }),
|
||||
debit: this.getAmountMeta(transaction.debit, { money: true }),
|
||||
|
||||
referenceTypeFormatted: transaction.referenceTypeFormatted,
|
||||
referenceType: transaction.referenceType,
|
||||
referenceId: transaction.referenceId,
|
||||
|
||||
contactId: transaction.contactId,
|
||||
contactType: transaction.contactType,
|
||||
contactTypeFormatted: transaction.contactType,
|
||||
|
||||
accountName: transaction.account.name,
|
||||
accountCode: transaction.account.code,
|
||||
accountId: transaction.accountId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mappes the given accounts transactions to report transactions.
|
||||
* @param {IAccountTransaction} transaction
|
||||
* @returns {ITransactionsByReferenceTransaction}
|
||||
*/
|
||||
private transactionsMapper = (
|
||||
transactions: IAccountTransaction[]
|
||||
): ITransactionsByReferenceTransaction[] => {
|
||||
return transactions.map(this.transactionMapper);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the report data.
|
||||
* @returns {ITransactionsByReferenceTransaction}
|
||||
*/
|
||||
public reportData(): ITransactionsByReferenceTransaction[] {
|
||||
return this.transactionsMapper(this.transactions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import HasTenancyService from 'services/Tenancy/TenancyService';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { IAccount, IAccountTransaction, ITransactionsByReferenceQuery } from 'interfaces';
|
||||
|
||||
@Service()
|
||||
export default class TransactionsByReferenceRepository {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the accounts transactions of the givne reference id and type.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} referenceId - Reference id.
|
||||
* @param {string} referenceType - Reference type.
|
||||
* @return {Promise<IAccountTransaction[]>}
|
||||
*/
|
||||
public getTransactions(
|
||||
tenantId: number,
|
||||
referenceId: number,
|
||||
referenceType: string,
|
||||
): Promise<(IAccountTransaction & { account: IAccount }) []> {
|
||||
const { AccountTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
return AccountTransaction.query()
|
||||
.where('reference_id', referenceId)
|
||||
.where('reference_type', referenceType)
|
||||
.withGraphFetched('account');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import HasTenancyService from 'services/Tenancy/TenancyService';
|
||||
import {
|
||||
ITransactionsByReferenceQuery,
|
||||
ITransactionsByReferenceTransaction,
|
||||
} from 'interfaces';
|
||||
import TransactionsByReferenceRepository from './TransactionsByReferenceRepository';
|
||||
import TransactionsByReferenceReport from './TransactionsByReferenceReport';
|
||||
|
||||
@Service()
|
||||
export default class TransactionsByReferenceService {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@Inject()
|
||||
reportRepository: TransactionsByReferenceRepository;
|
||||
|
||||
/**
|
||||
* Default query of transactions by reference report.
|
||||
*/
|
||||
get defaultQuery(): ITransactionsByReferenceQuery {
|
||||
return {
|
||||
numberFormat: {
|
||||
precision: 2,
|
||||
divideOn1000: false,
|
||||
showZero: false,
|
||||
formatMoney: 'total',
|
||||
negativeFormat: 'mines',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve accounts transactions by given reference id and type.
|
||||
* @param {number} tenantId
|
||||
* @param {ITransactionsByReferenceQuery} filter
|
||||
*/
|
||||
public async getTransactionsByReference(
|
||||
tenantId: number,
|
||||
query: ITransactionsByReferenceQuery
|
||||
): Promise<{
|
||||
data: ITransactionsByReferenceTransaction[];
|
||||
}> {
|
||||
const filter = {
|
||||
...this.defaultQuery,
|
||||
...query,
|
||||
};
|
||||
|
||||
// Retrieve the accounts transactions of the given reference.
|
||||
const transactions = await this.reportRepository.getTransactions(
|
||||
tenantId,
|
||||
filter.referenceId,
|
||||
filter.referenceType
|
||||
);
|
||||
|
||||
// Settings tenant service.
|
||||
const settings = this.tenancy.settings(tenantId);
|
||||
const baseCurrency = settings.get({
|
||||
group: 'organization',
|
||||
key: 'base_currency',
|
||||
});
|
||||
|
||||
// Transactions by reference report.
|
||||
const report = new TransactionsByReferenceReport(
|
||||
transactions,
|
||||
filter,
|
||||
baseCurrency
|
||||
);
|
||||
|
||||
return {
|
||||
data: report.reportData(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { omit } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -225,8 +226,8 @@ export default class InventoryAdjustmentService {
|
||||
|
||||
/**
|
||||
* Publish the inventory adjustment transaction.
|
||||
* @param tenantId
|
||||
* @param inventoryAdjustmentId
|
||||
* @param {number} tenantId
|
||||
* @param {number} inventoryAdjustmentId
|
||||
*/
|
||||
async publishInventoryAdjustment(
|
||||
tenantId: number,
|
||||
@@ -265,24 +266,38 @@ export default class InventoryAdjustmentService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses inventory adjustments list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the inventory adjustments paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IInventoryAdjustmentsFilter} adjustmentsFilter
|
||||
*/
|
||||
async getInventoryAdjustments(
|
||||
public async getInventoryAdjustments(
|
||||
tenantId: number,
|
||||
adjustmentsFilter: IInventoryAdjustmentsFilter
|
||||
filterDTO: IInventoryAdjustmentsFilter
|
||||
): Promise<{
|
||||
inventoryAdjustments: IInventoryAdjustment[];
|
||||
pagination: IPaginationMeta;
|
||||
}> {
|
||||
const { InventoryAdjustment } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses inventory adjustments list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
InventoryAdjustment,
|
||||
adjustmentsFilter
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await InventoryAdjustment.query()
|
||||
.onBuild((query) => {
|
||||
@@ -291,7 +306,7 @@ export default class InventoryAdjustmentService {
|
||||
|
||||
dynamicFilter.buildQuery()(query);
|
||||
})
|
||||
.pagination(adjustmentsFilter.page - 1, adjustmentsFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
inventoryAdjustments: results,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Inject } from 'typedi';
|
||||
import { difference } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -377,6 +378,18 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses items categories filter DTO.
|
||||
* @param {} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parsesListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
// Parses stringified filter roles.
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve item categories list.
|
||||
* @param {number} tenantId
|
||||
@@ -384,16 +397,22 @@ export default class ItemCategoriesService implements IItemCategoriesService {
|
||||
*/
|
||||
public async getItemCategoriesList(
|
||||
tenantId: number,
|
||||
filter: IItemCategoriesFilter,
|
||||
filterDTO: IItemCategoriesFilter,
|
||||
authorizedUser: ISystemUser
|
||||
): Promise<{ itemCategories: IItemCategory[]; filterMeta: IFilterMeta }> {
|
||||
const { ItemCategory } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses list filter DTO.
|
||||
const filter = this.parsesListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
ItemCategory,
|
||||
filter
|
||||
);
|
||||
|
||||
// Items categories.
|
||||
const itemCategories = await ItemCategory.query().onBuild((query) => {
|
||||
// Subquery to calculate sumation of assocaited items to the item category.
|
||||
query.select('*', ItemCategory.relatedQuery('items').count().as('count'));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defaultTo, difference } from 'lodash';
|
||||
import { defaultTo } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -525,20 +526,37 @@ export default class ItemsService implements IItemsService {
|
||||
return this.transformItemToResponse(tenantId, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses items list filter DTO.
|
||||
* @param {} filterDTO - Filter DTO.
|
||||
*/
|
||||
private parseItemsListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve items datatable list.
|
||||
* @param {number} tenantId
|
||||
* @param {IItemsFilter} itemsFilter
|
||||
*/
|
||||
public async itemsList(tenantId: number, itemsFilter: IItemsFilter) {
|
||||
public async itemsList(tenantId: number, filterDTO: IItemsFilter) {
|
||||
const { Item } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses items list filter DTO.
|
||||
const filter = this.parseItemsListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Item,
|
||||
itemsFilter
|
||||
filter
|
||||
);
|
||||
const { results: items, pagination } = await Item.query()
|
||||
.onBuild((builder) => {
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
|
||||
builder.withGraphFetched('inventoryAccount');
|
||||
builder.withGraphFetched('sellAccount');
|
||||
builder.withGraphFetched('costAccount');
|
||||
@@ -546,7 +564,7 @@ export default class ItemsService implements IItemsService {
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(itemsFilter.page - 1, itemsFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
const results = items.map((item) =>
|
||||
this.transformItemToResponse(tenantId, item)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { difference, sumBy, omit, map } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import { ServiceError, ServiceErrors } from 'exceptions';
|
||||
import * as R from 'ramda';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import {
|
||||
IManualJournalDTO,
|
||||
IManualJournalsService,
|
||||
@@ -768,33 +769,47 @@ export default class ManualJournalsService implements IManualJournalsService {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses filter DTO of the manual journals list.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve manual journals datatable list.
|
||||
* @param {number} tenantId
|
||||
* @param {IManualJournalsFilter} filter
|
||||
* @param {number} tenantId -
|
||||
* @param {IManualJournalsFilter} filter -
|
||||
*/
|
||||
public async getManualJournals(
|
||||
tenantId: number,
|
||||
filter: IManualJournalsFilter
|
||||
filterDTO: IManualJournalsFilter
|
||||
): Promise<{
|
||||
manualJournals: IManualJournal;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { ManualJournal } = this.tenancy.models(tenantId);
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic service.
|
||||
const dynamicService = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
ManualJournal,
|
||||
filter
|
||||
);
|
||||
|
||||
this.logger.info('[manual_journals] trying to get manual journals list.', {
|
||||
tenantId,
|
||||
filter,
|
||||
});
|
||||
const { results, pagination } = await ManualJournal.query()
|
||||
.onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
dynamicService.buildQuery()(builder);
|
||||
builder.withGraphFetched('entries.account');
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
@@ -802,7 +817,7 @@ export default class ManualJournalsService implements IManualJournalsService {
|
||||
return {
|
||||
manualJournals: results,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
filterMeta: dynamicService.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { omit, sumBy, difference } from 'lodash';
|
||||
import { sumBy, difference } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -25,7 +26,7 @@ import TenancyService from 'services/Tenancy/TenancyService';
|
||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||
import { entriesAmountDiff, formatDateFields } from 'utils';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from 'data/AccountTypes';
|
||||
import { ACCOUNT_TYPE } from 'data/AccountTypes';
|
||||
import VendorsService from 'services/Contacts/VendorsService';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@@ -635,6 +636,12 @@ export default class BillPaymentsService implements IBillPaymentsService {
|
||||
]);
|
||||
}
|
||||
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve bill payment paginted and filterable list.
|
||||
* @param {number} tenantId
|
||||
@@ -642,34 +649,40 @@ export default class BillPaymentsService implements IBillPaymentsService {
|
||||
*/
|
||||
public async listBillPayments(
|
||||
tenantId: number,
|
||||
billPaymentsFilter: IBillPaymentsFilter
|
||||
filterDTO: IBillPaymentsFilter
|
||||
): Promise<{
|
||||
billPayments: IBillPayment;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { BillPayment } = this.tenancy.models(tenantId);
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
BillPayment,
|
||||
billPaymentsFilter
|
||||
filter
|
||||
);
|
||||
this.logger.info('[bill_payment] try to get bill payments list.', {
|
||||
tenantId,
|
||||
});
|
||||
|
||||
const { results, pagination } = await BillPayment.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
builder.withGraphFetched('paymentAccount');
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
dynamicList.buildQuery()(builder);
|
||||
})
|
||||
.pagination(billPaymentsFilter.page - 1, billPaymentsFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
billPayments: results,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { omit, runInContext, sumBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import composeAsync from 'async/compose';
|
||||
import {
|
||||
EventDispatcher,
|
||||
@@ -521,6 +522,16 @@ export default class BillsService
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses bills list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve bills data table list.
|
||||
* @param {number} tenantId -
|
||||
@@ -528,28 +539,33 @@ export default class BillsService
|
||||
*/
|
||||
public async getBills(
|
||||
tenantId: number,
|
||||
billsFilter: IBillsFilter
|
||||
filterDTO: IBillsFilter
|
||||
): Promise<{
|
||||
bills: IBill;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses bills list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
Bill,
|
||||
billsFilter
|
||||
filter,
|
||||
);
|
||||
this.logger.info('[bills] trying to get bills data table.', {
|
||||
tenantId,
|
||||
billsFilter,
|
||||
filter,
|
||||
});
|
||||
const { results, pagination } = await Bill.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(billsFilter.page - 1, billsFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
bills: results,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Service, Inject } from 'typedi';
|
||||
import { camelCase, upperFirst } from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
import { buildFilter } from 'objection-filter';
|
||||
import { IModel } from 'interfaces';
|
||||
import { IModel, IModelMeta } from 'interfaces';
|
||||
import {
|
||||
getModelFields,
|
||||
} from 'lib/ViewRolesBuilder'
|
||||
@@ -102,4 +102,18 @@ export default class ResourceService {
|
||||
|
||||
return buildFilter(resourceModel).build(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource meta.
|
||||
* @param {number} tenantId
|
||||
* @param {string} modelName
|
||||
* @returns {IModelMeta}
|
||||
*/
|
||||
public getResourceMeta(tenantId: number, modelName: string): IModelMeta {
|
||||
const resourceModel = this.getResourceModel(tenantId, modelName);
|
||||
|
||||
const settings = resourceModel.meta();
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { omit, sumBy, difference } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -613,6 +614,16 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
|
||||
return saleInvoices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses payments receive list filter DTO.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve payment receives paginated and filterable list.
|
||||
* @param {number} tenantId
|
||||
@@ -620,33 +631,39 @@ export default class PaymentReceiveService implements IPaymentsReceiveService {
|
||||
*/
|
||||
public async listPaymentReceives(
|
||||
tenantId: number,
|
||||
paymentReceivesFilter: IPaymentReceivesFilter
|
||||
filterDTO: IPaymentReceivesFilter
|
||||
): Promise<{
|
||||
paymentReceives: IPaymentReceive[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
PaymentReceive,
|
||||
paymentReceivesFilter
|
||||
filter
|
||||
);
|
||||
|
||||
const { results, pagination } = await PaymentReceive.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('depositAccount');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
dynamicList.buildQuery()(builder);
|
||||
})
|
||||
.pagination(
|
||||
paymentReceivesFilter.page - 1,
|
||||
paymentReceivesFilter.pageSize
|
||||
filter.page - 1,
|
||||
filter.pageSize
|
||||
);
|
||||
|
||||
return {
|
||||
paymentReceives: results,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import { filter, omit, sumBy } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
IEstimatesFilter,
|
||||
IFilterMeta,
|
||||
@@ -412,6 +413,16 @@ export default class SaleEstimateService implements ISalesEstimatesService{
|
||||
return estimate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses estimates list filter DTO.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves estimates filterable and paginated list.
|
||||
* @param {number} tenantId -
|
||||
@@ -419,17 +430,22 @@ export default class SaleEstimateService implements ISalesEstimatesService{
|
||||
*/
|
||||
public async estimatesList(
|
||||
tenantId: number,
|
||||
estimatesFilter: IEstimatesFilter
|
||||
filterDTO: IEstimatesFilter
|
||||
): Promise<{
|
||||
salesEstimates: ISaleEstimate[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
SaleEstimate,
|
||||
estimatesFilter
|
||||
filter,
|
||||
);
|
||||
|
||||
const { results, pagination } = await SaleEstimate.query()
|
||||
@@ -438,7 +454,7 @@ export default class SaleEstimateService implements ISalesEstimatesService{
|
||||
builder.withGraphFetched('entries');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(estimatesFilter.page - 1, estimatesFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
salesEstimates: results,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit, sumBy, join, entries } from 'lodash';
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import moment from 'moment';
|
||||
import composeAsync from 'async/compose';
|
||||
import {
|
||||
@@ -647,6 +648,17 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
return saleInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sale invoice list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sales invoices filterable and paginated list.
|
||||
* @param {Request} req
|
||||
@@ -655,22 +667,27 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
*/
|
||||
public async salesInvoicesList(
|
||||
tenantId: number,
|
||||
salesInvoicesFilter: ISalesInvoicesFilter
|
||||
filterDTO: ISalesInvoicesFilter
|
||||
): Promise<{
|
||||
salesInvoices: ISaleInvoice[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
SaleInvoice,
|
||||
salesInvoicesFilter
|
||||
filter
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] try to get sales invoices list.', {
|
||||
tenantId,
|
||||
salesInvoicesFilter,
|
||||
filter,
|
||||
});
|
||||
const { results, pagination } = await SaleInvoice.query()
|
||||
.onBuild((builder) => {
|
||||
@@ -678,7 +695,7 @@ export default class SaleInvoicesService implements ISalesInvoicesService {
|
||||
builder.withGraphFetched('customer');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(salesInvoicesFilter.page - 1, salesInvoicesFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
salesInvoices: results,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { omit, sumBy } from 'lodash';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -406,6 +407,16 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
||||
return saleReceipt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sale receipts list filter DTO.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve sales receipts paginated and filterable list.
|
||||
* @param {number} tenantId
|
||||
@@ -413,17 +424,22 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
||||
*/
|
||||
public async salesReceiptsList(
|
||||
tenantId: number,
|
||||
salesReceiptsFilter: ISaleReceiptFilter
|
||||
filterDTO: ISaleReceiptFilter
|
||||
): Promise<{
|
||||
salesReceipts: ISaleReceipt[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses the stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
SaleReceipt,
|
||||
salesReceiptsFilter
|
||||
filter,
|
||||
);
|
||||
|
||||
this.logger.info('[sale_receipt] try to get sales receipts list.', {
|
||||
@@ -437,7 +453,7 @@ export default class SalesReceiptService implements ISalesReceiptsService {
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(salesReceiptsFilter.page - 1, salesReceiptsFilter.pageSize);
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
return {
|
||||
salesReceipts: results,
|
||||
|
||||
Reference in New Issue
Block a user