diff --git a/server/src/api/controllers/Inventory/InventoryAdjustments.ts b/server/src/api/controllers/Inventory/InventoryAdjustments.ts index e08f721f3..8889a556e 100644 --- a/server/src/api/controllers/Inventory/InventoryAdjustments.ts +++ b/server/src/api/controllers/Inventory/InventoryAdjustments.ts @@ -16,6 +16,13 @@ export default class InventoryAdjustmentsController extends BaseController { router() { const router = Router(); + router.post( + '/:id/publish', + [param('id').exists().isNumeric().toInt()], + this.validationResult, + this.asyncMiddleware(this.publishInventoryAdjustment.bind(this)), + this.handleServiceErrors + ); router.delete( '/:id', [param('id').exists().isNumeric().toInt()], @@ -62,6 +69,7 @@ export default class InventoryAdjustmentsController extends BaseController { .exists() .isFloat() .toInt(), + check('publish').default(false).isBoolean().toBoolean(), ]; } @@ -124,6 +132,34 @@ export default class InventoryAdjustmentsController extends BaseController { } } + /** + * 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 inventory adjustments paginated list. * @param {Request} req 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 76e23f391..4774fcd23 100644 --- a/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js +++ b/server/src/database/migrations/20200810121809_create_inventory_adjustments_table.js @@ -9,6 +9,7 @@ exports.up = function(knex) { table.string('reference_no').index(); table.string('description'); table.integer('user_id').unsigned(); + table.date('published_at'); table.timestamps(); }); }; diff --git a/server/src/interfaces/InventoryAdjustment.ts b/server/src/interfaces/InventoryAdjustment.ts index e9a4402ad..ee17b73aa 100644 --- a/server/src/interfaces/InventoryAdjustment.ts +++ b/server/src/interfaces/InventoryAdjustment.ts @@ -1,9 +1,8 @@ - type IAdjustmentTypes = 'increment' | 'decrement'; export interface IQuickInventoryAdjustmentDTO { date: Date | string; - type: IAdjustmentTypes, + type: IAdjustmentTypes; adjustmentAccountId: number; reason: string; description: string; @@ -11,31 +10,33 @@ export interface IQuickInventoryAdjustmentDTO { itemId: number; quantity: number; cost: number; -}; + publish: boolean; +} export interface IInventoryAdjustment { - id?: number, + id?: number; date: Date | string; adjustmentAccountId: number; reason: string; description: string; referenceNo: string; - inventoryDirection?: 'IN' | 'OUT', + inventoryDirection?: 'IN' | 'OUT'; entries: IInventoryAdjustmentEntry[]; userId: number; -}; + publishedAt?: Date|null; +} export interface IInventoryAdjustmentEntry { - id?: number, - adjustmentId?: number, - index: number, + id?: number; + adjustmentId?: number; + index: number; itemId: number; quantity?: number; cost?: number; value?: number; -}; +} -export interface IInventoryAdjustmentsFilter{ - page: number, - pageSize: number, -}; \ No newline at end of file +export interface IInventoryAdjustmentsFilter { + page: number; + pageSize: number; +} diff --git a/server/src/models/InventoryAdjustment.js b/server/src/models/InventoryAdjustment.js index df16a1a5e..9f2a7d779 100644 --- a/server/src/models/InventoryAdjustment.js +++ b/server/src/models/InventoryAdjustment.js @@ -20,7 +20,7 @@ export default class InventoryAdjustment extends TenantModel { * Virtual attributes. */ static get virtualAttributes() { - return ['inventoryDirection']; + return ['inventoryDirection', 'isPublished']; } /** @@ -30,6 +30,14 @@ export default class InventoryAdjustment extends TenantModel { return InventoryAdjustment.getInventoryDirection(this.type); } + /** + * Detarmines whether the adjustment is published. + * @return {boolean} + */ + get isPublished() { + return !!this.publishedAt; + } + static getInventoryDirection(type) { const directions = { 'increment': 'IN', diff --git a/server/src/services/Inventory/InventoryAdjustmentService.ts b/server/src/services/Inventory/InventoryAdjustmentService.ts index b448514bd..3fed1f326 100644 --- a/server/src/services/Inventory/InventoryAdjustmentService.ts +++ b/server/src/services/Inventory/InventoryAdjustmentService.ts @@ -1,5 +1,6 @@ import { Inject, Service } from 'typedi'; import { omit } from 'lodash'; +import moment from 'moment'; import { EventDispatcher, EventDispatcherInterface, @@ -54,16 +55,21 @@ export default class InventoryAdjustmentService { authorizedUser: ISystemUser ): IInventoryAdjustment { return { - ...omit(adjustmentDTO, ['quantity', 'cost', 'itemId']), + ...omit(adjustmentDTO, ['quantity', 'cost', 'itemId', 'publish']), userId: authorizedUser.id, + ...(adjustmentDTO.publish + ? { + publishedAt: moment().toMySqlDateTime(), + } + : {}), entries: [ { index: 1, itemId: adjustmentDTO.itemId, ...('increment' === adjustmentDTO.type ? { - quantity: adjustmentDTO.quantity, - cost: adjustmentDTO.cost, + quantity: adjustmentDTO.quantity, + cost: adjustmentDTO.cost, } : {}), ...('decrement' === adjustmentDTO.type @@ -212,6 +218,50 @@ export default class InventoryAdjustmentService { ); } + /** + * Publish the inventory adjustment transaction. + * @param tenantId + * @param inventoryAdjustmentId + */ + async publishInventoryAdjustment( + tenantId: number, + inventoryAdjustmentId: number + ): Promise { + const { InventoryAdjustment } = this.tenancy.models(tenantId); + + // Retrieve the inventory adjustment or throw not found service error. + const oldInventoryAdjustment = await this.getInventoryAdjustmentOrThrowError( + tenantId, + inventoryAdjustmentId + ); + this.logger.info('[inventory_adjustment] trying to publish adjustment.', { + tenantId, + inventoryAdjustmentId, + }); + // Publish the inventory adjustment transaction. + await InventoryAdjustment.query() + .findById(inventoryAdjustmentId) + .patch({ + publishedAt: moment().toMySqlDateTime(), + }); + + // Retrieve the inventory adjustment after the modification. + const inventoryAdjustment = await InventoryAdjustment.query() + .findById(inventoryAdjustmentId) + .withGraphFetched('entries'); + + // Triggers `onInventoryAdjustmentDeleted` event. + await this.eventDispatcher.dispatch( + events.inventoryAdjustment.onPublished, + { + tenantId, + inventoryAdjustmentId, + inventoryAdjustment, + oldInventoryAdjustment, + } + ); + } + /** * Retrieve the inventory adjustments paginated list. * @param {number} tenantId @@ -246,7 +296,7 @@ export default class InventoryAdjustmentService { async writeInventoryTransactions( tenantId: number, inventoryAdjustment: IInventoryAdjustment, - override: boolean = false, + override: boolean = false ): Promise { // Gets the next inventory lot number. const lotNumber = this.inventoryService.getNextLotNumber(tenantId); diff --git a/server/src/subscribers/Inventory/Inventory.ts b/server/src/subscribers/Inventory/Inventory.ts index 848519c97..d346aef43 100644 --- a/server/src/subscribers/Inventory/Inventory.ts +++ b/server/src/subscribers/Inventory/Inventory.ts @@ -30,7 +30,7 @@ export class InventorySubscriber { if (dependsComputeJobs.length === 0) { this.startingDate = null; - await this.saleInvoicesCost.scheduleWriteJournalEntries( + await this.saleInvoicesCost.scheduleWriteJournalEntries( tenantId, startingDate ); diff --git a/server/src/subscribers/Inventory/InventoryAdjustment.ts b/server/src/subscribers/Inventory/InventoryAdjustment.ts index d454f8226..cd39d39a7 100644 --- a/server/src/subscribers/Inventory/InventoryAdjustment.ts +++ b/server/src/subscribers/Inventory/InventoryAdjustment.ts @@ -27,6 +27,9 @@ export default class InventoryAdjustmentsSubscriber { tenantId, inventoryAdjustment, }) { + // Can't continue if the inventory adjustment is not published. + if (!inventoryAdjustment.isPublished) { return; } + await this.inventoryAdjustment.writeInventoryTransactions( tenantId, inventoryAdjustment @@ -39,11 +42,29 @@ export default class InventoryAdjustmentsSubscriber { @On(events.inventoryAdjustment.onDeleted) async handleRevertInventoryTransactionsOnceDeleted({ tenantId, - inventoryAdjustmentId + inventoryAdjustmentId, + oldInventoryTransaction, }) { + // Can't continue if the inventory adjustment is not published. + if (!oldInventoryTransaction.isPublished) { return; } + await this.inventoryAdjustment.revertInventoryTransactions( tenantId, inventoryAdjustmentId, ); } + + /** + * Handles writing inventory transactions once the quick adjustment created. + */ + @On(events.inventoryAdjustment.onPublished) + async handleWriteInventoryTransactionsOncePublished({ + tenantId, + inventoryAdjustment, + }) { + await this.inventoryAdjustment.writeInventoryTransactions( + tenantId, + inventoryAdjustment + ) + } } \ No newline at end of file diff --git a/server/src/subscribers/events.ts b/server/src/subscribers/events.ts index 0e55b0406..62c41d7c2 100644 --- a/server/src/subscribers/events.ts +++ b/server/src/subscribers/events.ts @@ -193,9 +193,13 @@ export default { onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted' }, + /** + * Inventory adjustment service. + */ inventoryAdjustment: { onCreated: 'onInventoryAdjustmentCreated', onQuickCreated: 'onInventoryAdjustmentQuickCreated', onDeleted: 'onInventoryAdjustmentDeleted', + onPublished: 'onInventoryAdjustmentPublished', } }