From a42a80beb89dd5d3a5b6a616c02ab056cbcca757 Mon Sep 17 00:00:00 2001 From: Mikhail Chusavitin Date: Fri, 27 Feb 2026 10:11:20 +0300 Subject: [PATCH] fix(bom): preserve local vendor spec on config import --- internal/services/sync/service.go | 3 + .../sync/service_projects_push_test.go | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/internal/services/sync/service.go b/internal/services/sync/service.go index 3451d1e..f914869 100644 --- a/internal/services/sync/service.go +++ b/internal/services/sync/service.go @@ -148,6 +148,9 @@ func (s *Service) ImportConfigurationsToLocal() (*ConfigImportResult, error) { if localCfg.Line <= 0 && existing.Line > 0 { localCfg.Line = existing.Line } + // vendor_spec is local-only for BOM tab and is not stored on server. + // Preserve it during server pull updates. + localCfg.VendorSpec = existing.VendorSpec result.Updated++ } else { result.Imported++ diff --git a/internal/services/sync/service_projects_push_test.go b/internal/services/sync/service_projects_push_test.go index 6d2efd0..687c2c0 100644 --- a/internal/services/sync/service_projects_push_test.go +++ b/internal/services/sync/service_projects_push_test.go @@ -315,6 +315,70 @@ func TestImportConfigurationsToLocalPullsLine(t *testing.T) { } } +func TestImportConfigurationsToLocalPreservesLocalVendorSpec(t *testing.T) { + local := newLocalDBForSyncTest(t) + serverDB := newServerDBForSyncTest(t) + + cfg := models.Configuration{ + UUID: "server-vendorspec-config", + OwnerUsername: "tester", + Name: "Cfg VendorSpec Pull", + Items: models.ConfigItems{{LotName: "CPU_PULL", Quantity: 1, UnitPrice: 900}}, + ServerCount: 1, + Line: 50, + } + total := cfg.Items.Total() + cfg.TotalPrice = &total + if err := serverDB.Create(&cfg).Error; err != nil { + t.Fatalf("seed server config: %v", err) + } + + localSpec := localdb.VendorSpec{ + { + SortOrder: 10, + VendorPartnumber: "GPU-NVHGX-H200-8141", + Quantity: 1, + Description: "NVIDIA HGX Delta-Next GPU Baseboard", + LotMappings: []localdb.VendorSpecLotMapping{ + {LotName: "GPU_NV_H200_141GB_SXM_(HGX)", QuantityPerPN: 8}, + }, + }, + } + if err := local.SaveConfiguration(&localdb.LocalConfiguration{ + UUID: cfg.UUID, + OriginalUsername: "tester", + Name: "Local cfg", + Items: localdb.LocalConfigItems{{LotName: "CPU_PULL", Quantity: 1, UnitPrice: 900}}, + IsActive: true, + SyncStatus: "synced", + Line: 50, + VendorSpec: localSpec, + CreatedAt: time.Now().Add(-30 * time.Minute), + UpdatedAt: time.Now().Add(-30 * time.Minute), + }); err != nil { + t.Fatalf("seed local configuration: %v", err) + } + + svc := syncsvc.NewServiceWithDB(serverDB, local) + if _, err := svc.ImportConfigurationsToLocal(); err != nil { + t.Fatalf("import configurations to local: %v", err) + } + + localCfg, err := local.GetConfigurationByUUID(cfg.UUID) + if err != nil { + t.Fatalf("load local config: %v", err) + } + if len(localCfg.VendorSpec) != 1 { + t.Fatalf("expected local vendor_spec preserved, got %d rows", len(localCfg.VendorSpec)) + } + if localCfg.VendorSpec[0].VendorPartnumber != "GPU-NVHGX-H200-8141" { + t.Fatalf("unexpected vendor_partnumber after import: %q", localCfg.VendorSpec[0].VendorPartnumber) + } + if len(localCfg.VendorSpec[0].LotMappings) != 1 || localCfg.VendorSpec[0].LotMappings[0].LotName != "GPU_NV_H200_141GB_SXM_(HGX)" { + t.Fatalf("unexpected lot mappings after import: %+v", localCfg.VendorSpec[0].LotMappings) + } +} + func TestPushPendingChangesCreateThenUpdateBeforeFirstPush(t *testing.T) { local := newLocalDBForSyncTest(t) serverDB := newServerDBForSyncTest(t)