Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; import { MrpRun, PlannedOrder, Forecast, MasterProductionSchedule } from '../schemas'; import { BomRoutingService } from './bom-routing.service'; import { InventoryService } from '../../inventory/services/inventory.service'; @Injectable() export class MrpEngineService { constructor( @InjectModel(MrpRun.name) private readonly mrpRunModel: Model<MrpRun>, @InjectModel(PlannedOrder.name) private readonly plannedOrderModel: Model<PlannedOrder>, @InjectModel(Forecast.name) private readonly forecastModel: Model<Forecast>, @InjectModel(MasterProductionSchedule.name) private readonly mpsModel: Model<MasterProductionSchedule>, private readonly bomRoutingService: BomRoutingService, private readonly inventoryService: InventoryService ) {} async createForecast(tenantId: string, payload: any): Promise<Forecast> { const fc = new this.forecastModel({ tenantId, forecastCode: payload.forecastCode, itemCode: payload.itemCode, forecastDate: new Date(payload.forecastDate), quantity: payload.quantity, status: 'Active', }); return fc.save(); } async runMrp(tenantId: string, userId: string): Promise<MrpRun> { const run = new this.mrpRunModel({ tenantId, mrpRunCode: `MRP-${Date.now()}`, runDate: new Date(), runByUserId: userId, status: 'Running', }); await run.save(); try { // 1. Gather all active demand (forecasts + MPS lines) const forecasts = await this.forecastModel.find({ tenantId, status: 'Active' }).exec(); const mpsLines = await this.mpsModel.find({ tenantId, status: 'Active' }).exec(); const demands: { itemCode: string; qty: number; date: Date }[] = []; forecasts.forEach(f => demands.push({ itemCode: f.itemCode, qty: f.quantity, date: f.forecastDate })); mpsLines.forEach(m => demands.push({ itemCode: m.itemCode, qty: m.quantity, date: m.scheduleDate })); // 2. Process each demand line for (const demand of demands) { // Fetch current on hand balance const balances = await this.inventoryService.ledgerService.getBalances(tenantId, { itemCode: demand.itemCode }); const onHandQty = balances.reduce((sum, b) => sum + (b.quantityOnHand || 0), 0); // Fetch reorder and safety stock settings from item definition const itemDef = await this.inventoryService.itemService.getItemByCode(tenantId, demand.itemCode); const safetyStock = itemDef?.safetyStock || 0; const netRequirement = demand.qty + safetyStock - onHandQty; if (netRequirement > 0) { // Explode BOM lines const explodedComponents = await this.bomRoutingService.explodeBom(tenantId, demand.itemCode, netRequirement); if (explodedComponents.length > 0) { // Exploded components become Planned Purchase or Production Orders for (const comp of explodedComponents) { const compOrder = new this.plannedOrderModel({ tenantId, mrpRunId: run._id, itemCode: comp.componentItemCode, orderType: comp.isPhantom ? 'Production' : 'Purchase', quantity: comp.quantityNeeded, suggestedReleaseDate: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000), // 5 days lead time assumption suggestedDueDate: demand.date, status: 'Suggested', }); await compOrder.save(); } // Also create planned production order for the output item itself const outputOrder = new this.plannedOrderModel({ tenantId, mrpRunId: run._id, itemCode: demand.itemCode, orderType: 'Production', quantity: netRequirement, suggestedReleaseDate: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), // 3 days lead time suggestedDueDate: demand.date, status: 'Suggested', }); await outputOrder.save(); } else { // No BOM, suggest purchase order const purchaseOrder = new this.plannedOrderModel({ tenantId, mrpRunId: run._id, itemCode: demand.itemCode, orderType: 'Purchase', quantity: netRequirement, suggestedReleaseDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days purchase lead time suggestedDueDate: demand.date, status: 'Suggested', }); await purchaseOrder.save(); } } } run.status = 'Completed'; await run.save(); } catch (e: any) { run.status = 'Failed'; run.errorMessage = e.message; await run.save(); } return run; } async getPlannedOrders(tenantId: string): Promise<PlannedOrder[]> { return this.plannedOrderModel.find({ tenantId, status: 'Suggested' }).exec(); } } |