Commit Graph

332 Commits

Author SHA1 Message Date
Eric Gullickson
26e4116f20 fix: remove obsolete ownership_costs migration 003
Migration 003 attempted to rename columns (start_date -> period_start)
that don't exist because migration 001 was already modified to use the
correct column names. This caused deployment failures.

The migration was obsolete since 001 already creates the table with
period_start/period_end columns.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 21:20:35 -06:00
Eric Gullickson
8c570288f9 fix: standardize checkboxes to use MUI Checkbox component (refs #35)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Replace raw HTML checkboxes with MUI Checkbox wrapped in FormControlLabel
for consistent styling and theme integration across:
- DocumentForm.tsx (shared vehicles + scan maintenance checkboxes)
- VehicleForm.tsx (TCO enabled checkbox)
- SignupForm.tsx (terms acceptance checkbox)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 21:01:00 -06:00
ec8e6ee5d2 Merge pull request 'feat: Document feature enhancements (#31)' (#32) from issue-31-document-enhancements into main
All checks were successful
Deploy to Staging / Build Images (push) Successful in 4m43s
Deploy to Staging / Deploy to Staging (push) Successful in 38s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Reviewed-on: #32
2026-01-15 02:35:55 +00:00
4284cd9fc5 Merge pull request 'fix: add dynamic timeout for document uploads (#33)' (#34) from issue-33-document-upload-timeout into main
All checks were successful
Deploy to Staging / Build Images (push) Successful in 25s
Deploy to Staging / Deploy to Staging (push) Successful in 28s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Reviewed-on: #34
2026-01-15 02:33:20 +00:00
Eric Gullickson
a3b119a953 fix: resolve document upload hang by fixing stream pipeline (refs #33)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m22s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
The upload was hanging silently because breaking early from a
`for await` loop on a Node.js stream corrupts the stream's internal
state. The remaining stream could not be used afterward.

Changes:
- Collect ALL chunks from the file stream before processing
- Use subarray() for file type detection header (first 4100 bytes)
- Create single readable stream from complete buffer for storage
- Remove broken headerStream + remainingStream piping logic

This fixes the root cause where uploads would hang after logging
"Document upload requested" without ever completing or erroring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 20:28:19 -06:00
Eric Gullickson
1014475c0f fix: add dynamic timeout for document uploads (refs #33)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m43s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Document uploads were failing with "timeout of 10000ms exceeded" error
because the global axios client timeout (10s) was too short for
medium-sized files (1-5MB).

Added calculateUploadTimeout() function that calculates timeout based on
file size: 30s base + 10s per MB. This allows uploads to complete on
slower connections while still having reasonable timeout limits.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 20:16:17 -06:00
Eric Gullickson
354ce47fc4 fix: remove debug console.log statements (refs #31)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m40s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:45:51 -06:00
Eric Gullickson
bdb329f7c3 feat: add context-aware document delete from vehicle screen (refs #31)
- Created DeleteDocumentConfirmDialog with context-aware messaging:
  - Primary vehicle with no shares: Full delete
  - Shared vehicle: Remove association only
  - Primary vehicle with shares: Full delete (affects all)
- Integrated documents display in VehicleDetailPage records table
- Added delete button per document with 44px touch target
- Document deletion uses appropriate backend calls based on context
- Mobile-friendly dialog with responsive design

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:41:52 -06:00
Eric Gullickson
b71e2cff3c feat: add document edit functionality with multi-vehicle support (refs #31)
Implemented comprehensive document editing capabilities:

1. Created EditDocumentDialog component:
   - Responsive MUI Dialog with fullScreen on mobile
   - Wraps DocumentForm in edit mode
   - Proper close handlers with refetch

2. Enhanced DocumentForm to support edit mode:
   - Added mode prop ('create' | 'edit')
   - Pre-populate all fields from initialValues
   - Use useUpdateDocument hook when in edit mode
   - Multi-select for shared vehicles (insurance only)
   - Vehicle and document type disabled in edit mode
   - Optional file upload in edit mode
   - Dynamic button text (Create/Save Changes)

3. Updated DocumentDetailPage:
   - Added Edit button with proper touch targets
   - Integrated EditDocumentDialog
   - Refetch document on successful edit

Mobile-first implementation:
- All touch targets >= 44px
- Dialog goes fullScreen on mobile
- Form fields stack on mobile
- Shared vehicle checkboxes have min-h-[44px]
- Buttons use flex-wrap for mobile overflow

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:38:20 -06:00
Eric Gullickson
8968cad805 feat: display vehicle names instead of UUIDs in document views (refs #31)
- Created shared utility getVehicleLabel() for consistent vehicle display
- Updated DocumentsPage to show vehicle names with clickable links
- Added "Shared with X vehicles" indicator for multi-vehicle docs
- Updated DocumentDetailPage with vehicle name and shared vehicle list
- Updated DocumentsMobileScreen with vehicle names and "Shared" indicator
- All vehicle names link to vehicle detail pages
- Mobile-first with 44px touch targets on all links

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:34:02 -06:00
Eric Gullickson
e558fdf8f9 feat: add frontend document-vehicle API client and hooks (refs #31)
- Update DocumentRecord interface to include sharedVehicleIds array
- Add optional sharedVehicleIds to Create/UpdateDocumentRequest types
- Add documentsApi.listByVehicle() method for fetching by vehicle
- Add documentsApi.addSharedVehicle() for linking vehicles
- Add documentsApi.removeVehicleFromDocument() for unlinking
- Add useDocumentsByVehicle() query hook with vehicle filter
- Add useAddSharedVehicle() mutation with optimistic updates
- Add useRemoveVehicleFromDocument() mutation with optimistic updates
- Ensure query invalidation includes both documents and documents-by-vehicle keys
- Update test mocks to include sharedVehicleIds field
- Fix optimistic update in useCreateDocument to include new fields

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:31:03 -06:00
Eric Gullickson
5dbc17e28d feat: add document-vehicle API endpoints and context-aware delete (refs #31)
Updates documents backend service and API to support multi-vehicle insurance documents:
- Service: createDocument/updateDocument validate and handle sharedVehicleIds for insurance docs
- Service: addVehicleToDocument validates ownership and adds vehicles to shared array
- Service: removeVehicleFromDocument with context-aware delete logic:
  - Shared vehicle only: remove from array
  - Primary with no shared: soft delete document
  - Primary with shared: promote first shared to primary
- Service: getDocumentsByVehicle returns all docs for a vehicle (primary or shared)
- Controller: Added handlers for listByVehicle, addVehicle, removeVehicle with proper error handling
- Routes: Added POST/DELETE /documents/:id/vehicles/:vehicleId and GET /documents/by-vehicle/:vehicleId
- Validation: Added DocumentVehicleParamsSchema for vehicle management routes

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:28:00 -06:00
Eric Gullickson
57debe4252 feat: add shared_vehicle_ids schema and repository methods (refs #31)
- Add migration 004_add_shared_vehicle_ids.sql with UUID array column and GIN index
- Update DocumentRecord interface to include sharedVehicleIds field
- Add sharedVehicleIds to CreateDocumentBody and UpdateDocumentBody schemas
- Update repository mapDocumentRecord() to map shared_vehicle_ids from database
- Update insert() and batchInsert() to handle sharedVehicleIds
- Update updateMetadata() to support sharedVehicleIds updates
- Add addSharedVehicle() method using atomic array_append()
- Add removeSharedVehicle() method using atomic array_remove()
- Add listByVehicle() method to query by primary or shared vehicle

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 19:24:34 -06:00
a5d828b6c1 Merge pull request 'refactor: Link ownership-costs to documents feature (#29)' (#30) from issue-29-link-ownership-costs into main
All checks were successful
Deploy to Staging / Build Images (push) Successful in 25s
Deploy to Staging / Deploy to Staging (push) Successful in 28s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Reviewed-on: #30
2026-01-15 01:23:56 +00:00
Eric Gullickson
025ab30726 fix: add schema migration for ownership_costs table (refs #29)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m21s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
The ownership_costs table was created with an outdated schema that had
different column names (start_date/end_date vs period_start/period_end)
and was missing the notes column. This migration aligns the database
schema with the current code expectations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 21:51:44 -06:00
Eric Gullickson
1d95eba395 fix: resolve lint error in ownership-costs types (refs #29)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Milestone 6: Change empty interface to type alias to fix
@typescript-eslint/no-empty-object-type error

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 21:37:30 -06:00
Eric Gullickson
f0deab8210 feat: add frontend ownership-costs feature (refs #29)
Milestone 4: Complete frontend with:
- Types aligned with backend schema
- API client for CRUD operations
- React Query hooks with optimistic updates
- OwnershipCostForm with all 6 cost types
- OwnershipCostsList with edit/delete actions
- Mobile-friendly (44px touch targets)
- Full dark mode support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 21:35:44 -06:00
Eric Gullickson
7928b87ef5 feat: integrate DocumentsService with ownership_costs (refs #29)
Milestone 2: Auto-create ownership_cost when insurance/registration
document is created with cost data (premium or cost field).

- Add OwnershipCostsService integration
- Auto-create cost on document create when amount > 0
- Sync cost changes on document update
- mapDocumentTypeToCostType() validation
- extractCostAmount() for premium/cost field extraction
- CASCADE delete handled by FK constraint

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 21:30:02 -06:00
Eric Gullickson
81b1c3dd70 feat: create ownership_costs backend feature capsule (refs #29)
Milestone 1: Complete backend feature with:
- Migration with CHECK (amount > 0) constraint
- Repository with mapRow() for snake_case -> camelCase
- Service with CRUD and vehicle authorization
- Controller with HTTP handlers
- Routes registered at /api/ownership-costs
- Validation with Zod schemas
- README with endpoint documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 21:28:43 -06:00
5f07123646 Merge pull request 'feat: Total Cost of Ownership (TCO) per Vehicle' (#28) from issue-15-add-tco-feature into main
All checks were successful
Deploy to Staging / Build Images (push) Successful in 24s
Deploy to Staging / Deploy to Staging (push) Successful in 27s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Reviewed-on: #28
2026-01-14 03:08:34 +00:00
Eric Gullickson
395670c3bd fix: add ownership-costs to migration order and improve error handling (refs #15)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
- Add 'features/ownership-costs' to MIGRATION_ORDER in run-all.ts
- Improve OwnershipCostsList error display to not block the page
- Show friendly message when feature needs migration
2026-01-13 08:15:53 -06:00
Eric Gullickson
cb93e3ccc5 feat: integrate ownership-costs UI into vehicle detail pages (refs #15)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m43s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
- Add OwnershipCostsList to desktop VehicleDetailPage
- Add OwnershipCostsList to mobile VehicleDetailMobile
- Users can now view, add, edit, and delete recurring costs directly
  from the vehicle detail view
2026-01-13 07:57:23 -06:00
Eric Gullickson
a8c4eba8d1 feat: add ownership-costs feature capsule (refs #15)
- Create ownership_costs table for recurring vehicle costs
- Add backend feature capsule with types, repository, service, routes
- Update TCO calculation to use ownership_costs (with fallback to legacy vehicle fields)
- Add taxCosts and otherCosts to TCO response
- Create frontend ownership-costs feature with form, list, API, hooks
- Update TCODisplay to show all cost types

This implements a more flexible approach to tracking recurring ownership costs
(insurance, registration, tax, other) with explicit date ranges and optional
document association.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 21:28:25 -06:00
Eric Gullickson
5c93150a58 fix: add TCO unit tests and fix blocking issues (refs #15)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m34s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Quality Review Fixes:
- Add comprehensive unit tests for getTCO() method (12 test cases)
- Add tests for normalizeRecurringCost() via getTCO integration
- Add future date validation guard in calculateMonthsOwned()
- Fix pre-existing unused React import in VehicleLimitDialog.test.tsx
- Fix pre-existing test parameter types in vehicles.service.test.ts

Test Coverage:
- Vehicle not found / unauthorized access
- Missing optional TCO fields handling
- Zero odometer (costPerDistance = 0)
- Monthly/semi-annual/annual cost normalization
- Division by zero guard (new purchase)
- Future purchase date handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 20:32:15 -06:00
Eric Gullickson
9e8f9a1932 feat: add TCO display component (refs #15)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 5m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 5s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
- Create TCODisplay component showing lifetime cost and cost per distance
- Display cost breakdown (purchase, insurance, registration, fuel, maintenance)
- Integrate into VehicleDetailPage right-justified next to vehicle details
- Responsive layout: stacks vertically on mobile, side-by-side on desktop
- Only shows when tcoEnabled is true

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 20:05:31 -06:00
Eric Gullickson
5e40754c68 feat: add ownership cost fields to vehicle form (refs #15)
- Add CostInterval type and TCOResponse interface
- Add TCO fields to Vehicle, CreateVehicleRequest, UpdateVehicleRequest
- Add "Ownership Costs" section to VehicleForm with:
  - Purchase price and date
  - Insurance cost and interval
  - Registration cost and interval
  - TCO display toggle
- Add getTCO API method
- Mobile-responsive grid layout with 44px touch targets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 20:04:21 -06:00
Eric Gullickson
47de6898cd feat: add TCO API endpoint (refs #15)
- Add GET /api/vehicles/:id/tco route
- Add getTCO controller method with error handling
- Returns 200 with TCO data, 404 for not found, 403 for unauthorized

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 20:02:15 -06:00
Eric Gullickson
381f602e9f feat: add TCO calculation service (refs #15)
- Add TCOResponse interface
- Add getTCO() method aggregating all cost sources
- Add normalizeRecurringCost() with division-by-zero guard
- Integrate FuelLogsService and MaintenanceService for cost data
- Respect user preferences for distance unit and currency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 20:01:24 -06:00
Eric Gullickson
35fd1782b4 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>
2026-01-12 19:59:41 -06:00
Eric Gullickson
8517b1ded2 feat: add TCO types and repository updates (refs #15)
- Add CostInterval type and PAYMENTS_PER_YEAR constant
- Add 7 TCO fields to Vehicle, CreateVehicleRequest, UpdateVehicleRequest
- Update VehicleResponse and Body types
- Update mapRow() with snake_case to camelCase mapping
- Update create(), update(), batchInsert() for new fields
- Add Zod validation for TCO fields with interval enum

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:58:59 -06:00
Eric Gullickson
b0d79a26ae feat: add TCO fields migration (refs #15)
Add database columns for Total Cost of Ownership:
- purchase_price, purchase_date
- insurance_cost, insurance_interval
- registration_cost, registration_interval
- tco_enabled toggle

Includes CHECK constraints for interval values and non-negative costs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 19:56:30 -06:00
Eric Gullickson
9059c09d2f Merge branch 'main' of 172.30.1.72:egullickson/motovaultpro
All checks were successful
Deploy to Staging / Build Images (push) Successful in 26s
Deploy to Staging / Deploy to Staging (push) Successful in 28s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
2026-01-11 21:52:36 -06:00
Eric Gullickson
34401179bd chore: update script default 2026-01-11 21:52:23 -06:00
6f86b1e7e9 Merge pull request 'feat: Add user data import feature (Fixes #26)' (#27) from issue-26-add-user-data-import into main
All checks were successful
Deploy to Staging / Build Images (push) Successful in 24s
Deploy to Staging / Deploy to Staging (push) Successful in 37s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Reviewed-on: #27
2026-01-12 03:22:31 +00:00
Eric Gullickson
28574b0eb4 fix: preserve vehicle identity by checking ID first in merge mode (refs #26)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m21s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Critical fix for merge mode vehicle matching logic.

Problem:
- Vehicles with same license plate but no VIN were matched to the same existing vehicle
- Example: 2 vehicles with license plate "TEST-123" both updated the same vehicle
- Result: "Updated: 2" but only 1 vehicle in database, second vehicle overwrites first

Root Cause:
- Matching order was: VIN → license plate
- Both vehicles had no VIN and same license plate
- Both matched the same existing vehicle by license plate

Solution:
- New matching order: ID → VIN → license plate
- Preserves vehicle identity across export/import cycles
- Vehicles exported with IDs will update the same vehicle on re-import
- New vehicles (no matching ID) will be created as new records
- Security check: Verify ID belongs to same user before matching

Benefits:
- Export-modify-import workflow now works correctly
- Vehicles maintain identity across imports
- Users can safely import data with duplicate license plates
- Prevents unintended overwrites

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 21:15:30 -06:00
Eric Gullickson
62b4dc31ab debug: add comprehensive logging to vehicle import merge (refs #26)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m52s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Added detailed logging to diagnose import merge issues:

- Log vehicle count at merge start
- Log each vehicle being processed with VIN/make/model/year
- Log when existing vehicles are found (by VIN or license plate)
- Log successful vehicle creation with new vehicle ID
- Log errors with full context (userId, VIN, make, model, error message)
- Log merge completion with summary statistics

This will help diagnose why vehicles show as "successfully imported" but don't appear in the UI.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 21:03:20 -06:00
Eric Gullickson
f48a18287b fix: prevent vehicle duplication and enforce tier limits in merge mode (refs #26)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m20s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 28s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Critical bug fixes for import merge mode:

1. Vehicle duplication bug (RULE 0 - CRITICAL):
   - Previous: Vehicles without VINs always inserted as new, creating duplicates
   - Fixed: Check by VIN first, then fallback to license plate matching
   - Impact: Prevents duplicate vehicles on repeated imports

2. Vehicle limit bypass (RULE 0 - CRITICAL):
   - Previous: Direct repo.create() bypassed tier-based vehicle limits
   - Fixed: Use VehiclesService.createVehicle() which enforces FOR UPDATE locking and tier checks
   - Impact: Free users properly limited to 1 vehicle, prevents limit violations

Changes:
- Added VehiclesService to import service constructor
- Updated mergeVehicles() to check VIN then license plate for matches
- Replace repo.create() with service.createVehicle() for limit enforcement
- Added VehicleLimitExceededError handling with clear error messages

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 20:54:38 -06:00
Eric Gullickson
566deae5af fix: match import button style to export button (refs #26)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m39s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Desktop changes:
- Replace ImportButton component with MUI Button matching Export style
- Use hidden file input with validation
- Dark red/maroon button with consistent styling

Mobile changes:
- Update both Import and Export buttons to use primary-500 style
- Consistent dark primary button appearance
- Maintains 44px touch target requirement

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 20:23:56 -06:00
Eric Gullickson
5648f4c3d0 fix: add import UI to desktop settings page (refs #26)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m38s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 37s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 20:16:42 -06:00
Eric Gullickson
197927ef31 test: add integration tests and documentation (refs #26)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 20:05:06 -06:00
Eric Gullickson
7a5579df7b feat: add frontend import UI (refs #26)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 19:58:17 -06:00
Eric Gullickson
068db991a4 chore: Update footer 2026-01-11 19:51:34 -06:00
Eric Gullickson
a35d05f08a feat: add import service and API layer (refs #26)
Implements Milestone 3: Backend import service and API with:

Service Layer (user-import.service.ts):
- generatePreview(): extract archive, validate, detect VIN conflicts
- executeMerge(): chunk-based import (100 records/batch), UPDATE existing by VIN, INSERT new via batchInsert
- executeReplace(): transactional DELETE all user data, batchInsert all records
- Conflict detection: VIN duplicates in vehicles
- Error handling: collect errors per record, continue, report in summary
- File handling: copy vehicle images and documents from archive to storage
- Cleanup: delete temp directory in finally block

API Layer:
- POST /api/user/import: multipart upload, mode selection (merge/replace)
- POST /api/user/import/preview: preview without executing import
- Authentication: fastify.authenticate preHandler
- Content-Type validation: application/gzip or application/x-gzip
- Magic byte validation: FileType.fromBuffer verifies tar.gz
- Request validation: Zod schema for mode selection
- Response: ImportResult with success, mode, summary, warnings

Files Created:
- backend/src/features/user-import/domain/user-import.service.ts
- backend/src/features/user-import/api/user-import.controller.ts
- backend/src/features/user-import/api/user-import.routes.ts
- backend/src/features/user-import/api/user-import.validation.ts

Files Updated:
- backend/src/app.ts: register userImportRoutes with /api prefix

Quality:
- Type-check: PASS (0 errors)
- Linting: PASS (0 errors, 470 warnings - all pre-existing)
- Repository pattern: snake_case→camelCase conversion
- User-scoped: all queries filter by user_id
- Transaction boundaries: Replace mode atomic, Merge mode per-batch

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 19:50:59 -06:00
Eric Gullickson
ffadc48b4f feat: add archive extraction and validation service (refs #26)
Implement archive service to extract and validate user data import archives. Validates manifest structure, data files, and ensures archive format compatibility with export feature.

- user-import.types.ts: Type definitions for import feature
- user-import-archive.service.ts: Archive extraction and validation
- Validates manifest version (1.0.0) and required fields
- Validates all data files exist and contain valid JSON
- Temp directory pattern mirrors export (/tmp/user-import-work)
- Cleanup method for archive directories

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 19:30:43 -06:00
Eric Gullickson
e6af7ed5d5 feat: add batch insert operations to repositories (refs #26)
Add batchInsert methods to vehicles, fuel-logs, maintenance, and documents repositories. Multi-value INSERT syntax provides 10-100x performance improvement over individual operations for bulk data import.

- vehicles.repository: batchInsert for vehicles
- fuel-logs.repository: batchInsert for fuel logs
- maintenance.repository: batchInsertRecords and batchInsertSchedules
- documents.repository: batchInsert for documents
- All methods support empty array (immediate return) and optional transaction client
- Fix lint error: replace require() with ES6 import in test mock

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 19:28:11 -06:00
Eric Gullickson
bb8fdf33cf chore: update docs
All checks were successful
Deploy to Staging / Build Images (push) Successful in 22s
Deploy to Staging / Deploy to Staging (push) Successful in 37s
Deploy to Staging / Verify Staging (push) Successful in 6s
Deploy to Staging / Notify Staging Ready (push) Successful in 5s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
2026-01-11 18:13:58 -06:00
d5e95ebcd0 Merge pull request 'feat: Add tier-based vehicle limit enforcement (#23)' (#25) from issue-23-vehicle-limit-enforcement into main
Some checks failed
Deploy to Staging / Build Images (push) Successful in 25s
Deploy to Staging / Verify Staging (push) Has been cancelled
Deploy to Staging / Notify Staging Ready (push) Has been cancelled
Deploy to Staging / Notify Staging Failure (push) Has been cancelled
Deploy to Staging / Deploy to Staging (push) Has been cancelled
Reviewed-on: #25
2026-01-12 00:13:21 +00:00
Eric Gullickson
8703e7758a fix: Replace COUNT(*) with SELECT id in FOR UPDATE query (refs #23)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 2m18s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 29s
Deploy to Staging / Verify Staging (pull_request) Successful in 7s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
PostgreSQL error 0A000 (feature_not_supported) occurs when using
FOR UPDATE with aggregate functions like COUNT(*). Row-level locking
requires actual rows to lock.

Changes:
- Select id column instead of COUNT(*) aggregate
- Count rows in application using .length
- Maintains transaction isolation and race condition prevention

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 18:08:49 -06:00
Eric Gullickson
20189a1d37 feat: Add tier-based vehicle limit enforcement (refs #23)
All checks were successful
Deploy to Staging / Build Images (pull_request) Successful in 4m37s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 38s
Deploy to Staging / Verify Staging (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Ready (pull_request) Successful in 6s
Deploy to Staging / Notify Staging Failure (pull_request) Has been skipped
Backend:
- Add VEHICLE_LIMITS configuration to feature-tiers.ts
- Add getVehicleLimit, canAddVehicle helper functions
- Implement transaction-based limit check with FOR UPDATE locking
- Add VehicleLimitExceededError and 403 TIER_REQUIRED response
- Add countByUserId to VehiclesRepository
- Add comprehensive tests for all limit logic

Frontend:
- Add getResourceLimit, isAtResourceLimit to useTierAccess hook
- Create VehicleLimitDialog component with mobile/desktop modes
- Add useVehicleLimitCheck shared hook for limit state
- Update VehiclesPage with limit checks and lock icon
- Update VehiclesMobileScreen with limit checks
- Add tests for VehicleLimitDialog

Implements vehicle limits per tier (Free: 2, Pro: 5, Enterprise: unlimited)
with race condition prevention and consistent UX across mobile/desktop.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-11 16:36:53 -06:00
dff743ca36 Merge pull request 'feat: Add VIN decoding with NHTSA vPIC API (#9)' (#24) from issue-9-vin-decoding into main
All checks were successful
Deploy to Staging / Build Images (push) Successful in 23s
Deploy to Staging / Deploy to Staging (push) Successful in 37s
Deploy to Staging / Verify Staging (push) Successful in 7s
Deploy to Staging / Notify Staging Ready (push) Successful in 6s
Deploy to Staging / Notify Staging Failure (push) Has been skipped
Reviewed-on: #24
2026-01-11 22:22:35 +00:00