mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat(contacts): auto-complete contacts.
feat(items): auto-complete items. feat(resources): resource columns feat. feat(contacts): retrieve specific contact details.
This commit is contained in:
@@ -170,7 +170,10 @@ export default class AccountsController extends BaseController {
|
|||||||
accountDTO
|
accountDTO
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({ id: account.id });
|
return res.status(200).send({
|
||||||
|
id: account.id,
|
||||||
|
message: 'The account has been created successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -258,7 +261,11 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.accountsService.activateAccount(tenantId, accountId, true);
|
await this.accountsService.activateAccount(tenantId, accountId, true);
|
||||||
return res.status(200).send({ id: accountId });
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: accountId,
|
||||||
|
message: 'The account has been activated successfully.'
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -276,7 +283,11 @@ export default class AccountsController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.accountsService.activateAccount(tenantId, accountId, false);
|
await this.accountsService.activateAccount(tenantId, accountId, false);
|
||||||
return res.status(200).send({ id: accountId });
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: accountId,
|
||||||
|
message: 'The account has been inactivated successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,100 @@
|
|||||||
import { check, param, query, body, ValidationChain } from 'express-validator';
|
import { check, param, query, body, ValidationChain } from 'express-validator';
|
||||||
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
|
import { Inject } from 'typedi';
|
||||||
import BaseController from 'api/controllers/BaseController';
|
import BaseController from 'api/controllers/BaseController';
|
||||||
|
import ContactsService from 'services/Contacts/ContactsService';
|
||||||
import { DATATYPES_LENGTH } from 'data/DataTypes';
|
import { DATATYPES_LENGTH } from 'data/DataTypes';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
|
@Service()
|
||||||
export default class ContactsController extends BaseController {
|
export default class ContactsController extends BaseController {
|
||||||
|
@Inject()
|
||||||
|
contactsService: ContactsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Express router.
|
||||||
|
*/
|
||||||
|
router() {
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:id',
|
||||||
|
[param('id').exists().isNumeric().toInt()],
|
||||||
|
this.validationResult,
|
||||||
|
this.asyncMiddleware(this.getContact.bind(this))
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
'/auto-complete',
|
||||||
|
[...this.autocompleteQuerySchema],
|
||||||
|
this.validationResult,
|
||||||
|
this.asyncMiddleware(this.autocompleteContacts.bind(this))
|
||||||
|
);
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-complete list query validation schema.
|
||||||
|
*/
|
||||||
|
get autocompleteQuerySchema() {
|
||||||
|
return [
|
||||||
|
query('column_sort_by').optional().trim().escape(),
|
||||||
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
|
query('limit').optional().isNumeric().toInt(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve details of the given contact.
|
||||||
|
* @param {Request} req -
|
||||||
|
* @param {Response} res -
|
||||||
|
* @param {NextFunction} next -
|
||||||
|
*/
|
||||||
|
async getContact(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const { id: contactId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const contact = await this.contactsService.getContact(
|
||||||
|
tenantId,
|
||||||
|
contactId,
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
customer: this.transfromToResponse(contact),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve auto-complete contacts list.
|
||||||
|
* @param {Request} req - Request object.
|
||||||
|
* @param {Response} res - Response object.
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
async autocompleteContacts(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const filter = {
|
||||||
|
filterRoles: [],
|
||||||
|
sortOrder: 'asc',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
limit: 10,
|
||||||
|
...this.matchedQueryData(req),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const contacts = await this.contactsService.autocompleteContacts(
|
||||||
|
tenantId,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
return res.status(200).send({ contacts });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {ValidationChain[]}
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,12 +4,16 @@ import { check, query, param } from 'express-validator';
|
|||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import InventoryAdjustmentService from 'services/Inventory/InventoryAdjustmentService';
|
import InventoryAdjustmentService from 'services/Inventory/InventoryAdjustmentService';
|
||||||
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class InventoryAdjustmentsController extends BaseController {
|
export default class InventoryAdjustmentsController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
inventoryAdjustmentService: InventoryAdjustmentService;
|
inventoryAdjustmentService: InventoryAdjustmentService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -42,6 +46,7 @@ export default class InventoryAdjustmentsController extends BaseController {
|
|||||||
[...this.validateListQuerySchema],
|
[...this.validateListQuerySchema],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.asyncMiddleware(this.getInventoryAdjustments.bind(this)),
|
this.asyncMiddleware(this.getInventoryAdjustments.bind(this)),
|
||||||
|
this.dynamicListService.handlerErrorsToResponse,
|
||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
return router;
|
return router;
|
||||||
@@ -191,6 +196,9 @@ export default class InventoryAdjustmentsController extends BaseController {
|
|||||||
const filter = {
|
const filter = {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 12,
|
pageSize: 12,
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
filterRoles: [],
|
||||||
...this.matchedQueryData(req),
|
...this.matchedQueryData(req),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ export default class ItemsController extends BaseController {
|
|||||||
asyncMiddleware(this.deleteItem.bind(this)),
|
asyncMiddleware(this.deleteItem.bind(this)),
|
||||||
this.handlerServiceErrors
|
this.handlerServiceErrors
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
'/auto-complete',
|
||||||
|
this.autocompleteQuerySchema,
|
||||||
|
this.asyncMiddleware(this.autocompleteList.bind(this)),
|
||||||
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id',
|
'/:id',
|
||||||
[...this.validateSpecificItemSchema],
|
[...this.validateSpecificItemSchema],
|
||||||
@@ -201,6 +206,48 @@ export default class ItemsController extends BaseController {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate autocomplete list query schema.
|
||||||
|
*/
|
||||||
|
get autocompleteQuerySchema() {
|
||||||
|
return [
|
||||||
|
query('column_sort_by').optional().trim().escape(),
|
||||||
|
query('sort_order').optional().isIn(['desc', 'asc']),
|
||||||
|
|
||||||
|
query('stringified_filter_roles').optional().isJSON(),
|
||||||
|
query('limit').optional().isNumeric().toInt(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-complete list.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
async autocompleteList(req: Request, res: Response, next: NextFunction) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const filter = {
|
||||||
|
filterRoles: [],
|
||||||
|
sortOrder: 'asc',
|
||||||
|
columnSortBy: 'created_at',
|
||||||
|
limit: 10,
|
||||||
|
...this.matchedQueryData(req),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const items = await this.itemsService.autocompleteItems(
|
||||||
|
tenantId,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
items,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given item details to the storage.
|
* Stores the given item details to the storage.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
@@ -237,7 +284,7 @@ export default class ItemsController extends BaseController {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: itemId,
|
id: itemId,
|
||||||
message: 'The item has been edited successfully.'
|
message: 'The item has been edited successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -302,7 +349,7 @@ export default class ItemsController extends BaseController {
|
|||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
id: itemId,
|
id: itemId,
|
||||||
message: 'The item has been deleted successfully.'
|
message: 'The item has been deleted successfully.',
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
@@ -481,7 +528,9 @@ export default class ItemsController extends BaseController {
|
|||||||
}
|
}
|
||||||
if (error.errorType === 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT') {
|
if (error.errorType === 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT') {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
errors: [{ type: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', code: 330 }],
|
errors: [
|
||||||
|
{ type: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', code: 330 },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import FinancialStatements from 'api/controllers/FinancialStatements';
|
|||||||
import Expenses from 'api/controllers/Expenses';
|
import Expenses from 'api/controllers/Expenses';
|
||||||
import Settings from 'api/controllers/Settings';
|
import Settings from 'api/controllers/Settings';
|
||||||
import Currencies from 'api/controllers/Currencies';
|
import Currencies from 'api/controllers/Currencies';
|
||||||
|
import Contacts from 'api/controllers/Contacts/Contacts';
|
||||||
import Customers from 'api/controllers/Contacts/Customers';
|
import Customers from 'api/controllers/Contacts/Customers';
|
||||||
import Vendors from 'api/controllers/Contacts/Vendors';
|
import Vendors from 'api/controllers/Contacts/Vendors';
|
||||||
import Sales from 'api/controllers/Sales'
|
import Sales from 'api/controllers/Sales'
|
||||||
@@ -93,6 +94,7 @@ export default () => {
|
|||||||
dashboard.use('/item_categories', Container.get(ItemCategories).router());
|
dashboard.use('/item_categories', Container.get(ItemCategories).router());
|
||||||
dashboard.use('/expenses', Container.get(Expenses).router());
|
dashboard.use('/expenses', Container.get(Expenses).router());
|
||||||
dashboard.use('/financial_statements', Container.get(FinancialStatements).router());
|
dashboard.use('/financial_statements', Container.get(FinancialStatements).router());
|
||||||
|
dashboard.use('/contacts', Container.get(Contacts).router());
|
||||||
dashboard.use('/customers', Container.get(Customers).router());
|
dashboard.use('/customers', Container.get(Customers).router());
|
||||||
dashboard.use('/vendors', Container.get(Vendors).router());
|
dashboard.use('/vendors', Container.get(Vendors).router());
|
||||||
dashboard.use('/sales', Container.get(Sales).router());
|
dashboard.use('/sales', Container.get(Sales).router());
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ export default {
|
|||||||
key: "number_prefix",
|
key: "number_prefix",
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'increment_mode',
|
||||||
|
type: 'string'
|
||||||
|
}
|
||||||
],
|
],
|
||||||
payment_receives: [
|
payment_receives: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -205,3 +205,12 @@ export interface ICustomersFilter extends IDynamicListFilter {
|
|||||||
pageSize?: number,
|
pageSize?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IContactsAutoCompleteFilter {
|
||||||
|
limit: number,
|
||||||
|
keyword: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IContactAutoCompleteItem {
|
||||||
|
displayName: string,
|
||||||
|
contactService: string,
|
||||||
|
}
|
||||||
@@ -1,25 +1,28 @@
|
|||||||
|
|
||||||
export interface IDynamicFilter {
|
export interface IDynamicFilter {
|
||||||
setTableName(tableName: string): void;
|
setTableName(tableName: string): void;
|
||||||
buildQuery(): void;
|
buildQuery(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFilterRole {
|
export interface IFilterRole {
|
||||||
fieldKey: string,
|
fieldKey: string;
|
||||||
value: string,
|
value: string;
|
||||||
condition?: string,
|
condition?: string;
|
||||||
index?: number,
|
index?: number;
|
||||||
comparator?: string,
|
comparator?: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IDynamicListFilterDTO {
|
export interface IDynamicListFilterDTO {
|
||||||
customViewId?: number,
|
customViewId?: number;
|
||||||
filterRoles?: IFilterRole[],
|
filterRoles?: IFilterRole[];
|
||||||
columnSortBy: string,
|
columnSortBy: string;
|
||||||
sortOrder: string,
|
sortOrder: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDynamicListService {
|
export interface IDynamicListService {
|
||||||
dynamicList(tenantId: number, model: any, filter: IDynamicListFilterDTO): Promise<any>;
|
dynamicList(
|
||||||
|
tenantId: number,
|
||||||
|
model: any,
|
||||||
|
filter: IDynamicListFilterDTO
|
||||||
|
): Promise<any>;
|
||||||
handlerErrorsToResponse(error, req, res, next): void;
|
handlerErrorsToResponse(error, req, res, next): void;
|
||||||
}
|
}
|
||||||
@@ -77,3 +77,9 @@ export interface IItemsFilter extends IDynamicListFilter {
|
|||||||
page: number,
|
page: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IItemsAutoCompleteFilter {
|
||||||
|
limit: number,
|
||||||
|
keyword: string,
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { IDynamicListFilter } from 'interfaces/DynamicFilter';
|
||||||
import { IItemEntry, IItemEntryDTO } from "./ItemEntry";
|
import { IItemEntry, IItemEntryDTO } from "./ItemEntry";
|
||||||
|
|
||||||
export interface ISaleInvoice {
|
export interface ISaleInvoice {
|
||||||
@@ -36,7 +37,7 @@ export interface ISaleInvoiceEditDTO extends ISaleInvoiceDTO {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ISalesInvoicesFilter{
|
export interface ISalesInvoicesFilter extends IDynamicListFilter{
|
||||||
page: number,
|
page: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { forEach, uniqBy } from 'lodash';
|
import { forEach, uniqBy } from 'lodash';
|
||||||
import {
|
import { buildFilterRolesJoins } from 'lib/ViewRolesBuilder';
|
||||||
buildFilterRolesJoins,
|
|
||||||
} from 'lib/ViewRolesBuilder';
|
|
||||||
import { IModel } from 'interfaces';
|
import { IModel } from 'interfaces';
|
||||||
|
|
||||||
export default class DynamicFilter {
|
export default class DynamicFilter {
|
||||||
@@ -20,7 +18,7 @@ export default class DynamicFilter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set filter.
|
* Set filter.
|
||||||
* @param {*} filterRole -
|
* @param {*} filterRole - Filter role.
|
||||||
*/
|
*/
|
||||||
setFilter(filterRole) {
|
setFilter(filterRole) {
|
||||||
filterRole.setModel(this.model);
|
filterRole.setModel(this.model);
|
||||||
@@ -36,14 +34,22 @@ export default class DynamicFilter {
|
|||||||
|
|
||||||
this.filters.forEach((filter) => {
|
this.filters.forEach((filter) => {
|
||||||
const { filterRoles } = filter;
|
const { filterRoles } = filter;
|
||||||
|
|
||||||
buildersCallbacks.push(filter.buildQuery());
|
buildersCallbacks.push(filter.buildQuery());
|
||||||
tableColumns.push(...(Array.isArray(filterRoles)) ? filterRoles : [filterRoles]);
|
tableColumns.push(
|
||||||
|
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
buildersCallbacks.forEach((builderCallback) => {
|
buildersCallbacks.forEach((builderCallback) => {
|
||||||
builderCallback(builder);
|
builderCallback(builder);
|
||||||
});
|
});
|
||||||
buildFilterRolesJoins(this.model, uniqBy(tableColumns, 'columnKey'))(builder);
|
|
||||||
|
buildFilterRolesJoins(
|
||||||
|
this.model,
|
||||||
|
uniqBy(tableColumns, 'columnKey')
|
||||||
|
)(builder);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { difference } from 'lodash';
|
import { difference } from 'lodash';
|
||||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||||
import {
|
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
||||||
buildFilterQuery,
|
|
||||||
} from 'lib/ViewRolesBuilder';
|
|
||||||
import { IFilterRole } from 'interfaces';
|
import { IFilterRole } from 'interfaces';
|
||||||
|
|
||||||
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||||
|
filterRoles: IFilterRole[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
* @param {Array} filterRoles -
|
* @param {Array} filterRoles -
|
||||||
@@ -13,6 +13,7 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
|||||||
*/
|
*/
|
||||||
constructor(filterRoles: IFilterRole[]) {
|
constructor(filterRoles: IFilterRole[]) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.filterRoles = filterRoles;
|
this.filterRoles = filterRoles;
|
||||||
this.setResponseMeta();
|
this.setResponseMeta();
|
||||||
}
|
}
|
||||||
@@ -23,9 +24,10 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
|||||||
*/
|
*/
|
||||||
private buildLogicExpression(): string {
|
private buildLogicExpression(): string {
|
||||||
let expression = '';
|
let expression = '';
|
||||||
|
|
||||||
this.filterRoles.forEach((role, index) => {
|
this.filterRoles.forEach((role, index) => {
|
||||||
expression += (index === 0) ?
|
expression +=
|
||||||
`${role.index} ` : `${role.condition} ${role.index} `;
|
index === 0 ? `${role.index} ` : `${role.condition} ${role.index} `;
|
||||||
});
|
});
|
||||||
return expression.trim();
|
return expression.trim();
|
||||||
}
|
}
|
||||||
@@ -45,7 +47,7 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
|||||||
*/
|
*/
|
||||||
setResponseMeta() {
|
setResponseMeta() {
|
||||||
this.responseMeta = {
|
this.responseMeta = {
|
||||||
filterRoles: this.filterRoles
|
filterRoles: this.filterRoles,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||||
import { getRoleFieldColumn, validateFieldKeyExistance } from 'lib/ViewRolesBuilder';
|
import {
|
||||||
|
getRoleFieldColumn,
|
||||||
|
validateFieldKeyExistance,
|
||||||
|
getTableFromRelationColumn,
|
||||||
|
} from 'lib/ViewRolesBuilder';
|
||||||
|
|
||||||
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||||
sortRole: { fieldKey: string, order: string } = {};
|
sortRole: { fieldKey: string; order: string } = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -19,6 +23,9 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
|||||||
this.setResponseMeta();
|
this.setResponseMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the given field key with the model.
|
||||||
|
*/
|
||||||
validate() {
|
validate() {
|
||||||
validateFieldKeyExistance(this.model, this.sortRole.fieldKey);
|
validateFieldKeyExistance(this.model, this.sortRole.fieldKey);
|
||||||
}
|
}
|
||||||
@@ -27,15 +34,41 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
|||||||
* Builds database query of sort by column on the given direction.
|
* Builds database query of sort by column on the given direction.
|
||||||
*/
|
*/
|
||||||
buildQuery() {
|
buildQuery() {
|
||||||
return (builder) => {
|
const fieldRelation = getRoleFieldColumn(
|
||||||
const fieldRelation = getRoleFieldColumn(this.model, this.sortRole.fieldKey);
|
this.model,
|
||||||
|
this.sortRole.fieldKey
|
||||||
|
);
|
||||||
const comparatorColumn =
|
const comparatorColumn =
|
||||||
fieldRelation.relationColumn ||
|
fieldRelation.relationColumn ||
|
||||||
`${this.tableName}.${fieldRelation.column}`;
|
`${this.tableName}.${fieldRelation.column}`;
|
||||||
|
|
||||||
|
if (typeof fieldRelation.sortQuery !== 'undefined') {
|
||||||
|
return (builder) => {
|
||||||
|
fieldRelation.sortQuery(builder, this.sortRole);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (builder) => {
|
||||||
if (this.sortRole.fieldKey) {
|
if (this.sortRole.fieldKey) {
|
||||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
||||||
}
|
}
|
||||||
|
this.joinBuildQuery()(builder);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
joinBuildQuery() {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { IView, IViewRole } from 'interfaces';
|
import { IView, IViewRole } from 'interfaces';
|
||||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||||
import {
|
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
||||||
buildFilterQuery,
|
|
||||||
} from 'lib/ViewRolesBuilder';
|
|
||||||
|
|
||||||
export default class DynamicFilterViews extends DynamicFilterRoleAbstructor {
|
export default class DynamicFilterViews extends DynamicFilterRoleAbstructor {
|
||||||
viewId: number;
|
viewId: number;
|
||||||
@@ -38,7 +36,11 @@ export default class DynamicFilterViews extends DynamicFilterRoleAbstructor {
|
|||||||
*/
|
*/
|
||||||
buildQuery() {
|
buildQuery() {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
buildFilterQuery(this.model, this.filterRoles, this.logicExpression)(builder);
|
buildFilterQuery(
|
||||||
|
this.model,
|
||||||
|
this.filterRoles,
|
||||||
|
this.logicExpression
|
||||||
|
)(builder);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,11 +51,11 @@ export default class DynamicFilterViews extends DynamicFilterRoleAbstructor {
|
|||||||
this.responseMeta = {
|
this.responseMeta = {
|
||||||
view: {
|
view: {
|
||||||
logicExpression: this.logicExpression,
|
logicExpression: this.logicExpression,
|
||||||
filterRoles: this.filterRoles.map((filterRole) =>
|
filterRoles: this.filterRoles.map((filterRole) => ({
|
||||||
({ ...omit(filterRole, ['id', 'viewId']) })
|
...omit(filterRole, ['id', 'viewId']),
|
||||||
),
|
})),
|
||||||
customViewId: this.viewId,
|
customViewId: this.viewId,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,10 @@ import Parser from 'lib/LogicEvaluation/Parser';
|
|||||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||||
import { IFilterRole, IModel } from 'interfaces';
|
import { IFilterRole, IModel } from 'interfaces';
|
||||||
|
|
||||||
const numberRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
const numberRoleQueryBuilder = (
|
||||||
|
role: IFilterRole,
|
||||||
|
comparatorColumn: string
|
||||||
|
) => {
|
||||||
switch (role.comparator) {
|
switch (role.comparator) {
|
||||||
case 'equals':
|
case 'equals':
|
||||||
case 'equal':
|
case 'equal':
|
||||||
@@ -67,12 +70,16 @@ const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||||
switch(role.comparator) {
|
switch (role.comparator) {
|
||||||
case 'after':
|
case 'after':
|
||||||
case 'before':
|
case 'before':
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
const comparator = role.comparator === 'before' ? '<' : '>';
|
const comparator = role.comparator === 'before' ? '<' : '>';
|
||||||
const hasTimeFormat = moment(role.value, 'YYYY-MM-DD HH:MM', true).isValid();
|
const hasTimeFormat = moment(
|
||||||
|
role.value,
|
||||||
|
'YYYY-MM-DD HH:MM',
|
||||||
|
true
|
||||||
|
).isValid();
|
||||||
const targetDate = moment(role.value);
|
const targetDate = moment(role.value);
|
||||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||||
|
|
||||||
@@ -88,7 +95,11 @@ const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
|||||||
};
|
};
|
||||||
case 'in':
|
case 'in':
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
const hasTimeFormat = moment(role.value, 'YYYY-MM-DD HH:MM', true).isValid();
|
const hasTimeFormat = moment(
|
||||||
|
role.value,
|
||||||
|
'YYYY-MM-DD HH:MM',
|
||||||
|
true
|
||||||
|
).isValid();
|
||||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||||
|
|
||||||
if (hasTimeFormat) {
|
if (hasTimeFormat) {
|
||||||
@@ -112,7 +123,7 @@ const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
|||||||
*/
|
*/
|
||||||
export function getRoleFieldColumn(model: IModel, fieldKey: string) {
|
export function getRoleFieldColumn(model: IModel, fieldKey: string) {
|
||||||
const tableFields = model.fields;
|
const tableFields = model.fields;
|
||||||
return (tableFields[fieldKey]) ? tableFields[fieldKey] : null;
|
return tableFields[fieldKey] ? tableFields[fieldKey] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,9 +133,10 @@ export function getRoleFieldColumn(model: IModel, fieldKey: string) {
|
|||||||
*/
|
*/
|
||||||
export function buildRoleQuery(model: IModel, role: IFilterRole) {
|
export function buildRoleQuery(model: IModel, role: IFilterRole) {
|
||||||
const fieldRelation = getRoleFieldColumn(model, role.fieldKey);
|
const fieldRelation = getRoleFieldColumn(model, role.fieldKey);
|
||||||
const comparatorColumn = fieldRelation.relationColumn || `${model.tableName}.${fieldRelation.column}`;
|
const comparatorColumn =
|
||||||
|
fieldRelation.relationColumn ||
|
||||||
|
`${model.tableName}.${fieldRelation.column}`;
|
||||||
|
|
||||||
//
|
|
||||||
if (typeof fieldRelation.query !== 'undefined') {
|
if (typeof fieldRelation.query !== 'undefined') {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
fieldRelation.query(builder, role);
|
fieldRelation.query(builder, role);
|
||||||
@@ -149,13 +161,13 @@ export function buildRoleQuery(model: IModel, role: IFilterRole) {
|
|||||||
*/
|
*/
|
||||||
export const getTableFromRelationColumn = (column: string) => {
|
export const getTableFromRelationColumn = (column: string) => {
|
||||||
const splitedColumn = column.split('.');
|
const splitedColumn = column.split('.');
|
||||||
return (splitedColumn.length > 0) ? splitedColumn[0] : '';
|
return splitedColumn.length > 0 ? splitedColumn[0] : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds view roles join queries.
|
* Builds view roles join queries.
|
||||||
* @param {String} tableName -
|
* @param {String} tableName - Table name.
|
||||||
* @param {Array} roles -
|
* @param {Array} roles - Roles.
|
||||||
*/
|
*/
|
||||||
export function buildFilterRolesJoins(model: IModel, roles: IFilterRole[]) {
|
export function buildFilterRolesJoins(model: IModel, roles: IFilterRole[]) {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
@@ -164,7 +176,13 @@ export function buildFilterRolesJoins(model: IModel, roles: IFilterRole[]) {
|
|||||||
|
|
||||||
if (fieldColumn.relation) {
|
if (fieldColumn.relation) {
|
||||||
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
||||||
builder.join(joinTable, `${model.tableName}.${fieldColumn.column}`, '=', fieldColumn.relation);
|
|
||||||
|
builder.join(
|
||||||
|
joinTable,
|
||||||
|
`${model.tableName}.${fieldColumn.column}`,
|
||||||
|
'=',
|
||||||
|
fieldColumn.relation
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -176,7 +194,12 @@ export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
|||||||
|
|
||||||
if (fieldColumn.relation) {
|
if (fieldColumn.relation) {
|
||||||
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
||||||
builder.join(joinTable, `${model.tableName}.${fieldColumn.column}`, '=', fieldColumn.relation);
|
builder.join(
|
||||||
|
joinTable,
|
||||||
|
`${model.tableName}.${fieldColumn.column}`,
|
||||||
|
'=',
|
||||||
|
fieldColumn.relation
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -187,7 +210,11 @@ export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
|||||||
* @param {Array} roles -
|
* @param {Array} roles -
|
||||||
* @return {Function}
|
* @return {Function}
|
||||||
*/
|
*/
|
||||||
export function buildFilterRolesQuery(model: IModel, roles: IFilterRole[], logicExpression: string = '') {
|
export function buildFilterRolesQuery(
|
||||||
|
model: IModel,
|
||||||
|
roles: IFilterRole[],
|
||||||
|
logicExpression: string = ''
|
||||||
|
) {
|
||||||
const rolesIndexSet = {};
|
const rolesIndexSet = {};
|
||||||
|
|
||||||
roles.forEach((role) => {
|
roles.forEach((role) => {
|
||||||
@@ -211,7 +238,11 @@ export function buildFilterRolesQuery(model: IModel, roles: IFilterRole[], logic
|
|||||||
* @param {Array} roles -
|
* @param {Array} roles -
|
||||||
* @param {String} logicExpression -
|
* @param {String} logicExpression -
|
||||||
*/
|
*/
|
||||||
export const buildFilterQuery = (model: IModel, roles: IFilterRole[], logicExpression: string) => {
|
export const buildFilterQuery = (
|
||||||
|
model: IModel,
|
||||||
|
roles: IFilterRole[],
|
||||||
|
logicExpression: string
|
||||||
|
) => {
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
||||||
};
|
};
|
||||||
@@ -233,7 +264,6 @@ export function mapViewRolesToConditionals(viewRoles) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function mapFilterRolesToDynamicFilter(roles) {
|
export function mapFilterRolesToDynamicFilter(roles) {
|
||||||
return roles.map((role) => ({
|
return roles.map((role) => ({
|
||||||
...role,
|
...role,
|
||||||
@@ -247,9 +277,14 @@ export function mapFilterRolesToDynamicFilter(roles) {
|
|||||||
* @param {String} columnKey -
|
* @param {String} columnKey -
|
||||||
* @param {String} sortDirection -
|
* @param {String} sortDirection -
|
||||||
*/
|
*/
|
||||||
export function buildSortColumnQuery(model: IModel, columnKey: string, sortDirection: string) {
|
export function buildSortColumnQuery(
|
||||||
|
model: IModel,
|
||||||
|
columnKey: string,
|
||||||
|
sortDirection: string
|
||||||
|
) {
|
||||||
const fieldRelation = getRoleFieldColumn(model, columnKey);
|
const fieldRelation = getRoleFieldColumn(model, columnKey);
|
||||||
const sortColumn = fieldRelation.relation || `${model.tableName}.${fieldRelation.column}`;
|
const sortColumn =
|
||||||
|
fieldRelation.relation || `${model.tableName}.${fieldRelation.column}`;
|
||||||
|
|
||||||
return (builder) => {
|
return (builder) => {
|
||||||
builder.orderBy(sortColumn, sortDirection);
|
builder.orderBy(sortColumn, sortDirection);
|
||||||
@@ -257,22 +292,34 @@ export function buildSortColumnQuery(model: IModel, columnKey: string, sortDirec
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateFilterLogicExpression(logicExpression: string, indexes) {
|
export function validateFilterLogicExpression(
|
||||||
|
logicExpression: string,
|
||||||
|
indexes
|
||||||
|
) {
|
||||||
const logicExpIndexes = logicExpression.match(/\d+/g) || [];
|
const logicExpIndexes = logicExpression.match(/\d+/g) || [];
|
||||||
const diff = difference(logicExpIndexes.map(Number), indexes);
|
const diff = difference(logicExpIndexes.map(Number), indexes);
|
||||||
|
|
||||||
return (diff.length > 0) ? false : true;
|
return diff.length > 0 ? false : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateRolesLogicExpression(logicExpression: string, roles: IFilterRole[]) {
|
export function validateRolesLogicExpression(
|
||||||
return validateFilterLogicExpression(logicExpression, roles.map((r) => r.index));
|
logicExpression: string,
|
||||||
|
roles: IFilterRole[]
|
||||||
|
) {
|
||||||
|
return validateFilterLogicExpression(
|
||||||
|
logicExpression,
|
||||||
|
roles.map((r) => r.index)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
||||||
return model?.fields?.[fieldKey] || false;
|
return model?.fields?.[fieldKey] || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateFilterRolesFieldsExistance(model, filterRoles: IFilterRole[]) {
|
export function validateFilterRolesFieldsExistance(
|
||||||
|
model,
|
||||||
|
filterRoles: IFilterRole[]
|
||||||
|
) {
|
||||||
return filterRoles.filter((filterRole: IFilterRole) => {
|
return filterRoles.filter((filterRole: IFilterRole) => {
|
||||||
return !validateFieldKeyExistance(model, filterRole.fieldKey);
|
return !validateFieldKeyExistance(model, filterRole.fieldKey);
|
||||||
});
|
});
|
||||||
@@ -287,8 +334,12 @@ export function getModelFieldsKeys(Model: IModel) {
|
|||||||
const fields = Object.keys(Model.fields);
|
const fields = Object.keys(Model.fields);
|
||||||
|
|
||||||
return fields.sort((a, b) => {
|
return fields.sort((a, b) => {
|
||||||
if (a < b) { return -1; }
|
if (a < b) {
|
||||||
if (a > b) { return 1; }
|
return -1;
|
||||||
|
}
|
||||||
|
if (a > b) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -302,5 +353,5 @@ export function getModelFields(Model: IModel) {
|
|||||||
...field,
|
...field,
|
||||||
key: fieldKey,
|
key: fieldKey,
|
||||||
};
|
};
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,19 +215,11 @@ export default class Account extends TenantModel {
|
|||||||
label: 'Account name',
|
label: 'Account name',
|
||||||
column: 'name',
|
column: 'name',
|
||||||
columnType: 'string',
|
columnType: 'string',
|
||||||
|
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
label: 'Account type',
|
label: 'Account type',
|
||||||
column: 'account_type_id',
|
column: 'account_type',
|
||||||
relation: 'account_types.id',
|
|
||||||
relationColumn: 'account_types.key',
|
|
||||||
|
|
||||||
fieldType: 'options',
|
|
||||||
optionsResource: 'AccountType',
|
|
||||||
optionsKey: 'key',
|
|
||||||
optionsLabel: 'label',
|
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
label: 'Description',
|
label: 'Description',
|
||||||
|
|||||||
@@ -74,13 +74,11 @@ export default class BillPayment extends TenantModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource fields.
|
||||||
|
*/
|
||||||
static get fields() {
|
static get fields() {
|
||||||
return {
|
return {
|
||||||
created_at: {
|
|
||||||
label: 'Created at',
|
|
||||||
column: 'created_at',
|
|
||||||
columnType: 'date',
|
|
||||||
},
|
|
||||||
vendor: {
|
vendor: {
|
||||||
lable: "Vendor name",
|
lable: "Vendor name",
|
||||||
column: 'vendor_id',
|
column: 'vendor_id',
|
||||||
@@ -96,7 +94,7 @@ export default class BillPayment extends TenantModel {
|
|||||||
payment_account: {
|
payment_account: {
|
||||||
label: "Payment account",
|
label: "Payment account",
|
||||||
column: "payment_account_id",
|
column: "payment_account_id",
|
||||||
relation: "accounts",
|
relation: "accounts.id",
|
||||||
relationColumn: "accounts.name",
|
relationColumn: "accounts.name",
|
||||||
|
|
||||||
fieldType: 'options',
|
fieldType: 'options',
|
||||||
@@ -116,7 +114,7 @@ export default class BillPayment extends TenantModel {
|
|||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
fieldType: 'date',
|
fieldType: 'date',
|
||||||
},
|
},
|
||||||
reference: {
|
reference_no: {
|
||||||
label: "Reference No.",
|
label: "Reference No.",
|
||||||
column: "reference",
|
column: "reference",
|
||||||
columnType: 'string',
|
columnType: 'string',
|
||||||
@@ -127,7 +125,12 @@ export default class BillPayment extends TenantModel {
|
|||||||
column: "description",
|
column: "description",
|
||||||
columnType: 'string',
|
columnType: 'string',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
}
|
},
|
||||||
|
created_at: {
|
||||||
|
label: 'Created at',
|
||||||
|
column: 'created_at',
|
||||||
|
columnType: 'date',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,12 +165,6 @@ export default class Expense extends TenantModel {
|
|||||||
label: "Published",
|
label: "Published",
|
||||||
column: "published",
|
column: "published",
|
||||||
},
|
},
|
||||||
user: {
|
|
||||||
label: "User",
|
|
||||||
column: "user_id",
|
|
||||||
relation: "users.id",
|
|
||||||
relationColumn: "users.id",
|
|
||||||
},
|
|
||||||
created_at: {
|
created_at: {
|
||||||
label: "Created at",
|
label: "Created at",
|
||||||
column: "created_at",
|
column: "created_at",
|
||||||
|
|||||||
@@ -79,4 +79,54 @@ export default class InventoryAdjustment extends TenantModel {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model defined fields.
|
||||||
|
*/
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,16 +140,19 @@ export default class Item extends TenantModel {
|
|||||||
label: 'Cost account',
|
label: 'Cost account',
|
||||||
column: 'cost_account_id',
|
column: 'cost_account_id',
|
||||||
relation: 'accounts.id',
|
relation: 'accounts.id',
|
||||||
|
relationColumn: 'accounts.name',
|
||||||
},
|
},
|
||||||
sell_account: {
|
sell_account: {
|
||||||
label: 'Sell account',
|
label: 'Sell account',
|
||||||
column: 'sell_account_id',
|
column: 'sell_account_id',
|
||||||
relation: 'accounts.id',
|
relation: 'accounts.id',
|
||||||
|
relationColumn: 'accounts.name',
|
||||||
},
|
},
|
||||||
inventory_account: {
|
inventory_account: {
|
||||||
label: "Inventory account",
|
label: "Inventory account",
|
||||||
column: 'inventory_account_id',
|
column: 'inventory_account_id',
|
||||||
relation: 'accounts.id',
|
relation: 'accounts.id',
|
||||||
|
relationColumn: 'accounts.name',
|
||||||
},
|
},
|
||||||
sell_description: {
|
sell_description: {
|
||||||
label: "Sell description",
|
label: "Sell description",
|
||||||
@@ -170,18 +173,21 @@ export default class Item extends TenantModel {
|
|||||||
category: {
|
category: {
|
||||||
label: "Category",
|
label: "Category",
|
||||||
column: 'category_id',
|
column: 'category_id',
|
||||||
relation: 'categories.id',
|
relation: 'items_categories.id',
|
||||||
},
|
relationColumn: 'items_categories.name',
|
||||||
user: {
|
|
||||||
label: 'User',
|
|
||||||
column: 'user_id',
|
|
||||||
relation: 'users.id',
|
|
||||||
relationColumn: 'users.id',
|
|
||||||
},
|
},
|
||||||
|
// user: {
|
||||||
|
// label: 'User',
|
||||||
|
// column: 'user_id',
|
||||||
|
// relation: 'users.id',
|
||||||
|
// relationColumn: 'users.',
|
||||||
|
// },
|
||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
}
|
columnType: 'date',
|
||||||
|
fieldType: 'date',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ export default class ItemCategory extends TenantModel {
|
|||||||
}],
|
}],
|
||||||
columnType: 'string',
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
|
count: {
|
||||||
|
label: 'Count',
|
||||||
|
column: 'count',
|
||||||
|
sortQuery: this.sortCountQuery
|
||||||
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
@@ -98,4 +103,8 @@ export default class ItemCategory extends TenantModel {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static sortCountQuery(query, role) {
|
||||||
|
query.orderBy('count', role.order);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
import { query } from 'winston';
|
||||||
|
|
||||||
export default class ManualJournal extends TenantModel {
|
export default class ManualJournal extends TenantModel {
|
||||||
/**
|
/**
|
||||||
@@ -20,9 +21,7 @@ export default class ManualJournal extends TenantModel {
|
|||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return [
|
return ['isPublished'];
|
||||||
'isPublished',
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,6 +32,17 @@ export default class ManualJournal extends TenantModel {
|
|||||||
return !!this.publishedAt;
|
return !!this.publishedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model modifiers.
|
||||||
|
*/
|
||||||
|
static get modifiers() {
|
||||||
|
return {
|
||||||
|
sortByStatus(query, order) {
|
||||||
|
query.orderByRaw(`PUBLISHED_AT IS NULL ${order}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
@@ -51,7 +61,7 @@ export default class ManualJournal extends TenantModel {
|
|||||||
},
|
},
|
||||||
filter(query) {
|
filter(query) {
|
||||||
query.orderBy('index', 'ASC');
|
query.orderBy('index', 'ASC');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
transactions: {
|
transactions: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
@@ -77,8 +87,8 @@ export default class ManualJournal extends TenantModel {
|
|||||||
},
|
},
|
||||||
filter(query) {
|
filter(query) {
|
||||||
query.where('model_name', 'ManualJournal');
|
query.where('model_name', 'ManualJournal');
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +112,10 @@ export default class ManualJournal extends TenantModel {
|
|||||||
column: 'reference',
|
column: 'reference',
|
||||||
columnType: 'string',
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
status: {
|
journal_type: {
|
||||||
label: 'Status',
|
label: 'Journal type',
|
||||||
column: 'status',
|
column: 'journal_type',
|
||||||
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
amount: {
|
amount: {
|
||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
@@ -116,15 +127,12 @@ export default class ManualJournal extends TenantModel {
|
|||||||
column: 'description',
|
column: 'description',
|
||||||
columnType: 'string',
|
columnType: 'string',
|
||||||
},
|
},
|
||||||
user: {
|
status: {
|
||||||
label: 'User',
|
label: 'Status',
|
||||||
column: 'user_id',
|
column: 'status',
|
||||||
relation: 'users.id',
|
sortQuery(query, role) {
|
||||||
relationColumn: 'users.id',
|
query.modify('sortByStatus', role.order);
|
||||||
},
|
},
|
||||||
journal_type: {
|
|
||||||
label: 'Journal type',
|
|
||||||
column: 'journal_type',
|
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel';
|
|||||||
|
|
||||||
export default class PaymentReceive extends TenantModel {
|
export default class PaymentReceive extends TenantModel {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name.
|
||||||
*/
|
*/
|
||||||
static get tableName() {
|
static get tableName() {
|
||||||
return 'payment_receives';
|
return 'payment_receives';
|
||||||
@@ -16,11 +16,14 @@ export default class PaymentReceive extends TenantModel {
|
|||||||
return ['created_at', 'updated_at'];
|
return ['created_at', 'updated_at'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resourcable model.
|
||||||
|
*/
|
||||||
static get resourceable() {
|
static get resourceable() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
@@ -79,6 +82,9 @@ export default class PaymentReceive extends TenantModel {
|
|||||||
customer: {
|
customer: {
|
||||||
label: 'Customer',
|
label: 'Customer',
|
||||||
column: 'customer_id',
|
column: 'customer_id',
|
||||||
|
relation: 'contacts.id',
|
||||||
|
relationColumn: 'contacts.displayName',
|
||||||
|
|
||||||
fieldType: 'options',
|
fieldType: 'options',
|
||||||
optionsResource: 'customers',
|
optionsResource: 'customers',
|
||||||
optionsKey: 'id',
|
optionsKey: 'id',
|
||||||
@@ -102,10 +108,11 @@ export default class PaymentReceive extends TenantModel {
|
|||||||
columnType: 'string',
|
columnType: 'string',
|
||||||
fieldType: 'text',
|
fieldType: 'text',
|
||||||
},
|
},
|
||||||
deposit_acount: {
|
deposit_account: {
|
||||||
column: 'deposit_account_id',
|
column: 'deposit_account_id',
|
||||||
lable: 'Deposit account',
|
lable: 'Deposit account',
|
||||||
relation: "accounts.id",
|
relation: "accounts.id",
|
||||||
|
relationColumn: 'accounts.name',
|
||||||
optionsResource: "account",
|
optionsResource: "account",
|
||||||
},
|
},
|
||||||
payment_receive_no: {
|
payment_receive_no: {
|
||||||
@@ -125,9 +132,6 @@ export default class PaymentReceive extends TenantModel {
|
|||||||
column: 'created_at',
|
column: 'created_at',
|
||||||
columnType: 'date',
|
columnType: 'date',
|
||||||
},
|
},
|
||||||
user: {
|
|
||||||
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,9 @@ export default class SaleEstimate extends TenantModel {
|
|||||||
customer: {
|
customer: {
|
||||||
label: 'Customer',
|
label: 'Customer',
|
||||||
column: 'customer_id',
|
column: 'customer_id',
|
||||||
|
relation: 'contacts.id',
|
||||||
|
relationColumn: 'contacts.displayName',
|
||||||
|
|
||||||
fieldType: 'options',
|
fieldType: 'options',
|
||||||
optionsResource: 'customers',
|
optionsResource: 'customers',
|
||||||
optionsKey: 'id',
|
optionsKey: 'id',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import moment from 'moment';
|
|||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import { defaultToTransform } from 'utils';
|
import { defaultToTransform } from 'utils';
|
||||||
import { QueryBuilder } from 'knex';
|
import { QueryBuilder } from 'knex';
|
||||||
|
import { query } from 'winston';
|
||||||
|
|
||||||
export default class SaleInvoice extends TenantModel {
|
export default class SaleInvoice extends TenantModel {
|
||||||
/**
|
/**
|
||||||
@@ -198,6 +199,18 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
*/
|
*/
|
||||||
fromDate(query, fromDate) {
|
fromDate(query, fromDate) {
|
||||||
query.where('invoice_date', '<=', fromDate)
|
query.where('invoice_date', '<=', fromDate)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Sort the sale invoices by full-payment invoices.
|
||||||
|
*/
|
||||||
|
sortByStatus(query, order) {
|
||||||
|
query.orderByRaw(`PAYMENT_AMOUNT = BALANCE ${order}`);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Sort the sale invoices by the due amount.
|
||||||
|
*/
|
||||||
|
sortByDueAmount(query, order) {
|
||||||
|
query.orderByRaw(`BALANCE - PAYMENT_AMOUNT ${order}`)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -293,6 +306,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
customer: {
|
customer: {
|
||||||
label: 'Customer',
|
label: 'Customer',
|
||||||
column: 'customer_id',
|
column: 'customer_id',
|
||||||
|
relation: 'contacts.id',
|
||||||
|
relationColumn: 'contacts.displayName',
|
||||||
|
|
||||||
fieldType: 'options',
|
fieldType: 'options',
|
||||||
optionsResource: 'customers',
|
optionsResource: 'customers',
|
||||||
optionsKey: 'id',
|
optionsKey: 'id',
|
||||||
@@ -351,6 +367,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
column: 'due_amount',
|
column: 'due_amount',
|
||||||
columnType: 'number',
|
columnType: 'number',
|
||||||
fieldType: 'number',
|
fieldType: 'number',
|
||||||
|
sortQuery(query, role) {
|
||||||
|
query.modify('sortByDueAmount', role.order);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
created_at: {
|
created_at: {
|
||||||
label: 'Created at',
|
label: 'Created at',
|
||||||
@@ -389,6 +408,9 @@ export default class SaleInvoice extends TenantModel {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
sortQuery(query, role) {
|
||||||
|
query.modify('sortByStatus', role.order);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,22 +16,7 @@ import {
|
|||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import AccountTypesUtils from 'lib/AccountTypes';
|
import AccountTypesUtils from 'lib/AccountTypes';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
const ERRORS = {
|
|
||||||
ACCOUNT_NOT_FOUND: 'account_not_found',
|
|
||||||
ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found',
|
|
||||||
PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found',
|
|
||||||
ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique',
|
|
||||||
ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue',
|
|
||||||
PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type',
|
|
||||||
ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed',
|
|
||||||
ACCOUNT_PREDEFINED: 'account_predefined',
|
|
||||||
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
|
||||||
PREDEFINED_ACCOUNTS: 'predefined_accounts',
|
|
||||||
ACCOUNTS_HAVE_TRANSACTIONS: 'accounts_have_transactions',
|
|
||||||
CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE: 'close_account_and_to_account_not_same_type',
|
|
||||||
ACCOUNTS_NOT_FOUND: 'accounts_not_found',
|
|
||||||
}
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class AccountsService {
|
export default class AccountsService {
|
||||||
|
|||||||
16
server/src/services/Accounts/constants.ts
Normal file
16
server/src/services/Accounts/constants.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const ERRORS = {
|
||||||
|
ACCOUNT_NOT_FOUND: 'account_not_found',
|
||||||
|
ACCOUNT_TYPE_NOT_FOUND: 'account_type_not_found',
|
||||||
|
PARENT_ACCOUNT_NOT_FOUND: 'parent_account_not_found',
|
||||||
|
ACCOUNT_CODE_NOT_UNIQUE: 'account_code_not_unique',
|
||||||
|
ACCOUNT_NAME_NOT_UNIQUE: 'account_name_not_unqiue',
|
||||||
|
PARENT_ACCOUNT_HAS_DIFFERENT_TYPE: 'parent_has_different_type',
|
||||||
|
ACCOUNT_TYPE_NOT_ALLOWED_TO_CHANGE: 'account_type_not_allowed_to_changed',
|
||||||
|
ACCOUNT_PREDEFINED: 'account_predefined',
|
||||||
|
ACCOUNT_HAS_ASSOCIATED_TRANSACTIONS: 'account_has_associated_transactions',
|
||||||
|
PREDEFINED_ACCOUNTS: 'predefined_accounts',
|
||||||
|
ACCOUNTS_HAVE_TRANSACTIONS: 'accounts_have_transactions',
|
||||||
|
CLOSE_ACCOUNT_AND_TO_ACCOUNT_NOT_SAME_TYPE:
|
||||||
|
'close_account_and_to_account_not_same_type',
|
||||||
|
ACCOUNTS_NOT_FOUND: 'accounts_not_found',
|
||||||
|
};
|
||||||
@@ -3,7 +3,13 @@ import { difference, upperFirst, omit } from 'lodash';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { IContact, IContactNewDTO, IContactEditDTO } from 'interfaces';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
|
import {
|
||||||
|
IContact,
|
||||||
|
IContactNewDTO,
|
||||||
|
IContactEditDTO,
|
||||||
|
IContactsAutoCompleteFilter,
|
||||||
|
} from 'interfaces';
|
||||||
import JournalPoster from '../Accounting/JournalPoster';
|
import JournalPoster from '../Accounting/JournalPoster';
|
||||||
|
|
||||||
type TContactService = 'customer' | 'vendor';
|
type TContactService = 'customer' | 'vendor';
|
||||||
@@ -17,6 +23,9 @@ export default class ContactsService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
tenancy: TenancyService;
|
tenancy: TenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
@Inject('logger')
|
@Inject('logger')
|
||||||
logger: any;
|
logger: any;
|
||||||
|
|
||||||
@@ -166,11 +175,40 @@ export default class ContactsService {
|
|||||||
async getContact(
|
async getContact(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
contactId: number,
|
contactId: number,
|
||||||
contactService: TContactService
|
contactService?: TContactService
|
||||||
) {
|
) {
|
||||||
return this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
return this.getContactByIdOrThrowError(tenantId, contactId, contactService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve auto-complete contacts list.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IContactsAutoCompleteFilter} contactsFilter -
|
||||||
|
* @return {IContactAutoCompleteItem}
|
||||||
|
*/
|
||||||
|
async autocompleteContacts(
|
||||||
|
tenantId: number,
|
||||||
|
contactsFilter: IContactsAutoCompleteFilter
|
||||||
|
) {
|
||||||
|
const { Contact } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
// Dynamic list.
|
||||||
|
const dynamicList = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
Contact,
|
||||||
|
contactsFilter,
|
||||||
|
);
|
||||||
|
// Retrieve contacts list by the given query.
|
||||||
|
const contacts = await Contact.query().onBuild((builder) => {
|
||||||
|
if (contactsFilter.keyword) {
|
||||||
|
builder.where('display_name', 'LIKE', contactsFilter.keyword);
|
||||||
|
}
|
||||||
|
dynamicList.buildQuery()(builder);
|
||||||
|
builder.limit(contactsFilter.limit);
|
||||||
|
});
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve contacts or throw not found error if one of ids were not found
|
* Retrieve contacts or throw not found error if one of ids were not found
|
||||||
* on the storage.
|
* on the storage.
|
||||||
@@ -182,7 +220,7 @@ export default class ContactsService {
|
|||||||
async getContactsOrThrowErrorNotFound(
|
async getContactsOrThrowErrorNotFound(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
contactsIds: number[],
|
contactsIds: number[],
|
||||||
contactService: TContactService,
|
contactService: TContactService
|
||||||
) {
|
) {
|
||||||
const { Contact } = this.tenancy.models(tenantId);
|
const { Contact } = this.tenancy.models(tenantId);
|
||||||
const contacts = await Contact.query()
|
const contacts = await Contact.query()
|
||||||
@@ -240,10 +278,7 @@ export default class ContactsService {
|
|||||||
journal.fromTransactions(contactsTransactions);
|
journal.fromTransactions(contactsTransactions);
|
||||||
journal.removeEntries();
|
journal.removeEntries();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([journal.saveBalance(), journal.deleteEntries()]);
|
||||||
journal.saveBalance(),
|
|
||||||
journal.deleteEntries(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,7 +303,6 @@ export default class ContactsService {
|
|||||||
contactId,
|
contactId,
|
||||||
contactService
|
contactService
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should the opening balance date be required.
|
// Should the opening balance date be required.
|
||||||
if (!contact.openingBalanceAt && !openingBalanceAt) {
|
if (!contact.openingBalanceAt && !openingBalanceAt) {
|
||||||
throw new ServiceError(ERRORS.OPENING_BALANCE_DATE_REQUIRED);
|
throw new ServiceError(ERRORS.OPENING_BALANCE_DATE_REQUIRED);
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ export default class CustomersService {
|
|||||||
tenantId,
|
tenantId,
|
||||||
customerId,
|
customerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Retrieve the customer of throw not found service error.
|
// Retrieve the customer of throw not found service error.
|
||||||
await this.getCustomerByIdOrThrowError(tenantId, customerId);
|
await this.getCustomerByIdOrThrowError(tenantId, customerId);
|
||||||
|
|
||||||
@@ -375,7 +374,6 @@ export default class CustomersService {
|
|||||||
const salesInvoice = await saleInvoiceRepository.find({
|
const salesInvoice = await saleInvoiceRepository.find({
|
||||||
customer_id: customerId,
|
customer_id: customerId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (salesInvoice.length > 0) {
|
if (salesInvoice.length > 0) {
|
||||||
throw new ServiceError('customer_has_invoices');
|
throw new ServiceError('customer_has_invoices');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Service, Inject } from "typedi";
|
import { Service, Inject } from 'typedi';
|
||||||
import validator from 'is-my-json-valid';
|
import validator from 'is-my-json-valid';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
@@ -37,7 +37,11 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
* @param {number} viewId
|
* @param {number} viewId
|
||||||
* @return {Promise<IView>}
|
* @return {Promise<IView>}
|
||||||
*/
|
*/
|
||||||
private async getCustomViewOrThrowError(tenantId: number, viewId: number, model: IModel) {
|
private async getCustomViewOrThrowError(
|
||||||
|
tenantId: number,
|
||||||
|
viewId: number,
|
||||||
|
model: IModel
|
||||||
|
) {
|
||||||
const { viewRepository } = this.tenancy.repositories(tenantId);
|
const { viewRepository } = this.tenancy.repositories(tenantId);
|
||||||
const view = await viewRepository.findOneById(viewId, 'roles');
|
const view = await viewRepository.findOneById(viewId, 'roles');
|
||||||
|
|
||||||
@@ -67,8 +71,14 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
* @param {IFilterRole[]} filterRoles
|
* @param {IFilterRole[]} filterRoles
|
||||||
* @throws {ServiceError}
|
* @throws {ServiceError}
|
||||||
*/
|
*/
|
||||||
private validateRolesFieldsExistance(model: IModel, filterRoles: IFilterRole[]) {
|
private validateRolesFieldsExistance(
|
||||||
const invalidFieldsKeys = validateFilterRolesFieldsExistance(model, filterRoles);
|
model: IModel,
|
||||||
|
filterRoles: IFilterRole[]
|
||||||
|
) {
|
||||||
|
const invalidFieldsKeys = validateFilterRolesFieldsExistance(
|
||||||
|
model,
|
||||||
|
filterRoles
|
||||||
|
);
|
||||||
|
|
||||||
if (invalidFieldsKeys.length > 0) {
|
if (invalidFieldsKeys.length > 0) {
|
||||||
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
|
throw new ServiceError(ERRORS.FILTER_ROLES_FIELDS_NOT_FOUND);
|
||||||
@@ -100,17 +110,24 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamic listing.
|
* Dynamic listing.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {IModel} model
|
* @param {IModel} model - Model.
|
||||||
* @param {IDynamicListFilterDTO} filter
|
* @param {IDynamicListFilterDTO} filter - Dynamic filter DTO.
|
||||||
*/
|
*/
|
||||||
public async dynamicList(tenantId: number, model: IModel, filter: IDynamicListFilterDTO) {
|
public async dynamicList(
|
||||||
|
tenantId: number,
|
||||||
|
model: IModel,
|
||||||
|
filter: IDynamicListFilterDTO
|
||||||
|
) {
|
||||||
const dynamicFilter = new DynamicFilter(model);
|
const dynamicFilter = new DynamicFilter(model);
|
||||||
|
|
||||||
// Custom view filter roles.
|
// Custom view filter roles.
|
||||||
if (filter.customViewId) {
|
if (filter.customViewId) {
|
||||||
const view = await this.getCustomViewOrThrowError(tenantId, filter.customViewId, model);
|
const view = await this.getCustomViewOrThrowError(
|
||||||
|
tenantId,
|
||||||
|
filter.customViewId,
|
||||||
|
model
|
||||||
|
);
|
||||||
const viewFilter = new DynamicFilterViews(view);
|
const viewFilter = new DynamicFilterViews(view);
|
||||||
dynamicFilter.setFilter(viewFilter);
|
dynamicFilter.setFilter(viewFilter);
|
||||||
}
|
}
|
||||||
@@ -119,7 +136,8 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
this.validateSortColumnExistance(model, filter.columnSortBy);
|
this.validateSortColumnExistance(model, filter.columnSortBy);
|
||||||
|
|
||||||
const sortByFilter = new DynamicFilterSortBy(
|
const sortByFilter = new DynamicFilterSortBy(
|
||||||
filter.columnSortBy, filter.sortOrder
|
filter.columnSortBy,
|
||||||
|
filter.sortOrder
|
||||||
);
|
);
|
||||||
dynamicFilter.setFilter(sortByFilter);
|
dynamicFilter.setFilter(sortByFilter);
|
||||||
}
|
}
|
||||||
@@ -146,7 +164,12 @@ export default class DynamicListService implements IDynamicListService {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
public handlerErrorsToResponse(error: Error, req: Request, res: Response, next: NextFunction) {
|
public handlerErrorsToResponse(
|
||||||
|
error: Error,
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (error.errorType === 'sort_column_not_found') {
|
if (error.errorType === 'sort_column_not_found') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import AccountsService from 'services/Accounts/AccountsService';
|
import AccountsService from 'services/Accounts/AccountsService';
|
||||||
import ItemsService from 'services/Items/ItemsService';
|
import ItemsService from 'services/Items/ItemsService';
|
||||||
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import HasTenancyService from 'services/Tenancy/TenancyService';
|
import HasTenancyService from 'services/Tenancy/TenancyService';
|
||||||
import InventoryService from './Inventory';
|
import InventoryService from './Inventory';
|
||||||
|
|
||||||
@@ -45,6 +46,9 @@ export default class InventoryAdjustmentService {
|
|||||||
@Inject()
|
@Inject()
|
||||||
inventoryService: InventoryService;
|
inventoryService: InventoryService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
dynamicListService: DynamicListingService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the quick inventory adjustment DTO to model object.
|
* Transformes the quick inventory adjustment DTO to model object.
|
||||||
* @param {IQuickInventoryAdjustmentDTO} adjustmentDTO -
|
* @param {IQuickInventoryAdjustmentDTO} adjustmentDTO -
|
||||||
@@ -208,7 +212,7 @@ export default class InventoryAdjustmentService {
|
|||||||
await this.eventDispatcher.dispatch(events.inventoryAdjustment.onDeleted, {
|
await this.eventDispatcher.dispatch(events.inventoryAdjustment.onDeleted, {
|
||||||
tenantId,
|
tenantId,
|
||||||
inventoryAdjustmentId,
|
inventoryAdjustmentId,
|
||||||
oldInventoryAdjustment
|
oldInventoryAdjustment,
|
||||||
});
|
});
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
'[inventory_adjustment] the adjustment deleted successfully.',
|
'[inventory_adjustment] the adjustment deleted successfully.',
|
||||||
@@ -275,9 +279,18 @@ export default class InventoryAdjustmentService {
|
|||||||
}> {
|
}> {
|
||||||
const { InventoryAdjustment } = this.tenancy.models(tenantId);
|
const { InventoryAdjustment } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
InventoryAdjustment,
|
||||||
|
adjustmentsFilter
|
||||||
|
);
|
||||||
const { results, pagination } = await InventoryAdjustment.query()
|
const { results, pagination } = await InventoryAdjustment.query()
|
||||||
.withGraphFetched('entries.item')
|
.onBuild((query) => {
|
||||||
.withGraphFetched('adjustmentAccount')
|
query.withGraphFetched('entries.item');
|
||||||
|
query.withGraphFetched('adjustmentAccount');
|
||||||
|
|
||||||
|
dynamicFilter.buildQuery()(query);
|
||||||
|
})
|
||||||
.pagination(adjustmentsFilter.page - 1, adjustmentsFilter.pageSize);
|
.pagination(adjustmentsFilter.page - 1, adjustmentsFilter.pageSize);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import {
|
|||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
} from 'decorators/eventDispatcher';
|
} from 'decorators/eventDispatcher';
|
||||||
import events from 'subscribers/events';
|
import events from 'subscribers/events';
|
||||||
import { IItemsFilter, IItemsService, IItemDTO, IItem } from 'interfaces';
|
import {
|
||||||
|
IItemsFilter,
|
||||||
|
IItemsService,
|
||||||
|
IItemDTO,
|
||||||
|
IItem,
|
||||||
|
IItemsAutoCompleteFilter,
|
||||||
|
} from 'interfaces';
|
||||||
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
import DynamicListingService from 'services/DynamicListing/DynamicListService';
|
||||||
import TenancyService from 'services/Tenancy/TenancyService';
|
import TenancyService from 'services/Tenancy/TenancyService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
@@ -16,6 +22,7 @@ import {
|
|||||||
ACCOUNT_TYPE,
|
ACCOUNT_TYPE,
|
||||||
} from 'data/AccountTypes';
|
} from 'data/AccountTypes';
|
||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class ItemsService implements IItemsService {
|
export default class ItemsService implements IItemsService {
|
||||||
@Inject()
|
@Inject()
|
||||||
@@ -496,6 +503,34 @@ export default class ItemsService implements IItemsService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve auto-complete items list.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {IItemsAutoCompleteFilter} itemsFilter -
|
||||||
|
*/
|
||||||
|
public async autocompleteItems(
|
||||||
|
tenantId: number,
|
||||||
|
itemsFilter: IItemsAutoCompleteFilter
|
||||||
|
) {
|
||||||
|
const { Item } = this.tenancy.models(tenantId);
|
||||||
|
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||||
|
tenantId,
|
||||||
|
Item,
|
||||||
|
itemsFilter
|
||||||
|
);
|
||||||
|
const items = await Item.query().onBuild((builder) => {
|
||||||
|
builder.withGraphFetched('category');
|
||||||
|
|
||||||
|
dynamicFilter.buildQuery()(builder);
|
||||||
|
builder.limit(itemsFilter.limit);
|
||||||
|
});
|
||||||
|
|
||||||
|
// const autocompleteItems = this.transformAutoCompleteItems(items);
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transformAutoCompleteItems(item)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the given item or items have no associated invoices or bills.
|
* Validates the given item or items have no associated invoices or bills.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
|
|||||||
@@ -591,6 +591,7 @@ export default class BillPaymentsService {
|
|||||||
.onBuild((builder) => {
|
.onBuild((builder) => {
|
||||||
builder.withGraphFetched('vendor');
|
builder.withGraphFetched('vendor');
|
||||||
builder.withGraphFetched('paymentAccount');
|
builder.withGraphFetched('paymentAccount');
|
||||||
|
|
||||||
dynamicFilter.buildQuery()(builder);
|
dynamicFilter.buildQuery()(builder);
|
||||||
})
|
})
|
||||||
.pagination(billPaymentsFilter.page - 1, billPaymentsFilter.pageSize);
|
.pagination(billPaymentsFilter.page - 1, billPaymentsFilter.pageSize);
|
||||||
|
|||||||
@@ -27,17 +27,7 @@ import ItemsService from 'services/Items/ItemsService';
|
|||||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||||
import JournalCommands from 'services/Accounting/JournalCommands';
|
import JournalCommands from 'services/Accounting/JournalCommands';
|
||||||
import JournalPosterService from 'services/Sales/JournalPosterService';
|
import JournalPosterService from 'services/Sales/JournalPosterService';
|
||||||
|
import { ERRORS } from './constants';
|
||||||
const ERRORS = {
|
|
||||||
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
|
|
||||||
BILL_VENDOR_NOT_FOUND: 'BILL_VENDOR_NOT_FOUND',
|
|
||||||
BILL_ITEMS_NOT_PURCHASABLE: 'BILL_ITEMS_NOT_PURCHASABLE',
|
|
||||||
BILL_NUMBER_EXISTS: 'BILL_NUMBER_EXISTS',
|
|
||||||
BILL_ITEMS_NOT_FOUND: 'BILL_ITEMS_NOT_FOUND',
|
|
||||||
BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND',
|
|
||||||
NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS',
|
|
||||||
BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor bills services.
|
* Vendor bills services.
|
||||||
|
|||||||
10
server/src/services/Purchases/constants.ts
Normal file
10
server/src/services/Purchases/constants.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export const ERRORS = {
|
||||||
|
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
|
||||||
|
BILL_VENDOR_NOT_FOUND: 'BILL_VENDOR_NOT_FOUND',
|
||||||
|
BILL_ITEMS_NOT_PURCHASABLE: 'BILL_ITEMS_NOT_PURCHASABLE',
|
||||||
|
BILL_NUMBER_EXISTS: 'BILL_NUMBER_EXISTS',
|
||||||
|
BILL_ITEMS_NOT_FOUND: 'BILL_ITEMS_NOT_FOUND',
|
||||||
|
BILL_ENTRIES_IDS_NOT_FOUND: 'BILL_ENTRIES_IDS_NOT_FOUND',
|
||||||
|
NOT_PURCHASE_ABLE_ITEMS: 'NOT_PURCHASE_ABLE_ITEMS',
|
||||||
|
BILL_ALREADY_OPEN: 'BILL_ALREADY_OPEN',
|
||||||
|
};
|
||||||
@@ -29,20 +29,7 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
|||||||
import CustomersService from 'services/Contacts/CustomersService';
|
import CustomersService from 'services/Contacts/CustomersService';
|
||||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||||
import JournalPosterService from './JournalPosterService';
|
import JournalPosterService from './JournalPosterService';
|
||||||
import SaleInvoiceRepository from 'repositories/SaleInvoiceRepository';
|
import { ERRORS } from './constants';
|
||||||
|
|
||||||
const ERRORS = {
|
|
||||||
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
|
||||||
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
|
||||||
SALE_INVOICE_ALREADY_DELIVERED: 'SALE_INVOICE_ALREADY_DELIVERED',
|
|
||||||
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
|
||||||
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
|
||||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
|
||||||
INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT:
|
|
||||||
'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT',
|
|
||||||
INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES:
|
|
||||||
'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sales invoices service
|
* Sales invoices service
|
||||||
|
|||||||
12
server/src/services/Sales/constants.ts
Normal file
12
server/src/services/Sales/constants.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export const ERRORS = {
|
||||||
|
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||||
|
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
||||||
|
SALE_INVOICE_ALREADY_DELIVERED: 'SALE_INVOICE_ALREADY_DELIVERED',
|
||||||
|
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
||||||
|
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
||||||
|
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
||||||
|
INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT:
|
||||||
|
'INVOICE_AMOUNT_SMALLER_THAN_PAYMENT_AMOUNT',
|
||||||
|
INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES:
|
||||||
|
'INVOICE_HAS_ASSOCIATED_PAYMENT_ENTRIES',
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user