From 35fd1782b44ddf44192bd251ae1124c666620d77 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:59:41 -0600 Subject: [PATCH] feat: add maintenance cost aggregation for TCO (refs #15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add MaintenanceCostStats interface - Add getVehicleMaintenanceCosts() method to maintenance service - Validates numeric cost values and throws on invalid data 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../maintenance/domain/maintenance.service.ts | 16 +++++++++++++++- .../maintenance/domain/maintenance.types.ts | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/src/features/maintenance/domain/maintenance.service.ts b/backend/src/features/maintenance/domain/maintenance.service.ts index 7990ac4..da04b7e 100644 --- a/backend/src/features/maintenance/domain/maintenance.service.ts +++ b/backend/src/features/maintenance/domain/maintenance.service.ts @@ -9,7 +9,8 @@ import type { MaintenanceRecordResponse, MaintenanceScheduleResponse, MaintenanceCategory, - ScheduleType + ScheduleType, + MaintenanceCostStats } from './maintenance.types'; import { validateSubtypes } from './maintenance.types'; import { MaintenanceRepository } from '../data/maintenance.repository'; @@ -63,6 +64,19 @@ export class MaintenanceService { return records.map(r => this.toRecordResponse(r)); } + async getVehicleMaintenanceCosts(vehicleId: string, userId: string): Promise { + const records = await this.repo.findRecordsByVehicleId(vehicleId, userId); + const totalCost = records.reduce((sum, r) => { + if (r.cost === null || r.cost === undefined) return sum; + const cost = Number(r.cost); + if (isNaN(cost)) { + throw new Error(`Invalid cost value for maintenance record ${r.id}`); + } + return sum + cost; + }, 0); + return { totalCost, recordCount: records.length }; + } + async updateRecord(userId: string, id: string, patch: UpdateMaintenanceRecordRequest): Promise { const existing = await this.repo.findRecordById(id, userId); if (!existing) return null; diff --git a/backend/src/features/maintenance/domain/maintenance.types.ts b/backend/src/features/maintenance/domain/maintenance.types.ts index caeeeb5..ce85ac1 100644 --- a/backend/src/features/maintenance/domain/maintenance.types.ts +++ b/backend/src/features/maintenance/domain/maintenance.types.ts @@ -162,6 +162,12 @@ export interface MaintenanceRecordResponse extends MaintenanceRecord { subtypeCount: number; } +// TCO aggregation stats +export interface MaintenanceCostStats { + totalCost: number; + recordCount: number; +} + export interface MaintenanceScheduleResponse extends MaintenanceSchedule { subtypeCount: number; isDueSoon?: boolean;