Handle vehicle association logic and create fuel log or maintenance records from OCR results.
Vehicle Association
Single vehicle: Auto-associate with the user's only vehicle via VehiclesService.getUserVehicles(userId). If exactly 1 vehicle, use it.
Multiple vehicles: Cannot auto-associate. Insert into pending_vehicle_associations table with extracted_data JSONB and document_id. Create in-app notification prompting user to select vehicle.
No vehicles: Send error email - user must add a vehicle first.
Record Creation
Fuel log: Map OCR ReceiptExtractionResponse fields to CreateFuelLogRequest:
merchantName -> stationName
transactionDate -> date
totalAmount -> totalCost
fuelQuantity -> gallons
pricePerUnit -> pricePerGallon
fuelGrade -> grade
Maintenance record: Map OCR ReceiptExtractionResponse fields to CreateMaintenanceRecordRequest:
merchantName -> shopName
transactionDate -> date
totalAmount -> cost
Service name -> category + subtypes
odometer -> odometerReading
Set receiptDocumentId to stored document ID
Files
Logic within backend/src/features/email-ingestion/domain/email-ingestion.service.ts (extends service from sub-issue #3)
Acceptance Criteria
Single-vehicle users have records auto-created
Multi-vehicle users get pending association + in-app notification
No-vehicle users receive error email
Fuel log created with correct field mapping
Maintenance record created with correct field mapping and receipt document linked
Relates to #149
## Scope
Handle vehicle association logic and create fuel log or maintenance records from OCR results.
### Vehicle Association
- **Single vehicle**: Auto-associate with the user's only vehicle via `VehiclesService.getUserVehicles(userId)`. If exactly 1 vehicle, use it.
- **Multiple vehicles**: Cannot auto-associate. Insert into `pending_vehicle_associations` table with extracted_data JSONB and document_id. Create in-app notification prompting user to select vehicle.
- **No vehicles**: Send error email - user must add a vehicle first.
### Record Creation
- **Fuel log**: Map OCR `ReceiptExtractionResponse` fields to `CreateFuelLogRequest`:
- merchantName -> stationName
- transactionDate -> date
- totalAmount -> totalCost
- fuelQuantity -> gallons
- pricePerUnit -> pricePerGallon
- fuelGrade -> grade
- **Maintenance record**: Map OCR `ReceiptExtractionResponse` fields to `CreateMaintenanceRecordRequest`:
- merchantName -> shopName
- transactionDate -> date
- totalAmount -> cost
- Service name -> category + subtypes
- odometer -> odometerReading
- Set `receiptDocumentId` to stored document ID
### Files
- Logic within `backend/src/features/email-ingestion/domain/email-ingestion.service.ts` (extends service from sub-issue #3)
## Acceptance Criteria
- [ ] Single-vehicle users have records auto-created
- [ ] Multi-vehicle users get pending association + in-app notification
- [ ] No-vehicle users receive error email
- [ ] Fuel log created with correct field mapping
- [ ] Maintenance record created with correct field mapping and receipt document linked
No vehicles (0): Sends error email via receipt_failed template telling user to add a vehicle first
Single vehicle (1): Auto-associates and creates the actual fuel log or maintenance record
Multiple vehicles (2+): Creates pending association with in-app notification (existing behavior)
Fuel Log Creation
Maps ExtractedReceiptData to EnhancedCreateFuelLogRequest
vendor -> locationData.stationName
date -> dateTime (falls back to current date)
gallons -> fuelUnits
pricePerGallon -> costPerUnit (derives from total/gallons if missing)
fuelType -> mapped to FuelType enum (gasoline/diesel/electric)
odometerReading -> odometerReading
Maintenance Record Creation
Maps ExtractedReceiptData to CreateMaintenanceRecordRequest
shopName or vendor -> shopName
date -> date
total -> cost
category -> mapped to MaintenanceCategory (routine_maintenance/repair/performance_upgrade)
subtypes -> validated against category with case-insensitive matching and fallback to first valid subtype
odometerReading -> odometerReading
description -> notes
Sets receiptDocumentId when available
Error Handling
Record creation failure is graceful: logs error, sends "partially processed" notification
Notification includes referenceId pointing to the created record for navigation
Validation
TypeScript: 0 errors
ESLint: 0 errors (0 new warnings)
Commit: fce6075 on issue-16-maintenance-receipt-upload-ocr
Verdict: PASS | Next: Quality review
## Milestone: Vehicle Association and Record Creation
**Phase**: Execution | **Agent**: Feature Agent | **Status**: PASS
### Changes
Updated `backend/src/features/email-ingestion/domain/email-ingestion.service.ts` with:
**Vehicle Association (3-way branching)**
- **No vehicles (0)**: Sends error email via `receipt_failed` template telling user to add a vehicle first
- **Single vehicle (1)**: Auto-associates and creates the actual fuel log or maintenance record
- **Multiple vehicles (2+)**: Creates pending association with in-app notification (existing behavior)
**Fuel Log Creation**
- Maps `ExtractedReceiptData` to `EnhancedCreateFuelLogRequest`
- `vendor` -> `locationData.stationName`
- `date` -> `dateTime` (falls back to current date)
- `gallons` -> `fuelUnits`
- `pricePerGallon` -> `costPerUnit` (derives from total/gallons if missing)
- `fuelType` -> mapped to `FuelType` enum (gasoline/diesel/electric)
- `odometerReading` -> `odometerReading`
**Maintenance Record Creation**
- Maps `ExtractedReceiptData` to `CreateMaintenanceRecordRequest`
- `shopName` or `vendor` -> `shopName`
- `date` -> `date`
- `total` -> `cost`
- `category` -> mapped to `MaintenanceCategory` (routine_maintenance/repair/performance_upgrade)
- `subtypes` -> validated against category with case-insensitive matching and fallback to first valid subtype
- `odometerReading` -> `odometerReading`
- `description` -> `notes`
- Sets `receiptDocumentId` when available
**Error Handling**
- Record creation failure is graceful: logs error, sends "partially processed" notification
- Notification includes `referenceId` pointing to the created record for navigation
### Validation
- TypeScript: 0 errors
- ESLint: 0 errors (0 new warnings)
- Commit: `fce6075` on `issue-16-maintenance-receipt-upload-ocr`
*Verdict*: PASS | *Next*: Quality review
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Relates to #149
Scope
Handle vehicle association logic and create fuel log or maintenance records from OCR results.
Vehicle Association
VehiclesService.getUserVehicles(userId). If exactly 1 vehicle, use it.pending_vehicle_associationstable with extracted_data JSONB and document_id. Create in-app notification prompting user to select vehicle.Record Creation
ReceiptExtractionResponsefields toCreateFuelLogRequest:ReceiptExtractionResponsefields toCreateMaintenanceRecordRequest:receiptDocumentIdto stored document IDFiles
backend/src/features/email-ingestion/domain/email-ingestion.service.ts(extends service from sub-issue #3)Acceptance Criteria
Milestone: Vehicle Association and Record Creation
Phase: Execution | Agent: Feature Agent | Status: PASS
Changes
Updated
backend/src/features/email-ingestion/domain/email-ingestion.service.tswith:Vehicle Association (3-way branching)
receipt_failedtemplate telling user to add a vehicle firstFuel Log Creation
ExtractedReceiptDatatoEnhancedCreateFuelLogRequestvendor->locationData.stationNamedate->dateTime(falls back to current date)gallons->fuelUnitspricePerGallon->costPerUnit(derives from total/gallons if missing)fuelType-> mapped toFuelTypeenum (gasoline/diesel/electric)odometerReading->odometerReadingMaintenance Record Creation
ExtractedReceiptDatatoCreateMaintenanceRecordRequestshopNameorvendor->shopNamedate->datetotal->costcategory-> mapped toMaintenanceCategory(routine_maintenance/repair/performance_upgrade)subtypes-> validated against category with case-insensitive matching and fallback to first valid subtypeodometerReading->odometerReadingdescription->notesreceiptDocumentIdwhen availableError Handling
referenceIdpointing to the created record for navigationValidation
fce6075onissue-16-maintenance-receipt-upload-ocrVerdict: PASS | Next: Quality review