feat: Re-compute the given items cost job.

feat: Optimize the architecture.
This commit is contained in:
Ahmed Bouhuolia
2020-08-18 02:28:08 +02:00
parent 4e68a7db71
commit d423365a19
44 changed files with 1605 additions and 798 deletions

View File

@@ -0,0 +1,25 @@
import { Router } from 'express'
import basicAuth from 'express-basic-auth';
import agendash from 'agendash'
import { Container } from 'typedi'
import config from '@/../config/config'
export default class AgendashController {
static router() {
const router = Router();
const agendaInstance = Container.get('agenda')
router.use('/dash',
basicAuth({
users: {
[config.agendash.user]: config.agendash.password,
},
challenge: true,
}),
agendash(agendaInstance)
);
return router;
}
}

View File

@@ -1,512 +0,0 @@
import express from 'express';
import { check, query, validationResult } from 'express-validator';
import { difference } from 'lodash';
import fs from 'fs';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import {
mapViewRolesToConditionals,
mapFilterRolesToDynamicFilter,
} from '@/lib/ViewRolesBuilder';
import {
DynamicFilter,
DynamicFilterSortBy,
DynamicFilterViews,
DynamicFilterFilterRoles,
} from '@/lib/DynamicFilter';
import Logger from '@/services/Logger';
const fsPromises = fs.promises;
export default {
/**
* Router constructor.
*/
router() {
const router = express.Router();
router.post('/:id',
this.editItem.validation,
asyncMiddleware(this.editItem.handler)
);
router.post('/',
this.newItem.validation,
asyncMiddleware(this.newItem.handler)
);
router.delete('/:id',
this.deleteItem.validation,
asyncMiddleware(this.deleteItem.handler)
);
router.delete('/',
this.bulkDeleteItems.validation,
asyncMiddleware(this.bulkDeleteItems.handler)
);
router.get('/',
this.listItems.validation,
asyncMiddleware(this.listItems.handler)
);
return router;
},
/**
* Creates a new item.
*/
newItem: {
validation: [
check('name').exists(),
check('type').exists().trim().escape()
.isIn(['service', 'non-inventory', 'inventory']),
check('sku').optional({ nullable: true }).trim().escape(),
check('purchasable').exists().isBoolean().toBoolean(),
check('sellable').exists().isBoolean().toBoolean(),
check('cost_price').exists().isNumeric().toFloat(),
check('sell_price').exists().isNumeric().toFloat(),
check('cost_account_id').exists().isInt().toInt(),
check('sell_account_id').exists().isInt().toInt(),
check('inventory_account_id')
.if(check('type').equals('inventory'))
.exists()
.isInt()
.toInt(),
check('sell_description').optional().trim().escape(),
check('cost_description').optional().trim().escape(),
check('category_id').optional({ nullable: true }).isInt().toInt(),
check('custom_fields').optional().isArray({ min: 1 }),
check('custom_fields.*.key').exists().isNumeric().toInt(),
check('custom_fields.*.value').exists(),
check('note').optional(),
check('media_ids').optional().isArray(),
check('media_ids.*').exists().isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { user } = req;
const form = {
custom_fields: [],
media_ids: [],
...req.body,
};
const {
Account,
Resource,
ResourceField,
ItemCategory,
Item,
MediaLink,
} = req.models;
const errorReasons = [];
const costAccountPromise = Account.query().findById(form.cost_account_id);
const sellAccountPromise = Account.query().findById(form.sell_account_id);
const inventoryAccountPromise = (form.type === 'inventory')
? Account.query().findById(form.inventory_account_id) : null;
const itemCategoryPromise = (form.category_id)
? ItemCategory.query().findById(form.category_id) : null;
// Validate the custom fields key and value type.
if (form.custom_fields.length > 0) {
const customFieldsKeys = form.custom_fields.map((field) => field.key);
// Get resource id than get all resource fields.
const resource = await Resource.where('name', 'items').fetch();
const fields = await ResourceField.query((builder) => {
builder.where('resource_id', resource.id);
builder.whereIn('key', customFieldsKeys);
}).fetchAll();
const storedFieldsKey = fields.map((f) => f.attributes.key);
// Get all not defined resource fields.
const notFoundFields = difference(customFieldsKeys, storedFieldsKey);
if (notFoundFields.length > 0) {
errorReasons.push({ type: 'FIELD_KEY_NOT_FOUND', code: 150, fields: notFoundFields });
}
}
const [
costAccount,
sellAccount,
itemCategory,
inventoryAccount,
] = await Promise.all([
costAccountPromise,
sellAccountPromise,
itemCategoryPromise,
inventoryAccountPromise,
]);
if (!costAccount) {
errorReasons.push({ type: 'COST_ACCOUNT_NOT_FOUND', code: 100 });
}
if (!sellAccount) {
errorReasons.push({ type: 'SELL_ACCOUNT_NOT_FOUND', code: 120 });
}
if (!itemCategory && form.category_id) {
errorReasons.push({ type: 'ITEM_CATEGORY_NOT_FOUND', code: 140 });
}
if (!inventoryAccount && form.type === 'inventory') {
errorReasons.push({ type: 'INVENTORY_ACCOUNT_NOT_FOUND', code: 150 });
}
if (errorReasons.length > 0) {
return res.boom.badRequest(null, { errors: errorReasons });
}
const bulkSaveMediaLinks = [];
const item = await Item.query().insertAndFetch({
name: form.name,
type: form.type,
sku: form.sku,
cost_price: form.cost_price,
sell_price: form.sell_price,
sell_account_id: form.sell_account_id,
cost_account_id: form.cost_account_id,
currency_code: form.currency_code,
category_id: form.category_id,
user_id: user.id,
note: form.note,
});
form.media_ids.forEach((mediaId) => {
const oper = MediaLink.query().insert({
model_name: 'Item',
media_id: mediaId,
model_id: item.id,
});
bulkSaveMediaLinks.push(oper);
});
// Save the media links.
await Promise.all([
...bulkSaveMediaLinks,
]);
return res.status(200).send({ id: item.id });
},
},
/**
* Edit the given item.
*/
editItem: {
validation: [
check('name').exists(),
check('type')
.exists()
.trim()
.escape()
.isIn(['product', 'service']),
check('cost_price').exists().isNumeric(),
check('sell_price').exists().isNumeric(),
check('cost_account_id').exists().isInt(),
check('sell_account_id').exists().isInt(),
check('category_id').optional({ nullable: true }).isInt().toInt(),
check('note').optional().trim().escape(),
check('attachment').optional(),
check('sell_description').optional().trim().escape(),
check('cost_description').optional().trim().escape(),
check('purchasable').exists().isBoolean().toBoolean(),
check('sellable').exists().isBoolean().toBoolean(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const { Account, Item, ItemCategory, MediaLink } = req.models;
const { id } = req.params;
const form = {
custom_fields: [],
...req.body,
};
const item = await Item.query().findById(id).withGraphFetched('media');
if (!item) {
return res.boom.notFound(null, {
errors: [{ type: 'ITEM.NOT.FOUND', code: 100 }],
});
}
const errorReasons = [];
const costAccountPromise = Account.query().findById(form.cost_account_id);
const sellAccountPromise = Account.query().findById(form.sell_account_id);
const itemCategoryPromise = (form.category_id)
? ItemCategory.query().findById(form.category_id) : null;
const [costAccount, sellAccount, itemCategory] = await Promise.all([
costAccountPromise, sellAccountPromise, itemCategoryPromise,
]);
if (!costAccount) {
errorReasons.push({ type: 'COST_ACCOUNT_NOT_FOUND', code: 100 });
}
if (!sellAccount) {
errorReasons.push({ type: 'SELL_ACCOUNT_NOT_FOUND', code: 120 });
}
if (!itemCategory && form.category_id) {
errorReasons.push({ type: 'ITEM_CATEGORY_NOT_FOUND', code: 140 });
}
const attachment = req.files && req.files.attachment ? req.files.attachment : null;
const attachmentsMimes = ['image/png', 'image/jpeg'];
// Validate the attachment.
if (attachment && attachmentsMimes.indexOf(attachment.mimetype) === -1) {
errorReasons.push({ type: 'ATTACHMENT.MINETYPE.NOT.SUPPORTED', code: 160 });
}
if (errorReasons.length > 0) {
return res.boom.badRequest(null, { errors: errorReasons });
}
if (attachment) {
const publicPath = 'storage/app/public/';
const tenantPath = `${publicPath}${req.organizationId}`;
try {
await fsPromises.unlink(`${tenantPath}/${item.attachmentFile}`);
} catch (error) {
Logger.log('error', 'Delete item attachment file delete failed.', { error });
}
try {
await attachment.mv(`${tenantPath}/${attachment.md5}.png`);
} catch (error) {
return res.status(400).send({
errors: [{ type: 'ATTACHMENT.UPLOAD.FAILED', code: 600 }],
});
}
}
const updatedItem = await Item.query().findById(id).patch({
name: form.name,
type: form.type,
cost_price: form.cost_price,
sell_price: form.sell_price,
currency_code: form.currency_code,
sell_account_id: form.sell_account_id,
cost_account_id: form.cost_account_id,
category_id: form.category_id,
note: form.note,
});
// Save links of new inserted media that associated to the item model.
const itemMediaIds = item.media.map((m) => m.id);
const newInsertedMedia = difference(form.media_ids, itemMediaIds);
const bulkSaveMediaLink = [];
newInsertedMedia.forEach((mediaId) => {
const oper = MediaLink.query().insert({
model_name: 'Journal',
model_id: manualJournal.id,
media_id: mediaId,
});
bulkSaveMediaLink.push(oper);
});
await Promise.all([ ...newInsertedMedia ]);
return res.status(200).send({ id: updatedItem.id });
},
},
/**
* Delete the given item from the storage.
*/
deleteItem: {
validation: [],
async handler(req, res) {
const { id } = req.params;
const { Item } = req.models;
const item = await Item.query().findById(id);
if (!item) {
return res.boom.notFound(null, {
errors: [{ type: 'ITEM_NOT_FOUND', code: 100 }],
});
}
// Delete the fucking the given item id.
await Item.query().findById(item.id).delete();
if (item.attachmentFile) {
const publicPath = 'storage/app/public/';
const tenantPath = `${publicPath}${req.organizationId}`;
try {
await fsPromises.unlink(`${tenantPath}/${item.attachmentFile}`);
} catch (error) {
Logger.log('error', 'Delete item attachment file delete failed.', { error });
}
}
return res.status(200).send();
},
},
/**
* Bulk delete the given items ids.
*/
bulkDeleteItems: {
validation: [
query('ids').isArray({ min: 2 }),
query('ids.*').isNumeric().toInt(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const filter = { ids: [], ...req.query };
const { Item } = req.models;
const items = await Item.query().whereIn('id', filter.ids);
const storedItemsIds = items.map((a) => a.id);
const notFoundItems = difference(filter.ids, storedItemsIds);
// Validate the not found items.
if (notFoundItems.length > 0) {
return res.status(404).send({
errors: [{ type: 'ITEMS.NOT.FOUND', code: 200, ids: notFoundItems }],
});
}
// Delete the given items ids.
await Item.query().whereIn('id', storedItemsIds).delete();
return res.status(200).send();
},
},
/**
* Retrive the list items with pagination meta.
*/
listItems: {
validation: [
query('column_sort_order').optional().isIn(['created_at', 'name', 'amount', 'sku']),
query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(),
],
async handler(req, res) {
const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) {
return res.boom.badData(null, {
code: 'validation_error', ...validationErrors,
});
}
const errorReasons = [];
const viewConditions = [];
const { Resource, Item, View } = req.models;
const itemsResource = await Resource.query()
.where('name', 'items')
.withGraphFetched('fields')
.first();
if (!itemsResource) {
return res.status(400).send({
errors: [{ type: 'ITEMS_RESOURCE_NOT_FOUND', code: 200 }],
});
}
const filter = {
column_sort_order: '',
sort_order: '',
page: 1,
page_size: 10,
custom_view_id: null,
filter_roles: [],
...req.query,
};
if (filter.stringified_filter_roles) {
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
}
const view = await View.query().onBuild((builder) => {
if (filter.custom_view_id) {
builder.where('id', filter.custom_view_id);
} else {
builder.where('favourite', true);
}
builder.where('resource_id', itemsResource.id);
builder.withGraphFetched('roles.field');
builder.withGraphFetched('columns');
builder.first();
});
const resourceFieldsKeys = itemsResource.fields.map((c) => c.key);
const dynamicFilter = new DynamicFilter(Item.tableName);
// Dynamic filter with view roles.
if (view && view.roles.length > 0) {
const viewFilter = new DynamicFilterViews(
mapViewRolesToConditionals(view.roles),
view.rolesLogicExpression,
);
if (!viewFilter.validateFilterRoles()) {
errorReasons.push({ type: 'VIEW.LOGIC.EXPRESSION.INVALID', code: 400 });
}
dynamicFilter.setFilter(viewFilter);
}
// Dynamic filter with filter roles.
if (filter.filter_roles.length > 0) {
// Validate the accounts resource fields.
const filterRoles = new DynamicFilterFilterRoles(
mapFilterRolesToDynamicFilter(filter.filter_roles),
itemsResource.fields,
);
dynamicFilter.setFilter(filterRoles);
if (filterRoles.validateFilterRoles().length > 0) {
errorReasons.push({ type: 'ITEMS.RESOURCE.HAS.NO.FIELDS', code: 500 });
}
}
// Dynamic filter with column sort order.
if (filter.column_sort_order) {
if (resourceFieldsKeys.indexOf(filter.column_sort_order) === -1) {
errorReasons.push({ type: 'COLUMN.SORT.ORDER.NOT.FOUND', code: 300 });
}
const sortByFilter = new DynamicFilterSortBy(
filter.column_sort_order,
filter.sort_order,
);
dynamicFilter.setFilter(sortByFilter);
}
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
const items = await Item.query().onBuild((builder) => {
builder.withGraphFetched('costAccount');
builder.withGraphFetched('sellAccount');
builder.withGraphFetched('inventoryAccount');
builder.withGraphFetched('category');
dynamicFilter.buildQuery()(builder);
}).pagination(filter.page - 1, filter.page_size);
return res.status(200).send({
items,
...(view) && {
customViewId: view.id,
viewColumns: view.columns,
viewConditions,
},
});
},
},
};

View File

@@ -0,0 +1,409 @@
import { Router, Request, Response } from 'express';
import { check, param, query, oneOf, ValidationChain } from 'express-validator';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import validateMiddleware from '@/http/middleware/validateMiddleware';
import ItemsService from '@/services/Items/ItemsService';
import DynamicListing from '@/services/DynamicListing/DynamicListing';
import DynamicListingBuilder from '@/services/DynamicListing/DynamicListingBuilder';
import { dynamicListingErrorsToResponse } from '@/services/DynamicListing/hasDynamicListing';
export default class ItemsController {
/**
* Router constructor.
*/
static router() {
const router = Router();
router.post(
'/',
this.validateItemSchema,
validateMiddleware,
asyncMiddleware(this.validateCategoryExistance),
asyncMiddleware(this.validateCostAccountExistance),
asyncMiddleware(this.validateSellAccountExistance),
asyncMiddleware(this.validateInventoryAccountExistance),
asyncMiddleware(this.validateItemNameExistance),
asyncMiddleware(this.newItem),
);
router.post(
'/:id', [
...this.validateItemSchema,
...this.validateSpecificItemSchema,
],
validateMiddleware,
asyncMiddleware(this.validateItemExistance),
asyncMiddleware(this.validateCategoryExistance),
asyncMiddleware(this.validateCostAccountExistance),
asyncMiddleware(this.validateSellAccountExistance),
asyncMiddleware(this.validateInventoryAccountExistance),
asyncMiddleware(this.validateItemNameExistance),
asyncMiddleware(this.editItem),
);
router.delete(
'/:id',
this.validateSpecificItemSchema,
validateMiddleware,
asyncMiddleware(this.validateItemExistance),
asyncMiddleware(this.deleteItem),
);
router.get(
'/:id',
this.validateSpecificItemSchema,
validateMiddleware,
asyncMiddleware(this.validateItemExistance),
asyncMiddleware(this.getItem),
);
router.get(
'/',
this.validateListQuerySchema,
validateMiddleware,
asyncMiddleware(this.listItems),
);
return router;
}
/**
* Validate item schema.
*
* @param {Request} req -
* @param {Response} res -
* @return {ValidationChain[]} - validation chain.
*/
static get validateItemSchema(
req: Request,
res: Response,
next: Function,
): ValidationChain[] {
return [
check('name').exists(),
check('type').exists().trim().escape()
.isIn(['service', 'non-inventory', 'inventory']),
check('sku').optional({ nullable: true }).trim().escape(),
// Purchase attributes.
check('purchasable').optional().isBoolean().toBoolean(),
check('cost_price')
.if(check('purchasable').equals('true'))
.exists()
.isNumeric()
.toFloat(),
check('cost_account_id')
.if(check('purchasable').equals('true'))
.exists()
.isInt()
.toInt(),
// Sell attributes.
check('sellable').optional().isBoolean().toBoolean(),
check('sell_price')
.if(check('sellable').equals('true'))
.exists()
.isNumeric()
.toFloat(),
check('sell_account_id')
.if(check('sellable').equals('true'))
.exists()
.isInt()
.toInt(),
check('inventory_account_id')
.if(check('type').equals('inventory'))
.exists()
.isInt()
.toInt(),
check('sell_description').optional({ nullable: true }).trim().escape(),
check('cost_description').optional({ nullable: true }).trim().escape(),
check('category_id').optional({ nullable: true }).isInt().toInt(),
check('note').optional(),
check('media_ids').optional().isArray(),
check('media_ids.*').exists().isNumeric().toInt(),
];
}
/**
* Validate specific item params schema.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
static get validateSpecificItemSchema(): ValidationChain[] {
return [
param('id').exists().isNumeric().toInt(),
];
}
static get validateListQuerySchema() {
return [
query('column_sort_order').optional().isIn(['created_at', 'name', 'amount', 'sku']),
query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(),
]
}
/**
* Validates the given item existance on the storage.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
static async validateItemExistance(req: Request, res: Response, next: Function) {
const { Item } = req.models;
const itemId: number = req.params.id;
const foundItem = await Item.query().findById(itemId);
if (!foundItem) {
return res.status(400).send({
errors: [{ type: 'ITEM.NOT.FOUND', code: 100 }],
});
}
next();
}
/**
* Validate wether the given item name already exists on the storage.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
static async validateItemNameExistance(req: Request, res: Response, next: Function) {
const { Item } = req.models;
const item = req.body;
const itemId: number = req.params.id;
const foundItems: [] = await Item.query().onBuild((builder: any) => {
builder.where('name', item.name);
if (itemId) {
builder.whereNot('id', itemId);
}
});
if (foundItems.length > 0) {
return res.status(400).send({
errors: [{ type: 'ITEM.NAME.ALREADY.EXISTS', code: 210 }],
});
}
next();
}
/**
* Validate wether the given category existance on the storage.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
static async validateCategoryExistance(req: Request, res: Response, next: Function) {
const { ItemCategory } = req.models;
const item = req.body;
if (item.category_id) {
const foundCategory = await ItemCategory.query().findById(item.category_id);
if (!foundCategory) {
return res.status(400).send({
errors: [{ type: 'ITEM_CATEGORY.NOT.FOUND', code: 140 }],
});
}
}
next();
}
/**
* Validate wether the given cost account exists on the storage.
* @param {Request} req
* @param {Response} res
* @param {Function} next
*/
static async validateCostAccountExistance(req: Request, res: Response, next: Function) {
const { Account } = req.models;
const item = req.body;
if (item.cost_account_id) {
const foundAccount = await Account.query().findById(item.cost_account_id);
if (!foundAccount) {
return res.status(400).send({
errors: [{ type: 'COST.ACCOUNT.NOT.FOUND', code: 120 }],
});
}
}
next();
}
/**
* Validate wether the given sell account exists on the storage.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
static async validateSellAccountExistance(req: Request, res: Response, next: Function) {
const { Account } = req.models;
const item = req.body;
if (item.sell_account_id) {
const foundAccount = await Account.query().findById(item.sell_account_id);
if (!foundAccount) {
return res.status(400).send({
errors: [{ type: 'SELL.ACCOUNT.NOT.FOUND', code: 130 }],
});
}
}
next();
}
/**
* Validates wether the given inventory account exists on the storage.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
static async validateInventoryAccountExistance(req: Request, res: Response, next: Function) {
const { Account } = req.models;
const item = req.body;
if (item.inventory_account_id) {
const foundAccount = await Account.query().findById(item.inventory_account_id);
if (!foundAccount) {
return res.status(400).send({
errors: [{ type: 'INVENTORY.ACCOUNT.NOT.FOUND', code: 200}],
});
}
}
next();
}
/**
* Stores the given item details to the storage.
* @param {Request} req
* @param {Response} res
*/
static async newItem(req: Request, res: Response,) {
const item = req.body;
const storedItem = await ItemsService.newItem(item);
return res.status(200).send({ id: storedItem.id });
}
/**
* Updates the given item details on the storage.
* @param {Request} req
* @param {Response} res
*/
static async editItem(req: Request, res: Response) {
const item = req.body;
const itemId: number = req.params.id;
const updatedItem = await ItemsService.editItem(item, itemId);
return res.status(200).send({ id: itemId });
}
/**
* Deletes the given item from the storage.
* @param {Request} req
* @param {Response} res
*/
static async deleteItem(req: Request, res: Response) {
const itemId: number = req.params.id;
await ItemsService.deleteItem(itemId);
return res.status(200).send({ id: itemId });
}
/**
* Retrieve details the given item id.
* @param {Request} req
* @param {Response} res
* @return {Response}
*/
static async getItem(req: Request, res: Response) {
const itemId: number = req.params.id;
const storedItem = await ItemsService.getItemWithMetadata(itemId);
return res.status(200).send({ item: storedItem });
}
/**
* Listing items with pagination metadata.
* @param {Request} req
* @param {Response} res
*/
static async listItems(req: Request, res: Response) {
const filter = {
filter_roles: [],
sort_order: 'asc',
page: 1,
page_size: 10,
...req.query,
};
if (filter.stringified_filter_roles) {
filter.filter_roles = JSON.parse(filter.stringified_filter_roles);
}
const { Resource, Item, View } = req.models;
const resource = await Resource.query()
.remember()
.where('name', 'items')
.withGraphFetched('fields')
.first();
if (!resource) {
return res.status(400).send({
errors: [{ type: 'ITEMS.RESOURCE.NOT_FOUND', code: 200 }],
});
}
const viewMeta = await View.query()
.modify('allMetadata')
.modify('specificOrFavourite', filter.custom_view_id)
.where('resource_id', resource.id)
.first();
const listingBuilder = new DynamicListingBuilder();
const errorReasons = [];
listingBuilder.addModelClass(Item);
listingBuilder.addCustomViewId(filter.custom_view_id);
listingBuilder.addFilterRoles(filter.filter_roles);
listingBuilder.addSortBy(filter.sort_by, filter.sort_order);
listingBuilder.addView(viewMeta);
const dynamicListing = new DynamicListing(listingBuilder);
if (dynamicListing instanceof Error) {
const errors = dynamicListingErrorsToResponse(dynamicListing);
errorReasons.push(...errors);
}
if (errorReasons.length > 0) {
return res.status(400).send({ errors: errorReasons });
}
const items = await Item.query().onBuild((builder: any) => {
builder.withGraphFetched('costAccount');
builder.withGraphFetched('sellAccount');
builder.withGraphFetched('inventoryAccount');
builder.withGraphFetched('category');
dynamicListing.buildQuery()(builder);
return builder;
}).pagination(filter.page - 1, filter.page_size);
return res.status(200).send({
items: {
...items,
...(viewMeta
? {
viewMeta: {
custom_view_id: viewMeta.id,
view_columns: viewMeta.columns,
}
}
: {}),
},
});
}
}

View File

@@ -1,8 +1,6 @@
import { Router, Request, Response } from 'express';
import InventoryService from '@/services/Inventory/Inventory';
export default class Ping {
/**
* Router constur
*/
@@ -17,46 +15,14 @@ export default class Ping {
}
/**
*
* Handle the ping request.
* @param {Request} req
* @param {Response} res
*/
static async ping(req: Request, res: Response) {
const result = await InventoryService.trackingInventoryLotsCost([
{
id: 1,
date: '2020-02-02',
direction: 'IN',
itemId: 1,
quantity: 100,
rate: 10,
transactionType: 'Bill',
transactionId: 1,
remaining: 100,
},
{
id: 2,
date: '2020-02-02',
direction: 'OUT',
itemId: 1,
quantity: 80,
rate: 10,
transactionType: 'SaleInvoice',
transactionId: 1,
},
{
id: 3,
date: '2020-02-02',
direction: 'OUT',
itemId: 2,
quantity: 500,
rate: 10,
transactionType: 'SaleInvoice',
transactionId: 2,
},
]);
return res.status(200).send({ id: 1231231 });
static async ping(req: Request, res: Response)
{
return res.status(200).send({
server: true,
});
}
}

View File

@@ -23,14 +23,14 @@ import Media from '@/http/controllers/Media';
import JWTAuth from '@/http/middleware/jwtAuth';
import TenancyMiddleware from '@/http/middleware/TenancyMiddleware';
import Ping from '@/http/controllers/Ping';
import Agendash from '@/http/controllers/Agendash';
export default (app) => {
// app.use('/api/oauth2', OAuth2.router());
app.use('/api/auth', Authentication.router());
app.use('/api/invite', InviteUsers.router());
app.use('/api/ping', Ping.router());
const dashboard = express.Router();
const dashboard = express.Router();
dashboard.use(JWTAuth);
dashboard.use(TenancyMiddleware);
@@ -53,6 +53,8 @@ export default (app) => {
dashboard.use('/api/resources', Resources.router());
dashboard.use('/api/exchange_rates', ExchangeRates.router());
dashboard.use('/api/media', Media.router());
dashboard.use('/api/ping', Ping.router());
app.use('/', dashboard);
app.use('/agendash', Agendash.router());
app.use('/', dashboard);
};