Files
edr-platform/apps/edr-freight-api/src/modules/fuel/fuel.service.ts
2026-07-16 00:33:31 +00:00

134 lines
4.6 KiB
TypeScript

import { ConflictException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FuelRepository } from './fuel.repository';
import { FuelPurchase } from './entities/fuel-purchase.entity';
import { FuelConsumption } from './entities/fuel-consumption.entity';
import { CreateFuelPurchaseDto } from './dto/create-fuel-purchase.dto';
@Injectable()
export class FuelService {
constructor(
private readonly fuelRepository: FuelRepository,
@InjectRepository(FuelPurchase)
private readonly purchaseRepository: Repository<FuelPurchase>,
) {}
async recordFuelPurchase(dto: CreateFuelPurchaseDto): Promise<FuelPurchase> {
// Reject a re-submitted receipt for the same vehicle (double-entry guard).
if (dto.receiptNumber) {
const duplicate = await this.purchaseRepository.findOne({
where: { vehicleId: dto.vehicleId, receiptNumber: dto.receiptNumber },
});
if (duplicate) {
throw new ConflictException(
`A fuel purchase with receipt number ${dto.receiptNumber} already exists for this vehicle`,
);
}
}
const totalCost = dto.liters * dto.costPerLiter;
const purchase = this.purchaseRepository.create({
...dto,
totalCost,
});
const saved = await this.purchaseRepository.save(purchase);
// Update monthly consumption
await this.updateMonthlyConsumption(dto.vehicleId, new Date(dto.purchaseDate));
return saved;
}
async getAllFuelPurchases(): Promise<FuelPurchase[]> {
return this.purchaseRepository
.createQueryBuilder('purchase')
.leftJoinAndSelect('purchase.vehicle', 'vehicle')
.orderBy('purchase.purchaseDate', 'DESC')
.getMany();
}
async getFuelPurchases(
vehicleId: string,
startDate: Date,
endDate: Date,
): Promise<FuelPurchase[]> {
return this.fuelRepository.findByVehicleAndDateRange(vehicleId, startDate, endDate);
}
async getMonthlyConsumption(
vehicleId: string,
month: Date,
): Promise<FuelConsumption | null> {
return this.fuelRepository.getMonthlyConsumption(vehicleId, month);
}
async getFleetFuelStats(monthsBack: number = 12) {
const endDate = new Date();
const startDate = new Date(endDate.getFullYear(), endDate.getMonth() - monthsBack, 1);
const purchases = await this.purchaseRepository
.createQueryBuilder('purchase')
.where('purchase.purchaseDate BETWEEN :startDate AND :endDate', { startDate, endDate })
.getMany();
const totalLiters = purchases.reduce((sum: number, p: FuelPurchase) => sum + Number(p.liters), 0);
const totalCost = purchases.reduce((sum: number, p: FuelPurchase) => sum + Number(p.totalCost), 0);
const averagePrice = totalLiters > 0 ? totalCost / totalLiters : 0;
return {
totalPurchases: purchases.length,
totalLiters,
totalCost,
averagePricePerLiter: averagePrice,
averageEfficiency: 0, // Placeholder - would need distance data
dateRange: { startDate, endDate },
};
}
async getVehicleFuelStats(vehicleId: string, monthsBack: number = 12) {
const endDate = new Date();
const startDate = new Date(endDate.getFullYear(), endDate.getMonth() - monthsBack, 1);
const purchases = await this.getFuelPurchases(vehicleId, startDate, endDate);
const totalLiters = purchases.reduce((sum: number, p: FuelPurchase) => sum + Number(p.liters), 0);
const totalCost = purchases.reduce((sum: number, p: FuelPurchase) => sum + Number(p.totalCost), 0);
const averagePrice = totalLiters > 0 ? totalCost / totalLiters : 0;
return {
vehicleId,
totalPurchases: purchases.length,
totalLiters,
totalCost,
averagePricePerLiter: averagePrice,
dateRange: { startDate, endDate },
};
}
private async updateMonthlyConsumption(vehicleId: string, date: Date): Promise<void> {
const monthStart = new Date(date.getFullYear(), date.getMonth(), 1);
const monthEnd = new Date(monthStart.getFullYear(), monthStart.getMonth() + 1, 1);
const purchases = await this.fuelRepository.findByVehicleAndDateRange(
vehicleId,
monthStart,
monthEnd,
);
const totalLiters = purchases.reduce((sum: number, p: FuelPurchase) => sum + Number(p.liters), 0);
const totalCost = purchases.reduce((sum: number, p: FuelPurchase) => sum + Number(p.totalCost), 0);
const numberOfPurchases = purchases.length;
const averageCostPerLiter = totalLiters > 0 ? totalCost / totalLiters : 0;
await this.fuelRepository.updateMonthlyConsumption(vehicleId, monthStart, {
totalLiters,
totalCost,
numberOfPurchases,
averageCostPerLiter,
});
}
}