Files
bigcapital/packages/server/src/api/controllers/Inventory/InventoryAdjustments.ts
2023-02-03 11:57:50 +02:00

346 lines
9.2 KiB
TypeScript

import { Inject, Service } from 'typedi';
import { Router, Request, Response, NextFunction } from 'express';
import { check, query, param } from 'express-validator';
import { ServiceError } from '@/exceptions';
import BaseController from '../BaseController';
import InventoryAdjustmentService from '@/services/Inventory/InventoryAdjustmentService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { AbilitySubject, InventoryAdjustmentAction } from '@/interfaces';
import CheckPolicies from '../../middleware/CheckPolicies';
@Service()
export default class InventoryAdjustmentsController extends BaseController {
@Inject()
inventoryAdjustmentService: InventoryAdjustmentService;
@Inject()
dynamicListService: DynamicListingService;
/**
* Router constructor.
*/
router() {
const router = Router();
router.post(
'/:id/publish',
CheckPolicies(
InventoryAdjustmentAction.EDIT,
AbilitySubject.InventoryAdjustment
),
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.publishInventoryAdjustment.bind(this)),
this.handleServiceErrors
);
router.delete(
'/:id',
CheckPolicies(
InventoryAdjustmentAction.DELETE,
AbilitySubject.InventoryAdjustment
),
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.deleteInventoryAdjustment.bind(this)),
this.handleServiceErrors
);
router.post(
'/quick',
CheckPolicies(
InventoryAdjustmentAction.CREATE,
AbilitySubject.InventoryAdjustment
),
this.validatateQuickAdjustment,
this.validationResult,
this.asyncMiddleware(this.createQuickInventoryAdjustment.bind(this)),
this.handleServiceErrors
);
router.get(
'/:id',
CheckPolicies(
InventoryAdjustmentAction.VIEW,
AbilitySubject.InventoryAdjustment
),
[param('id').exists().isNumeric().toInt()],
this.validationResult,
this.asyncMiddleware(this.getInventoryAdjustment.bind(this)),
this.handleServiceErrors
);
router.get(
'/',
CheckPolicies(
InventoryAdjustmentAction.VIEW,
AbilitySubject.InventoryAdjustment
),
[...this.validateListQuerySchema],
this.validationResult,
this.asyncMiddleware(this.getInventoryAdjustments.bind(this)),
this.dynamicListService.handlerErrorsToResponse,
this.handleServiceErrors
);
return router;
}
/**
* Validate list query schema
*/
get validateListQuerySchema() {
return [
query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(),
];
}
/**
* Quick inventory adjustment validation schema.
*/
get validatateQuickAdjustment() {
return [
check('date').exists().isISO8601(),
check('type')
.exists()
.isIn(['increment', 'decrement', 'value_adjustment']),
check('reference_no').exists(),
check('adjustment_account_id').exists().isInt().toInt(),
check('reason').exists().isString().exists(),
check('description').optional().isString(),
check('item_id').exists().isInt().toInt(),
check('quantity')
.if(check('type').exists().isIn(['increment', 'decrement']))
.exists()
.isInt()
.toInt(),
check('cost')
.if(check('type').exists().isIn(['increment']))
.exists()
.isFloat()
.toInt(),
check('publish').default(false).isBoolean().toBoolean(),
check('warehouse_id').optional({ nullable: true }).isNumeric().toInt(),
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
/**
* Creates a quick inventory adjustment.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async createQuickInventoryAdjustment(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId, user } = req;
const quickInventoryAdjustment = this.matchedBodyData(req);
try {
const inventoryAdjustment =
await this.inventoryAdjustmentService.createQuickAdjustment(
tenantId,
quickInventoryAdjustment,
user
);
return res.status(200).send({
id: inventoryAdjustment.id,
message: 'The inventory adjustment has been created successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Deletes the given inventory adjustment transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async deleteInventoryAdjustment(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: adjustmentId } = req.params;
try {
await this.inventoryAdjustmentService.deleteInventoryAdjustment(
tenantId,
adjustmentId
);
return res.status(200).send({
id: adjustmentId,
message: 'The inventory adjustment has been deleted successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Publish the given inventory adjustment transaction.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async publishInventoryAdjustment(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: adjustmentId } = req.params;
try {
await this.inventoryAdjustmentService.publishInventoryAdjustment(
tenantId,
adjustmentId
);
return res.status(200).send({
id: adjustmentId,
message: 'The inventory adjustment has been published successfully.',
});
} catch (error) {
next(error);
}
}
/**
* Retrieve the specific inventory adjustment transaction of the given id.
* @param {Request} req -
* @param {Response} res -
* @param {NextFunction} next -
*/
async getInventoryAdjustment(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const { id: adjustmentId } = req.params;
try {
const inventoryAdjustment =
await this.inventoryAdjustmentService.getInventoryAdjustment(
tenantId,
adjustmentId
);
return res.status(200).send({
data: this.transfromToResponse(inventoryAdjustment),
});
} catch (error) {
next(error);
}
}
/**
* Retrieve the inventory adjustments paginated list.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
async getInventoryAdjustments(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req;
const filter = {
page: 1,
pageSize: 12,
columnSortBy: 'created_at',
sortOrder: 'desc',
filterRoles: [],
...this.matchedQueryData(req),
};
try {
const { pagination, inventoryAdjustments } =
await this.inventoryAdjustmentService.getInventoryAdjustments(
tenantId,
filter
);
return res.status(200).send({
inventoy_adjustments: inventoryAdjustments,
pagination: this.transfromToResponse(pagination),
});
} catch (error) {
next(error);
}
}
/**
* Handles service errors.
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
*/
private handleServiceErrors(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof ServiceError) {
if (error.errorType === 'INVENTORY_ADJUSTMENT_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'INVENTORY_ADJUSTMENT.NOT.FOUND',
code: 100,
message: 'The inventory adjustment not found.',
},
],
});
}
if (error.errorType === 'NOT_FOUND') {
return res.status(400).send({
errors: [{ type: 'ITEM.NOT.FOUND', code: 140 }],
});
}
if (error.errorType === 'account_not_found') {
return res.boom.notFound('The given account not found.', {
errors: [{ type: 'ACCOUNT.NOT.FOUND', code: 100 }],
});
}
if (error.errorType === 'ITEM_SHOULD_BE_INVENTORY_TYPE') {
return res.boom.badRequest(
'You could not make adjustment on item has no inventory type.',
{ errors: [{ type: 'ITEM_SHOULD_BE_INVENTORY_TYPE', code: 300 }] }
);
}
if (error.errorType === 'INVENTORY_ADJUSTMENT_ALREADY_PUBLISHED') {
return res.boom.badRequest('', {
errors: [
{ type: 'INVENTORY_ADJUSTMENT_ALREADY_PUBLISHED', code: 400 },
],
});
}
if (error.errorType === 'TRANSACTIONS_DATE_LOCKED') {
return res.boom.badRequest(null, {
errors: [
{
type: 'TRANSACTIONS_DATE_LOCKED',
code: 4900,
data: { ...error.payload },
},
],
});
}
}
next(error);
}
}