diff --git a/internal/localdb/localdb.go b/internal/localdb/localdb.go index 11b2c87..110312d 100644 --- a/internal/localdb/localdb.go +++ b/internal/localdb/localdb.go @@ -1088,7 +1088,9 @@ func (l *LocalDB) RepairPendingChanges() (int, []string, error) { return repaired, remainingErrors, nil } -// repairProjectChange validates and fixes project data +// repairProjectChange validates and fixes project data. +// Note: This only validates local data. Server-side conflicts (like duplicate code+variant) +// are handled by sync service layer with deduplication logic. func (l *LocalDB) repairProjectChange(change *PendingChange) error { project, err := l.GetProjectByUUID(change.EntityUUID) if err != nil { @@ -1123,6 +1125,20 @@ func (l *LocalDB) repairProjectChange(change *PendingChange) error { modified = true } + // Check for local duplicates with same (code, variant) + var duplicate LocalProject + err = l.db.Where("code = ? AND variant = ? AND uuid != ?", project.Code, project.Variant, project.UUID). + First(&duplicate).Error + if err == nil { + // Found local duplicate - deduplicate by appending UUID suffix to variant + if project.Variant == "" { + project.Variant = project.UUID[:8] + } else { + project.Variant = project.Variant + "-" + project.UUID[:8] + } + modified = true + } + if modified { if err := l.SaveProject(project); err != nil { return fmt.Errorf("saving repaired project: %w", err) diff --git a/internal/services/sync/service.go b/internal/services/sync/service.go index 95480e5..24882c6 100644 --- a/internal/services/sync/service.go +++ b/internal/services/sync/service.go @@ -856,10 +856,29 @@ func (s *Service) pushProjectChange(change *localdb.PendingChange) error { } } - if err := projectRepo.UpsertByUUID(&project); err != nil { - return fmt.Errorf("upsert project on server: %w", err) + // Try upsert by UUID first + err = projectRepo.UpsertByUUID(&project) + if err != nil { + // Check if it's a duplicate (code, variant) constraint violation + // In this case, find existing project with same (code, variant) and link to it + var existing models.Project + lookupErr := mariaDB.Where("code = ? AND variant = ?", project.Code, project.Variant).First(&existing).Error + if lookupErr == nil { + // Found duplicate - link local project to existing server project + slog.Info("project duplicate found, linking to existing", + "local_uuid", project.UUID, + "server_uuid", existing.UUID, + "server_id", existing.ID, + "code", project.Code, + "variant", project.Variant) + project.ID = existing.ID + } else { + // Not a duplicate issue, return original error + return fmt.Errorf("upsert project on server: %w", err) + } } + // Update local project with server ID localProject, localErr := s.localDB.GetProjectByUUID(project.UUID) if localErr == nil { if project.ID > 0 { diff --git a/web/templates/base.html b/web/templates/base.html index 2140686..535d826 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -45,10 +45,10 @@
-