All files / src/domains/assets/services asset-maintenance.service.ts

0% Statements 0/41
0% Branches 0/20
0% Functions 0/6
0% Lines 0/36

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                                                                                                                                                                                                                   
import { Injectable, BadRequestException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { Asset, AssetWorkOrder, MaintenanceSchedule } from '../schemas';
 
@Injectable()
export class AssetMaintenanceService {
  constructor(
    @InjectModel(Asset.name)
    private readonly assetModel: Model<Asset>,
    @InjectModel(AssetWorkOrder.name)
    private readonly workOrderModel: Model<AssetWorkOrder>,
    @InjectModel(MaintenanceSchedule.name)
    private readonly scheduleModel: Model<MaintenanceSchedule>
  ) {}
 
  async createSchedule(tenantId: string, data: any): Promise<MaintenanceSchedule> {
    const nextDueAt = new Date();
    nextDueAt.setDate(nextDueAt.getDate() + (data.calendarIntervalDays || 30));
 
    const schedule = new this.scheduleModel({
      ...data,
      tenantId,
      nextDueAt
    });
    return schedule.save();
  }
 
  async createWorkOrder(
    tenantId: string,
    assetId: string,
    type: string,
    priority: string,
    estimatedCostMinor: number
  ): Promise<AssetWorkOrder> {
    const count = await this.workOrderModel.countDocuments({ tenantId }).exec();
    const workOrderNumber = `WO-${String(count + 1).padStart(6, '0')}`;
 
    const wo = new this.workOrderModel({
      tenantId,
      workOrderNumber,
      assetId: new Types.ObjectId(assetId),
      workOrderType: type,
      priority,
      status: 'draft',
      estimatedCostMinor
    });
 
    await this.assetModel.updateOne({ _id: assetId }, { status: 'under_maintenance' });
 
    return wo.save();
  }
 
  async assignWorkOrder(tenantId: string, id: string, technicianId: string): Promise<AssetWorkOrder> {
    const wo = await this.workOrderModel.findOne({ _id: id, tenantId }).exec();
    if (!wo) throw new BadRequestException('Work order not found');
 
    wo.status = 'assigned';
    wo.assignedTechnicianId = technicianId;
    return wo.save();
  }
 
  async completeWorkOrder(
    tenantId: string,
    id: string,
    actualCostMinor: number,
    partsCostMinor: number,
    downtimeHours: number,
    notes?: string
  ): Promise<AssetWorkOrder> {
    const wo = await this.workOrderModel.findOne({ _id: id, tenantId }).exec();
    if (!wo) throw new BadRequestException('Work order not found');
 
    wo.status = 'completed';
    wo.actualCostMinor = actualCostMinor;
    wo.partsCostMinor = partsCostMinor;
    wo.downtimeHours = downtimeHours;
    wo.completionNotes = notes;
    wo.completedAt = new Date();
    await wo.save();
 
    // Revert asset status to available
    await this.assetModel.updateOne({ _id: wo.assetId }, {
      status: 'available',
      nextMaintenanceAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // Next run in 30 days
    });
 
    return wo;
  }
 
  // Spare parts checks (Integrates with Inventory ATP limits checks via events or simple check rules)
  async checkAndReserveSpareParts(
    tenantId: string,
    woId: string,
    partsList: Array<{ itemCode: string; qty: number }>
  ): Promise<boolean> {
    // In production, we publish an event like `inventory.reservation-requested.v1`
    // Or check stock directly from database collections.
    // For this implementation, we return true as we trigger ATP check rules
    for (const part of partsList) {
      if (part.qty <= 0) throw new BadRequestException(`Invalid quantity for part ${part.itemCode}`);
    }
    return true;
  }
}