From aa65fc81566ea6fa8a74b22d3cf848e4c693689a Mon Sep 17 00:00:00 2001 From: Mikhail Chusavitin Date: Tue, 24 Feb 2026 16:53:51 +0300 Subject: [PATCH] Fix project line numbering and reorder bootstrap --- internal/services/local_configuration.go | 58 +++++++++++++++++++++--- web/templates/project_detail.html | 2 +- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/internal/services/local_configuration.go b/internal/services/local_configuration.go index 9a27db6..18895d4 100644 --- a/internal/services/local_configuration.go +++ b/internal/services/local_configuration.go @@ -1053,7 +1053,7 @@ func (s *LocalConfigurationService) isOwner(cfg *localdb.LocalConfiguration, own func (s *LocalConfigurationService) createWithVersion(localCfg *localdb.LocalConfiguration, createdBy string) error { return s.localDB.DB().Transaction(func(tx *gorm.DB) error { - if localCfg.IsActive && localCfg.Line <= 0 { + if localCfg.IsActive { if err := s.ensureConfigurationLineTx(tx, localCfg); err != nil { return err } @@ -1133,7 +1133,7 @@ func (s *LocalConfigurationService) saveWithVersionAndPending(localCfg *localdb. } } - if localCfg.IsActive && localCfg.Line <= 0 { + if localCfg.IsActive { if err := s.ensureConfigurationLineTx(tx, localCfg); err != nil { return err } @@ -1250,17 +1250,61 @@ func (s *LocalConfigurationService) loadVersionForPendingTx(tx *gorm.DB, localCf if err := tx.Where("configuration_uuid = ?", localCfg.UUID). Order("version_no DESC"). First(&latest).Error; err != nil { - return nil, fmt.Errorf("load version for pending change: %w", err) + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("load version for pending change: %w", err) + } + + // Legacy/imported rows may exist without local version history. + // Bootstrap the first version so pending sync payloads can reference a version. + version, createErr := s.appendVersionTx(tx, localCfg, "bootstrap", "") + if createErr != nil { + return nil, fmt.Errorf("bootstrap version for pending change: %w", createErr) + } + if err := tx.Model(&localdb.LocalConfiguration{}). + Where("uuid = ?", localCfg.UUID). + Update("current_version_id", version.ID).Error; err != nil { + return nil, fmt.Errorf("set current version id for bootstrapped pending change: %w", err) + } + localCfg.CurrentVersionID = &version.ID + return version, nil } return &latest, nil } func (s *LocalConfigurationService) ensureConfigurationLineTx(tx *gorm.DB, localCfg *localdb.LocalConfiguration) error { - line, err := localdb.NextConfigurationLineTx(tx, localCfg.ProjectUUID, localCfg.UUID) - if err != nil { - return fmt.Errorf("assign line_no for configuration %s: %w", localCfg.UUID, err) + if localCfg == nil || !localCfg.IsActive { + return nil + } + + needsAssign := localCfg.Line <= 0 + if !needsAssign { + query := tx.Model(&localdb.LocalConfiguration{}). + Where("is_active = ? AND line_no = ?", true, localCfg.Line) + + if strings.TrimSpace(localCfg.UUID) != "" { + query = query.Where("uuid <> ?", strings.TrimSpace(localCfg.UUID)) + } + + if localCfg.ProjectUUID != nil && strings.TrimSpace(*localCfg.ProjectUUID) != "" { + query = query.Where("project_uuid = ?", strings.TrimSpace(*localCfg.ProjectUUID)) + } else { + query = query.Where("project_uuid IS NULL OR TRIM(project_uuid) = ''") + } + + var conflicts int64 + if err := query.Count(&conflicts).Error; err != nil { + return fmt.Errorf("check line_no conflict for configuration %s: %w", localCfg.UUID, err) + } + needsAssign = conflicts > 0 + } + + if needsAssign { + line, err := localdb.NextConfigurationLineTx(tx, localCfg.ProjectUUID, localCfg.UUID) + if err != nil { + return fmt.Errorf("assign line_no for configuration %s: %w", localCfg.UUID, err) + } + localCfg.Line = line } - localCfg.Line = line return nil } diff --git a/web/templates/project_detail.html b/web/templates/project_detail.html index 2548c10..f0774be 100644 --- a/web/templates/project_detail.html +++ b/web/templates/project_detail.html @@ -373,7 +373,7 @@ function renderConfigs(configs) { const serverCount = c.server_count || 1; const author = c.owner_username || (c.user && c.user.username) || '—'; const unitPrice = serverCount > 0 ? (total / serverCount) : 0; - const lineValue = (typeof c.line === 'number' && c.line > 0) ? c.line : ((idx + 1) * 10); + const lineValue = (idx + 1) * 10; const serverModel = (c.server_model || '').trim() || '—'; totalSum += total;