import { Knex } from 'knex'; import { omit, get, isNumber } from 'lodash'; import * as R from 'ramda'; import { ICreateWarehouseTransferDTO, IWarehouseTransfer, IWarehouseTransferCreate, IWarehouseTransferCreated, IWarehouseTransferEntryDTO, IInventoryItemCostMeta, } from '@/interfaces'; import { Service, Inject } from 'typedi'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import UnitOfWork from '@/services/UnitOfWork'; import events from '@/subscribers/events'; import ItemsEntriesService from '@/services/Items/ItemsEntriesService'; import { CommandWarehouseTransfer } from './CommandWarehouseTransfer'; import { InventoryItemCostService } from '@/services/Inventory/InventoryCostsService'; import { WarehouseTransferAutoIncrement } from './WarehouseTransferAutoIncrement'; @Service() export class CreateWarehouseTransfer extends CommandWarehouseTransfer { @Inject() private tenancy: HasTenancyService; @Inject() private uow: UnitOfWork; @Inject() private eventPublisher: EventPublisher; @Inject() private itemsEntries: ItemsEntriesService; @Inject() private inventoryItemCost: InventoryItemCostService; @Inject() private autoIncrementOrders: WarehouseTransferAutoIncrement; /** * Transformes the givne new warehouse transfer DTO to model. * @param {ICreateWarehouseTransferDTO} warehouseTransferDTO * @returns {IWarehouseTransfer} */ private transformDTOToModel = async ( tenantId: number, warehouseTransferDTO: ICreateWarehouseTransferDTO ): Promise => { const entries = await this.transformEntries( tenantId, warehouseTransferDTO, warehouseTransferDTO.entries ); // Retrieves the auto-increment the warehouse transfer number. const autoNextNumber = this.autoIncrementOrders.getNextTransferNumber(tenantId); // Warehouse transfer order transaction number. const transactionNumber = warehouseTransferDTO.transactionNumber || autoNextNumber; return { ...omit(warehouseTransferDTO, ['transferDelivered', 'transferInitiated']), transactionNumber, ...(warehouseTransferDTO.transferDelivered ? { transferDeliveredAt: new Date(), } : {}), ...(warehouseTransferDTO.transferDelivered || warehouseTransferDTO.transferInitiated ? { transferInitiatedAt: new Date(), } : {}), entries, }; }; /** * Assoc average cost to the entry that has no cost. * @param {Promise} inventoryItemsCostMap - * @param {IWarehouseTransferEntryDTO} entry - */ private transformEntryAssocAverageCost = R.curry( ( inventoryItemsCostMap: Map, entry: IWarehouseTransferEntryDTO ): IWarehouseTransferEntryDTO => { const itemValuation = inventoryItemsCostMap.get(entry.itemId); const itemCost = get(itemValuation, 'average', 0); return isNumber(entry.cost) ? entry : R.assoc('cost', itemCost, entry); } ); /** * Transformes warehouse transfer entries. * @param {number} tenantId * @param {ICreateWarehouseTransferDTO} warehouseTransferDTO * @param {IWarehouseTransferEntryDTO[]} entries * @returns {Promise} */ public transformEntries = async ( tenantId: number, warehouseTransferDTO: ICreateWarehouseTransferDTO, entries: IWarehouseTransferEntryDTO[] ): Promise => { const inventoryItemsIds = warehouseTransferDTO.entries.map((e) => e.itemId); // Retrieves the inventory items valuation map. const inventoryItemsCostMap = await this.inventoryItemCost.getItemsInventoryValuation( tenantId, inventoryItemsIds, warehouseTransferDTO.date ); // Assoc average cost to the entry. const assocAverageCost = this.transformEntryAssocAverageCost( inventoryItemsCostMap ); return R.map(assocAverageCost)(entries); }; /** * Authorize warehouse transfer before creating. * @param {number} tenantId * @param {ICreateWarehouseTransferDTO} warehouseTransferDTO */ public authorize = async ( tenantId: number, warehouseTransferDTO: ICreateWarehouseTransferDTO ) => { // Validate warehouse from and to should not be the same. this.validateWarehouseFromToNotSame(warehouseTransferDTO); // Retrieves the from warehouse or throw not found service error. const fromWarehouse = await this.getFromWarehouseOrThrow( tenantId, warehouseTransferDTO.fromWarehouseId ); // Retrieves the to warehouse or throw not found service error. const toWarehouse = await this.getToWarehouseOrThrow( tenantId, warehouseTransferDTO.toWarehouseId ); // Validates the not found entries items ids. const items = await this.itemsEntries.validateItemsIdsExistance( tenantId, warehouseTransferDTO.entries ); // Validate the items entries should be inventory type. this.validateItemsShouldBeInventory(items); }; /** * Creates a new warehouse transfer transaction. * @param {number} tenantId - * @param {ICreateWarehouseTransferDTO} warehouseDTO - * @returns {Promise} */ public createWarehouseTransfer = async ( tenantId: number, warehouseTransferDTO: ICreateWarehouseTransferDTO ): Promise => { const { WarehouseTransfer } = this.tenancy.models(tenantId); // Authorize warehouse transfer before creating. await this.authorize(tenantId, warehouseTransferDTO); // Transformes the warehouse transfer DTO to model. const warehouseTransferModel = await this.transformDTOToModel( tenantId, warehouseTransferDTO ); // Create warehouse transfer under unit-of-work. return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { // Triggers `onWarehouseTransferCreate` event. await this.eventPublisher.emitAsync(events.warehouseTransfer.onCreate, { trx, warehouseTransferDTO, tenantId, } as IWarehouseTransferCreate); // Stores the warehouse transfer transaction graph to the storage. const warehouseTransfer = await WarehouseTransfer.query( trx ).upsertGraphAndFetch({ ...warehouseTransferModel, }); // Triggers `onWarehouseTransferCreated` event. await this.eventPublisher.emitAsync(events.warehouseTransfer.onCreated, { trx, warehouseTransfer, warehouseTransferDTO, tenantId, } as IWarehouseTransferCreated); return warehouseTransfer; }); }; }