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 { AccountType } from './account-type.entity.js';
|
||||||
import { Company } from '../../auth/entities/company.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' })
|
@Entity({ schema: 'financial', name: 'accounts' })
|
||||||
@Index('idx_accounts_tenant_id', ['tenantId'])
|
@Index('idx_accounts_tenant_id', ['tenantId'])
|
||||||
@Index('idx_accounts_company_id', ['companyId'])
|
@Index('idx_accounts_company_id', ['companyId'])
|
||||||
@ -52,6 +81,57 @@ export class Account {
|
|||||||
@Column({ type: 'text', nullable: true })
|
@Column({ type: 'text', nullable: true })
|
||||||
notes: string | null;
|
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
|
// Relations
|
||||||
@ManyToOne(() => Company)
|
@ManyToOne(() => Company)
|
||||||
@JoinColumn({ name: 'company_id' })
|
@JoinColumn({ name: 'company_id' })
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Account entities
|
// Account entities
|
||||||
export { AccountType, AccountTypeEnum } from './account-type.entity.js';
|
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';
|
export { AccountMapping, AccountMappingType } from './account-mapping.entity.js';
|
||||||
|
|
||||||
// Journal entities
|
// Journal entities
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Core Inventory Entities
|
// 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
|
// Re-export Warehouse from canonical location in warehouses module
|
||||||
export { Warehouse } from '../../warehouses/entities/warehouse.entity.js';
|
export { Warehouse } from '../../warehouses/entities/warehouse.entity.js';
|
||||||
export { Location } from './location.entity.js';
|
export { Location } from './location.entity.js';
|
||||||
|
|||||||
@ -13,12 +13,18 @@ import { Lot } from './lot.entity.js';
|
|||||||
/**
|
/**
|
||||||
* Inventory Product Entity (schema: inventory.products)
|
* 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
|
* NOTE: This is NOT a duplicate of products/entities/product.entity.ts
|
||||||
*
|
*
|
||||||
* Key differences:
|
* Key differences:
|
||||||
* - This entity: inventory.products - Warehouse/stock management focused (Odoo-style)
|
* - This entity: inventory.products - Warehouse/stock management focused (Odoo-style)
|
||||||
* - Has: valuationMethod, tracking (lot/serial), isStorable, StockQuant/Lot relations
|
* - Has: valuationMethod, tracking (lot/serial), isStorable, StockQuant/Lot relations
|
||||||
* - Used by: Inventory module for stock tracking, valuation, picking operations
|
* - Used by: Inventory module for stock tracking, valuation, picking operations
|
||||||
|
* - TRANSPORT: tipoRefaccion, numeroParte, unidadesCompatibles
|
||||||
*
|
*
|
||||||
* - Products entity: products.products - Commerce/retail focused
|
* - Products entity: products.products - Commerce/retail focused
|
||||||
* - Has: SAT codes, tax rates, detailed dimensions, min/max stock, reorder points
|
* - Has: SAT codes, tax rates, detailed dimensions, min/max stock, reorder points
|
||||||
@ -33,6 +39,24 @@ export enum ProductType {
|
|||||||
SERVICE = 'service',
|
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 {
|
export enum TrackingType {
|
||||||
NONE = 'none',
|
NONE = 'none',
|
||||||
LOT = 'lot',
|
LOT = 'lot',
|
||||||
@ -139,6 +163,77 @@ export class Product {
|
|||||||
@Column({ type: 'boolean', default: true, nullable: false })
|
@Column({ type: 'boolean', default: true, nullable: false })
|
||||||
active: boolean;
|
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
|
// Relations
|
||||||
@OneToMany(() => StockQuant, (stockQuant) => stockQuant.product)
|
@OneToMany(() => StockQuant, (stockQuant) => stockQuant.product)
|
||||||
stockQuants: StockQuant[];
|
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 { Quotation } from './quotation.entity';
|
||||||
export { QuotationItem } from './quotation-item.entity';
|
export { QuotationItem } from './quotation-item.entity';
|
||||||
export { SalesOrder } from './sales-order.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';
|
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