From 097b9fdb3ad0b07af508155e949b55510bda9607 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sun, 10 Jan 2021 13:37:02 +0200 Subject: [PATCH] fix: issues in inventory adjustments service. --- .../Inventory/InventoryAdjustments.ts | 89 +++++++++++++------ ...1809_create_inventory_adjustments_table.js | 3 + ...ate_inventory_adjustments_entries_table.js | 5 +- server/src/interfaces/InventoryAdjustment.ts | 14 +-- server/src/models/InventoryAdjustment.js | 31 ++++++- server/src/models/InventoryAdjustmentEntry.js | 23 +++-- .../Inventory/InventoryAdjustmentService.ts | 65 +++++++++++--- 7 files changed, 172 insertions(+), 58 deletions(-) diff --git a/server/src/api/controllers/Inventory/InventoryAdjustments.ts b/server/src/api/controllers/Inventory/InventoryAdjustments.ts index 1d951a3e9..bf6553f23 100644 --- a/server/src/api/controllers/Inventory/InventoryAdjustments.ts +++ b/server/src/api/controllers/Inventory/InventoryAdjustments.ts @@ -2,8 +2,8 @@ import { Inject, Service } from 'typedi'; import { Router, Request, Response, NextFunction } from 'express'; import { check, param } from 'express-validator'; import { ServiceError } from 'exceptions'; -import BaseController from "../BaseController"; -import InventoryAdjustmentService from "services/Inventory/InventoryAdjustmentService"; +import BaseController from '../BaseController'; +import InventoryAdjustmentService from 'services/Inventory/InventoryAdjustmentService'; @Service() export default class InventoryAdjustmentsController extends BaseController { @@ -18,12 +18,10 @@ export default class InventoryAdjustmentsController extends BaseController { router.delete( '/:id', - [ - param('id').exists().isNumeric().toInt(), - ], + [param('id').exists().isNumeric().toInt()], this.validationResult, this.asyncMiddleware(this.deleteInventoryAdjustment.bind(this)), - this.handleServiceErrors, + this.handleServiceErrors ); router.post( '/quick', @@ -46,14 +44,24 @@ export default class InventoryAdjustmentsController extends BaseController { get validatateQuickAdjustment() { return [ check('date').exists().isISO8601(), - check('type').exists().isIn(['increment', 'decrement', 'value_adjustment']), + 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('new_quantity').optional().isInt(), - check('new_value').optional().toFloat(), + check('quantity') + .if(check('type').exists().isIn(['increment', 'decrement'])) + .exists() + .isInt() + .toInt(), + check('cost') + .if(check('type').exists().isIn(['increment'])) + .exists() + .isFloat() + .toInt(), ]; } @@ -63,15 +71,24 @@ export default class InventoryAdjustmentsController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - async createQuickInventoryAdjustment(req: Request, res: Response, next: NextFunction) { - const { tenantId } = req; + async createQuickInventoryAdjustment( + req: Request, + res: Response, + next: NextFunction + ) { + const { tenantId, user } = req; const quickInventoryAdjustment = this.matchedBodyData(req); + console.log(quickInventoryAdjustment); try { - await this.inventoryAdjustmentService - .createQuickAdjustment(tenantId, quickInventoryAdjustment); - + 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) { @@ -85,14 +102,20 @@ export default class InventoryAdjustmentsController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - async deleteInventoryAdjustment(req: Request, res: Response, next: NextFunction) { + async deleteInventoryAdjustment( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId } = req; const { id: adjustmentId } = req.params; try { - await this.inventoryAdjustmentService - .deleteInventoryAdjustment(tenantId, adjustmentId); - + await this.inventoryAdjustmentService.deleteInventoryAdjustment( + tenantId, + adjustmentId + ); + return res.status(200).send({ message: 'The inventory adjustment has been deleted successfully.', }); @@ -103,11 +126,15 @@ export default class InventoryAdjustmentsController extends BaseController { /** * Retrieve the inventory adjustments paginated list. - * @param {Request} req - * @param {Response} res - * @param {NextFunction} next + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next */ - async getInventoryAdjustments(req: Request, res: Response, next: NextFunction) { + async getInventoryAdjustments( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId } = req; const filter = { page: 1, @@ -119,9 +146,11 @@ export default class InventoryAdjustmentsController extends BaseController { const { pagination, inventoryAdjustments, - } = await this.inventoryAdjustmentService - .getInventoryAdjustments(tenantId, filter); - + } = await this.inventoryAdjustmentService.getInventoryAdjustments( + tenantId, + filter + ); + return res.status(200).send({ inventoy_adjustments: inventoryAdjustments, pagination: this.transfromToResponse(pagination), @@ -147,7 +176,11 @@ export default class InventoryAdjustmentsController extends BaseController { if (error instanceof ServiceError) { if (error.errorType === 'INVENTORY_ADJUSTMENT_NOT_FOUND') { return res.status(400).send({ - errors: [{ type: 'INVENTORY_ADJUSTMENT.NOT.FOUND', code: 100 }], + errors: [{ + type: 'INVENTORY_ADJUSTMENT.NOT.FOUND', + code: 100, + message: 'The inventory adjustment not found.' + }], }); } if (error.errorType === 'NOT_FOUND') { @@ -163,10 +196,10 @@ export default class InventoryAdjustmentsController extends BaseController { 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 }], } + { errors: [{ type: 'ITEM_SHOULD_BE_INVENTORY_TYPE', code: 300 }] } ); } } next(error); } -} \ No newline at end of file +} diff --git a/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js b/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js index b0b2351dd..76e23f391 100644 --- a/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js +++ b/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js @@ -4,9 +4,12 @@ exports.up = function(knex) { table.increments(); table.date('date').index(); table.string('type').index(); + table.integer('adjustment_account_id').unsigned().references('id').inTable('accounts'); table.string('reason'); table.string('reference_no').index(); table.string('description'); + table.integer('user_id').unsigned(); + table.timestamps(); }); }; diff --git a/server/src/database/migrations/20200810121810_create_inventory_adjustments_entries_table.js b/server/src/database/migrations/20200810121810_create_inventory_adjustments_entries_table.js index e58402ff2..10143f622 100644 --- a/server/src/database/migrations/20200810121810_create_inventory_adjustments_entries_table.js +++ b/server/src/database/migrations/20200810121810_create_inventory_adjustments_entries_table.js @@ -5,8 +5,9 @@ exports.up = function(knex) { table.integer('adjustment_id').unsigned().index().references('id').inTable('inventory_adjustments'); table.integer('index').unsigned(); table.integer('item_id').unsigned().index().references('id').inTable('items'); - table.decimal('new_quantity').unsigned(); - table.decimal('new_cost').unsigned(); + table.integer('quantity'); + table.decimal('cost').unsigned(); + table.decimal('value').unsigned(); }); }; diff --git a/server/src/interfaces/InventoryAdjustment.ts b/server/src/interfaces/InventoryAdjustment.ts index d42ccf905..7ae92f701 100644 --- a/server/src/interfaces/InventoryAdjustment.ts +++ b/server/src/interfaces/InventoryAdjustment.ts @@ -9,8 +9,8 @@ export interface IQuickInventoryAdjustmentDTO { description: string; referenceNo: string; itemId: number; - newQuantity: number; - newValue: number; + quantity: number; + cost: number; }; export interface IInventoryAdjustment { @@ -20,7 +20,8 @@ export interface IInventoryAdjustment { reason: string; description: string; referenceNo: string; - entries: IInventoryAdjustmentEntry[] + entries: IInventoryAdjustmentEntry[]; + userId: number; }; export interface IInventoryAdjustmentEntry { @@ -28,9 +29,10 @@ export interface IInventoryAdjustmentEntry { adjustmentId?: number, index: number, itemId: number; - newQuantity: number; - newValue: number; -} + quantity?: number; + cost?: number; + value?: number; +}; export interface IInventoryAdjustmentsFilter{ page: number, diff --git a/server/src/models/InventoryAdjustment.js b/server/src/models/InventoryAdjustment.js index 5edf37e4c..bbb31e86e 100644 --- a/server/src/models/InventoryAdjustment.js +++ b/server/src/models/InventoryAdjustment.js @@ -9,21 +9,44 @@ export default class InventoryAdjustment extends TenantModel { return 'inventory_adjustments'; } + /** + * Timestamps columns. + */ + get timestamps() { + return ['created_at']; + } + /** * Relationship mapping. */ static get relationMappings() { - const Account = require('models/InventoryAdjustmentEntry'); - + const InventoryAdjustmentEntry = require('models/InventoryAdjustmentEntry'); + const Account = require('models/Account'); + return { + /** + * Adjustment entries. + */ entries: { - relation: Model.BelongsToOneRelation, - modelClass: Account.default, + relation: Model.HasManyRelation, + modelClass: InventoryAdjustmentEntry.default, join: { from: 'inventory_adjustments.id', to: 'inventory_adjustments_entries.adjustmentId', }, }, + + /** + * Inventory adjustment account. + */ + adjustmentAccount: { + relation: Model.BelongsToOneRelation, + modelClass: Account.default, + join: { + from: 'inventory_adjustments.adjustmentAccountId', + to: 'accounts.id', + }, + }, }; } } diff --git a/server/src/models/InventoryAdjustmentEntry.js b/server/src/models/InventoryAdjustmentEntry.js index eb0e72b8c..2e7159fcd 100644 --- a/server/src/models/InventoryAdjustmentEntry.js +++ b/server/src/models/InventoryAdjustmentEntry.js @@ -3,7 +3,7 @@ import TenantModel from 'models/TenantModel'; export default class InventoryAdjustmentEntry extends TenantModel { /** - * Table name + * Table name. */ static get tableName() { return 'inventory_adjustments_entries'; @@ -13,17 +13,30 @@ export default class InventoryAdjustmentEntry extends TenantModel { * Relationship mapping. */ static get relationMappings() { - const Account = require('models/InventoryAdjustment'); - + const InventoryAdjustment = require('models/InventoryAdjustment'); + const Item = require('models/Item'); + return { - entries: { + inventoryAdjustment: { relation: Model.BelongsToOneRelation, - modelClass: Account.default, + modelClass: InventoryAdjustment.default, join: { from: 'inventory_adjustments_entries.adjustmentId', to: 'inventory_adjustments.id', }, }, + + /** + * Entry item. + */ + item: { + relation: Model.BelongsToOneRelation, + modelClass: Item.default, + join: { + from: 'inventory_adjustments_entries.itemId', + to: 'items.id', + }, + }, }; } } diff --git a/server/src/services/Inventory/InventoryAdjustmentService.ts b/server/src/services/Inventory/InventoryAdjustmentService.ts index 5e02df7f4..7f243f361 100644 --- a/server/src/services/Inventory/InventoryAdjustmentService.ts +++ b/server/src/services/Inventory/InventoryAdjustmentService.ts @@ -9,7 +9,8 @@ import { IQuickInventoryAdjustmentDTO, IInventoryAdjustment, IPaginationMeta, - IInventoryAdjustmentsFilter + IInventoryAdjustmentsFilter, + ISystemUser, } from 'interfaces'; import events from 'subscribers/events'; import AccountsService from 'services/Accounts/AccountsService'; @@ -44,16 +45,26 @@ export default class InventoryAdjustmentService { * @return {IInventoryAdjustment} */ transformQuickAdjToModel( - adjustmentDTO: IQuickInventoryAdjustmentDTO + adjustmentDTO: IQuickInventoryAdjustmentDTO, + authorizedUser: ISystemUser ): IInventoryAdjustment { return { - ...omit(adjustmentDTO, ['newQuantity', 'newCost', 'itemId']), + ...omit(adjustmentDTO, ['quantity', 'cost', 'itemId']), + userId: authorizedUser.id, entries: [ { index: 1, itemId: adjustmentDTO.itemId, - newQuantity: adjustmentDTO.newQuantity, - newCost: adjustmentDTO.newCost, + ...(['increment', 'decrement'].indexOf(adjustmentDTO.type) !== -1 + ? { + quantity: adjustmentDTO.quantity, + } + : {}), + ...('increment' === adjustmentDTO.type + ? { + cost: adjustmentDTO.cost, + } + : {}), }, ], }; @@ -97,10 +108,18 @@ export default class InventoryAdjustmentService { */ async createQuickAdjustment( tenantId: number, - quickAdjustmentDTO: IQuickInventoryAdjustmentDTO - ) { + quickAdjustmentDTO: IQuickInventoryAdjustmentDTO, + authorizedUser: ISystemUser + ): Promise { const { InventoryAdjustment } = this.tenancy.models(tenantId); + this.logger.info( + '[inventory_adjustment] trying to create quick adjustment..', + { + tenantId, + quickAdjustmentDTO, + } + ); // Retrieve the adjustment account or throw not found error. const adjustmentAccount = await this.accountsService.getAccountOrThrowError( tenantId, @@ -116,10 +135,10 @@ export default class InventoryAdjustmentService { // Transform the DTO to inventory adjustment model. const invAdjustmentObject = this.transformQuickAdjToModel( - quickAdjustmentDTO + quickAdjustmentDTO, + authorizedUser ); - - await InventoryAdjustment.query().upsertGraph({ + const inventoryAdjustment = await InventoryAdjustment.query().upsertGraph({ ...invAdjustmentObject, }); // Triggers `onInventoryAdjustmentQuickCreated` event. @@ -129,6 +148,14 @@ export default class InventoryAdjustmentService { tenantId, } ); + this.logger.info( + '[inventory_adjustment] quick adjustment created successfully.', + { + tenantId, + quickAdjustmentDTO, + } + ); + return inventoryAdjustment; } /** @@ -150,6 +177,10 @@ export default class InventoryAdjustmentService { InventoryAdjustment, } = this.tenancy.models(tenantId); + this.logger.info('[inventory_adjustment] trying to delete adjustment.', { + tenantId, + inventoryAdjustmentId, + }); // Deletes the inventory adjustment entries. await InventoryAdjustmentEntry.query() .where('adjustment_id', inventoryAdjustmentId) @@ -162,12 +193,19 @@ export default class InventoryAdjustmentService { await this.eventDispatcher.dispatch(events.inventoryAdjustment.onDeleted, { tenantId, }); + this.logger.info( + '[inventory_adjustment] the adjustment deleted successfully.', + { + tenantId, + inventoryAdjustmentId, + } + ); } /** * Retrieve the inventory adjustments paginated list. - * @param {number} tenantId - * @param {IInventoryAdjustmentsFilter} adjustmentsFilter + * @param {number} tenantId + * @param {IInventoryAdjustmentsFilter} adjustmentsFilter */ async getInventoryAdjustments( tenantId: number, @@ -179,7 +217,8 @@ export default class InventoryAdjustmentService { const { InventoryAdjustment } = this.tenancy.models(tenantId); const { results, pagination } = await InventoryAdjustment.query() - .withGraphFetched('entries') + .withGraphFetched('entries.item') + .withGraphFetched('adjustmentAccount') .pagination(adjustmentsFilter.page - 1, adjustmentsFilter.pageSize); return {