Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,8 @@ public async Task GetAdvancedDashboardService_WithExpensesInWindow_IncludesExpen
}

[Fact]
public async Task GetAdvancedDashboardService_WithExpenses_NetSavingsIsCombinedMinusExpenses()
public async Task
GetAdvancedDashboardService_WithFuelCostAndMileageRate_NetSavingsUsesFuelCostOnly()
{
using var dbContext = CreateDbContext();
var rider = await CreateRiderAsync(dbContext, "NetSavings Rider");
Expand All @@ -437,6 +438,8 @@ public async Task GetAdvancedDashboardService_WithExpenses_NetSavingsIsCombinedM
RiderId = rider.UserId,
RideDateTimeLocal = DateTime.Now.AddMonths(-3),
Miles = 100m,
SnapshotAverageCarMpg = 20m,
GasPricePerGallon = 3m,
SnapshotMileageRateCents = 67m,
CreatedAtUtc = DateTime.UtcNow,
}
Expand All @@ -458,10 +461,13 @@ public async Task GetAdvancedDashboardService_WithExpenses_NetSavingsIsCombinedM
var service = new GetAdvancedDashboardService(dbContext, TimeProvider.System);
var result = await service.GetAsync(rider.UserId);

// 100 miles × 67 = 6700 combined savings - 30 expenses = 6670 net
Assert.Equal(6700m, result.SavingsWindows.AllTime.CombinedSavings);
// 100 miles / 20 mpg × $3 = $15 fuel cost avoided - $30 expenses = -$15 net
// Mileage-rate savings still exists, but it does not feed net savings.
Assert.Equal(15m, result.SavingsWindows.AllTime.FuelCostAvoided);
Assert.Equal(6700m, result.SavingsWindows.AllTime.MileageRateSavings);
Assert.Equal(6715m, result.SavingsWindows.AllTime.CombinedSavings);
Assert.Equal(30m, result.SavingsWindows.AllTime.TotalExpenses);
Assert.Equal(6670m, result.SavingsWindows.AllTime.NetSavings);
Assert.Equal(-15m, result.SavingsWindows.AllTime.NetSavings);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,12 @@ private static AdvancedSavingsWindow BuildWindow(
oilChangeSavings = crossings > 0 ? RoundTo2(crossings * oilChangePrice.Value) : 0m;
}

// Net savings: gross savings + oil-change offset − expenses.
// Null only when all savings are unavailable and there are no expenses.
// Net savings: fuel-cost avoided plus oil-change offset minus expenses.
decimal? netSavings = null;
bool hasSavingsData = combinedSavings.HasValue || oilChangeSavings.HasValue;
bool hasSavingsData = fuelCostAvoided.HasValue || oilChangeSavings.HasValue;
if (hasSavingsData || totalExpenses > 0m)
{
netSavings = RoundTo2(
(combinedSavings ?? 0m) + (oilChangeSavings ?? 0m) - totalExpenses
);
netSavings = RoundTo2((fuelCostAvoided ?? 0m) + (oilChangeSavings ?? 0m) - totalExpenses);
}

return new AdvancedSavingsWindow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public sealed record AdvancedSavingsWindow(
/// </summary>
decimal? OilChangeSavings,
/// <summary>
/// Net financial position: (FuelCostAvoided ?? 0) + (MileageRateSavings ?? 0) + (OilChangeSavings ?? 0) − TotalExpenses.
/// Null only when all savings components are null and expenses are zero.
/// Net financial position: (FuelCostAvoided ?? 0) + (OilChangeSavings ?? 0) − TotalExpenses.
/// Null only when fuel/oil savings are unavailable and expenses are zero.
/// Can be negative when expenses exceed savings.
/// </summary>
decimal? NetSavings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ function WindowRow({ window: w }: WindowRowProps) {
) : null}
</td>
<td className="savings-windows-cell">{formatCurrency(w.mileageRateSavings)}</td>
<td className="savings-windows-cell savings-windows-combined">
{formatCurrency(w.combinedSavings)}
</td>
<td className="savings-windows-cell">{formatCurrency(w.totalExpenses)}</td>
<td className="savings-windows-cell">{formatCurrency(w.oilChangeSavings)}</td>
<td
Expand Down Expand Up @@ -96,7 +93,6 @@ export function SavingsWindowsTable({
<th className="savings-windows-cell savings-windows-header">Gallons Saved</th>
<th className="savings-windows-cell savings-windows-header">Fuel Cost Avoided</th>
<th className="savings-windows-cell savings-windows-header">Mileage Rate</th>
<th className="savings-windows-cell savings-windows-header">Combined Savings</th>
<th className="savings-windows-cell savings-windows-header">Expenses</th>
<th className="savings-windows-cell savings-windows-header">Oil Change Savings</th>
<th className="savings-windows-cell savings-windows-header">Net Savings</th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,6 @@
color: var(--adv-ink);
}

.savings-windows-combined {
font-weight: 700;
color: #065f46;
}

.savings-windows-row:last-child .savings-windows-cell {
border-bottom: none;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export interface AdvancedSavingsWindow {
*/
oilChangeSavings: number | null;
/**
* Net financial position: combinedSavings + oilChangeSavings − totalExpenses.
* Null only when all savings are null and expenses are zero. Can be negative.
* Net financial position: fuelCostAvoided + oilChangeSavings − totalExpenses.
* Null only when fuel/oil savings are unavailable and expenses are zero. Can be negative.
*/
netSavings: number | null;
}
Expand Down