A whole bunch of changes
This commit is contained in:
parent
901ec678a3
commit
956231048e
122 changed files with 5442 additions and 914 deletions
|
|
@ -1,19 +1,55 @@
|
|||
import '../models/fuel_entry.dart';
|
||||
import '../models/vehicle.dart';
|
||||
|
||||
/// One vehicle's totals within a [FuelReport]'s date range.
|
||||
/// One vehicle's totals — and, for the line-items/receipt-images report
|
||||
/// sections, its individual entries — within a [FuelReport]'s date range.
|
||||
/// [entries] is sorted oldest-first, the natural order for a ledger of
|
||||
/// line items building up to a total.
|
||||
class VehicleReportRow {
|
||||
final Vehicle vehicle;
|
||||
final int entryCount;
|
||||
final List<FuelEntry> entries;
|
||||
final double totalGallons;
|
||||
final double totalCost;
|
||||
|
||||
VehicleReportRow({
|
||||
required this.vehicle,
|
||||
required this.entryCount,
|
||||
required this.entries,
|
||||
required this.totalGallons,
|
||||
required this.totalCost,
|
||||
});
|
||||
|
||||
int get entryCount => entries.length;
|
||||
}
|
||||
|
||||
/// One entry paired with the vehicle it belongs to — [FuelEntry] only knows
|
||||
/// [FuelEntry.vehicleId], not the [Vehicle] itself, so anything that needs
|
||||
/// to show both together (like [FuelReport.allEntries], for a combined
|
||||
/// cross-vehicle list) needs this pairing.
|
||||
class ReportEntry {
|
||||
final FuelEntry entry;
|
||||
final Vehicle vehicle;
|
||||
|
||||
ReportEntry({required this.entry, required this.vehicle});
|
||||
}
|
||||
|
||||
/// Every qualifying entry in a single calendar month, across every vehicle
|
||||
/// in the report — the "Gallons by month" breakdown is this, one per
|
||||
/// month present in [FuelReport.monthlyTotals].
|
||||
class MonthlyTotal {
|
||||
/// The first of the month, e.g. `DateTime(2026, 8)` for August 2026.
|
||||
final DateTime month;
|
||||
final int fillCount;
|
||||
final double totalGallons;
|
||||
final double totalCost;
|
||||
|
||||
MonthlyTotal({
|
||||
required this.month,
|
||||
required this.fillCount,
|
||||
required this.totalGallons,
|
||||
required this.totalCost,
|
||||
});
|
||||
|
||||
double get avgPricePerGallon => totalGallons == 0 ? 0 : totalCost / totalGallons;
|
||||
}
|
||||
|
||||
/// Per-vehicle fuel totals for entries with a receipt, dated within
|
||||
|
|
@ -27,6 +63,47 @@ class FuelReport {
|
|||
|
||||
double get totalGallons => rows.fold(0.0, (sum, r) => sum + r.totalGallons);
|
||||
double get totalCost => rows.fold(0.0, (sum, r) => sum + r.totalCost);
|
||||
int get fillCount => rows.fold(0, (sum, r) => sum + r.entryCount);
|
||||
double get avgPricePerGallon => totalGallons == 0 ? 0 : totalCost / totalGallons;
|
||||
|
||||
/// Every entry across every vehicle, oldest first — the natural order for
|
||||
/// a combined "purchase detail" ledger that isn't grouped by vehicle.
|
||||
List<ReportEntry> get allEntries {
|
||||
final list = [
|
||||
for (final row in rows)
|
||||
for (final entry in row.entries) ReportEntry(entry: entry, vehicle: row.vehicle),
|
||||
];
|
||||
list.sort((a, b) => a.entry.date.compareTo(b.entry.date));
|
||||
return list;
|
||||
}
|
||||
|
||||
/// [allEntries] grouped by calendar month, in chronological order — the
|
||||
/// "Purchase detail" section's grouping, and the source data for
|
||||
/// [monthlyTotals].
|
||||
Map<DateTime, List<ReportEntry>> get entriesByMonth {
|
||||
final map = <DateTime, List<ReportEntry>>{};
|
||||
for (final reportEntry in allEntries) {
|
||||
final month = DateTime(reportEntry.entry.date.year, reportEntry.entry.date.month);
|
||||
(map[month] ??= []).add(reportEntry);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// One [MonthlyTotal] per calendar month with qualifying entries, oldest
|
||||
/// first — the "Gallons by month" chart/table's data.
|
||||
List<MonthlyTotal> get monthlyTotals {
|
||||
final byMonth = entriesByMonth;
|
||||
final months = byMonth.keys.toList()..sort();
|
||||
return [
|
||||
for (final month in months)
|
||||
MonthlyTotal(
|
||||
month: month,
|
||||
fillCount: byMonth[month]!.length,
|
||||
totalGallons: byMonth[month]!.fold(0.0, (sum, re) => sum + re.entry.gallons),
|
||||
totalCost: byMonth[month]!.fold(0.0, (sum, re) => sum + re.entry.totalCost),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a per-vehicle fuel summary for [startDate]–[endDate] (inclusive,
|
||||
|
|
@ -68,11 +145,12 @@ FuelReport buildFuelReport({
|
|||
// No matching (active) vehicle — e.g. deleted since — nothing sane to
|
||||
// attribute these entries to.
|
||||
if (vehicle == null) continue;
|
||||
final sortedEntries = entry.value.toList()..sort((a, b) => a.date.compareTo(b.date));
|
||||
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),
|
||||
entries: sortedEntries,
|
||||
totalGallons: sortedEntries.fold(0.0, (sum, e) => sum + e.gallons),
|
||||
totalCost: sortedEntries.fold(0.0, (sum, e) => sum + e.totalCost),
|
||||
));
|
||||
}
|
||||
rows.sort((a, b) => a.vehicle.displayLabel.compareTo(b.vehicle.displayLabel));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue