import { Repository, FindOptionsWhere, ILike, Between, In } from 'typeorm'; import { AppDataSource } from '../../../config/typeorm.js'; import { CargaCombustible, TipoCargaCombustible, EstadoGasto } from '../entities/carga-combustible.entity.js'; /** * Search parameters for fuel loads */ export interface CargaCombustibleSearchParams { tenantId: string; search?: string; unidadId?: string; viajeId?: string; operadorId?: string; tipoCarga?: TipoCargaCombustible; estado?: EstadoGasto; estados?: EstadoGasto[]; fechaDesde?: Date; fechaHasta?: Date; limit?: number; offset?: number; } /** * DTO for creating a fuel load */ export interface CreateCargaCombustibleDto { unidadId: string; viajeId?: string; operadorId: string; tipoCarga: TipoCargaCombustible; tipoCombustible: string; litros: number; precioLitro: number; total: number; odometroCarga?: number; estacionId?: string; estacionNombre?: string; estacionDireccion?: string; latitud?: number; longitud?: number; numeroVale?: string; numeroFactura?: string; folioTicket?: string; fechaCarga: Date; fotoTicketUrl?: string; } /** * DTO for updating a fuel load */ export interface UpdateCargaCombustibleDto extends Partial { estado?: EstadoGasto; rendimientoCalculado?: number; } /** * Service for managing fuel loads (Cargas de Combustible) */ export class CargaCombustibleService { private repository: Repository; constructor() { this.repository = AppDataSource.getRepository(CargaCombustible); } /** * Find all fuel loads with filters */ async findAll(params: CargaCombustibleSearchParams): Promise<{ data: CargaCombustible[]; total: number }> { const { tenantId, search, unidadId, viajeId, operadorId, tipoCarga, estado, estados, fechaDesde, fechaHasta, limit = 50, offset = 0, } = params; const where: FindOptionsWhere[] = []; const baseWhere: FindOptionsWhere = { tenantId }; if (unidadId) { baseWhere.unidadId = unidadId; } if (viajeId) { baseWhere.viajeId = viajeId; } if (operadorId) { baseWhere.operadorId = operadorId; } if (tipoCarga) { baseWhere.tipoCarga = tipoCarga; } if (estado) { baseWhere.estado = estado; } if (estados && estados.length > 0) { baseWhere.estado = In(estados); } if (fechaDesde && fechaHasta) { baseWhere.fechaCarga = Between(fechaDesde, fechaHasta); } if (search) { where.push( { ...baseWhere, estacionNombre: ILike(`%${search}%`) }, { ...baseWhere, numeroVale: ILike(`%${search}%`) }, { ...baseWhere, folioTicket: ILike(`%${search}%`) } ); } else { where.push(baseWhere); } const [data, total] = await this.repository.findAndCount({ where, take: limit, skip: offset, order: { fechaCarga: 'DESC' }, }); return { data, total }; } /** * Find a single fuel load by ID */ async findOne(tenantId: string, id: string): Promise { return this.repository.findOne({ where: { id, tenantId }, }); } /** * Create a new fuel load */ async create(tenantId: string, dto: CreateCargaCombustibleDto, createdBy: string): Promise { const carga = this.repository.create({ ...dto, tenantId, estado: EstadoGasto.PENDIENTE, createdById: createdBy, }); return this.repository.save(carga); } /** * Update an existing fuel load */ async update( tenantId: string, id: string, dto: UpdateCargaCombustibleDto ): Promise { const carga = await this.findOne(tenantId, id); if (!carga) return null; Object.assign(carga, dto); return this.repository.save(carga); } /** * Approve a fuel load */ async aprobar(tenantId: string, id: string, aprobadoPor: string): Promise { const carga = await this.findOne(tenantId, id); if (!carga) return null; if (carga.estado !== EstadoGasto.PENDIENTE) { throw new Error('Solo se pueden aprobar cargas pendientes'); } carga.estado = EstadoGasto.APROBADO; carga.aprobadoPor = aprobadoPor; carga.aprobadoFecha = new Date(); return this.repository.save(carga); } /** * Reject a fuel load */ async rechazar(tenantId: string, id: string, aprobadoPor: string): Promise { const carga = await this.findOne(tenantId, id); if (!carga) return null; if (carga.estado !== EstadoGasto.PENDIENTE) { throw new Error('Solo se pueden rechazar cargas pendientes'); } carga.estado = EstadoGasto.RECHAZADO; carga.aprobadoPor = aprobadoPor; carga.aprobadoFecha = new Date(); return this.repository.save(carga); } /** * Delete a fuel load (soft or hard) */ async delete(tenantId: string, id: string): Promise { const carga = await this.findOne(tenantId, id); if (!carga) return false; const result = await this.repository.delete(id); return (result.affected ?? 0) > 0; } /** * Get fuel loads by unit */ async getCargasPorUnidad(unidadId: string, tenantId: string): Promise { return this.repository.find({ where: { unidadId, tenantId }, order: { fechaCarga: 'DESC' }, take: 50, }); } /** * Get fuel loads by trip */ async getCargasPorViaje(viajeId: string, tenantId: string): Promise { return this.repository.find({ where: { viajeId, tenantId }, order: { fechaCarga: 'ASC' }, }); } /** * Get pending fuel loads */ async getCargasPendientes(tenantId: string): Promise { return this.repository.find({ where: { tenantId, estado: EstadoGasto.PENDIENTE }, order: { fechaCarga: 'DESC' }, }); } /** * Calculate fuel performance for a unit in a period */ async calcularRendimiento( unidadId: string, tenantId: string, fechaInicio: Date, fechaFin: Date ): Promise<{ totalLitros: number; totalKm: number; rendimiento: number }> { const cargas = await this.repository.find({ where: { unidadId, tenantId, estado: EstadoGasto.APROBADO, fechaCarga: Between(fechaInicio, fechaFin), }, order: { fechaCarga: 'ASC' }, }); if (cargas.length < 2) { return { totalLitros: 0, totalKm: 0, rendimiento: 0 }; } let totalLitros = 0; let kmInicial = cargas[0].odometroCarga || 0; let kmFinal = kmInicial; for (const carga of cargas) { totalLitros += Number(carga.litros); if (carga.odometroCarga) { kmFinal = carga.odometroCarga; } } const totalKm = kmFinal - kmInicial; const rendimiento = totalLitros > 0 ? totalKm / totalLitros : 0; return { totalLitros, totalKm, rendimiento }; } } export const cargaCombustibleService = new CargaCombustibleService();