/** * @ai-summary Data access layer for fuel logs * @ai-context Handles database operations and MPG calculations */ import { Pool } from 'pg'; import { FuelLog, CreateFuelLogRequest, FuelStats } from '../domain/fuel-logs.types'; export class FuelLogsRepository { constructor(private pool: Pool) {} async create(data: CreateFuelLogRequest & { userId: string, mpg?: number }): Promise { const query = ` INSERT INTO fuel_logs ( user_id, vehicle_id, date, odometer, gallons, price_per_gallon, total_cost, station, location, notes ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING * `; const values = [ data.userId, data.vehicleId, data.date, data.odometer, data.gallons, data.pricePerGallon, data.totalCost, data.station, data.location, data.notes ]; const result = await this.pool.query(query, values); return this.mapRow(result.rows[0]); } async findByVehicleId(vehicleId: string): Promise { const query = ` SELECT * FROM fuel_logs WHERE vehicle_id = $1 ORDER BY date DESC, created_at DESC `; const result = await this.pool.query(query, [vehicleId]); return result.rows.map(row => this.mapRow(row)); } async findByUserId(userId: string): Promise { const query = ` SELECT * FROM fuel_logs WHERE user_id = $1 ORDER BY date DESC, created_at DESC `; const result = await this.pool.query(query, [userId]); return result.rows.map(row => this.mapRow(row)); } async findById(id: string): Promise { const query = 'SELECT * FROM fuel_logs WHERE id = $1'; const result = await this.pool.query(query, [id]); if (result.rows.length === 0) { return null; } return this.mapRow(result.rows[0]); } async getPreviousLog(vehicleId: string, date: string, odometer: number): Promise { const query = ` SELECT * FROM fuel_logs WHERE vehicle_id = $1 AND (date < $2 OR (date = $2 AND odometer < $3)) ORDER BY date DESC, odometer DESC LIMIT 1 `; const result = await this.pool.query(query, [vehicleId, date, odometer]); if (result.rows.length === 0) { return null; } return this.mapRow(result.rows[0]); } async update(id: string, data: Partial): Promise { const fields = []; const values = []; let paramCount = 1; // Build dynamic update query if (data.date !== undefined) { fields.push(`date = $${paramCount++}`); values.push(data.date); } if (data.odometer !== undefined) { fields.push(`odometer = $${paramCount++}`); values.push(data.odometer); } if (data.gallons !== undefined) { fields.push(`gallons = $${paramCount++}`); values.push(data.gallons); } if (data.pricePerGallon !== undefined) { fields.push(`price_per_gallon = $${paramCount++}`); values.push(data.pricePerGallon); } if (data.totalCost !== undefined) { fields.push(`total_cost = $${paramCount++}`); values.push(data.totalCost); } if (data.station !== undefined) { fields.push(`station = $${paramCount++}`); values.push(data.station); } if (data.location !== undefined) { fields.push(`location = $${paramCount++}`); values.push(data.location); } if (data.notes !== undefined) { fields.push(`notes = $${paramCount++}`); values.push(data.notes); } // mpg column removed; efficiency is computed dynamically if (fields.length === 0) { return this.findById(id); } values.push(id); const query = ` UPDATE fuel_logs SET ${fields.join(', ')} WHERE id = $${paramCount} RETURNING * `; const result = await this.pool.query(query, values); if (result.rows.length === 0) { return null; } return this.mapRow(result.rows[0]); } async delete(id: string): Promise { const query = 'DELETE FROM fuel_logs WHERE id = $1'; const result = await this.pool.query(query, [id]); return (result.rowCount ?? 0) > 0; } async getStats(vehicleId: string): Promise { const query = ` SELECT COUNT(*) as log_count, SUM(gallons) as total_gallons, SUM(total_cost) as total_cost, AVG(price_per_gallon) as avg_price_per_gallon, MAX(odometer) - MIN(odometer) as total_miles FROM fuel_logs WHERE vehicle_id = $1 `; const result = await this.pool.query(query, [vehicleId]); if (result.rows.length === 0 || result.rows[0].log_count === '0') { return null; } const row = result.rows[0]; return { logCount: parseInt(row.log_count), totalGallons: parseFloat(row.total_gallons) || 0, totalCost: parseFloat(row.total_cost) || 0, averagePricePerGallon: parseFloat(row.avg_price_per_gallon) || 0, averageMPG: 0, totalMiles: parseInt(row.total_miles) || 0, }; } private mapRow(row: any): FuelLog { return { id: row.id, userId: row.user_id, vehicleId: row.vehicle_id, date: row.date, odometer: row.odometer, gallons: parseFloat(row.gallons), pricePerGallon: parseFloat(row.price_per_gallon), totalCost: parseFloat(row.total_cost), station: row.station, location: row.location, notes: row.notes, createdAt: row.created_at, updatedAt: row.updated_at, }; } // Enhanced API support (new schema) async createEnhanced(data: { userId: string; vehicleId: string; dateTime: Date; odometerReading?: number; tripDistance?: number; fuelType: string; fuelGrade?: string | null; fuelUnits: number; costPerUnit: number; totalCost: number; locationData?: any; notes?: string; }): Promise { const query = ` INSERT INTO fuel_logs ( user_id, vehicle_id, date, date_time, odometer, trip_distance, fuel_type, fuel_grade, fuel_units, cost_per_unit, gallons, price_per_gallon, total_cost, location_data, notes ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) RETURNING * `; const values = [ data.userId, data.vehicleId, data.dateTime.toISOString().slice(0, 10), data.dateTime, data.odometerReading ?? null, data.tripDistance ?? null, data.fuelType, data.fuelGrade ?? null, data.fuelUnits, data.costPerUnit, data.fuelUnits, // legacy support data.costPerUnit, // legacy support data.totalCost, data.locationData ?? null, data.notes ?? null ]; const res = await this.pool.query(query, values); return res.rows[0]; } async findByVehicleIdEnhanced(vehicleId: string): Promise { const res = await this.pool.query( `SELECT * FROM fuel_logs WHERE vehicle_id = $1 ORDER BY date_time DESC NULLS LAST, date DESC NULLS LAST, created_at DESC`, [vehicleId] ); return res.rows; } async findByUserIdEnhanced(userId: string): Promise { const res = await this.pool.query( `SELECT * FROM fuel_logs WHERE user_id = $1 ORDER BY date_time DESC NULLS LAST, date DESC NULLS LAST, created_at DESC`, [userId] ); return res.rows; } async findByIdEnhanced(id: string): Promise { const res = await this.pool.query(`SELECT * FROM fuel_logs WHERE id = $1`, [id]); return res.rows[0] || null; } async getPreviousLogByOdometer(vehicleId: string, odometerReading: number): Promise { const res = await this.pool.query( `SELECT * FROM fuel_logs WHERE vehicle_id = $1 AND odometer IS NOT NULL AND odometer < $2 ORDER BY odometer DESC LIMIT 1`, [vehicleId, odometerReading] ); return res.rows[0] || null; } async getLatestLogForVehicle(vehicleId: string): Promise { const res = await this.pool.query( `SELECT * FROM fuel_logs WHERE vehicle_id = $1 ORDER BY date_time DESC NULLS LAST, date DESC NULLS LAST, created_at DESC LIMIT 1`, [vehicleId] ); return res.rows[0] || null; } async updateEnhanced(id: string, data: { dateTime?: Date; odometerReading?: number; tripDistance?: number; fuelType?: string; fuelGrade?: string | null; fuelUnits?: number; costPerUnit?: number; locationData?: any; notes?: string; }): Promise { const fields = []; const values = []; let paramCount = 1; // Build dynamic update query for enhanced schema if (data.dateTime !== undefined) { fields.push(`date_time = $${paramCount++}`); fields.push(`date = $${paramCount++}`); values.push(data.dateTime); values.push(data.dateTime.toISOString().slice(0, 10)); } if (data.odometerReading !== undefined) { fields.push(`odometer = $${paramCount++}`); values.push(data.odometerReading); } if (data.tripDistance !== undefined) { fields.push(`trip_distance = $${paramCount++}`); values.push(data.tripDistance); } if (data.fuelType !== undefined) { fields.push(`fuel_type = $${paramCount++}`); values.push(data.fuelType); } if (data.fuelGrade !== undefined) { fields.push(`fuel_grade = $${paramCount++}`); values.push(data.fuelGrade); } if (data.fuelUnits !== undefined) { fields.push(`fuel_units = $${paramCount++}`); fields.push(`gallons = $${paramCount++}`); // legacy support values.push(data.fuelUnits); values.push(data.fuelUnits); } if (data.costPerUnit !== undefined) { fields.push(`cost_per_unit = $${paramCount++}`); fields.push(`price_per_gallon = $${paramCount++}`); // legacy support values.push(data.costPerUnit); values.push(data.costPerUnit); } if (data.locationData !== undefined) { fields.push(`location_data = $${paramCount++}`); values.push(data.locationData); } if (data.notes !== undefined) { fields.push(`notes = $${paramCount++}`); values.push(data.notes); } // Recalculate total cost if both fuelUnits and costPerUnit are present if (data.fuelUnits !== undefined && data.costPerUnit !== undefined) { fields.push(`total_cost = $${paramCount++}`); values.push(data.fuelUnits * data.costPerUnit); } if (fields.length === 0) { return this.findByIdEnhanced(id); } values.push(id); const query = ` UPDATE fuel_logs SET ${fields.join(', ')}, updated_at = NOW() WHERE id = $${paramCount} RETURNING * `; const result = await this.pool.query(query, values); if (result.rows.length === 0) { return null; } return result.rows[0]; } }