From 28574b0eb47761455f88af5e889ea30caaa7f636 Mon Sep 17 00:00:00 2001 From: Eric Gullickson <16152721+ericgullickson@users.noreply.github.com> Date: Sun, 11 Jan 2026 21:15:30 -0600 Subject: [PATCH] fix: preserve vehicle identity by checking ID first in merge mode (refs #26) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../user-import/domain/user-import.service.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/backend/src/features/user-import/domain/user-import.service.ts b/backend/src/features/user-import/domain/user-import.service.ts index b14faf9..d859d19 100644 --- a/backend/src/features/user-import/domain/user-import.service.ts +++ b/backend/src/features/user-import/domain/user-import.service.ts @@ -246,23 +246,41 @@ export class UserImportService { try { logger.debug('Processing vehicle', { userId, + id: vehicle.id, vin: vehicle.vin, make: vehicle.make, model: vehicle.model, year: vehicle.year, + licensePlate: vehicle.licensePlate, }); let existing = null; - // Try to find existing vehicle by VIN first - if (vehicle.vin && vehicle.vin.trim().length > 0) { + // Try to find existing vehicle by ID first (preserves identity across exports) + if (vehicle.id) { + existing = await this.vehiclesRepo.findById(vehicle.id); + // Verify it belongs to the same user + if (existing && existing.userId !== userId) { + logger.warn('Vehicle ID belongs to different user, ignoring', { + vehicleId: vehicle.id, + expectedUserId: userId, + actualUserId: existing.userId, + }); + existing = null; + } else if (existing) { + logger.debug('Found existing vehicle by ID', { vehicleId: existing.id }); + } + } + + // Try to find existing vehicle by VIN if not found by ID + if (!existing && vehicle.vin && vehicle.vin.trim().length > 0) { existing = await this.vehiclesRepo.findByUserAndVIN(userId, vehicle.vin.trim()); if (existing) { logger.debug('Found existing vehicle by VIN', { vehicleId: existing.id, vin: vehicle.vin }); } } - // If not found by VIN and license plate exists, try license plate + // If not found by ID or VIN, and license plate exists, try license plate if (!existing && vehicle.licensePlate && vehicle.licensePlate.trim().length > 0) { const allUserVehicles = await this.vehiclesRepo.findByUserId(userId); existing = allUserVehicles.find(