P2 - Tests unitarios creados (5 archivos): - carta-porte/__tests__/carta-porte.service.spec.ts - auth/__tests__/roles.service.spec.ts - auth/__tests__/permissions.service.spec.ts - tarifas-transporte/__tests__/tarifas.service.spec.ts - tarifas-transporte/__tests__/lanes.service.spec.ts P3 - Servicios implementados (19 servicios): combustible-gastos (5): - CargaCombustibleService, CrucePeajeService, GastoViajeService - AnticipoViaticoService, ControlRendimientoService hr (7 + DTOs): - EmployeesService, DepartmentsService, PuestosService - ContractsService, LeaveTypesService, LeaveAllocationsService, LeavesService reports (7): - ReportDefinitionService, ReportExecutionService, ReportScheduleService - DashboardService, KpiSnapshotService, CustomReportService, DataModelService Config: Excluir tests del build TypeScript (tsconfig.json) Total: ~8,200 líneas de código Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
294 lines
7.0 KiB
TypeScript
294 lines
7.0 KiB
TypeScript
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<CreateCargaCombustibleDto> {
|
|
estado?: EstadoGasto;
|
|
rendimientoCalculado?: number;
|
|
}
|
|
|
|
/**
|
|
* Service for managing fuel loads (Cargas de Combustible)
|
|
*/
|
|
export class CargaCombustibleService {
|
|
private repository: Repository<CargaCombustible>;
|
|
|
|
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<CargaCombustible>[] = [];
|
|
const baseWhere: FindOptionsWhere<CargaCombustible> = { 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<CargaCombustible | null> {
|
|
return this.repository.findOne({
|
|
where: { id, tenantId },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new fuel load
|
|
*/
|
|
async create(tenantId: string, dto: CreateCargaCombustibleDto, createdBy: string): Promise<CargaCombustible> {
|
|
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<CargaCombustible | null> {
|
|
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<CargaCombustible | null> {
|
|
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<CargaCombustible | null> {
|
|
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<boolean> {
|
|
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<CargaCombustible[]> {
|
|
return this.repository.find({
|
|
where: { unidadId, tenantId },
|
|
order: { fechaCarga: 'DESC' },
|
|
take: 50,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get fuel loads by trip
|
|
*/
|
|
async getCargasPorViaje(viajeId: string, tenantId: string): Promise<CargaCombustible[]> {
|
|
return this.repository.find({
|
|
where: { viajeId, tenantId },
|
|
order: { fechaCarga: 'ASC' },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get pending fuel loads
|
|
*/
|
|
async getCargasPendientes(tenantId: string): Promise<CargaCombustible[]> {
|
|
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();
|