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