feat: Add combustible-gastos and tarifas-transporte entities
New entities for combustible-gastos module: - anticipo-viatico.entity.ts - carga-combustible.entity.ts - control-rendimiento.entity.ts - cruce-peaje.entity.ts - gasto-viaje.entity.ts New entities for tarifas-transporte module: - factura-transporte.entity.ts - fuel-surcharge.entity.ts - lane.entity.ts - linea-factura.entity.ts - recargo-catalogo.entity.ts - tarifa.entity.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
99d18bb340
commit
2722920e12
@ -0,0 +1,114 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* Estado del Anticipo
|
||||
*/
|
||||
export enum EstadoAnticipo {
|
||||
SOLICITADO = 'SOLICITADO',
|
||||
APROBADO = 'APROBADO',
|
||||
ENTREGADO = 'ENTREGADO',
|
||||
COMPROBANDO = 'COMPROBANDO',
|
||||
LIQUIDADO = 'LIQUIDADO',
|
||||
RECHAZADO = 'RECHAZADO',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'fuel', name: 'anticipos_viaticos' })
|
||||
@Index('idx_anticipo_viaje', ['viajeId'])
|
||||
@Index('idx_anticipo_operador', ['operadorId'])
|
||||
@Index('idx_anticipo_estado', ['tenantId', 'estado'])
|
||||
export class AnticipoViatico {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Viaje y operador
|
||||
@Column({ name: 'viaje_id', type: 'uuid' })
|
||||
viajeId: string;
|
||||
|
||||
@Column({ name: 'operador_id', type: 'uuid' })
|
||||
operadorId: string;
|
||||
|
||||
// Anticipo
|
||||
@Column({ name: 'monto_solicitado', type: 'decimal', precision: 12, scale: 2 })
|
||||
montoSolicitado: number;
|
||||
|
||||
@Column({ name: 'monto_aprobado', type: 'decimal', precision: 12, scale: 2, nullable: true })
|
||||
montoAprobado: number | null;
|
||||
|
||||
@Column({ name: 'monto_comprobado', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||
montoComprobado: number;
|
||||
|
||||
@Column({ name: 'monto_reintegro', type: 'decimal', precision: 12, scale: 2, default: 0 })
|
||||
montoReintegro: number;
|
||||
|
||||
// Conceptos desglosados
|
||||
@Column({ name: 'combustible_estimado', type: 'decimal', precision: 12, scale: 2, nullable: true })
|
||||
combustibleEstimado: number | null;
|
||||
|
||||
@Column({ name: 'peajes_estimado', type: 'decimal', precision: 12, scale: 2, nullable: true })
|
||||
peajesEstimado: number | null;
|
||||
|
||||
@Column({ name: 'viaticos_estimado', type: 'decimal', precision: 12, scale: 2, nullable: true })
|
||||
viaticosEstimado: number | null;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'varchar', length: 20, default: EstadoAnticipo.SOLICITADO })
|
||||
estado: string;
|
||||
|
||||
// Fechas
|
||||
@Column({ name: 'fecha_solicitud', type: 'timestamptz', default: () => 'NOW()' })
|
||||
fechaSolicitud: Date;
|
||||
|
||||
@Column({ name: 'fecha_aprobacion', type: 'timestamptz', nullable: true })
|
||||
fechaAprobacion: Date | null;
|
||||
|
||||
@Column({ name: 'fecha_entrega', type: 'timestamptz', nullable: true })
|
||||
fechaEntrega: Date | null;
|
||||
|
||||
@Column({ name: 'fecha_liquidacion', type: 'timestamptz', nullable: true })
|
||||
fechaLiquidacion: Date | null;
|
||||
|
||||
// Aprobaciones
|
||||
@Column({ name: 'aprobado_por', type: 'uuid', nullable: true })
|
||||
aprobadoPor: string | null;
|
||||
|
||||
@Column({ name: 'entregado_por', type: 'uuid', nullable: true })
|
||||
entregadoPor: string | null;
|
||||
|
||||
@Column({ name: 'liquidado_por', type: 'uuid', nullable: true })
|
||||
liquidadoPor: string | null;
|
||||
|
||||
// Observaciones
|
||||
@Column({ type: 'text', nullable: true })
|
||||
observaciones: string | null;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid' })
|
||||
createdById: string;
|
||||
|
||||
// Helpers
|
||||
get saldoPendiente(): number {
|
||||
const aprobado = this.montoAprobado || 0;
|
||||
return aprobado - this.montoComprobado;
|
||||
}
|
||||
|
||||
get porcentajeComprobado(): number {
|
||||
const aprobado = this.montoAprobado || 0;
|
||||
return aprobado > 0 ? (this.montoComprobado / aprobado) * 100 : 0;
|
||||
}
|
||||
|
||||
get requiereReintegro(): boolean {
|
||||
return this.montoReintegro > 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* Tipo de Carga de Combustible
|
||||
*/
|
||||
export enum TipoCargaCombustible {
|
||||
VALE = 'VALE',
|
||||
TARJETA = 'TARJETA',
|
||||
EFECTIVO = 'EFECTIVO',
|
||||
FACTURA_DIRECTA = 'FACTURA_DIRECTA',
|
||||
}
|
||||
|
||||
/**
|
||||
* Tipo de Combustible
|
||||
*/
|
||||
export enum TipoCombustible {
|
||||
DIESEL = 'DIESEL',
|
||||
GASOLINA_MAGNA = 'GASOLINA_MAGNA',
|
||||
GASOLINA_PREMIUM = 'GASOLINA_PREMIUM',
|
||||
GAS_LP = 'GAS_LP',
|
||||
GAS_NATURAL = 'GAS_NATURAL',
|
||||
}
|
||||
|
||||
/**
|
||||
* Estado del Gasto
|
||||
*/
|
||||
export enum EstadoGasto {
|
||||
PENDIENTE = 'PENDIENTE',
|
||||
APROBADO = 'APROBADO',
|
||||
RECHAZADO = 'RECHAZADO',
|
||||
PAGADO = 'PAGADO',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'fuel', name: 'cargas_combustible' })
|
||||
@Index('idx_carga_unidad', ['unidadId'])
|
||||
@Index('idx_carga_viaje', ['viajeId'])
|
||||
@Index('idx_carga_fecha', ['tenantId', 'fechaCarga'])
|
||||
export class CargaCombustible {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Unidad y viaje
|
||||
@Column({ name: 'unidad_id', type: 'uuid' })
|
||||
unidadId: string;
|
||||
|
||||
@Column({ name: 'viaje_id', type: 'uuid', nullable: true })
|
||||
viajeId: string | null;
|
||||
|
||||
@Column({ name: 'operador_id', type: 'uuid' })
|
||||
operadorId: string;
|
||||
|
||||
// Carga
|
||||
@Column({ name: 'tipo_carga', type: 'enum', enum: TipoCargaCombustible })
|
||||
tipoCarga: TipoCargaCombustible;
|
||||
|
||||
@Column({ name: 'tipo_combustible', type: 'varchar', length: 20 })
|
||||
tipoCombustible: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 3 })
|
||||
litros: number;
|
||||
|
||||
@Column({ name: 'precio_litro', type: 'decimal', precision: 10, scale: 4 })
|
||||
precioLitro: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2 })
|
||||
total: number;
|
||||
|
||||
// Odómetro
|
||||
@Column({ name: 'odometro_carga', type: 'int', nullable: true })
|
||||
odometroCarga: number | null;
|
||||
|
||||
@Column({ name: 'rendimiento_calculado', type: 'decimal', precision: 6, scale: 2, nullable: true })
|
||||
rendimientoCalculado: number | null;
|
||||
|
||||
// Ubicación
|
||||
@Column({ name: 'estacion_id', type: 'uuid', nullable: true })
|
||||
estacionId: string | null;
|
||||
|
||||
@Column({ name: 'estacion_nombre', type: 'varchar', length: 200, nullable: true })
|
||||
estacionNombre: string | null;
|
||||
|
||||
@Column({ name: 'estacion_direccion', type: 'text', nullable: true })
|
||||
estacionDireccion: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
latitud: number | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
longitud: number | null;
|
||||
|
||||
// Vale/Factura
|
||||
@Column({ name: 'numero_vale', type: 'varchar', length: 50, nullable: true })
|
||||
numeroVale: string | null;
|
||||
|
||||
@Column({ name: 'numero_factura', type: 'varchar', length: 50, nullable: true })
|
||||
numeroFactura: string | null;
|
||||
|
||||
@Column({ name: 'folio_ticket', type: 'varchar', length: 50, nullable: true })
|
||||
folioTicket: string | null;
|
||||
|
||||
// Fecha
|
||||
@Column({ name: 'fecha_carga', type: 'timestamptz' })
|
||||
fechaCarga: Date;
|
||||
|
||||
// Aprobación
|
||||
@Column({ type: 'enum', enum: EstadoGasto, default: EstadoGasto.PENDIENTE })
|
||||
estado: EstadoGasto;
|
||||
|
||||
@Column({ name: 'aprobado_por', type: 'uuid', nullable: true })
|
||||
aprobadoPor: string | null;
|
||||
|
||||
@Column({ name: 'aprobado_fecha', type: 'timestamptz', nullable: true })
|
||||
aprobadoFecha: Date | null;
|
||||
|
||||
// Evidencia
|
||||
@Column({ name: 'foto_ticket_url', type: 'text', nullable: true })
|
||||
fotoTicketUrl: string | null;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid' })
|
||||
createdById: string;
|
||||
|
||||
// Helpers
|
||||
get costoPorLitro(): number {
|
||||
return this.litros > 0 ? this.total / this.litros : 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* Tipo de Anomalía en Rendimiento
|
||||
*/
|
||||
export enum TipoAnomaliaRendimiento {
|
||||
BAJO_RENDIMIENTO = 'BAJO_RENDIMIENTO',
|
||||
CONSUMO_EXCESIVO = 'CONSUMO_EXCESIVO',
|
||||
POSIBLE_ROBO = 'POSIBLE_ROBO',
|
||||
FALLA_MECANICA = 'FALLA_MECANICA',
|
||||
RUTA_INEFICIENTE = 'RUTA_INEFICIENTE',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'fuel', name: 'control_rendimiento' })
|
||||
@Index('idx_rendimiento_unidad', ['unidadId'])
|
||||
@Index('idx_rendimiento_fecha', ['tenantId', 'fechaInicio'])
|
||||
export class ControlRendimiento {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Unidad
|
||||
@Column({ name: 'unidad_id', type: 'uuid' })
|
||||
unidadId: string;
|
||||
|
||||
// Período
|
||||
@Column({ name: 'fecha_inicio', type: 'date' })
|
||||
fechaInicio: Date;
|
||||
|
||||
@Column({ name: 'fecha_fin', type: 'date' })
|
||||
fechaFin: Date;
|
||||
|
||||
// Métricas
|
||||
@Column({ name: 'km_recorridos', type: 'int' })
|
||||
kmRecorridos: number;
|
||||
|
||||
@Column({ name: 'litros_consumidos', type: 'decimal', precision: 12, scale: 3 })
|
||||
litrosConsumidos: number;
|
||||
|
||||
@Column({ name: 'rendimiento_real', type: 'decimal', precision: 6, scale: 2 })
|
||||
rendimientoReal: number;
|
||||
|
||||
@Column({ name: 'rendimiento_esperado', type: 'decimal', precision: 6, scale: 2, nullable: true })
|
||||
rendimientoEsperado: number | null;
|
||||
|
||||
@Column({ name: 'variacion_porcentaje', type: 'decimal', precision: 5, scale: 2, nullable: true })
|
||||
variacionPorcentaje: number | null;
|
||||
|
||||
// Costos
|
||||
@Column({ name: 'costo_total_combustible', type: 'decimal', precision: 15, scale: 2, nullable: true })
|
||||
costoTotalCombustible: number | null;
|
||||
|
||||
@Column({ name: 'costo_por_km', type: 'decimal', precision: 8, scale: 4, nullable: true })
|
||||
costoPorKm: number | null;
|
||||
|
||||
// Alertas
|
||||
@Column({ name: 'tiene_anomalia', type: 'boolean', default: false })
|
||||
tieneAnomalia: boolean;
|
||||
|
||||
@Column({ name: 'tipo_anomalia', type: 'varchar', length: 50, nullable: true })
|
||||
tipoAnomalia: string | null;
|
||||
|
||||
@Column({ name: 'descripcion_anomalia', type: 'text', nullable: true })
|
||||
descripcionAnomalia: string | null;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
// Helpers
|
||||
get eficiencia(): string {
|
||||
if (!this.rendimientoEsperado) return 'SIN_REFERENCIA';
|
||||
const ratio = this.rendimientoReal / this.rendimientoEsperado;
|
||||
if (ratio >= 0.95) return 'OPTIMO';
|
||||
if (ratio >= 0.85) return 'ACEPTABLE';
|
||||
if (ratio >= 0.75) return 'BAJO';
|
||||
return 'CRITICO';
|
||||
}
|
||||
|
||||
get diasPeriodo(): number {
|
||||
const inicio = new Date(this.fechaInicio);
|
||||
const fin = new Date(this.fechaFin);
|
||||
return Math.ceil((fin.getTime() - inicio.getTime()) / (1000 * 60 * 60 * 24)) + 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
import { EstadoGasto } from './carga-combustible.entity';
|
||||
|
||||
/**
|
||||
* Tipo de Pago de Peaje
|
||||
*/
|
||||
export enum TipoPagoPeaje {
|
||||
EFECTIVO = 'EFECTIVO',
|
||||
TAG = 'TAG',
|
||||
PREPAGO = 'PREPAGO',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'fuel', name: 'cruces_peaje' })
|
||||
@Index('idx_peaje_unidad', ['unidadId'])
|
||||
@Index('idx_peaje_viaje', ['viajeId'])
|
||||
@Index('idx_peaje_fecha', ['tenantId', 'fechaCruce'])
|
||||
export class CrucePeaje {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Unidad y viaje
|
||||
@Column({ name: 'unidad_id', type: 'uuid' })
|
||||
unidadId: string;
|
||||
|
||||
@Column({ name: 'viaje_id', type: 'uuid', nullable: true })
|
||||
viajeId: string | null;
|
||||
|
||||
@Column({ name: 'operador_id', type: 'uuid', nullable: true })
|
||||
operadorId: string | null;
|
||||
|
||||
// Peaje
|
||||
@Column({ name: 'caseta_nombre', type: 'varchar', length: 200 })
|
||||
casetaNombre: string;
|
||||
|
||||
@Column({ name: 'caseta_codigo', type: 'varchar', length: 50, nullable: true })
|
||||
casetaCodigo: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 200, nullable: true })
|
||||
carretera: string | null;
|
||||
|
||||
// Monto
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
monto: number;
|
||||
|
||||
@Column({ name: 'tipo_pago', type: 'varchar', length: 20, nullable: true })
|
||||
tipoPago: string | null;
|
||||
|
||||
// TAG
|
||||
@Column({ name: 'tag_numero', type: 'varchar', length: 50, nullable: true })
|
||||
tagNumero: string | null;
|
||||
|
||||
// Ubicación
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
latitud: number | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
longitud: number | null;
|
||||
|
||||
// Fecha
|
||||
@Column({ name: 'fecha_cruce', type: 'timestamptz' })
|
||||
fechaCruce: Date;
|
||||
|
||||
// Comprobante
|
||||
@Column({ name: 'numero_ticket', type: 'varchar', length: 50, nullable: true })
|
||||
numeroTicket: string | null;
|
||||
|
||||
@Column({ name: 'foto_ticket_url', type: 'text', nullable: true })
|
||||
fotoTicketUrl: string | null;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'enum', enum: EstadoGasto, default: EstadoGasto.APROBADO })
|
||||
estado: EstadoGasto;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
}
|
||||
109
src/modules/combustible-gastos/entities/gasto-viaje.entity.ts
Normal file
109
src/modules/combustible-gastos/entities/gasto-viaje.entity.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
import { EstadoGasto } from './carga-combustible.entity';
|
||||
|
||||
/**
|
||||
* Tipo de Gasto de Viaje
|
||||
*/
|
||||
export enum TipoGasto {
|
||||
COMBUSTIBLE = 'COMBUSTIBLE',
|
||||
PEAJE = 'PEAJE',
|
||||
VIATICO = 'VIATICO',
|
||||
HOSPEDAJE = 'HOSPEDAJE',
|
||||
ALIMENTOS = 'ALIMENTOS',
|
||||
ESTACIONAMIENTO = 'ESTACIONAMIENTO',
|
||||
MULTA = 'MULTA',
|
||||
MANIOBRA = 'MANIOBRA',
|
||||
REPARACION_MENOR = 'REPARACION_MENOR',
|
||||
OTRO = 'OTRO',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'fuel', name: 'gastos_viaje' })
|
||||
@Index('idx_gasto_viaje', ['viajeId'])
|
||||
@Index('idx_gasto_operador', ['operadorId'])
|
||||
@Index('idx_gasto_estado', ['tenantId', 'estado'])
|
||||
export class GastoViaje {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Viaje y operador
|
||||
@Column({ name: 'viaje_id', type: 'uuid' })
|
||||
viajeId: string;
|
||||
|
||||
@Column({ name: 'operador_id', type: 'uuid' })
|
||||
operadorId: string;
|
||||
|
||||
// Gasto
|
||||
@Column({ name: 'tipo_gasto', type: 'enum', enum: TipoGasto })
|
||||
tipoGasto: TipoGasto;
|
||||
|
||||
@Column({ type: 'varchar', length: 500 })
|
||||
descripcion: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2 })
|
||||
monto: number;
|
||||
|
||||
// Comprobante
|
||||
@Column({ name: 'tiene_factura', type: 'boolean', default: false })
|
||||
tieneFactura: boolean;
|
||||
|
||||
@Column({ name: 'numero_factura', type: 'varchar', length: 50, nullable: true })
|
||||
numeroFactura: string | null;
|
||||
|
||||
@Column({ name: 'numero_ticket', type: 'varchar', length: 50, nullable: true })
|
||||
numeroTicket: string | null;
|
||||
|
||||
@Column({ name: 'foto_comprobante_url', type: 'text', nullable: true })
|
||||
fotoComprobanteUrl: string | null;
|
||||
|
||||
// Ubicación
|
||||
@Column({ type: 'varchar', length: 200, nullable: true })
|
||||
lugar: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
latitud: number | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 7, nullable: true })
|
||||
longitud: number | null;
|
||||
|
||||
// Fecha
|
||||
@Column({ name: 'fecha_gasto', type: 'timestamptz' })
|
||||
fechaGasto: Date;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'enum', enum: EstadoGasto, default: EstadoGasto.PENDIENTE })
|
||||
estado: EstadoGasto;
|
||||
|
||||
@Column({ name: 'aprobado_por', type: 'uuid', nullable: true })
|
||||
aprobadoPor: string | null;
|
||||
|
||||
@Column({ name: 'aprobado_fecha', type: 'timestamptz', nullable: true })
|
||||
aprobadoFecha: Date | null;
|
||||
|
||||
@Column({ name: 'motivo_rechazo', type: 'text', nullable: true })
|
||||
motivoRechazo: string | null;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid' })
|
||||
createdById: string;
|
||||
|
||||
// Helpers
|
||||
get esDeducible(): boolean {
|
||||
return this.tieneFactura;
|
||||
}
|
||||
|
||||
get requiereAprobacion(): boolean {
|
||||
return this.estado === EstadoGasto.PENDIENTE;
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Combustible y Gastos Entities
|
||||
* Schema: fuel
|
||||
*/
|
||||
// TODO: Implement entities
|
||||
// - carga-combustible.entity.ts
|
||||
// - cruce-peaje.entity.ts
|
||||
// - gasto-viaje.entity.ts
|
||||
// - viatico.entity.ts
|
||||
export * from './carga-combustible.entity';
|
||||
export * from './cruce-peaje.entity';
|
||||
export * from './gasto-viaje.entity';
|
||||
export * from './anticipo-viatico.entity';
|
||||
export * from './control-rendimiento.entity';
|
||||
|
||||
@ -0,0 +1,173 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
OneToMany,
|
||||
} from 'typeorm';
|
||||
import { LineaFactura } from './linea-factura.entity';
|
||||
|
||||
/**
|
||||
* Estado de Factura
|
||||
*/
|
||||
export enum EstadoFactura {
|
||||
BORRADOR = 'BORRADOR',
|
||||
EMITIDA = 'EMITIDA',
|
||||
ENVIADA = 'ENVIADA',
|
||||
PAGADA = 'PAGADA',
|
||||
PARCIAL = 'PARCIAL',
|
||||
VENCIDA = 'VENCIDA',
|
||||
CANCELADA = 'CANCELADA',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'billing', name: 'facturas_transporte' })
|
||||
@Index('idx_factura_tenant', ['tenantId'])
|
||||
@Index('idx_factura_cliente', ['clienteId'])
|
||||
@Index('idx_factura_estado', ['tenantId', 'estado'])
|
||||
@Index('idx_factura_fecha', ['fechaEmision'])
|
||||
@Index('idx_factura_folio', ['tenantId', 'serie', 'folio'], { unique: true })
|
||||
export class FacturaTransporte {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Identificación
|
||||
@Column({ type: 'varchar', length: 10, nullable: true })
|
||||
serie: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
folio: string;
|
||||
|
||||
@Column({ name: 'uuid_cfdi', type: 'uuid', nullable: true })
|
||||
uuidCfdi: string | null;
|
||||
|
||||
// Cliente
|
||||
@Column({ name: 'cliente_id', type: 'uuid' })
|
||||
clienteId: string;
|
||||
|
||||
@Column({ name: 'cliente_rfc', type: 'varchar', length: 13 })
|
||||
clienteRfc: string;
|
||||
|
||||
@Column({ name: 'cliente_razon_social', type: 'varchar', length: 200 })
|
||||
clienteRazonSocial: string;
|
||||
|
||||
@Column({ name: 'cliente_uso_cfdi', type: 'varchar', length: 10, nullable: true })
|
||||
clienteUsoCfdi: string | null;
|
||||
|
||||
// Fechas
|
||||
@Column({ name: 'fecha_emision', type: 'timestamptz' })
|
||||
fechaEmision: Date;
|
||||
|
||||
@Column({ name: 'fecha_vencimiento', type: 'date', nullable: true })
|
||||
fechaVencimiento: Date | null;
|
||||
|
||||
// Totales
|
||||
@Column({ type: 'decimal', precision: 15, scale: 2 })
|
||||
subtotal: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
descuento: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
iva: number;
|
||||
|
||||
@Column({ name: 'retencion_iva', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
retencionIva: number;
|
||||
|
||||
@Column({ name: 'retencion_isr', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
retencionIsr: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 15, scale: 2 })
|
||||
total: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, default: 'MXN' })
|
||||
moneda: string;
|
||||
|
||||
@Column({ name: 'tipo_cambio', type: 'decimal', precision: 10, scale: 4, default: 1 })
|
||||
tipoCambio: number;
|
||||
|
||||
// Pago
|
||||
@Column({ name: 'forma_pago', type: 'varchar', length: 10, nullable: true })
|
||||
formaPago: string | null;
|
||||
|
||||
@Column({ name: 'metodo_pago', type: 'varchar', length: 10, nullable: true })
|
||||
metodoPago: string | null;
|
||||
|
||||
@Column({ name: 'condiciones_pago', type: 'varchar', length: 200, nullable: true })
|
||||
condicionesPago: string | null;
|
||||
|
||||
// Relacionados
|
||||
@Column({ name: 'viaje_ids', type: 'uuid', array: true, nullable: true })
|
||||
viajeIds: string[] | null;
|
||||
|
||||
@Column({ name: 'ot_ids', type: 'uuid', array: true, nullable: true })
|
||||
otIds: string[] | null;
|
||||
|
||||
// CFDI
|
||||
@Column({ name: 'xml_cfdi', type: 'text', nullable: true })
|
||||
xmlCfdi: string | null;
|
||||
|
||||
@Column({ name: 'pdf_url', type: 'text', nullable: true })
|
||||
pdfUrl: string | null;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'enum', enum: EstadoFactura, default: EstadoFactura.BORRADOR })
|
||||
estado: EstadoFactura;
|
||||
|
||||
// Pago
|
||||
@Column({ name: 'monto_pagado', type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
montoPagado: number;
|
||||
|
||||
@Column({ name: 'fecha_pago', type: 'timestamptz', nullable: true })
|
||||
fechaPago: Date | null;
|
||||
|
||||
// Cancelación
|
||||
@Column({ name: 'fecha_cancelacion', type: 'timestamptz', nullable: true })
|
||||
fechaCancelacion: Date | null;
|
||||
|
||||
@Column({ name: 'motivo_cancelacion', type: 'text', nullable: true })
|
||||
motivoCancelacion: string | null;
|
||||
|
||||
// Relations
|
||||
@OneToMany(() => LineaFactura, (linea) => linea.factura)
|
||||
lineas: LineaFactura[];
|
||||
|
||||
// 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;
|
||||
|
||||
// Helpers
|
||||
get saldoPendiente(): number {
|
||||
return this.total - this.montoPagado;
|
||||
}
|
||||
|
||||
get estaPagada(): boolean {
|
||||
return this.estado === EstadoFactura.PAGADA || this.montoPagado >= this.total;
|
||||
}
|
||||
|
||||
get estaVencida(): boolean {
|
||||
if (!this.fechaVencimiento || this.estaPagada) return false;
|
||||
return new Date() > new Date(this.fechaVencimiento);
|
||||
}
|
||||
|
||||
get diasVencida(): number {
|
||||
if (!this.fechaVencimiento || !this.estaVencida) return 0;
|
||||
const hoy = new Date();
|
||||
const vencimiento = new Date(this.fechaVencimiento);
|
||||
return Math.ceil((hoy.getTime() - vencimiento.getTime()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
get folioCompleto(): string {
|
||||
return this.serie ? `${this.serie}-${this.folio}` : this.folio;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity({ schema: 'billing', name: 'fuel_surcharge' })
|
||||
@Index('idx_fuel_surcharge_fecha', ['tenantId', 'fechaInicio', 'fechaFin'])
|
||||
export class FuelSurcharge {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Período
|
||||
@Column({ name: 'fecha_inicio', type: 'date' })
|
||||
fechaInicio: Date;
|
||||
|
||||
@Column({ name: 'fecha_fin', type: 'date' })
|
||||
fechaFin: Date;
|
||||
|
||||
// Precios de referencia
|
||||
@Column({ name: 'precio_diesel_referencia', type: 'decimal', precision: 10, scale: 4, nullable: true })
|
||||
precioDieselReferencia: number | null;
|
||||
|
||||
@Column({ name: 'precio_diesel_actual', type: 'decimal', precision: 10, scale: 4, nullable: true })
|
||||
precioDieselActual: number | null;
|
||||
|
||||
// Surcharge
|
||||
@Column({ name: 'porcentaje_surcharge', type: 'decimal', precision: 5, scale: 2 })
|
||||
porcentajeSurcharge: number;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'boolean', default: true })
|
||||
activo: boolean;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid' })
|
||||
createdById: string;
|
||||
|
||||
// Helpers
|
||||
get vigente(): boolean {
|
||||
const hoy = new Date();
|
||||
const inicio = new Date(this.fechaInicio);
|
||||
const fin = new Date(this.fechaFin);
|
||||
return this.activo && hoy >= inicio && hoy <= fin;
|
||||
}
|
||||
|
||||
get variacionPrecio(): number | null {
|
||||
if (!this.precioDieselReferencia || !this.precioDieselActual) return null;
|
||||
return ((this.precioDieselActual - this.precioDieselReferencia) / this.precioDieselReferencia) * 100;
|
||||
}
|
||||
|
||||
calcularRecargo(montoBase: number): number {
|
||||
return montoBase * (this.porcentajeSurcharge / 100);
|
||||
}
|
||||
|
||||
get descripcionPeriodo(): string {
|
||||
const inicio = new Date(this.fechaInicio).toLocaleDateString('es-MX');
|
||||
const fin = new Date(this.fechaFin).toLocaleDateString('es-MX');
|
||||
return `${inicio} - ${fin}`;
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
/**
|
||||
* Tarifas Entities
|
||||
* Tarifas de Transporte Entities
|
||||
* Schema: billing
|
||||
*/
|
||||
// TODO: Implement entities
|
||||
// - tarifa.entity.ts
|
||||
// - recargo.entity.ts
|
||||
// - contrato-tarifa.entity.ts
|
||||
// - lane.entity.ts
|
||||
export * from './lane.entity';
|
||||
export * from './tarifa.entity';
|
||||
export * from './recargo-catalogo.entity';
|
||||
export * from './factura-transporte.entity';
|
||||
export * from './linea-factura.entity';
|
||||
export * from './fuel-surcharge.entity';
|
||||
|
||||
72
src/modules/tarifas-transporte/entities/lane.entity.ts
Normal file
72
src/modules/tarifas-transporte/entities/lane.entity.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity({ schema: 'billing', name: 'lanes' })
|
||||
@Index('idx_lane_tenant', ['tenantId'])
|
||||
@Index('idx_lane_codigo', ['tenantId', 'codigo'], { unique: true })
|
||||
export class Lane {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Identificación
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
codigo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
nombre: string;
|
||||
|
||||
// Origen
|
||||
@Column({ name: 'origen_ciudad', type: 'varchar', length: 100 })
|
||||
origenCiudad: string;
|
||||
|
||||
@Column({ name: 'origen_estado', type: 'varchar', length: 100 })
|
||||
origenEstado: string;
|
||||
|
||||
@Column({ name: 'origen_codigo_postal', type: 'varchar', length: 10, nullable: true })
|
||||
origenCodigoPostal: string | null;
|
||||
|
||||
// Destino
|
||||
@Column({ name: 'destino_ciudad', type: 'varchar', length: 100 })
|
||||
destinoCiudad: string;
|
||||
|
||||
@Column({ name: 'destino_estado', type: 'varchar', length: 100 })
|
||||
destinoEstado: string;
|
||||
|
||||
@Column({ name: 'destino_codigo_postal', type: 'varchar', length: 10, nullable: true })
|
||||
destinoCodigoPostal: string | null;
|
||||
|
||||
// Distancia
|
||||
@Column({ name: 'distancia_km', type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
distanciaKm: number | null;
|
||||
|
||||
@Column({ name: 'tiempo_estimado_horas', type: 'decimal', precision: 6, scale: 2, nullable: true })
|
||||
tiempoEstimadoHoras: number | null;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'boolean', default: true })
|
||||
activo: boolean;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid' })
|
||||
createdById: string;
|
||||
|
||||
// Helpers
|
||||
get descripcionCorta(): string {
|
||||
return `${this.origenCiudad} → ${this.destinoCiudad}`;
|
||||
}
|
||||
|
||||
get descripcionCompleta(): string {
|
||||
return `${this.origenCiudad}, ${this.origenEstado} → ${this.destinoCiudad}, ${this.destinoEstado}`;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { FacturaTransporte } from './factura-transporte.entity';
|
||||
import { RecargoCatalogo } from './recargo-catalogo.entity';
|
||||
|
||||
@Entity({ schema: 'billing', name: 'lineas_factura' })
|
||||
@Index('idx_linea_factura', ['facturaId'])
|
||||
export class LineaFactura {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Column({ name: 'factura_id', type: 'uuid' })
|
||||
facturaId: string;
|
||||
|
||||
@ManyToOne(() => FacturaTransporte, (factura) => factura.lineas)
|
||||
@JoinColumn({ name: 'factura_id' })
|
||||
factura: FacturaTransporte;
|
||||
|
||||
// Secuencia
|
||||
@Column({ type: 'int' })
|
||||
linea: number;
|
||||
|
||||
// Concepto
|
||||
@Column({ type: 'text' })
|
||||
descripcion: string;
|
||||
|
||||
@Column({ name: 'clave_producto_sat', type: 'varchar', length: 10, nullable: true })
|
||||
claveProductoSat: string | null;
|
||||
|
||||
@Column({ name: 'unidad_sat', type: 'varchar', length: 10, nullable: true })
|
||||
unidadSat: string | null;
|
||||
|
||||
// Cantidades
|
||||
@Column({ type: 'decimal', precision: 12, scale: 4 })
|
||||
cantidad: number;
|
||||
|
||||
@Column({ name: 'precio_unitario', type: 'decimal', precision: 15, scale: 4 })
|
||||
precioUnitario: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 15, scale: 2, default: 0 })
|
||||
descuento: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 15, scale: 2 })
|
||||
importe: number;
|
||||
|
||||
// Impuestos
|
||||
@Column({ name: 'iva_tasa', type: 'decimal', precision: 5, scale: 2, default: 16 })
|
||||
ivaTasa: number;
|
||||
|
||||
@Column({ name: 'iva_monto', type: 'decimal', precision: 15, scale: 2, nullable: true })
|
||||
ivaMonto: number | null;
|
||||
|
||||
// Referencia
|
||||
@Column({ name: 'viaje_id', type: 'uuid', nullable: true })
|
||||
viajeId: string | null;
|
||||
|
||||
@Column({ name: 'ot_id', type: 'uuid', nullable: true })
|
||||
otId: string | null;
|
||||
|
||||
@Column({ name: 'recargo_id', type: 'uuid', nullable: true })
|
||||
recargoId: string | null;
|
||||
|
||||
@ManyToOne(() => RecargoCatalogo)
|
||||
@JoinColumn({ name: 'recargo_id' })
|
||||
recargo: RecargoCatalogo | null;
|
||||
|
||||
// Helpers
|
||||
get importeNeto(): number {
|
||||
return this.importe - this.descuento;
|
||||
}
|
||||
|
||||
get importeConIva(): number {
|
||||
return this.importeNeto + (this.ivaMonto || 0);
|
||||
}
|
||||
|
||||
calcularImporte(): number {
|
||||
return this.cantidad * this.precioUnitario;
|
||||
}
|
||||
|
||||
calcularIva(): number {
|
||||
return this.importeNeto * (this.ivaTasa / 100);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
|
||||
/**
|
||||
* Tipo de Recargo
|
||||
*/
|
||||
export enum TipoRecargo {
|
||||
FUEL_SURCHARGE = 'FUEL_SURCHARGE',
|
||||
DETENTION = 'DETENTION',
|
||||
MANIOBRAS = 'MANIOBRAS',
|
||||
CUSTODIA = 'CUSTODIA',
|
||||
ESCOLTA = 'ESCOLTA',
|
||||
PERNOCTA = 'PERNOCTA',
|
||||
ESTADIAS = 'ESTADIAS',
|
||||
FALSO_FLETE = 'FALSO_FLETE',
|
||||
SEGURO_ADICIONAL = 'SEGURO_ADICIONAL',
|
||||
OTRO = 'OTRO',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'billing', name: 'recargos_catalogo' })
|
||||
@Index('idx_recargo_tenant', ['tenantId'])
|
||||
@Index('idx_recargo_codigo', ['tenantId', 'codigo'], { unique: true })
|
||||
export class RecargoCatalogo {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Identificación
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
codigo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
nombre: string;
|
||||
|
||||
@Column({ type: 'enum', enum: TipoRecargo })
|
||||
tipo: TipoRecargo;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
descripcion: string | null;
|
||||
|
||||
// Monto
|
||||
@Column({ name: 'es_porcentaje', type: 'boolean', default: false })
|
||||
esPorcentaje: boolean;
|
||||
|
||||
@Column({ type: 'decimal', precision: 15, scale: 4 })
|
||||
monto: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, default: 'MXN' })
|
||||
moneda: string;
|
||||
|
||||
// Aplicación
|
||||
@Column({ name: 'aplica_automatico', type: 'boolean', default: false })
|
||||
aplicaAutomatico: boolean;
|
||||
|
||||
@Column({ name: 'condicion_aplicacion', type: 'text', nullable: true })
|
||||
condicionAplicacion: string | null;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'boolean', default: true })
|
||||
activo: boolean;
|
||||
|
||||
// Auditoría
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@Column({ name: 'created_by_id', type: 'uuid' })
|
||||
createdById: string;
|
||||
|
||||
// Helpers
|
||||
calcularRecargo(base: number): number {
|
||||
if (this.esPorcentaje) {
|
||||
return base * (this.monto / 100);
|
||||
}
|
||||
return this.monto;
|
||||
}
|
||||
|
||||
get descripcionMonto(): string {
|
||||
if (this.esPorcentaje) {
|
||||
return `${this.monto}%`;
|
||||
}
|
||||
return `$${this.monto.toFixed(2)} ${this.moneda}`;
|
||||
}
|
||||
}
|
||||
161
src/modules/tarifas-transporte/entities/tarifa.entity.ts
Normal file
161
src/modules/tarifas-transporte/entities/tarifa.entity.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Lane } from './lane.entity';
|
||||
|
||||
/**
|
||||
* Tipo de Tarifa
|
||||
*/
|
||||
export enum TipoTarifa {
|
||||
POR_VIAJE = 'POR_VIAJE',
|
||||
POR_KM = 'POR_KM',
|
||||
POR_TONELADA = 'POR_TONELADA',
|
||||
POR_M3 = 'POR_M3',
|
||||
POR_PALLET = 'POR_PALLET',
|
||||
POR_HORA = 'POR_HORA',
|
||||
MIXTA = 'MIXTA',
|
||||
}
|
||||
|
||||
@Entity({ schema: 'billing', name: 'tarifas' })
|
||||
@Index('idx_tarifa_tenant', ['tenantId'])
|
||||
@Index('idx_tarifa_cliente', ['clienteId'])
|
||||
@Index('idx_tarifa_lane', ['laneId'])
|
||||
@Index('idx_tarifa_activa', ['tenantId', 'activa', 'fechaInicio'])
|
||||
export class Tarifa {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
// Identificación
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
codigo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
nombre: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
descripcion: string | null;
|
||||
|
||||
// Cliente (opcional para tarifas generales)
|
||||
@Column({ name: 'cliente_id', type: 'uuid', nullable: true })
|
||||
clienteId: string | null;
|
||||
|
||||
// Lane (opcional)
|
||||
@Column({ name: 'lane_id', type: 'uuid', nullable: true })
|
||||
laneId: string | null;
|
||||
|
||||
@ManyToOne(() => Lane)
|
||||
@JoinColumn({ name: 'lane_id' })
|
||||
lane: Lane | null;
|
||||
|
||||
// Tipo de servicio
|
||||
@Column({ name: 'modalidad_servicio', type: 'varchar', length: 50, nullable: true })
|
||||
modalidadServicio: string | null;
|
||||
|
||||
@Column({ name: 'tipo_equipo', type: 'varchar', length: 50, nullable: true })
|
||||
tipoEquipo: string | null;
|
||||
|
||||
// Tipo de tarifa
|
||||
@Column({ name: 'tipo_tarifa', type: 'enum', enum: TipoTarifa })
|
||||
tipoTarifa: TipoTarifa;
|
||||
|
||||
// Precios
|
||||
@Column({ name: 'tarifa_base', type: 'decimal', precision: 15, scale: 2 })
|
||||
tarifaBase: number;
|
||||
|
||||
@Column({ name: 'tarifa_km', type: 'decimal', precision: 10, scale: 4, nullable: true })
|
||||
tarifaKm: number | null;
|
||||
|
||||
@Column({ name: 'tarifa_tonelada', type: 'decimal', precision: 10, scale: 4, nullable: true })
|
||||
tarifaTonelada: number | null;
|
||||
|
||||
@Column({ name: 'tarifa_m3', type: 'decimal', precision: 10, scale: 4, nullable: true })
|
||||
tarifaM3: number | null;
|
||||
|
||||
@Column({ name: 'tarifa_pallet', type: 'decimal', precision: 10, scale: 4, nullable: true })
|
||||
tarifaPallet: number | null;
|
||||
|
||||
@Column({ name: 'tarifa_hora', type: 'decimal', precision: 10, scale: 4, nullable: true })
|
||||
tarifaHora: number | null;
|
||||
|
||||
// Mínimos
|
||||
@Column({ name: 'minimo_facturar', type: 'decimal', precision: 15, scale: 2, nullable: true })
|
||||
minimoFacturar: number | null;
|
||||
|
||||
@Column({ name: 'peso_minimo_kg', type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
pesoMinimoKg: number | null;
|
||||
|
||||
// Moneda
|
||||
@Column({ type: 'varchar', length: 3, default: 'MXN' })
|
||||
moneda: string;
|
||||
|
||||
// Vigencia
|
||||
@Column({ name: 'fecha_inicio', type: 'date' })
|
||||
fechaInicio: Date;
|
||||
|
||||
@Column({ name: 'fecha_fin', type: 'date', nullable: true })
|
||||
fechaFin: Date | null;
|
||||
|
||||
// Estado
|
||||
@Column({ type: 'boolean', default: true })
|
||||
activa: boolean;
|
||||
|
||||
// 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;
|
||||
|
||||
// Helpers
|
||||
get vigente(): boolean {
|
||||
const hoy = new Date();
|
||||
const inicio = new Date(this.fechaInicio);
|
||||
const fin = this.fechaFin ? new Date(this.fechaFin) : null;
|
||||
return this.activa && hoy >= inicio && (!fin || hoy <= fin);
|
||||
}
|
||||
|
||||
get esEspecifica(): boolean {
|
||||
return !!this.clienteId;
|
||||
}
|
||||
|
||||
calcularMonto(params: { km?: number; toneladas?: number; m3?: number; pallets?: number; horas?: number }): number {
|
||||
let total = this.tarifaBase;
|
||||
|
||||
switch (this.tipoTarifa) {
|
||||
case TipoTarifa.POR_KM:
|
||||
total += (params.km || 0) * (this.tarifaKm || 0);
|
||||
break;
|
||||
case TipoTarifa.POR_TONELADA:
|
||||
total += (params.toneladas || 0) * (this.tarifaTonelada || 0);
|
||||
break;
|
||||
case TipoTarifa.POR_M3:
|
||||
total += (params.m3 || 0) * (this.tarifaM3 || 0);
|
||||
break;
|
||||
case TipoTarifa.POR_PALLET:
|
||||
total += (params.pallets || 0) * (this.tarifaPallet || 0);
|
||||
break;
|
||||
case TipoTarifa.POR_HORA:
|
||||
total += (params.horas || 0) * (this.tarifaHora || 0);
|
||||
break;
|
||||
case TipoTarifa.MIXTA:
|
||||
total += (params.km || 0) * (this.tarifaKm || 0);
|
||||
total += (params.toneladas || 0) * (this.tarifaTonelada || 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return this.minimoFacturar ? Math.max(total, this.minimoFacturar) : total;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user