From 962952149562fdbe283155508f648ab310368386 Mon Sep 17 00:00:00 2001 From: Mikhail Chusavitin Date: Wed, 12 Aug 2026 17:05:39 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D1=81=D0=BA=D0=BB=D0=B0=D0=B4=D1=81?= =?UTF-8?q?=D0=BA=D0=B8=D0=B5=20=D1=86=D0=B5=D0=BD=D1=8B=20=D0=B8=20=C2=AB?= =?UTF-8?q?=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BD=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D1=87=D0=B8=D0=B5=C2=BB=20=E2=80=94=20=D0=BD=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B9=D0=BA=D0=B8=20=D1=83=D1=80=D0=BE=D0=B2=D0=BD?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлен чекбокс «Складские цены» (по аналогии с «Аренда»), управляющий показом складских цен во всех ценовых интерфейсах конфигурации, включая CSV-экспорт. «Только наличие» перенесён из настроек цен конфигурации в настройки проекта и активен только при включённых складских ценах. Co-Authored-By: Claude Sonnet 5 --- cmd/qfs/main.go | 32 ++++++----- internal/handlers/export.go | 17 ++++-- .../configuration_business_fields_test.go | 3 - internal/localdb/converters.go | 52 ++++++++--------- internal/localdb/localdb.go | 2 + internal/localdb/migrations.go | 21 +++++++ internal/localdb/models.go | 33 +++++------ internal/localdb/snapshots.go | 45 +++++++-------- internal/models/configuration.go | 56 +++++++++---------- internal/models/project.go | 26 +++++---- internal/repository/project.go | 2 + internal/services/configuration.go | 1 - internal/services/local_configuration.go | 6 -- .../local_configuration_versioning_test.go | 1 - internal/services/project.go | 18 ++++-- internal/services/sync/service.go | 2 + web/templates/index.html | 55 ++++++++++-------- web/templates/project_detail.html | 38 ++++++++++++- 18 files changed, 244 insertions(+), 166 deletions(-) diff --git a/cmd/qfs/main.go b/cmd/qfs/main.go index a164b5e..1473129 100644 --- a/cmd/qfs/main.go +++ b/cmd/qfs/main.go @@ -1574,25 +1574,29 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect // Return simplified list of all projects (UUID + Name only) type ProjectSimple struct { - UUID string `json:"uuid"` - Code string `json:"code"` - Variant string `json:"variant"` - Name string `json:"name"` - IsActive bool `json:"is_active"` - RentalEnabled bool `json:"rental_enabled"` - CreatedAt time.Time `json:"created_at"` + UUID string `json:"uuid"` + Code string `json:"code"` + Variant string `json:"variant"` + Name string `json:"name"` + IsActive bool `json:"is_active"` + RentalEnabled bool `json:"rental_enabled"` + ShowStockPrices bool `json:"show_stock_prices"` + OnlyInStock bool `json:"only_in_stock"` + CreatedAt time.Time `json:"created_at"` } simplified := make([]ProjectSimple, 0, len(allProjects)) for _, p := range allProjects { simplified = append(simplified, ProjectSimple{ - UUID: p.UUID, - Code: p.Code, - Variant: p.Variant, - Name: derefString(p.Name), - IsActive: p.IsActive, - RentalEnabled: p.RentalEnabled, - CreatedAt: p.CreatedAt, + UUID: p.UUID, + Code: p.Code, + Variant: p.Variant, + Name: derefString(p.Name), + IsActive: p.IsActive, + RentalEnabled: p.RentalEnabled, + ShowStockPrices: p.ShowStockPrices, + OnlyInStock: p.OnlyInStock, + CreatedAt: p.CreatedAt, }) } diff --git a/internal/handlers/export.go b/internal/handlers/export.go index 8c056f5..b87dc06 100644 --- a/internal/handlers/export.go +++ b/internal/handlers/export.go @@ -238,11 +238,18 @@ func (h *ExportHandler) ExportConfigPricingCSV(c *gin.Context) { return } + var project *models.Project + if config.ProjectUUID != nil && *config.ProjectUUID != "" { + if p, err := h.projectService.GetByUUID(*config.ProjectUUID, h.dbUsername); err == nil { + project = p + } + } + opts := services.ProjectPricingExportOptions{ IncludeLOT: req.IncludeLOT, IncludeBOM: req.IncludeBOM, IncludeEstimate: req.IncludeEstimate, - IncludeStock: req.IncludeStock, + IncludeStock: req.IncludeStock && project != nil && project.ShowStockPrices, IncludeCompetitor: req.IncludeCompetitor, Basis: req.Basis, SaleMarkup: req.SaleMarkup, @@ -260,10 +267,8 @@ func (h *ExportHandler) ExportConfigPricingCSV(c *gin.Context) { } projectCode := config.Name - if config.ProjectUUID != nil && *config.ProjectUUID != "" { - if project, err := h.projectService.GetByUUID(*config.ProjectUUID, h.dbUsername); err == nil && project != nil { - projectCode = project.Code - } + if project != nil { + projectCode = project.Code } filename := fmt.Sprintf("%s (%s) %s %s SPEC.csv", @@ -309,7 +314,7 @@ func (h *ExportHandler) ExportProjectPricingCSV(c *gin.Context) { IncludeLOT: req.IncludeLOT, IncludeBOM: req.IncludeBOM, IncludeEstimate: req.IncludeEstimate, - IncludeStock: req.IncludeStock, + IncludeStock: req.IncludeStock && project.ShowStockPrices, IncludeCompetitor: req.IncludeCompetitor, Basis: req.Basis, SaleMarkup: req.SaleMarkup, diff --git a/internal/localdb/configuration_business_fields_test.go b/internal/localdb/configuration_business_fields_test.go index cf4bda3..ba830dc 100644 --- a/internal/localdb/configuration_business_fields_test.go +++ b/internal/localdb/configuration_business_fields_test.go @@ -19,7 +19,6 @@ func TestConfigurationConvertersPreserveBusinessFields(t *testing.T) { WarehousePricelistID: &warehouseID, CompetitorPricelistID: &competitorID, DisablePriceRefresh: true, - OnlyInStock: true, } local := ConfigurationToLocal(cfg) @@ -57,7 +56,6 @@ func TestConfigurationSnapshotPreservesBusinessFields(t *testing.T) { WarehousePricelistID: &warehouseID, CompetitorPricelistID: &competitorID, DisablePriceRefresh: true, - OnlyInStock: true, VendorSpec: VendorSpec{ { SortOrder: 10, @@ -110,7 +108,6 @@ func TestConfigurationFingerprintIncludesPricingSelectorsAndVendorSpec(t *testin WarehousePricelistID: &warehouseID, CompetitorPricelistID: &competitorID, DisablePriceRefresh: true, - OnlyInStock: true, VendorSpec: VendorSpec{ { SortOrder: 10, diff --git a/internal/localdb/converters.go b/internal/localdb/converters.go index 71e80be..faf1b98 100644 --- a/internal/localdb/converters.go +++ b/internal/localdb/converters.go @@ -73,7 +73,6 @@ func ConfigurationToLocal(cfg *models.Configuration) *LocalConfiguration { ConfigType: cfg.ConfigType, VendorSpec: modelVendorSpecToLocal(cfg.VendorSpec), DisablePriceRefresh: cfg.DisablePriceRefresh, - OnlyInStock: cfg.OnlyInStock, RentalItems: modelRentalItemsToLocal(cfg.RentalItems), RentalUpliftPercent: cfg.RentalUpliftPercent, Line: cfg.Line, @@ -124,7 +123,6 @@ func LocalToConfiguration(local *LocalConfiguration) *models.Configuration { ConfigType: local.ConfigType, VendorSpec: localVendorSpecToModel(local.VendorSpec), DisablePriceRefresh: local.DisablePriceRefresh, - OnlyInStock: local.OnlyInStock, RentalItems: localRentalItemsToModel(local.RentalItems), RentalUpliftPercent: local.RentalUpliftPercent, Line: local.Line, @@ -259,18 +257,20 @@ func localRentalItemsToModel(items RentalItemConditions) models.RentalItemCondit func ProjectToLocal(project *models.Project) *LocalProject { local := &LocalProject{ - UUID: project.UUID, - OwnerUsername: project.OwnerUsername, - Code: project.Code, - Variant: project.Variant, - Name: project.Name, - TrackerURL: project.TrackerURL, - IsActive: project.IsActive, - IsSystem: project.IsSystem, - RentalEnabled: project.RentalEnabled, - CreatedAt: project.CreatedAt, - UpdatedAt: project.UpdatedAt, - SyncStatus: "pending", + UUID: project.UUID, + OwnerUsername: project.OwnerUsername, + Code: project.Code, + Variant: project.Variant, + Name: project.Name, + TrackerURL: project.TrackerURL, + IsActive: project.IsActive, + IsSystem: project.IsSystem, + RentalEnabled: project.RentalEnabled, + ShowStockPrices: project.ShowStockPrices, + OnlyInStock: project.OnlyInStock, + CreatedAt: project.CreatedAt, + UpdatedAt: project.UpdatedAt, + SyncStatus: "pending", } if project.ID > 0 { serverID := project.ID @@ -281,17 +281,19 @@ func ProjectToLocal(project *models.Project) *LocalProject { func LocalToProject(local *LocalProject) *models.Project { project := &models.Project{ - UUID: local.UUID, - OwnerUsername: local.OwnerUsername, - Code: local.Code, - Variant: local.Variant, - Name: local.Name, - TrackerURL: local.TrackerURL, - IsActive: local.IsActive, - IsSystem: local.IsSystem, - RentalEnabled: local.RentalEnabled, - CreatedAt: local.CreatedAt, - UpdatedAt: local.UpdatedAt, + UUID: local.UUID, + OwnerUsername: local.OwnerUsername, + Code: local.Code, + Variant: local.Variant, + Name: local.Name, + TrackerURL: local.TrackerURL, + IsActive: local.IsActive, + IsSystem: local.IsSystem, + RentalEnabled: local.RentalEnabled, + ShowStockPrices: local.ShowStockPrices, + OnlyInStock: local.OnlyInStock, + CreatedAt: local.CreatedAt, + UpdatedAt: local.UpdatedAt, } if local.ServerID != nil { project.ID = *local.ServerID diff --git a/internal/localdb/localdb.go b/internal/localdb/localdb.go index 9c52121..a9844ca 100644 --- a/internal/localdb/localdb.go +++ b/internal/localdb/localdb.go @@ -212,6 +212,8 @@ CREATE TABLE local_projects ( is_active INTEGER NOT NULL DEFAULT 1, is_system INTEGER NOT NULL DEFAULT 0, rental_enabled INTEGER NOT NULL DEFAULT 0, + show_stock_prices INTEGER NOT NULL DEFAULT 0, + only_in_stock INTEGER NOT NULL DEFAULT 0, created_at DATETIME, updated_at DATETIME, synced_at DATETIME NULL, diff --git a/internal/localdb/migrations.go b/internal/localdb/migrations.go index 31ab948..19e5726 100644 --- a/internal/localdb/migrations.go +++ b/internal/localdb/migrations.go @@ -139,6 +139,11 @@ var localMigrations = []localMigration{ name: "Add price_quality to local_pricelist_items", run: addLocalPricelistItemPriceQuality, }, + { + id: "2026_08_12_local_project_stock_settings", + name: "Add show_stock_prices and only_in_stock to local_projects", + run: addLocalProjectStockSettings, + }, } func addLocalPricelistItemPriceQuality(tx *gorm.DB) error { @@ -249,6 +254,22 @@ func addLocalProjectRentalEnabled(tx *gorm.DB) error { return nil } +func addLocalProjectStockSettings(tx *gorm.DB) error { + stmts := []string{ + `ALTER TABLE local_projects ADD COLUMN show_stock_prices INTEGER NOT NULL DEFAULT 0`, + `ALTER TABLE local_projects ADD COLUMN only_in_stock INTEGER NOT NULL DEFAULT 0`, + } + for _, stmt := range stmts { + if err := tx.Exec(stmt).Error; err != nil { + if !strings.Contains(strings.ToLower(err.Error()), "duplicate") && + !strings.Contains(strings.ToLower(err.Error()), "exists") { + return err + } + } + } + return nil +} + type localPartnumberCatalogRow struct { Partnumber string LotsJSON LocalPartnumberBookLots diff --git a/internal/localdb/models.go b/internal/localdb/models.go index da8e6f9..d125fe0 100644 --- a/internal/localdb/models.go +++ b/internal/localdb/models.go @@ -111,7 +111,6 @@ type LocalConfiguration struct { WarehousePricelistID *uint `gorm:"index" json:"warehouse_pricelist_id,omitempty"` CompetitorPricelistID *uint `gorm:"index" json:"competitor_pricelist_id,omitempty"` DisablePriceRefresh bool `gorm:"default:false" json:"disable_price_refresh"` - OnlyInStock bool `gorm:"default:false" json:"only_in_stock"` RentalItems RentalItemConditions `gorm:"type:text" json:"rental_items,omitempty"` RentalUpliftPercent float64 `gorm:"default:0" json:"rental_uplift_percent"` VendorSpec VendorSpec `gorm:"type:text" json:"vendor_spec,omitempty"` @@ -133,21 +132,23 @@ func (LocalConfiguration) TableName() string { } type LocalProject struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - UUID string `gorm:"uniqueIndex;not null" json:"uuid"` - ServerID *uint `json:"server_id,omitempty"` - OwnerUsername string `gorm:"not null;index" json:"owner_username"` - Code string `gorm:"not null;index:idx_local_projects_code_variant,priority:1" json:"code"` - Variant string `gorm:"default:'';index:idx_local_projects_code_variant,priority:2" json:"variant"` - Name *string `json:"name,omitempty"` - TrackerURL string `json:"tracker_url"` - IsActive bool `gorm:"default:true;index" json:"is_active"` - IsSystem bool `gorm:"default:false;index" json:"is_system"` - RentalEnabled bool `gorm:"default:false" json:"rental_enabled"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - SyncedAt *time.Time `json:"synced_at,omitempty"` - SyncStatus string `gorm:"default:'local'" json:"sync_status"` // local/synced/pending + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + UUID string `gorm:"uniqueIndex;not null" json:"uuid"` + ServerID *uint `json:"server_id,omitempty"` + OwnerUsername string `gorm:"not null;index" json:"owner_username"` + Code string `gorm:"not null;index:idx_local_projects_code_variant,priority:1" json:"code"` + Variant string `gorm:"default:'';index:idx_local_projects_code_variant,priority:2" json:"variant"` + Name *string `json:"name,omitempty"` + TrackerURL string `json:"tracker_url"` + IsActive bool `gorm:"default:true;index" json:"is_active"` + IsSystem bool `gorm:"default:false;index" json:"is_system"` + RentalEnabled bool `gorm:"default:false" json:"rental_enabled"` + ShowStockPrices bool `gorm:"default:false" json:"show_stock_prices"` + OnlyInStock bool `gorm:"default:false" json:"only_in_stock"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + SyncedAt *time.Time `json:"synced_at,omitempty"` + SyncStatus string `gorm:"default:'local'" json:"sync_status"` // local/synced/pending } func (LocalProject) TableName() string { diff --git a/internal/localdb/snapshots.go b/internal/localdb/snapshots.go index d7586f3..0f8db72 100644 --- a/internal/localdb/snapshots.go +++ b/internal/localdb/snapshots.go @@ -30,7 +30,6 @@ func BuildConfigurationSnapshot(localCfg *LocalConfiguration) (string, error) { "warehouse_pricelist_id": localCfg.WarehousePricelistID, "competitor_pricelist_id": localCfg.CompetitorPricelistID, "disable_price_refresh": localCfg.DisablePriceRefresh, - "only_in_stock": localCfg.OnlyInStock, "rental_items": localCfg.RentalItems, "rental_uplift_percent": localCfg.RentalUpliftPercent, "vendor_spec": localCfg.VendorSpec, @@ -54,30 +53,29 @@ func BuildConfigurationSnapshot(localCfg *LocalConfiguration) (string, error) { // DecodeConfigurationSnapshot returns editable fields from one saved snapshot. func DecodeConfigurationSnapshot(data string) (*LocalConfiguration, error) { var snapshot struct { - ProjectUUID *string `json:"project_uuid"` - IsActive *bool `json:"is_active"` - Name string `json:"name"` - Items LocalConfigItems `json:"items"` - TotalPrice *float64 `json:"total_price"` - CustomPrice *float64 `json:"custom_price"` - Notes string `json:"notes"` - IsTemplate bool `json:"is_template"` - ServerCount int `json:"server_count"` - ServerModel string `json:"server_model"` - SupportCode string `json:"support_code"` - Article string `json:"article"` - PricelistID *uint `json:"pricelist_id"` - WarehousePricelistID *uint `json:"warehouse_pricelist_id"` - CompetitorPricelistID *uint `json:"competitor_pricelist_id"` + ProjectUUID *string `json:"project_uuid"` + IsActive *bool `json:"is_active"` + Name string `json:"name"` + Items LocalConfigItems `json:"items"` + TotalPrice *float64 `json:"total_price"` + CustomPrice *float64 `json:"custom_price"` + Notes string `json:"notes"` + IsTemplate bool `json:"is_template"` + ServerCount int `json:"server_count"` + ServerModel string `json:"server_model"` + SupportCode string `json:"support_code"` + Article string `json:"article"` + PricelistID *uint `json:"pricelist_id"` + WarehousePricelistID *uint `json:"warehouse_pricelist_id"` + CompetitorPricelistID *uint `json:"competitor_pricelist_id"` DisablePriceRefresh bool `json:"disable_price_refresh"` - OnlyInStock bool `json:"only_in_stock"` RentalItems RentalItemConditions `json:"rental_items"` RentalUpliftPercent float64 `json:"rental_uplift_percent"` - VendorSpec VendorSpec `json:"vendor_spec"` - Line int `json:"line"` - PriceUpdatedAt *time.Time `json:"price_updated_at"` - OriginalUserID uint `json:"original_user_id"` - OriginalUsername string `json:"original_username"` + VendorSpec VendorSpec `json:"vendor_spec"` + Line int `json:"line"` + PriceUpdatedAt *time.Time `json:"price_updated_at"` + OriginalUserID uint `json:"original_user_id"` + OriginalUsername string `json:"original_username"` } if err := json.Unmarshal([]byte(data), &snapshot); err != nil { @@ -106,7 +104,6 @@ func DecodeConfigurationSnapshot(data string) (*LocalConfiguration, error) { WarehousePricelistID: snapshot.WarehousePricelistID, CompetitorPricelistID: snapshot.CompetitorPricelistID, DisablePriceRefresh: snapshot.DisablePriceRefresh, - OnlyInStock: snapshot.OnlyInStock, RentalItems: snapshot.RentalItems, RentalUpliftPercent: snapshot.RentalUpliftPercent, VendorSpec: snapshot.VendorSpec, @@ -126,7 +123,6 @@ type configurationSpecPriceFingerprint struct { WarehousePricelistID *uint `json:"warehouse_pricelist_id,omitempty"` CompetitorPricelistID *uint `json:"competitor_pricelist_id,omitempty"` DisablePriceRefresh bool `json:"disable_price_refresh"` - OnlyInStock bool `json:"only_in_stock"` RentalItems RentalItemConditions `json:"rental_items,omitempty"` RentalUpliftPercent float64 `json:"rental_uplift_percent"` VendorSpec VendorSpec `json:"vendor_spec,omitempty"` @@ -174,7 +170,6 @@ func BuildConfigurationSpecPriceFingerprint(localCfg *LocalConfiguration) (strin WarehousePricelistID: localCfg.WarehousePricelistID, CompetitorPricelistID: localCfg.CompetitorPricelistID, DisablePriceRefresh: localCfg.DisablePriceRefresh, - OnlyInStock: localCfg.OnlyInStock, RentalItems: rentalItems, RentalUpliftPercent: localCfg.RentalUpliftPercent, VendorSpec: localCfg.VendorSpec, diff --git a/internal/models/configuration.go b/internal/models/configuration.go index b508d63..1ce20e1 100644 --- a/internal/models/configuration.go +++ b/internal/models/configuration.go @@ -126,38 +126,36 @@ func (v *VendorSpec) Scan(value interface{}) error { } type Configuration struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"` - UserID *uint `json:"user_id,omitempty"` // Legacy field, no longer required for ownership - OwnerUsername string `gorm:"size:100;not null;default:'';index" json:"owner_username"` - ProjectUUID *string `gorm:"size:36;index" json:"project_uuid,omitempty"` - AppVersion string `gorm:"size:64" json:"app_version,omitempty"` - Name string `gorm:"size:200;not null" json:"name"` - Items ConfigItems `gorm:"type:json;not null" json:"items"` - TotalPrice *float64 `gorm:"type:decimal(12,2)" json:"total_price"` - CustomPrice *float64 `gorm:"type:decimal(12,2)" json:"custom_price"` - Notes string `gorm:"type:text" json:"notes"` - IsTemplate bool `gorm:"default:false" json:"is_template"` - ServerCount int `gorm:"default:1" json:"server_count"` - ServerModel string `gorm:"size:100" json:"server_model,omitempty"` - SupportCode string `gorm:"size:20" json:"support_code,omitempty"` - Article string `gorm:"size:80" json:"article,omitempty"` - PricelistID *uint `gorm:"index" json:"pricelist_id,omitempty"` - WarehousePricelistID *uint `gorm:"index" json:"warehouse_pricelist_id,omitempty"` - CompetitorPricelistID *uint `gorm:"index" json:"competitor_pricelist_id,omitempty"` - VendorSpec VendorSpec `gorm:"type:json" json:"vendor_spec,omitempty"` - ConfigType string `gorm:"size:20;default:server" json:"config_type"` // "server" | "storage" - DisablePriceRefresh bool `gorm:"default:false" json:"disable_price_refresh"` - OnlyInStock bool `gorm:"default:false" json:"only_in_stock"` + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"` + UserID *uint `json:"user_id,omitempty"` // Legacy field, no longer required for ownership + OwnerUsername string `gorm:"size:100;not null;default:'';index" json:"owner_username"` + ProjectUUID *string `gorm:"size:36;index" json:"project_uuid,omitempty"` + AppVersion string `gorm:"size:64" json:"app_version,omitempty"` + Name string `gorm:"size:200;not null" json:"name"` + Items ConfigItems `gorm:"type:json;not null" json:"items"` + TotalPrice *float64 `gorm:"type:decimal(12,2)" json:"total_price"` + CustomPrice *float64 `gorm:"type:decimal(12,2)" json:"custom_price"` + Notes string `gorm:"type:text" json:"notes"` + IsTemplate bool `gorm:"default:false" json:"is_template"` + ServerCount int `gorm:"default:1" json:"server_count"` + ServerModel string `gorm:"size:100" json:"server_model,omitempty"` + SupportCode string `gorm:"size:20" json:"support_code,omitempty"` + Article string `gorm:"size:80" json:"article,omitempty"` + PricelistID *uint `gorm:"index" json:"pricelist_id,omitempty"` + WarehousePricelistID *uint `gorm:"index" json:"warehouse_pricelist_id,omitempty"` + CompetitorPricelistID *uint `gorm:"index" json:"competitor_pricelist_id,omitempty"` + VendorSpec VendorSpec `gorm:"type:json" json:"vendor_spec,omitempty"` + ConfigType string `gorm:"size:20;default:server" json:"config_type"` // "server" | "storage" + DisablePriceRefresh bool `gorm:"default:false" json:"disable_price_refresh"` RentalItems RentalItemConditions `gorm:"type:json" json:"rental_items,omitempty"` - RentalUpliftPercent float64 `gorm:"type:decimal(8,2);default:0" json:"rental_uplift_percent"` - Line int `gorm:"column:line_no;index" json:"line"` - PriceUpdatedAt *time.Time `gorm:"type:timestamp" json:"price_updated_at,omitempty"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - CurrentVersionNo int `gorm:"-" json:"current_version_no,omitempty"` + RentalUpliftPercent float64 `gorm:"type:decimal(8,2);default:0" json:"rental_uplift_percent"` + Line int `gorm:"column:line_no;index" json:"line"` + PriceUpdatedAt *time.Time `gorm:"type:timestamp" json:"price_updated_at,omitempty"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + CurrentVersionNo int `gorm:"-" json:"current_version_no,omitempty"` } func (Configuration) TableName() string { return "qt_configurations" } - diff --git a/internal/models/project.go b/internal/models/project.go index 1bec79d..5be344f 100644 --- a/internal/models/project.go +++ b/internal/models/project.go @@ -3,18 +3,20 @@ package models import "time" type Project struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"` - OwnerUsername string `gorm:"size:100;not null;index" json:"owner_username"` - Code string `gorm:"size:100;not null;index:idx_qt_projects_code_variant,priority:1" json:"code"` - Variant string `gorm:"size:100;not null;default:'';index:idx_qt_projects_code_variant,priority:2" json:"variant"` - Name *string `gorm:"size:200" json:"name,omitempty"` - TrackerURL string `gorm:"size:500" json:"tracker_url"` - IsActive bool `gorm:"default:true;index" json:"is_active"` - IsSystem bool `gorm:"default:false;index" json:"is_system"` - RentalEnabled bool `gorm:"default:false" json:"rental_enabled"` - CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"` + OwnerUsername string `gorm:"size:100;not null;index" json:"owner_username"` + Code string `gorm:"size:100;not null;index:idx_qt_projects_code_variant,priority:1" json:"code"` + Variant string `gorm:"size:100;not null;default:'';index:idx_qt_projects_code_variant,priority:2" json:"variant"` + Name *string `gorm:"size:200" json:"name,omitempty"` + TrackerURL string `gorm:"size:500" json:"tracker_url"` + IsActive bool `gorm:"default:true;index" json:"is_active"` + IsSystem bool `gorm:"default:false;index" json:"is_system"` + RentalEnabled bool `gorm:"default:false" json:"rental_enabled"` + ShowStockPrices bool `gorm:"default:false" json:"show_stock_prices"` + OnlyInStock bool `gorm:"default:false" json:"only_in_stock"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` } func (Project) TableName() string { diff --git a/internal/repository/project.go b/internal/repository/project.go index 13e18d7..e937247 100644 --- a/internal/repository/project.go +++ b/internal/repository/project.go @@ -39,6 +39,8 @@ func (r *ProjectRepository) UpsertByUUID(project *models.Project) error { "is_active", "is_system", "rental_enabled", + "show_stock_prices", + "only_in_stock", "updated_at", }), }).Create(project).Error; err != nil { diff --git a/internal/services/configuration.go b/internal/services/configuration.go index 7cf8ecd..009f490 100644 --- a/internal/services/configuration.go +++ b/internal/services/configuration.go @@ -34,7 +34,6 @@ type CreateConfigRequest struct { CompetitorPricelistID *uint `json:"competitor_pricelist_id,omitempty"` ConfigType string `json:"config_type,omitempty"` // "server" | "storage" DisablePriceRefresh bool `json:"disable_price_refresh"` - OnlyInStock bool `json:"only_in_stock"` } type ArticlePreviewRequest struct { diff --git a/internal/services/local_configuration.go b/internal/services/local_configuration.go index 15198fe..b28dae7 100644 --- a/internal/services/local_configuration.go +++ b/internal/services/local_configuration.go @@ -103,7 +103,6 @@ func (s *LocalConfigurationService) Create(ownerUsername string, req *CreateConf CompetitorPricelistID: req.CompetitorPricelistID, ConfigType: req.ConfigType, DisablePriceRefresh: req.DisablePriceRefresh, - OnlyInStock: req.OnlyInStock, CreatedAt: time.Now(), } if cfg.ConfigType == "" { @@ -204,7 +203,6 @@ func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, re localCfg.WarehousePricelistID = req.WarehousePricelistID localCfg.CompetitorPricelistID = req.CompetitorPricelistID localCfg.DisablePriceRefresh = req.DisablePriceRefresh - localCfg.OnlyInStock = req.OnlyInStock localCfg.UpdatedAt = time.Now() localCfg.SyncStatus = "pending" @@ -328,7 +326,6 @@ func (s *LocalConfigurationService) CloneToProject(configUUID string, ownerUsern WarehousePricelistID: original.WarehousePricelistID, CompetitorPricelistID: original.CompetitorPricelistID, DisablePriceRefresh: original.DisablePriceRefresh, - OnlyInStock: original.OnlyInStock, CreatedAt: time.Now(), } @@ -566,7 +563,6 @@ func (s *LocalConfigurationService) UpdateNoAuth(uuid string, req *CreateConfigR localCfg.WarehousePricelistID = req.WarehousePricelistID localCfg.CompetitorPricelistID = req.CompetitorPricelistID localCfg.DisablePriceRefresh = req.DisablePriceRefresh - localCfg.OnlyInStock = req.OnlyInStock localCfg.UpdatedAt = time.Now() localCfg.SyncStatus = "pending" @@ -685,7 +681,6 @@ func (s *LocalConfigurationService) CloneNoAuthToProjectFromVersion(configUUID s WarehousePricelistID: original.WarehousePricelistID, CompetitorPricelistID: original.CompetitorPricelistID, DisablePriceRefresh: original.DisablePriceRefresh, - OnlyInStock: original.OnlyInStock, CreatedAt: time.Now(), } @@ -1648,7 +1643,6 @@ func (s *LocalConfigurationService) rollbackToVersion(configurationUUID string, current.WarehousePricelistID = rollbackData.WarehousePricelistID current.CompetitorPricelistID = rollbackData.CompetitorPricelistID current.DisablePriceRefresh = rollbackData.DisablePriceRefresh - current.OnlyInStock = rollbackData.OnlyInStock current.VendorSpec = rollbackData.VendorSpec if rollbackData.Line > 0 { current.Line = rollbackData.Line diff --git a/internal/services/local_configuration_versioning_test.go b/internal/services/local_configuration_versioning_test.go index 03f19e2..09f891b 100644 --- a/internal/services/local_configuration_versioning_test.go +++ b/internal/services/local_configuration_versioning_test.go @@ -154,7 +154,6 @@ func TestUpdateNoAuthCreatesRevisionWhenPricingSettingsChanged(t *testing.T) { Items: models.ConfigItems{{LotName: "CPU_A", Quantity: 1, UnitPrice: 1000}}, ServerCount: 1, DisablePriceRefresh: true, - OnlyInStock: true, }); err != nil { t.Fatalf("update pricing settings: %v", err) } diff --git a/internal/services/project.go b/internal/services/project.go index 167d71f..b7bd769 100644 --- a/internal/services/project.go +++ b/internal/services/project.go @@ -46,11 +46,13 @@ type CreateProjectRequest struct { } type UpdateProjectRequest struct { - Code *string `json:"code,omitempty"` - Variant *string `json:"variant,omitempty"` - Name *string `json:"name,omitempty"` - TrackerURL *string `json:"tracker_url,omitempty"` - RentalEnabled *bool `json:"rental_enabled,omitempty"` + Code *string `json:"code,omitempty"` + Variant *string `json:"variant,omitempty"` + Name *string `json:"name,omitempty"` + TrackerURL *string `json:"tracker_url,omitempty"` + RentalEnabled *bool `json:"rental_enabled,omitempty"` + ShowStockPrices *bool `json:"show_stock_prices,omitempty"` + OnlyInStock *bool `json:"only_in_stock,omitempty"` } type ProjectConfigurationsResult struct { @@ -152,6 +154,12 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr if req.RentalEnabled != nil { localProject.RentalEnabled = *req.RentalEnabled } + if req.ShowStockPrices != nil { + localProject.ShowStockPrices = *req.ShowStockPrices + } + if req.OnlyInStock != nil { + localProject.OnlyInStock = *req.OnlyInStock + } localProject.UpdatedAt = time.Now() localProject.SyncStatus = "pending" if err := s.localDB.SaveProject(localProject); err != nil { diff --git a/internal/services/sync/service.go b/internal/services/sync/service.go index 6dd6969..4234c78 100644 --- a/internal/services/sync/service.go +++ b/internal/services/sync/service.go @@ -218,6 +218,8 @@ func (s *Service) ImportProjectsToLocal() (*ProjectImportResult, error) { existing.IsActive = project.IsActive existing.IsSystem = project.IsSystem existing.RentalEnabled = project.RentalEnabled + existing.ShowStockPrices = project.ShowStockPrices + existing.OnlyInStock = project.OnlyInStock existing.CreatedAt = project.CreatedAt existing.UpdatedAt = project.UpdatedAt serverID := project.ID diff --git a/web/templates/index.html b/web/templates/index.html index 5e8cbdc..6e6eb2d 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -227,7 +227,7 @@ LOT Кол-во Estimate - Склад + Склад Конкуренты Ручная цена @@ -239,7 +239,7 @@ Итого: — - — + — — — @@ -276,7 +276,7 @@ LOT Кол-во Estimate - Склад + Склад Конкуренты Ручная цена @@ -288,7 +288,7 @@ Итого: — - — + — — — @@ -395,10 +395,6 @@ Не обновлять цены -
@@ -411,6 +407,9 @@