- Fix controllers to use Promise<void> return types - Replace 'return res.status()' with 'res.status(); return;' - Add NextFunction parameter to handlers - Fix enum-style references with string literals - Replace 'deletedAt: null' with 'IsNull()' for TypeORM - Remove unused parameters and imports - Fix grouped object initialization in getByStatus - Remove non-existent 'updatedBy' property from WorkOrderPart Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
281 lines
7.9 KiB
TypeScript
281 lines
7.9 KiB
TypeScript
/**
|
|
* 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<string, WorkOrderStatus> = {
|
|
DRAFT: 'draft',
|
|
SCHEDULED: 'scheduled',
|
|
IN_PROGRESS: 'in_progress',
|
|
ON_HOLD: 'on_hold',
|
|
COMPLETED: 'completed',
|
|
CANCELLED: 'cancelled',
|
|
} as const;
|
|
|
|
export const WorkOrderPriorityValues: Record<string, WorkOrderPriority> = {
|
|
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<string, any>[];
|
|
|
|
// 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<string, any>;
|
|
|
|
// 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;
|
|
}
|