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 | import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; import { SubcontractOrder, ProductionOrder, ProductionOrderMaterial, ShopFloorExecution } from '../schemas'; import { EventBusService } from '../../../platform/events/event-bus.service'; @Injectable() export class CostingSubcontractService { constructor( @InjectModel(SubcontractOrder.name) private readonly subcontractModel: Model<SubcontractOrder>, @InjectModel(ProductionOrder.name) private readonly productionOrderModel: Model<ProductionOrder>, @InjectModel(ProductionOrderMaterial.name) private readonly orderMaterialModel: Model<ProductionOrderMaterial>, @InjectModel(ShopFloorExecution.name) private readonly executionModel: Model<ShopFloorExecution>, private readonly eventBus: EventBusService ) {} async createSubcontractOrder(tenantId: string, payload: any): Promise<SubcontractOrder> { const sub = new this.subcontractModel({ tenantId, subcontractOrderNumber: payload.subcontractOrderNumber, vendorId: payload.vendorId, itemCode: payload.itemCode, plannedQuantity: payload.plannedQuantity, serviceChargeMinor: payload.serviceChargeMinor || 0, bomId: new Types.ObjectId(payload.bomId), dueDate: new Date(payload.dueDate), status: 'draft', }); return sub.save(); } async recordSubcontractReceipt(tenantId: string, id: string, receivedQty: number): Promise<SubcontractOrder> { const sub = await this.subcontractModel.findOneAndUpdate( { _id: id, tenantId }, { $inc: { receivedQuantity: receivedQty }, status: 'partially_received' }, { new: true } ).exec(); if (!sub) throw new BadRequestException('Subcontract order not found'); if (sub.receivedQuantity >= sub.plannedQuantity) { sub.status = 'completed'; await sub.save(); } return sub; } async calculateProductionVariance(tenantId: string, orderId: string): Promise<{ estimatedCostMinor: number; actualCostMinor: number; varianceMinor: number; materialsVarianceMinor: number; laborVarianceMinor: number; }> { const order = await this.productionOrderModel.findOne({ _id: orderId, tenantId }).exec(); if (!order) throw new BadRequestException('Production order not found'); const materials = await this.orderMaterialModel.find({ tenantId, productionOrderId: order._id }).exec(); const executions = await this.executionModel.find({ tenantId, productionOrderId: order._id }).exec(); // Sum actual material costs (assuming 10 INR per item minor units = 1000 minor) const materialCost = materials.reduce((sum, m) => sum + (m.issuedQuantity - m.returnedQuantity) * 1000, 0); // Sum actual labor costs (assuming 15 INR per hour minor = 1500 minor) const laborCost = executions.reduce((sum, e) => sum + (e.laborHours * 1500), 0); // Estimate based on planned quantity (e.g. 800 INR standard per unit = 80000 minor) const estimate = order.plannedQuantity * 80000; const actual = materialCost + laborCost; const variance = actual - estimate; return { estimatedCostMinor: estimate, actualCostMinor: actual, varianceMinor: variance, materialsVarianceMinor: materialCost, laborVarianceMinor: laborCost, }; } async publishCostPosting(tenantId: string, orderId: string, type: 'wip' | 'receipt' | 'variance' | 'scrap', amountMinor: number): Promise<void> { const eventName = `manufacturing.${type}-posting-ready.v1`; await this.eventBus.publish( eventName, { tenantId, productionOrderId: orderId, amountMinor, timestamp: new Date(), }, tenantId ); } } |