feat: add maintenance cost aggregation for TCO (refs #15)

- 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 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-12 19:59:41 -06:00
parent 8517b1ded2
commit 35fd1782b4
2 changed files with 21 additions and 1 deletions

View File

@@ -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<MaintenanceCostStats> {
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<MaintenanceRecordResponse | null> {
const existing = await this.repo.findRecordById(id, userId);
if (!existing) return null;

View File

@@ -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;