All files / src/domains/manufacturing/services costing-subcontract.service.ts

0% Statements 0/37
0% Branches 0/28
0% Functions 0/7
0% Lines 0/31

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
    );
  }
}