/** * WorkOrder Entity - Ordenes de Trabajo de Mantenimiento * * Registro de mantenimientos preventivos y correctivos. * * @module Assets (MAE-015) */ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, OneToMany, JoinColumn, Index, } from 'typeorm'; import { Asset } from './asset.entity'; import { WorkOrderPart } from './work-order-part.entity'; export type MaintenanceType = 'preventive' | 'corrective' | 'predictive' | 'emergency'; export type WorkOrderStatus = 'draft' | 'scheduled' | 'in_progress' | 'on_hold' | 'completed' | 'cancelled'; export type WorkOrderPriority = 'low' | 'medium' | 'high' | 'critical'; // Const objects for runtime use export const WorkOrderStatusValues: Record = { DRAFT: 'draft', SCHEDULED: 'scheduled', IN_PROGRESS: 'in_progress', ON_HOLD: 'on_hold', COMPLETED: 'completed', CANCELLED: 'cancelled', } as const; export const WorkOrderPriorityValues: Record = { LOW: 'low', MEDIUM: 'medium', HIGH: 'high', CRITICAL: 'critical', } as const; @Entity('work_orders', { schema: 'assets' }) @Index(['tenantId', 'workOrderNumber'], { unique: true }) @Index(['tenantId', 'assetId']) @Index(['tenantId', 'status']) @Index(['tenantId', 'scheduledStartDate']) @Index(['tenantId', 'projectId']) export class WorkOrder { @PrimaryGeneratedColumn('uuid') id!: string; @Column({ name: 'tenant_id', type: 'uuid' }) @Index() tenantId!: string; // Identificacion @Column({ name: 'work_order_number', length: 50 }) workOrderNumber!: string; // Activo @Column({ name: 'asset_id', type: 'uuid' }) assetId!: string; @ManyToOne(() => Asset) @JoinColumn({ name: 'asset_id' }) asset!: Asset; @Column({ name: 'asset_code', length: 50, nullable: true }) assetCode?: string; @Column({ name: 'asset_name', length: 255, nullable: true }) assetName?: string; // Tipo y estado @Column({ name: 'maintenance_type', type: 'enum', enum: ['preventive', 'corrective', 'predictive', 'emergency'], enumName: 'maintenance_type', }) maintenanceType!: MaintenanceType; @Column({ type: 'enum', enum: ['draft', 'scheduled', 'in_progress', 'on_hold', 'completed', 'cancelled'], enumName: 'work_order_status', default: 'draft', }) status!: WorkOrderStatus; @Column({ type: 'enum', enum: ['low', 'medium', 'high', 'critical'], enumName: 'work_order_priority', default: 'medium', }) priority!: WorkOrderPriority; // Origen @Column({ name: 'schedule_id', type: 'uuid', nullable: true }) scheduleId?: string; @Column({ name: 'plan_id', type: 'uuid', nullable: true }) planId?: string; @Column({ name: 'is_scheduled', type: 'boolean', default: false }) isScheduled!: boolean; // Descripcion @Column({ length: 255 }) title!: string; @Column({ type: 'text', nullable: true }) description?: string; @Column({ name: 'problem_reported', type: 'text', nullable: true }) problemReported?: string; @Column({ type: 'text', nullable: true }) diagnosis?: string; // Ubicacion @Column({ name: 'project_id', type: 'uuid', nullable: true }) projectId?: string; @Column({ name: 'project_name', length: 255, nullable: true }) projectName?: string; @Column({ name: 'location_description', length: 255, nullable: true }) locationDescription?: string; // Fechas @Column({ name: 'requested_date', type: 'date' }) requestedDate!: Date; @Column({ name: 'scheduled_start_date', type: 'date', nullable: true }) scheduledStartDate?: Date; @Column({ name: 'scheduled_end_date', type: 'date', nullable: true }) scheduledEndDate?: Date; @Column({ name: 'actual_start_date', type: 'date', nullable: true }) actualStartDate?: Date; @Column({ name: 'actual_end_date', type: 'date', nullable: true }) actualEndDate?: Date; // Metricas del equipo al momento @Column({ name: 'hours_at_work_order', type: 'decimal', precision: 12, scale: 2, nullable: true }) hoursAtWorkOrder?: number; @Column({ name: 'kilometers_at_work_order', type: 'decimal', precision: 12, scale: 2, nullable: true }) kilometersAtWorkOrder?: number; // Asignacion @Column({ name: 'assigned_to_id', type: 'uuid', nullable: true }) assignedToId?: string; @Column({ name: 'assigned_to_name', length: 255, nullable: true }) assignedToName?: string; @Column({ name: 'team_ids', type: 'uuid', array: true, nullable: true }) teamIds?: string[]; // Solicitante @Column({ name: 'requested_by_id', type: 'uuid', nullable: true }) requestedById?: string; @Column({ name: 'requested_by_name', length: 255, nullable: true }) requestedByName?: string; // Aprobacion @Column({ name: 'approved_by_id', type: 'uuid', nullable: true }) approvedById?: string; @Column({ name: 'approved_at', type: 'timestamptz', nullable: true }) approvedAt?: Date; // Trabajo realizado @Column({ name: 'work_performed', type: 'text', nullable: true }) workPerformed?: string; @Column({ type: 'text', nullable: true }) findings?: string; @Column({ type: 'text', nullable: true }) recommendations?: string; // Checklist de actividades @Column({ name: 'activities_checklist', type: 'jsonb', nullable: true }) activitiesChecklist?: Record[]; // Tiempos @Column({ name: 'estimated_hours', type: 'decimal', precision: 5, scale: 2, nullable: true }) estimatedHours?: number; @Column({ name: 'actual_hours', type: 'decimal', precision: 5, scale: 2, nullable: true }) actualHours?: number; // Costos @Column({ name: 'labor_cost', type: 'decimal', precision: 18, scale: 2, default: 0 }) laborCost!: number; @Column({ name: 'parts_cost', type: 'decimal', precision: 18, scale: 2, default: 0 }) partsCost!: number; @Column({ name: 'external_service_cost', type: 'decimal', precision: 18, scale: 2, default: 0 }) externalServiceCost!: number; @Column({ name: 'other_costs', type: 'decimal', precision: 18, scale: 2, default: 0 }) otherCosts!: number; @Column({ name: 'total_cost', type: 'decimal', precision: 18, scale: 2, default: 0 }) totalCost!: number; // Partes utilizadas @Column({ name: 'parts_used_count', type: 'int', default: 0 }) partsUsedCount!: number; @OneToMany(() => WorkOrderPart, (part) => part.workOrder, { cascade: true }) partsUsed?: WorkOrderPart[]; // Documentos @Column({ name: 'photos_before', type: 'jsonb', nullable: true }) photosBefore?: string[]; @Column({ name: 'photos_after', type: 'jsonb', nullable: true }) photosAfter?: string[]; @Column({ type: 'jsonb', nullable: true }) documents?: string[]; // Firma de conformidad @Column({ name: 'completed_by_id', type: 'uuid', nullable: true }) completedById?: string; @Column({ name: 'completed_by_name', length: 255, nullable: true }) completedByName?: string; @Column({ name: 'completion_signature_url', length: 500, nullable: true }) completionSignatureUrl?: string; @Column({ name: 'completion_notes', type: 'text', nullable: true }) completionNotes?: string; // Seguimiento @Column({ name: 'requires_followup', type: 'boolean', default: false }) requiresFollowup!: boolean; @Column({ name: 'followup_notes', type: 'text', nullable: true }) followupNotes?: string; @Column({ name: 'followup_work_order_id', type: 'uuid', nullable: true }) followupWorkOrderId?: string; // Notas y metadatos @Column({ type: 'text', nullable: true }) notes?: string; @Column({ type: 'jsonb', nullable: true }) metadata?: Record; // Auditoria @Column({ name: 'created_by', type: 'uuid', nullable: true }) createdBy?: string; @Column({ name: 'updated_by', type: 'uuid', nullable: true }) updatedBy?: string; @CreateDateColumn({ name: 'created_at', type: 'timestamptz' }) createdAt!: Date; @UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' }) updatedAt!: Date; @Column({ name: 'deleted_at', type: 'timestamptz', nullable: true }) deletedAt?: Date; }