feat(entities): Complete viajes entities and adapt inventory/financial
- Add Viaje entity with transport-specific workflow states - Add ParadaViaje entity for multi-stop support - Add Pod entity for Proof of Delivery - Adapt inventory/product.entity with TipoRefaccion for fleet parts - Adapt financial/account.entity with TipoCuentaTransporte and TipoCentroCosto - Update index exports for all modified modules Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3cc989cf30
commit
99d18bb340
@ -12,6 +12,35 @@ import {
|
||||
import { AccountType } from './account-type.entity.js';
|
||||
import { Company } from '../../auth/entities/company.entity.js';
|
||||
|
||||
/**
|
||||
* Tipo de Cuenta de Transporte
|
||||
* Para clasificar cuentas específicas del giro
|
||||
*/
|
||||
export enum TipoCuentaTransporte {
|
||||
GENERAL = 'GENERAL', // Cuenta genérica
|
||||
COMBUSTIBLE = 'COMBUSTIBLE', // Gastos de combustible
|
||||
PEAJES = 'PEAJES', // Cruces de casetas
|
||||
MANTENIMIENTO = 'MANTENIMIENTO', // Mantenimiento de flota
|
||||
VIATICOS = 'VIATICOS', // Viáticos y hospedaje
|
||||
SEGURO = 'SEGURO', // Seguros de unidades/carga
|
||||
DEPRECIACION = 'DEPRECIACION', // Depreciación de flota
|
||||
NOMINA_OPERADORES = 'NOMINA_OPERADORES', // Sueldos operadores
|
||||
INGRESOS_FLETE = 'INGRESOS_FLETE', // Ingresos por fletes
|
||||
CARRIER = 'CARRIER', // Pagos a carriers terceros
|
||||
}
|
||||
|
||||
/**
|
||||
* Tipo de Centro de Costo
|
||||
*/
|
||||
export enum TipoCentroCosto {
|
||||
GENERAL = 'GENERAL',
|
||||
UNIDAD = 'UNIDAD', // Por unidad/vehículo
|
||||
RUTA = 'RUTA', // Por ruta/lane
|
||||
CLIENTE = 'CLIENTE', // Por cliente
|
||||
OPERADOR = 'OPERADOR', // Por operador
|
||||
SUCURSAL = 'SUCURSAL', // Por sucursal/patio
|
||||
}
|
||||
|
||||
@Entity({ schema: 'financial', name: 'accounts' })
|
||||
@Index('idx_accounts_tenant_id', ['tenantId'])
|
||||
@Index('idx_accounts_company_id', ['companyId'])
|
||||
@ -52,6 +81,57 @@ export class Account {
|
||||
@Column({ type: 'text', nullable: true })
|
||||
notes: string | null;
|
||||
|
||||
// === CAMPOS ESPECÍFICOS PARA TRANSPORTE ===
|
||||
|
||||
/**
|
||||
* Tipo de cuenta específico para operaciones de transporte
|
||||
*/
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: TipoCuentaTransporte,
|
||||
nullable: true,
|
||||
name: 'tipo_cuenta_transporte',
|
||||
})
|
||||
tipoCuentaTransporte: TipoCuentaTransporte | null;
|
||||
|
||||
/**
|
||||
* Tipo de centro de costo para análisis financiero
|
||||
*/
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: TipoCentroCosto,
|
||||
nullable: true,
|
||||
name: 'tipo_centro_costo',
|
||||
})
|
||||
tipoCentroCosto: TipoCentroCosto | null;
|
||||
|
||||
/**
|
||||
* ID del centro de costo asociado (unidad, operador, ruta, etc.)
|
||||
* Referencia dinámica según tipoCentroCosto
|
||||
*/
|
||||
@Column({ type: 'uuid', nullable: true, name: 'centro_costo_id' })
|
||||
centroCostoId: string | null;
|
||||
|
||||
/**
|
||||
* Nombre/descripción del centro de costo para reportes
|
||||
*/
|
||||
@Column({ type: 'varchar', length: 200, nullable: true, name: 'centro_costo_nombre' })
|
||||
centroCostoNombre: string | null;
|
||||
|
||||
/**
|
||||
* Indica si esta cuenta aplica para liquidaciones de viajes
|
||||
*/
|
||||
@Column({ type: 'boolean', default: false, name: 'aplica_liquidacion' })
|
||||
aplicaLiquidacion: boolean;
|
||||
|
||||
/**
|
||||
* Indica si requiere comprobante fiscal (CFDI)
|
||||
*/
|
||||
@Column({ type: 'boolean', default: false, name: 'requiere_cfdi' })
|
||||
requiereCfdi: boolean;
|
||||
|
||||
// === FIN CAMPOS TRANSPORTE ===
|
||||
|
||||
// Relations
|
||||
@ManyToOne(() => Company)
|
||||
@JoinColumn({ name: 'company_id' })
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Account entities
|
||||
export { AccountType, AccountTypeEnum } from './account-type.entity.js';
|
||||
export { Account } from './account.entity.js';
|
||||
export { Account, TipoCuentaTransporte, TipoCentroCosto } from './account.entity.js';
|
||||
export { AccountMapping, AccountMappingType } from './account-mapping.entity.js';
|
||||
|
||||
// Journal entities
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Core Inventory Entities
|
||||
export { Product } from './product.entity.js';
|
||||
export { Product, TipoRefaccion, ProductType, TrackingType, ValuationMethod } from './product.entity.js';
|
||||
// Re-export Warehouse from canonical location in warehouses module
|
||||
export { Warehouse } from '../../warehouses/entities/warehouse.entity.js';
|
||||
export { Location } from './location.entity.js';
|
||||
|
||||
@ -13,12 +13,18 @@ import { Lot } from './lot.entity.js';
|
||||
/**
|
||||
* Inventory Product Entity (schema: inventory.products)
|
||||
*
|
||||
* ADAPTADO PARA ERP TRANSPORTISTAS:
|
||||
* Este módulo se usa principalmente para gestionar refacciones de flota.
|
||||
* Incluye campos específicos para identificar partes de vehículos y su
|
||||
* compatibilidad con unidades de la flota.
|
||||
*
|
||||
* NOTE: This is NOT a duplicate of products/entities/product.entity.ts
|
||||
*
|
||||
* Key differences:
|
||||
* - This entity: inventory.products - Warehouse/stock management focused (Odoo-style)
|
||||
* - Has: valuationMethod, tracking (lot/serial), isStorable, StockQuant/Lot relations
|
||||
* - Used by: Inventory module for stock tracking, valuation, picking operations
|
||||
* - TRANSPORT: tipoRefaccion, numeroParte, unidadesCompatibles
|
||||
*
|
||||
* - Products entity: products.products - Commerce/retail focused
|
||||
* - Has: SAT codes, tax rates, detailed dimensions, min/max stock, reorder points
|
||||
@ -33,6 +39,24 @@ export enum ProductType {
|
||||
SERVICE = 'service',
|
||||
}
|
||||
|
||||
/**
|
||||
* Tipo de Refacción (específico para transporte)
|
||||
*/
|
||||
export enum TipoRefaccion {
|
||||
MOTOR = 'MOTOR',
|
||||
FRENOS = 'FRENOS',
|
||||
LLANTAS = 'LLANTAS',
|
||||
SUSPENSION = 'SUSPENSION',
|
||||
TRANSMISION = 'TRANSMISION',
|
||||
ELECTRICO = 'ELECTRICO',
|
||||
CARROCERIA = 'CARROCERIA',
|
||||
REFRIGERACION = 'REFRIGERACION',
|
||||
LUBRICANTES = 'LUBRICANTES',
|
||||
FILTROS = 'FILTROS',
|
||||
ACCESORIOS = 'ACCESORIOS',
|
||||
OTRO = 'OTRO',
|
||||
}
|
||||
|
||||
export enum TrackingType {
|
||||
NONE = 'none',
|
||||
LOT = 'lot',
|
||||
@ -139,6 +163,77 @@ export class Product {
|
||||
@Column({ type: 'boolean', default: true, nullable: false })
|
||||
active: boolean;
|
||||
|
||||
// === CAMPOS ESPECÍFICOS PARA REFACCIONES DE FLOTA ===
|
||||
|
||||
/**
|
||||
* Tipo de refacción (para clasificación en mantenimiento)
|
||||
*/
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: TipoRefaccion,
|
||||
nullable: true,
|
||||
name: 'tipo_refaccion',
|
||||
})
|
||||
tipoRefaccion: TipoRefaccion | null;
|
||||
|
||||
/**
|
||||
* Número de parte del fabricante (OEM)
|
||||
*/
|
||||
@Column({ type: 'varchar', length: 100, nullable: true, name: 'numero_parte' })
|
||||
numeroParte: string | null;
|
||||
|
||||
/**
|
||||
* Número de parte alterno/equivalente
|
||||
*/
|
||||
@Column({ type: 'varchar', length: 100, nullable: true, name: 'numero_parte_alterno' })
|
||||
numeroParteAlterno: string | null;
|
||||
|
||||
/**
|
||||
* Marca del fabricante de la refacción
|
||||
*/
|
||||
@Column({ type: 'varchar', length: 100, nullable: true, name: 'marca_refaccion' })
|
||||
marcaRefaccion: string | null;
|
||||
|
||||
/**
|
||||
* IDs de unidades donde esta refacción es compatible
|
||||
* Referencia a fleet.unidades
|
||||
*/
|
||||
@Column({ type: 'uuid', array: true, nullable: true, name: 'unidades_compatibles' })
|
||||
unidadesCompatibles: string[] | null;
|
||||
|
||||
/**
|
||||
* Modelos de vehículos compatibles (texto libre)
|
||||
* Ej: "Kenworth T680 2020-2024, Freightliner Cascadia"
|
||||
*/
|
||||
@Column({ type: 'text', nullable: true, name: 'modelos_compatibles' })
|
||||
modelosCompatibles: string | null;
|
||||
|
||||
/**
|
||||
* Vida útil estimada en kilómetros
|
||||
*/
|
||||
@Column({ type: 'int', nullable: true, name: 'vida_util_km' })
|
||||
vidaUtilKm: number | null;
|
||||
|
||||
/**
|
||||
* Vida útil estimada en horas de operación
|
||||
*/
|
||||
@Column({ type: 'int', nullable: true, name: 'vida_util_horas' })
|
||||
vidaUtilHoras: number | null;
|
||||
|
||||
/**
|
||||
* Indica si es una refacción crítica (afecta operación)
|
||||
*/
|
||||
@Column({ type: 'boolean', default: false, name: 'es_critica' })
|
||||
esCritica: boolean;
|
||||
|
||||
/**
|
||||
* Stock mínimo recomendado para refacciones críticas
|
||||
*/
|
||||
@Column({ type: 'int', nullable: true, name: 'stock_minimo_critico' })
|
||||
stockMinimoCritico: number | null;
|
||||
|
||||
// === FIN CAMPOS REFACCIONES ===
|
||||
|
||||
// Relations
|
||||
@OneToMany(() => StockQuant, (stockQuant) => stockQuant.product)
|
||||
stockQuants: StockQuant[];
|
||||
|
||||
@ -1,3 +1,12 @@
|
||||
/**
|
||||
* Ordenes de Transporte Entities
|
||||
* Schema: transport
|
||||
*/
|
||||
|
||||
// Entities de Transporte
|
||||
export * from './orden-transporte.entity';
|
||||
|
||||
// Entities heredadas de sales (para cotizaciones)
|
||||
export { Quotation } from './quotation.entity';
|
||||
export { QuotationItem } from './quotation-item.entity';
|
||||
export { SalesOrder } from './sales-order.entity';
|
||||
|
||||
@ -1 +1,10 @@
|
||||
/**
|
||||
* Viajes Entities
|
||||
* Schema: transport
|
||||
*/
|
||||
export * from './viaje.entity';
|
||||
export * from './parada-viaje.entity';
|
||||
export * from './pod.entity';
|
||||
|
||||
// Entities heredadas de projects
|
||||
export * from './timesheet.entity.js';
|
||||
|
||||
124
src/modules/viajes/entities/parada-viaje.entity.ts
Normal file
124
src/modules/viajes/entities/parada-viaje.entity.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Viaje } from './viaje.entity';
|
||||
|
||||
/**
|
||||
* Tipo de Parada
|
||||
*/
|
||||
export enum TipoParada {
|
||||
RECOLECCION = 'RECOLECCION',
|
||||
ENTREGA = 'ENTREGA',
|
||||
ESCALA = 'ESCALA',
|
||||
}
|
||||
|
||||
/**
|
||||
* Estado de la Parada
|
||||
*/
|
||||
export enum EstadoParada {
|
||||
PENDIENTE = 'PENDIENTE',
|
||||
EN_CAMINO = 'EN_CAMINO',
|
||||
LLEGADA = 'LLEGADA',
|
||||
EN_PROCESO = 'EN_PROCESO',
|
||||
COMPLETADA = 'COMPLETADA',
|
||||
OMITIDA = 'OMITIDA',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'transport', name: 'paradas_viaje' })
|
||||
@Index('idx_parada_viaje', ['viajeId'])
|
||||
export class ParadaViaje {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Column({ name: 'viaje_id', type: 'uuid' })
|
||||
viajeId: string;
|
||||
|
||||
@ManyToOne(() => Viaje)
|
||||
@JoinColumn({ name: 'viaje_id' })
|
||||
viaje: Viaje;
|
||||
|
||||
// Secuencia
|
||||
@Column({ type: 'int' })
|
||||
secuencia: number;
|
||||
|
||||
// Tipo de parada
|
||||
@Column({ type: 'enum', enum: TipoParada })
|
||||
tipo: TipoParada;
|
||||
|
||||
// Ubicación
|
||||
@Column({ type: 'text' })
|
||||
direccion: string;
|
||||
|
||||
@Column({ name: 'codigo_postal', type: 'varchar', length: 10, nullable: true })
|
||||
codigoPostal: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
ciudad: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
estado: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
latitud: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
longitud: number;
|
||||
|
||||
// Contacto
|
||||
@Column({ name: 'contacto_nombre', type: 'varchar', length: 200, nullable: true })
|
||||
contactoNombre: string;
|
||||
|
||||
@Column({ name: 'contacto_telefono', type: 'varchar', length: 30, nullable: true })
|
||||
contactoTelefono: string;
|
||||
|
||||
// Programación
|
||||
@Column({ name: 'hora_programada_llegada', type: 'timestamptz', nullable: true })
|
||||
horaProgramadaLlegada: Date;
|
||||
|
||||
@Column({ name: 'hora_programada_salida', type: 'timestamptz', nullable: true })
|
||||
horaProgramadaSalida: Date;
|
||||
|
||||
// Real
|
||||
@Column({ name: 'hora_real_llegada', type: 'timestamptz', nullable: true })
|
||||
horaRealLlegada: Date;
|
||||
|
||||
@Column({ name: 'hora_real_salida', type: 'timestamptz', nullable: true })
|
||||
horaRealSalida: Date;
|
||||
|
||||
// OTs asociadas
|
||||
@Column({ name: 'ots_ids', type: 'uuid', array: true, nullable: true })
|
||||
otsIds: string[];
|
||||
|
||||
// Estado
|
||||
@Column({ name: 'estado_parada', type: 'enum', enum: EstadoParada, default: EstadoParada.PENDIENTE })
|
||||
estadoParada: EstadoParada;
|
||||
|
||||
// Observaciones
|
||||
@Column({ type: 'text', nullable: true })
|
||||
observaciones: string;
|
||||
|
||||
// Helper para calcular tiempo de permanencia
|
||||
get tiempoPermanenciaMinutos(): number | null {
|
||||
if (this.horaRealLlegada && this.horaRealSalida) {
|
||||
const diff = this.horaRealSalida.getTime() - this.horaRealLlegada.getTime();
|
||||
return Math.round(diff / (1000 * 60));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper para verificar si está retrasada
|
||||
get estaRetrasada(): boolean {
|
||||
if (this.horaProgramadaLlegada && this.horaRealLlegada) {
|
||||
return this.horaRealLlegada > this.horaProgramadaLlegada;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
120
src/modules/viajes/entities/pod.entity.ts
Normal file
120
src/modules/viajes/entities/pod.entity.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Viaje } from './viaje.entity';
|
||||
import { ParadaViaje } from './parada-viaje.entity';
|
||||
|
||||
/**
|
||||
* Estado del POD (Proof of Delivery)
|
||||
*/
|
||||
export enum EstadoPod {
|
||||
PENDIENTE = 'PENDIENTE',
|
||||
PARCIAL = 'PARCIAL',
|
||||
COMPLETO = 'COMPLETO',
|
||||
RECHAZADO = 'RECHAZADO',
|
||||
}
|
||||
|
||||
/**
|
||||
* Foto de Evidencia
|
||||
*/
|
||||
export interface FotoEvidencia {
|
||||
url: string;
|
||||
descripcion?: string;
|
||||
tipo: 'MERCANCIA' | 'FIRMA' | 'DANIO' | 'SELLO' | 'DOCUMENTO' | 'OTRO';
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
@Entity({ schema: 'transport', name: 'pod' })
|
||||
@Index('idx_pod_viaje', ['viajeId'])
|
||||
@Index('idx_pod_estado', ['tenantId', 'estado'])
|
||||
export class Pod {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Column({ name: 'viaje_id', type: 'uuid' })
|
||||
viajeId: string;
|
||||
|
||||
@ManyToOne(() => Viaje)
|
||||
@JoinColumn({ name: 'viaje_id' })
|
||||
viaje: Viaje;
|
||||
|
||||
@Column({ name: 'parada_id', type: 'uuid', nullable: true })
|
||||
paradaId: string;
|
||||
|
||||
@ManyToOne(() => ParadaViaje)
|
||||
@JoinColumn({ name: 'parada_id' })
|
||||
parada: ParadaViaje;
|
||||
|
||||
@Column({ name: 'ot_id', type: 'uuid', nullable: true })
|
||||
otId: string;
|
||||
|
||||
// Estado POD
|
||||
@Column({ type: 'enum', enum: EstadoPod, default: EstadoPod.PENDIENTE })
|
||||
estado: EstadoPod;
|
||||
|
||||
// Recepción
|
||||
@Column({ name: 'receptor_nombre', type: 'varchar', length: 200, nullable: true })
|
||||
receptorNombre: string;
|
||||
|
||||
@Column({ name: 'receptor_identificacion', type: 'varchar', length: 50, nullable: true })
|
||||
receptorIdentificacion: string;
|
||||
|
||||
@Column({ name: 'fecha_recepcion', type: 'timestamptz', nullable: true })
|
||||
fechaRecepcion: Date;
|
||||
|
||||
// Firma digital (Base64 de la imagen de firma)
|
||||
@Column({ name: 'firma_digital', type: 'text', nullable: true })
|
||||
firmaDigital: string;
|
||||
|
||||
// Evidencias (URLs o IDs de archivos)
|
||||
@Column({ name: 'fotos_entrega', type: 'jsonb', nullable: true })
|
||||
fotosEntrega: FotoEvidencia[];
|
||||
|
||||
// Cantidades
|
||||
@Column({ name: 'piezas_entregadas', type: 'int', nullable: true })
|
||||
piezasEntregadas: number;
|
||||
|
||||
@Column({ name: 'piezas_rechazadas', type: 'int', nullable: true })
|
||||
piezasRechazadas: number;
|
||||
|
||||
@Column({ name: 'piezas_danadas', type: 'int', nullable: true })
|
||||
piezasDanadas: number;
|
||||
|
||||
// Observaciones
|
||||
@Column({ type: 'text', nullable: true })
|
||||
observaciones: string;
|
||||
|
||||
@Column({ name: 'motivo_rechazo', type: 'text', nullable: true })
|
||||
motivoRechazo: string;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid', nullable: true })
|
||||
createdById: string;
|
||||
|
||||
// Helpers
|
||||
get entregaCompleta(): boolean {
|
||||
return this.estado === EstadoPod.COMPLETO &&
|
||||
(this.piezasRechazadas || 0) === 0 &&
|
||||
(this.piezasDanadas || 0) === 0;
|
||||
}
|
||||
|
||||
get tieneFirma(): boolean {
|
||||
return !!this.firmaDigital;
|
||||
}
|
||||
|
||||
get tieneEvidenciasFotograficas(): boolean {
|
||||
return Array.isArray(this.fotosEntrega) && this.fotosEntrega.length > 0;
|
||||
}
|
||||
}
|
||||
167
src/modules/viajes/entities/viaje.entity.ts
Normal file
167
src/modules/viajes/entities/viaje.entity.ts
Normal file
@ -0,0 +1,167 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
OneToMany,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* Estado del Viaje
|
||||
*/
|
||||
export enum EstadoViaje {
|
||||
BORRADOR = 'BORRADOR',
|
||||
PLANEADO = 'PLANEADO',
|
||||
DESPACHADO = 'DESPACHADO',
|
||||
EN_TRANSITO = 'EN_TRANSITO',
|
||||
EN_DESTINO = 'EN_DESTINO',
|
||||
ENTREGADO = 'ENTREGADO',
|
||||
CERRADO = 'CERRADO',
|
||||
FACTURADO = 'FACTURADO',
|
||||
COBRADO = 'COBRADO',
|
||||
CANCELADO = 'CANCELADO',
|
||||
}
|
||||
|
||||
/**
|
||||
* Información de Sello
|
||||
*/
|
||||
export interface InfoSello {
|
||||
numero: string;
|
||||
tipo: 'PLASTICO' | 'METALICO' | 'ELECTRONICO' | 'CANDADO';
|
||||
colocadoPor?: string;
|
||||
foto?: string;
|
||||
timestamp?: Date;
|
||||
}
|
||||
|
||||
@Entity({ schema: 'transport', name: 'viajes' })
|
||||
@Index('idx_viaje_tenant', ['tenantId'])
|
||||
@Index('idx_viaje_estado', ['tenantId', 'estado'])
|
||||
@Index('idx_viaje_unidad', ['tenantId', 'unidadId'])
|
||||
@Index('idx_viaje_operador', ['tenantId', 'operadorId'])
|
||||
@Index('idx_viaje_fechas', ['tenantId', 'fechaSalidaProgramada'])
|
||||
export class Viaje {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Identificación
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
codigo: string;
|
||||
|
||||
// Unidad y operador (referencias a fleet schema)
|
||||
@Column({ name: 'unidad_id', type: 'uuid' })
|
||||
unidadId: string;
|
||||
|
||||
@Column({ name: 'remolque_id', type: 'uuid', nullable: true })
|
||||
remolqueId: string;
|
||||
|
||||
@Column({ name: 'operador_id', type: 'uuid' })
|
||||
operadorId: string;
|
||||
|
||||
// Ruta
|
||||
@Column({ name: 'origen_principal', type: 'varchar', length: 200, nullable: true })
|
||||
origenPrincipal: string;
|
||||
|
||||
@Column({ name: 'destino_principal', type: 'varchar', length: 200, nullable: true })
|
||||
destinoPrincipal: string;
|
||||
|
||||
@Column({ name: 'distancia_estimada_km', type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
distanciaEstimadaKm: number;
|
||||
|
||||
@Column({ name: 'tiempo_estimado_horas', type: 'decimal', precision: 6, scale: 2, nullable: true })
|
||||
tiempoEstimadoHoras: number;
|
||||
|
||||
// Fechas programadas
|
||||
@Column({ name: 'fecha_salida_programada', type: 'timestamptz', nullable: true })
|
||||
fechaSalidaProgramada: Date;
|
||||
|
||||
@Column({ name: 'fecha_llegada_programada', type: 'timestamptz', nullable: true })
|
||||
fechaLlegadaProgramada: Date;
|
||||
|
||||
// Fechas reales
|
||||
@Column({ name: 'fecha_salida_real', type: 'timestamptz', nullable: true })
|
||||
fechaSalidaReal: Date;
|
||||
|
||||
@Column({ name: 'fecha_llegada_real', type: 'timestamptz', nullable: true })
|
||||
fechaLlegadaReal: Date;
|
||||
|
||||
// Kilometraje
|
||||
@Column({ name: 'km_inicio', type: 'int', nullable: true })
|
||||
kmInicio: number;
|
||||
|
||||
@Column({ name: 'km_fin', type: 'int', nullable: true })
|
||||
kmFin: number;
|
||||
|
||||
// Calculado: km_recorridos = km_fin - km_inicio
|
||||
get kmRecorridos(): number | null {
|
||||
if (this.kmInicio != null && this.kmFin != null) {
|
||||
return this.kmFin - this.kmInicio;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'enum', enum: EstadoViaje, default: EstadoViaje.BORRADOR })
|
||||
estado: EstadoViaje;
|
||||
|
||||
// Checklist pre-viaje
|
||||
@Column({ name: 'checklist_completado', type: 'boolean', default: false })
|
||||
checklistCompletado: boolean;
|
||||
|
||||
@Column({ name: 'checklist_fecha', type: 'timestamptz', nullable: true })
|
||||
checklistFecha: Date;
|
||||
|
||||
@Column({ name: 'checklist_observaciones', type: 'text', nullable: true })
|
||||
checklistObservaciones: string;
|
||||
|
||||
// Sellos
|
||||
@Column({ name: 'sellos_salida', type: 'jsonb', nullable: true })
|
||||
sellosSalida: InfoSello[];
|
||||
|
||||
@Column({ name: 'sellos_llegada', type: 'jsonb', nullable: true })
|
||||
sellosLlegada: InfoSello[];
|
||||
|
||||
// Costos
|
||||
@Column({ name: 'costo_combustible', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
costoCombustible: number;
|
||||
|
||||
@Column({ name: 'costo_peajes', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
costoPeajes: number;
|
||||
|
||||
@Column({ name: 'costo_viaticos', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
costoViaticos: number;
|
||||
|
||||
@Column({ name: 'costo_otros', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
costoOtros: number;
|
||||
|
||||
@Column({ name: 'costo_total', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
costoTotal: number;
|
||||
|
||||
// Ingresos
|
||||
@Column({ name: 'ingreso_total', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
ingresoTotal: number;
|
||||
|
||||
// Calculado: margen = ingreso_total - costo_total
|
||||
get margen(): number {
|
||||
return (this.ingresoTotal || 0) - (this.costoTotal || 0);
|
||||
}
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid' })
|
||||
createdById: string;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({ name: 'updated_by_id', type: 'uuid', nullable: true })
|
||||
updatedById: string;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user