import '../models/fuel_entry.dart'; import '../models/vehicle.dart'; /// One vehicle's totals within a [FuelReport]'s date range. class VehicleReportRow { final Vehicle vehicle; final int entryCount; final double totalGallons; final double totalCost; VehicleReportRow({ required this.vehicle, required this.entryCount, required this.totalGallons, required this.totalCost, }); } /// Per-vehicle fuel totals for entries with a receipt, dated within /// [startDate]–[endDate] inclusive. See [buildFuelReport]. class FuelReport { final DateTime startDate; final DateTime endDate; final List rows; FuelReport({required this.startDate, required this.endDate, required this.rows}); double get totalGallons => rows.fold(0.0, (sum, r) => sum + r.totalGallons); double get totalCost => rows.fold(0.0, (sum, r) => sum + r.totalCost); } /// Builds a per-vehicle fuel summary for [startDate]–[endDate] (inclusive, /// judged by each entry's purchase date, in local time), restricted to /// entries that actually have a receipt attached — a report meant to /// substantiate a fuel tax refund claim is only as good as the /// documentation behind each entry, so an entry logged without a photo /// isn't something the report should be claiming on your behalf. "Has a /// receipt" means a local copy or a cloud copy — [FuelEntry.receiptImagePath] /// or [FuelEntry.receiptDriveFileId] — not specifically an already-synced /// one, since sync status is a technical detail unrelated to whether the /// purchase is documented. /// /// Vehicles with no qualifying entries in range are omitted rather than /// shown with an all-zero row. Rows are sorted by [Vehicle.displayLabel]. FuelReport buildFuelReport({ required List vehicles, required List fuelEntries, required DateTime startDate, required DateTime endDate, }) { final rangeStart = DateTime(startDate.year, startDate.month, startDate.day); final rangeEndExclusive = DateTime(endDate.year, endDate.month, endDate.day).add(const Duration(days: 1)); final vehiclesById = {for (final v in vehicles) v.id: v}; final entriesByVehicle = >{}; for (final entry in fuelEntries) { final hasReceipt = entry.receiptImagePath != null || entry.receiptDriveFileId != null; if (!hasReceipt) continue; if (entry.date.isBefore(rangeStart) || !entry.date.isBefore(rangeEndExclusive)) continue; (entriesByVehicle[entry.vehicleId] ??= []).add(entry); } final rows = []; for (final entry in entriesByVehicle.entries) { final vehicle = vehiclesById[entry.key]; // No matching (active) vehicle — e.g. deleted since — nothing sane to // attribute these entries to. if (vehicle == null) continue; rows.add(VehicleReportRow( vehicle: vehicle, entryCount: entry.value.length, totalGallons: entry.value.fold(0.0, (sum, e) => sum + e.gallons), totalCost: entry.value.fold(0.0, (sum, e) => sum + e.totalCost), )); } rows.sort((a, b) => a.vehicle.displayLabel.compareTo(b.vehicle.displayLabel)); return FuelReport(startDate: rangeStart, endDate: endDate, rows: rows); }