feat: складские цены и «только наличие» — настройки уровня проекта
Добавлен чекбокс «Складские цены» (по аналогии с «Аренда»), управляющий показом складских цен во всех ценовых интерфейсах конфигурации, включая CSV-экспорт. «Только наличие» перенесён из настроек цен конфигурации в настройки проекта и активен только при включённых складских ценах. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
18282452a1
commit
9629521495
+18
-14
@@ -1574,25 +1574,29 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
|||||||
|
|
||||||
// Return simplified list of all projects (UUID + Name only)
|
// Return simplified list of all projects (UUID + Name only)
|
||||||
type ProjectSimple struct {
|
type ProjectSimple struct {
|
||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Variant string `json:"variant"`
|
Variant string `json:"variant"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
RentalEnabled bool `json:"rental_enabled"`
|
RentalEnabled bool `json:"rental_enabled"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
ShowStockPrices bool `json:"show_stock_prices"`
|
||||||
|
OnlyInStock bool `json:"only_in_stock"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
simplified := make([]ProjectSimple, 0, len(allProjects))
|
simplified := make([]ProjectSimple, 0, len(allProjects))
|
||||||
for _, p := range allProjects {
|
for _, p := range allProjects {
|
||||||
simplified = append(simplified, ProjectSimple{
|
simplified = append(simplified, ProjectSimple{
|
||||||
UUID: p.UUID,
|
UUID: p.UUID,
|
||||||
Code: p.Code,
|
Code: p.Code,
|
||||||
Variant: p.Variant,
|
Variant: p.Variant,
|
||||||
Name: derefString(p.Name),
|
Name: derefString(p.Name),
|
||||||
IsActive: p.IsActive,
|
IsActive: p.IsActive,
|
||||||
RentalEnabled: p.RentalEnabled,
|
RentalEnabled: p.RentalEnabled,
|
||||||
CreatedAt: p.CreatedAt,
|
ShowStockPrices: p.ShowStockPrices,
|
||||||
|
OnlyInStock: p.OnlyInStock,
|
||||||
|
CreatedAt: p.CreatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,11 +238,18 @@ func (h *ExportHandler) ExportConfigPricingCSV(c *gin.Context) {
|
|||||||
return
|
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{
|
opts := services.ProjectPricingExportOptions{
|
||||||
IncludeLOT: req.IncludeLOT,
|
IncludeLOT: req.IncludeLOT,
|
||||||
IncludeBOM: req.IncludeBOM,
|
IncludeBOM: req.IncludeBOM,
|
||||||
IncludeEstimate: req.IncludeEstimate,
|
IncludeEstimate: req.IncludeEstimate,
|
||||||
IncludeStock: req.IncludeStock,
|
IncludeStock: req.IncludeStock && project != nil && project.ShowStockPrices,
|
||||||
IncludeCompetitor: req.IncludeCompetitor,
|
IncludeCompetitor: req.IncludeCompetitor,
|
||||||
Basis: req.Basis,
|
Basis: req.Basis,
|
||||||
SaleMarkup: req.SaleMarkup,
|
SaleMarkup: req.SaleMarkup,
|
||||||
@@ -260,10 +267,8 @@ func (h *ExportHandler) ExportConfigPricingCSV(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
projectCode := config.Name
|
projectCode := config.Name
|
||||||
if config.ProjectUUID != nil && *config.ProjectUUID != "" {
|
if project != nil {
|
||||||
if project, err := h.projectService.GetByUUID(*config.ProjectUUID, h.dbUsername); err == nil && project != nil {
|
projectCode = project.Code
|
||||||
projectCode = project.Code
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := fmt.Sprintf("%s (%s) %s %s SPEC.csv",
|
filename := fmt.Sprintf("%s (%s) %s %s SPEC.csv",
|
||||||
@@ -309,7 +314,7 @@ func (h *ExportHandler) ExportProjectPricingCSV(c *gin.Context) {
|
|||||||
IncludeLOT: req.IncludeLOT,
|
IncludeLOT: req.IncludeLOT,
|
||||||
IncludeBOM: req.IncludeBOM,
|
IncludeBOM: req.IncludeBOM,
|
||||||
IncludeEstimate: req.IncludeEstimate,
|
IncludeEstimate: req.IncludeEstimate,
|
||||||
IncludeStock: req.IncludeStock,
|
IncludeStock: req.IncludeStock && project.ShowStockPrices,
|
||||||
IncludeCompetitor: req.IncludeCompetitor,
|
IncludeCompetitor: req.IncludeCompetitor,
|
||||||
Basis: req.Basis,
|
Basis: req.Basis,
|
||||||
SaleMarkup: req.SaleMarkup,
|
SaleMarkup: req.SaleMarkup,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ func TestConfigurationConvertersPreserveBusinessFields(t *testing.T) {
|
|||||||
WarehousePricelistID: &warehouseID,
|
WarehousePricelistID: &warehouseID,
|
||||||
CompetitorPricelistID: &competitorID,
|
CompetitorPricelistID: &competitorID,
|
||||||
DisablePriceRefresh: true,
|
DisablePriceRefresh: true,
|
||||||
OnlyInStock: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local := ConfigurationToLocal(cfg)
|
local := ConfigurationToLocal(cfg)
|
||||||
@@ -57,7 +56,6 @@ func TestConfigurationSnapshotPreservesBusinessFields(t *testing.T) {
|
|||||||
WarehousePricelistID: &warehouseID,
|
WarehousePricelistID: &warehouseID,
|
||||||
CompetitorPricelistID: &competitorID,
|
CompetitorPricelistID: &competitorID,
|
||||||
DisablePriceRefresh: true,
|
DisablePriceRefresh: true,
|
||||||
OnlyInStock: true,
|
|
||||||
VendorSpec: VendorSpec{
|
VendorSpec: VendorSpec{
|
||||||
{
|
{
|
||||||
SortOrder: 10,
|
SortOrder: 10,
|
||||||
@@ -110,7 +108,6 @@ func TestConfigurationFingerprintIncludesPricingSelectorsAndVendorSpec(t *testin
|
|||||||
WarehousePricelistID: &warehouseID,
|
WarehousePricelistID: &warehouseID,
|
||||||
CompetitorPricelistID: &competitorID,
|
CompetitorPricelistID: &competitorID,
|
||||||
DisablePriceRefresh: true,
|
DisablePriceRefresh: true,
|
||||||
OnlyInStock: true,
|
|
||||||
VendorSpec: VendorSpec{
|
VendorSpec: VendorSpec{
|
||||||
{
|
{
|
||||||
SortOrder: 10,
|
SortOrder: 10,
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ func ConfigurationToLocal(cfg *models.Configuration) *LocalConfiguration {
|
|||||||
ConfigType: cfg.ConfigType,
|
ConfigType: cfg.ConfigType,
|
||||||
VendorSpec: modelVendorSpecToLocal(cfg.VendorSpec),
|
VendorSpec: modelVendorSpecToLocal(cfg.VendorSpec),
|
||||||
DisablePriceRefresh: cfg.DisablePriceRefresh,
|
DisablePriceRefresh: cfg.DisablePriceRefresh,
|
||||||
OnlyInStock: cfg.OnlyInStock,
|
|
||||||
RentalItems: modelRentalItemsToLocal(cfg.RentalItems),
|
RentalItems: modelRentalItemsToLocal(cfg.RentalItems),
|
||||||
RentalUpliftPercent: cfg.RentalUpliftPercent,
|
RentalUpliftPercent: cfg.RentalUpliftPercent,
|
||||||
Line: cfg.Line,
|
Line: cfg.Line,
|
||||||
@@ -124,7 +123,6 @@ func LocalToConfiguration(local *LocalConfiguration) *models.Configuration {
|
|||||||
ConfigType: local.ConfigType,
|
ConfigType: local.ConfigType,
|
||||||
VendorSpec: localVendorSpecToModel(local.VendorSpec),
|
VendorSpec: localVendorSpecToModel(local.VendorSpec),
|
||||||
DisablePriceRefresh: local.DisablePriceRefresh,
|
DisablePriceRefresh: local.DisablePriceRefresh,
|
||||||
OnlyInStock: local.OnlyInStock,
|
|
||||||
RentalItems: localRentalItemsToModel(local.RentalItems),
|
RentalItems: localRentalItemsToModel(local.RentalItems),
|
||||||
RentalUpliftPercent: local.RentalUpliftPercent,
|
RentalUpliftPercent: local.RentalUpliftPercent,
|
||||||
Line: local.Line,
|
Line: local.Line,
|
||||||
@@ -259,18 +257,20 @@ func localRentalItemsToModel(items RentalItemConditions) models.RentalItemCondit
|
|||||||
|
|
||||||
func ProjectToLocal(project *models.Project) *LocalProject {
|
func ProjectToLocal(project *models.Project) *LocalProject {
|
||||||
local := &LocalProject{
|
local := &LocalProject{
|
||||||
UUID: project.UUID,
|
UUID: project.UUID,
|
||||||
OwnerUsername: project.OwnerUsername,
|
OwnerUsername: project.OwnerUsername,
|
||||||
Code: project.Code,
|
Code: project.Code,
|
||||||
Variant: project.Variant,
|
Variant: project.Variant,
|
||||||
Name: project.Name,
|
Name: project.Name,
|
||||||
TrackerURL: project.TrackerURL,
|
TrackerURL: project.TrackerURL,
|
||||||
IsActive: project.IsActive,
|
IsActive: project.IsActive,
|
||||||
IsSystem: project.IsSystem,
|
IsSystem: project.IsSystem,
|
||||||
RentalEnabled: project.RentalEnabled,
|
RentalEnabled: project.RentalEnabled,
|
||||||
CreatedAt: project.CreatedAt,
|
ShowStockPrices: project.ShowStockPrices,
|
||||||
UpdatedAt: project.UpdatedAt,
|
OnlyInStock: project.OnlyInStock,
|
||||||
SyncStatus: "pending",
|
CreatedAt: project.CreatedAt,
|
||||||
|
UpdatedAt: project.UpdatedAt,
|
||||||
|
SyncStatus: "pending",
|
||||||
}
|
}
|
||||||
if project.ID > 0 {
|
if project.ID > 0 {
|
||||||
serverID := project.ID
|
serverID := project.ID
|
||||||
@@ -281,17 +281,19 @@ func ProjectToLocal(project *models.Project) *LocalProject {
|
|||||||
|
|
||||||
func LocalToProject(local *LocalProject) *models.Project {
|
func LocalToProject(local *LocalProject) *models.Project {
|
||||||
project := &models.Project{
|
project := &models.Project{
|
||||||
UUID: local.UUID,
|
UUID: local.UUID,
|
||||||
OwnerUsername: local.OwnerUsername,
|
OwnerUsername: local.OwnerUsername,
|
||||||
Code: local.Code,
|
Code: local.Code,
|
||||||
Variant: local.Variant,
|
Variant: local.Variant,
|
||||||
Name: local.Name,
|
Name: local.Name,
|
||||||
TrackerURL: local.TrackerURL,
|
TrackerURL: local.TrackerURL,
|
||||||
IsActive: local.IsActive,
|
IsActive: local.IsActive,
|
||||||
IsSystem: local.IsSystem,
|
IsSystem: local.IsSystem,
|
||||||
RentalEnabled: local.RentalEnabled,
|
RentalEnabled: local.RentalEnabled,
|
||||||
CreatedAt: local.CreatedAt,
|
ShowStockPrices: local.ShowStockPrices,
|
||||||
UpdatedAt: local.UpdatedAt,
|
OnlyInStock: local.OnlyInStock,
|
||||||
|
CreatedAt: local.CreatedAt,
|
||||||
|
UpdatedAt: local.UpdatedAt,
|
||||||
}
|
}
|
||||||
if local.ServerID != nil {
|
if local.ServerID != nil {
|
||||||
project.ID = *local.ServerID
|
project.ID = *local.ServerID
|
||||||
|
|||||||
@@ -212,6 +212,8 @@ CREATE TABLE local_projects (
|
|||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
is_active INTEGER NOT NULL DEFAULT 1,
|
||||||
is_system INTEGER NOT NULL DEFAULT 0,
|
is_system INTEGER NOT NULL DEFAULT 0,
|
||||||
rental_enabled 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,
|
created_at DATETIME,
|
||||||
updated_at DATETIME,
|
updated_at DATETIME,
|
||||||
synced_at DATETIME NULL,
|
synced_at DATETIME NULL,
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ var localMigrations = []localMigration{
|
|||||||
name: "Add price_quality to local_pricelist_items",
|
name: "Add price_quality to local_pricelist_items",
|
||||||
run: addLocalPricelistItemPriceQuality,
|
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 {
|
func addLocalPricelistItemPriceQuality(tx *gorm.DB) error {
|
||||||
@@ -249,6 +254,22 @@ func addLocalProjectRentalEnabled(tx *gorm.DB) error {
|
|||||||
return nil
|
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 {
|
type localPartnumberCatalogRow struct {
|
||||||
Partnumber string
|
Partnumber string
|
||||||
LotsJSON LocalPartnumberBookLots
|
LotsJSON LocalPartnumberBookLots
|
||||||
|
|||||||
+17
-16
@@ -111,7 +111,6 @@ type LocalConfiguration struct {
|
|||||||
WarehousePricelistID *uint `gorm:"index" json:"warehouse_pricelist_id,omitempty"`
|
WarehousePricelistID *uint `gorm:"index" json:"warehouse_pricelist_id,omitempty"`
|
||||||
CompetitorPricelistID *uint `gorm:"index" json:"competitor_pricelist_id,omitempty"`
|
CompetitorPricelistID *uint `gorm:"index" json:"competitor_pricelist_id,omitempty"`
|
||||||
DisablePriceRefresh bool `gorm:"default:false" json:"disable_price_refresh"`
|
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"`
|
RentalItems RentalItemConditions `gorm:"type:text" json:"rental_items,omitempty"`
|
||||||
RentalUpliftPercent float64 `gorm:"default:0" json:"rental_uplift_percent"`
|
RentalUpliftPercent float64 `gorm:"default:0" json:"rental_uplift_percent"`
|
||||||
VendorSpec VendorSpec `gorm:"type:text" json:"vendor_spec,omitempty"`
|
VendorSpec VendorSpec `gorm:"type:text" json:"vendor_spec,omitempty"`
|
||||||
@@ -133,21 +132,23 @@ func (LocalConfiguration) TableName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LocalProject struct {
|
type LocalProject struct {
|
||||||
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
UUID string `gorm:"uniqueIndex;not null" json:"uuid"`
|
UUID string `gorm:"uniqueIndex;not null" json:"uuid"`
|
||||||
ServerID *uint `json:"server_id,omitempty"`
|
ServerID *uint `json:"server_id,omitempty"`
|
||||||
OwnerUsername string `gorm:"not null;index" json:"owner_username"`
|
OwnerUsername string `gorm:"not null;index" json:"owner_username"`
|
||||||
Code string `gorm:"not null;index:idx_local_projects_code_variant,priority:1" json:"code"`
|
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"`
|
Variant string `gorm:"default:'';index:idx_local_projects_code_variant,priority:2" json:"variant"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
TrackerURL string `json:"tracker_url"`
|
TrackerURL string `json:"tracker_url"`
|
||||||
IsActive bool `gorm:"default:true;index" json:"is_active"`
|
IsActive bool `gorm:"default:true;index" json:"is_active"`
|
||||||
IsSystem bool `gorm:"default:false;index" json:"is_system"`
|
IsSystem bool `gorm:"default:false;index" json:"is_system"`
|
||||||
RentalEnabled bool `gorm:"default:false" json:"rental_enabled"`
|
RentalEnabled bool `gorm:"default:false" json:"rental_enabled"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
ShowStockPrices bool `gorm:"default:false" json:"show_stock_prices"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
OnlyInStock bool `gorm:"default:false" json:"only_in_stock"`
|
||||||
SyncedAt *time.Time `json:"synced_at,omitempty"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
SyncStatus string `gorm:"default:'local'" json:"sync_status"` // local/synced/pending
|
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 {
|
func (LocalProject) TableName() string {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ func BuildConfigurationSnapshot(localCfg *LocalConfiguration) (string, error) {
|
|||||||
"warehouse_pricelist_id": localCfg.WarehousePricelistID,
|
"warehouse_pricelist_id": localCfg.WarehousePricelistID,
|
||||||
"competitor_pricelist_id": localCfg.CompetitorPricelistID,
|
"competitor_pricelist_id": localCfg.CompetitorPricelistID,
|
||||||
"disable_price_refresh": localCfg.DisablePriceRefresh,
|
"disable_price_refresh": localCfg.DisablePriceRefresh,
|
||||||
"only_in_stock": localCfg.OnlyInStock,
|
|
||||||
"rental_items": localCfg.RentalItems,
|
"rental_items": localCfg.RentalItems,
|
||||||
"rental_uplift_percent": localCfg.RentalUpliftPercent,
|
"rental_uplift_percent": localCfg.RentalUpliftPercent,
|
||||||
"vendor_spec": localCfg.VendorSpec,
|
"vendor_spec": localCfg.VendorSpec,
|
||||||
@@ -54,30 +53,29 @@ func BuildConfigurationSnapshot(localCfg *LocalConfiguration) (string, error) {
|
|||||||
// DecodeConfigurationSnapshot returns editable fields from one saved snapshot.
|
// DecodeConfigurationSnapshot returns editable fields from one saved snapshot.
|
||||||
func DecodeConfigurationSnapshot(data string) (*LocalConfiguration, error) {
|
func DecodeConfigurationSnapshot(data string) (*LocalConfiguration, error) {
|
||||||
var snapshot struct {
|
var snapshot struct {
|
||||||
ProjectUUID *string `json:"project_uuid"`
|
ProjectUUID *string `json:"project_uuid"`
|
||||||
IsActive *bool `json:"is_active"`
|
IsActive *bool `json:"is_active"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Items LocalConfigItems `json:"items"`
|
Items LocalConfigItems `json:"items"`
|
||||||
TotalPrice *float64 `json:"total_price"`
|
TotalPrice *float64 `json:"total_price"`
|
||||||
CustomPrice *float64 `json:"custom_price"`
|
CustomPrice *float64 `json:"custom_price"`
|
||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
IsTemplate bool `json:"is_template"`
|
IsTemplate bool `json:"is_template"`
|
||||||
ServerCount int `json:"server_count"`
|
ServerCount int `json:"server_count"`
|
||||||
ServerModel string `json:"server_model"`
|
ServerModel string `json:"server_model"`
|
||||||
SupportCode string `json:"support_code"`
|
SupportCode string `json:"support_code"`
|
||||||
Article string `json:"article"`
|
Article string `json:"article"`
|
||||||
PricelistID *uint `json:"pricelist_id"`
|
PricelistID *uint `json:"pricelist_id"`
|
||||||
WarehousePricelistID *uint `json:"warehouse_pricelist_id"`
|
WarehousePricelistID *uint `json:"warehouse_pricelist_id"`
|
||||||
CompetitorPricelistID *uint `json:"competitor_pricelist_id"`
|
CompetitorPricelistID *uint `json:"competitor_pricelist_id"`
|
||||||
DisablePriceRefresh bool `json:"disable_price_refresh"`
|
DisablePriceRefresh bool `json:"disable_price_refresh"`
|
||||||
OnlyInStock bool `json:"only_in_stock"`
|
|
||||||
RentalItems RentalItemConditions `json:"rental_items"`
|
RentalItems RentalItemConditions `json:"rental_items"`
|
||||||
RentalUpliftPercent float64 `json:"rental_uplift_percent"`
|
RentalUpliftPercent float64 `json:"rental_uplift_percent"`
|
||||||
VendorSpec VendorSpec `json:"vendor_spec"`
|
VendorSpec VendorSpec `json:"vendor_spec"`
|
||||||
Line int `json:"line"`
|
Line int `json:"line"`
|
||||||
PriceUpdatedAt *time.Time `json:"price_updated_at"`
|
PriceUpdatedAt *time.Time `json:"price_updated_at"`
|
||||||
OriginalUserID uint `json:"original_user_id"`
|
OriginalUserID uint `json:"original_user_id"`
|
||||||
OriginalUsername string `json:"original_username"`
|
OriginalUsername string `json:"original_username"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(data), &snapshot); err != nil {
|
if err := json.Unmarshal([]byte(data), &snapshot); err != nil {
|
||||||
@@ -106,7 +104,6 @@ func DecodeConfigurationSnapshot(data string) (*LocalConfiguration, error) {
|
|||||||
WarehousePricelistID: snapshot.WarehousePricelistID,
|
WarehousePricelistID: snapshot.WarehousePricelistID,
|
||||||
CompetitorPricelistID: snapshot.CompetitorPricelistID,
|
CompetitorPricelistID: snapshot.CompetitorPricelistID,
|
||||||
DisablePriceRefresh: snapshot.DisablePriceRefresh,
|
DisablePriceRefresh: snapshot.DisablePriceRefresh,
|
||||||
OnlyInStock: snapshot.OnlyInStock,
|
|
||||||
RentalItems: snapshot.RentalItems,
|
RentalItems: snapshot.RentalItems,
|
||||||
RentalUpliftPercent: snapshot.RentalUpliftPercent,
|
RentalUpliftPercent: snapshot.RentalUpliftPercent,
|
||||||
VendorSpec: snapshot.VendorSpec,
|
VendorSpec: snapshot.VendorSpec,
|
||||||
@@ -126,7 +123,6 @@ type configurationSpecPriceFingerprint struct {
|
|||||||
WarehousePricelistID *uint `json:"warehouse_pricelist_id,omitempty"`
|
WarehousePricelistID *uint `json:"warehouse_pricelist_id,omitempty"`
|
||||||
CompetitorPricelistID *uint `json:"competitor_pricelist_id,omitempty"`
|
CompetitorPricelistID *uint `json:"competitor_pricelist_id,omitempty"`
|
||||||
DisablePriceRefresh bool `json:"disable_price_refresh"`
|
DisablePriceRefresh bool `json:"disable_price_refresh"`
|
||||||
OnlyInStock bool `json:"only_in_stock"`
|
|
||||||
RentalItems RentalItemConditions `json:"rental_items,omitempty"`
|
RentalItems RentalItemConditions `json:"rental_items,omitempty"`
|
||||||
RentalUpliftPercent float64 `json:"rental_uplift_percent"`
|
RentalUpliftPercent float64 `json:"rental_uplift_percent"`
|
||||||
VendorSpec VendorSpec `json:"vendor_spec,omitempty"`
|
VendorSpec VendorSpec `json:"vendor_spec,omitempty"`
|
||||||
@@ -174,7 +170,6 @@ func BuildConfigurationSpecPriceFingerprint(localCfg *LocalConfiguration) (strin
|
|||||||
WarehousePricelistID: localCfg.WarehousePricelistID,
|
WarehousePricelistID: localCfg.WarehousePricelistID,
|
||||||
CompetitorPricelistID: localCfg.CompetitorPricelistID,
|
CompetitorPricelistID: localCfg.CompetitorPricelistID,
|
||||||
DisablePriceRefresh: localCfg.DisablePriceRefresh,
|
DisablePriceRefresh: localCfg.DisablePriceRefresh,
|
||||||
OnlyInStock: localCfg.OnlyInStock,
|
|
||||||
RentalItems: rentalItems,
|
RentalItems: rentalItems,
|
||||||
RentalUpliftPercent: localCfg.RentalUpliftPercent,
|
RentalUpliftPercent: localCfg.RentalUpliftPercent,
|
||||||
VendorSpec: localCfg.VendorSpec,
|
VendorSpec: localCfg.VendorSpec,
|
||||||
|
|||||||
@@ -126,38 +126,36 @@ func (v *VendorSpec) Scan(value interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"`
|
UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"`
|
||||||
UserID *uint `json:"user_id,omitempty"` // Legacy field, no longer required for ownership
|
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"`
|
OwnerUsername string `gorm:"size:100;not null;default:'';index" json:"owner_username"`
|
||||||
ProjectUUID *string `gorm:"size:36;index" json:"project_uuid,omitempty"`
|
ProjectUUID *string `gorm:"size:36;index" json:"project_uuid,omitempty"`
|
||||||
AppVersion string `gorm:"size:64" json:"app_version,omitempty"`
|
AppVersion string `gorm:"size:64" json:"app_version,omitempty"`
|
||||||
Name string `gorm:"size:200;not null" json:"name"`
|
Name string `gorm:"size:200;not null" json:"name"`
|
||||||
Items ConfigItems `gorm:"type:json;not null" json:"items"`
|
Items ConfigItems `gorm:"type:json;not null" json:"items"`
|
||||||
TotalPrice *float64 `gorm:"type:decimal(12,2)" json:"total_price"`
|
TotalPrice *float64 `gorm:"type:decimal(12,2)" json:"total_price"`
|
||||||
CustomPrice *float64 `gorm:"type:decimal(12,2)" json:"custom_price"`
|
CustomPrice *float64 `gorm:"type:decimal(12,2)" json:"custom_price"`
|
||||||
Notes string `gorm:"type:text" json:"notes"`
|
Notes string `gorm:"type:text" json:"notes"`
|
||||||
IsTemplate bool `gorm:"default:false" json:"is_template"`
|
IsTemplate bool `gorm:"default:false" json:"is_template"`
|
||||||
ServerCount int `gorm:"default:1" json:"server_count"`
|
ServerCount int `gorm:"default:1" json:"server_count"`
|
||||||
ServerModel string `gorm:"size:100" json:"server_model,omitempty"`
|
ServerModel string `gorm:"size:100" json:"server_model,omitempty"`
|
||||||
SupportCode string `gorm:"size:20" json:"support_code,omitempty"`
|
SupportCode string `gorm:"size:20" json:"support_code,omitempty"`
|
||||||
Article string `gorm:"size:80" json:"article,omitempty"`
|
Article string `gorm:"size:80" json:"article,omitempty"`
|
||||||
PricelistID *uint `gorm:"index" json:"pricelist_id,omitempty"`
|
PricelistID *uint `gorm:"index" json:"pricelist_id,omitempty"`
|
||||||
WarehousePricelistID *uint `gorm:"index" json:"warehouse_pricelist_id,omitempty"`
|
WarehousePricelistID *uint `gorm:"index" json:"warehouse_pricelist_id,omitempty"`
|
||||||
CompetitorPricelistID *uint `gorm:"index" json:"competitor_pricelist_id,omitempty"`
|
CompetitorPricelistID *uint `gorm:"index" json:"competitor_pricelist_id,omitempty"`
|
||||||
VendorSpec VendorSpec `gorm:"type:json" json:"vendor_spec,omitempty"`
|
VendorSpec VendorSpec `gorm:"type:json" json:"vendor_spec,omitempty"`
|
||||||
ConfigType string `gorm:"size:20;default:server" json:"config_type"` // "server" | "storage"
|
ConfigType string `gorm:"size:20;default:server" json:"config_type"` // "server" | "storage"
|
||||||
DisablePriceRefresh bool `gorm:"default:false" json:"disable_price_refresh"`
|
DisablePriceRefresh bool `gorm:"default:false" json:"disable_price_refresh"`
|
||||||
OnlyInStock bool `gorm:"default:false" json:"only_in_stock"`
|
|
||||||
RentalItems RentalItemConditions `gorm:"type:json" json:"rental_items,omitempty"`
|
RentalItems RentalItemConditions `gorm:"type:json" json:"rental_items,omitempty"`
|
||||||
RentalUpliftPercent float64 `gorm:"type:decimal(8,2);default:0" json:"rental_uplift_percent"`
|
RentalUpliftPercent float64 `gorm:"type:decimal(8,2);default:0" json:"rental_uplift_percent"`
|
||||||
Line int `gorm:"column:line_no;index" json:"line"`
|
Line int `gorm:"column:line_no;index" json:"line"`
|
||||||
PriceUpdatedAt *time.Time `gorm:"type:timestamp" json:"price_updated_at,omitempty"`
|
PriceUpdatedAt *time.Time `gorm:"type:timestamp" json:"price_updated_at,omitempty"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
CurrentVersionNo int `gorm:"-" json:"current_version_no,omitempty"`
|
CurrentVersionNo int `gorm:"-" json:"current_version_no,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Configuration) TableName() string {
|
func (Configuration) TableName() string {
|
||||||
return "qt_configurations"
|
return "qt_configurations"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-12
@@ -3,18 +3,20 @@ package models
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"`
|
UUID string `gorm:"size:36;uniqueIndex;not null" json:"uuid"`
|
||||||
OwnerUsername string `gorm:"size:100;not null;index" json:"owner_username"`
|
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"`
|
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"`
|
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"`
|
Name *string `gorm:"size:200" json:"name,omitempty"`
|
||||||
TrackerURL string `gorm:"size:500" json:"tracker_url"`
|
TrackerURL string `gorm:"size:500" json:"tracker_url"`
|
||||||
IsActive bool `gorm:"default:true;index" json:"is_active"`
|
IsActive bool `gorm:"default:true;index" json:"is_active"`
|
||||||
IsSystem bool `gorm:"default:false;index" json:"is_system"`
|
IsSystem bool `gorm:"default:false;index" json:"is_system"`
|
||||||
RentalEnabled bool `gorm:"default:false" json:"rental_enabled"`
|
RentalEnabled bool `gorm:"default:false" json:"rental_enabled"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
ShowStockPrices bool `gorm:"default:false" json:"show_stock_prices"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
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 {
|
func (Project) TableName() string {
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ func (r *ProjectRepository) UpsertByUUID(project *models.Project) error {
|
|||||||
"is_active",
|
"is_active",
|
||||||
"is_system",
|
"is_system",
|
||||||
"rental_enabled",
|
"rental_enabled",
|
||||||
|
"show_stock_prices",
|
||||||
|
"only_in_stock",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
}),
|
}),
|
||||||
}).Create(project).Error; err != nil {
|
}).Create(project).Error; err != nil {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ type CreateConfigRequest struct {
|
|||||||
CompetitorPricelistID *uint `json:"competitor_pricelist_id,omitempty"`
|
CompetitorPricelistID *uint `json:"competitor_pricelist_id,omitempty"`
|
||||||
ConfigType string `json:"config_type,omitempty"` // "server" | "storage"
|
ConfigType string `json:"config_type,omitempty"` // "server" | "storage"
|
||||||
DisablePriceRefresh bool `json:"disable_price_refresh"`
|
DisablePriceRefresh bool `json:"disable_price_refresh"`
|
||||||
OnlyInStock bool `json:"only_in_stock"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArticlePreviewRequest struct {
|
type ArticlePreviewRequest struct {
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ func (s *LocalConfigurationService) Create(ownerUsername string, req *CreateConf
|
|||||||
CompetitorPricelistID: req.CompetitorPricelistID,
|
CompetitorPricelistID: req.CompetitorPricelistID,
|
||||||
ConfigType: req.ConfigType,
|
ConfigType: req.ConfigType,
|
||||||
DisablePriceRefresh: req.DisablePriceRefresh,
|
DisablePriceRefresh: req.DisablePriceRefresh,
|
||||||
OnlyInStock: req.OnlyInStock,
|
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
if cfg.ConfigType == "" {
|
if cfg.ConfigType == "" {
|
||||||
@@ -204,7 +203,6 @@ func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, re
|
|||||||
localCfg.WarehousePricelistID = req.WarehousePricelistID
|
localCfg.WarehousePricelistID = req.WarehousePricelistID
|
||||||
localCfg.CompetitorPricelistID = req.CompetitorPricelistID
|
localCfg.CompetitorPricelistID = req.CompetitorPricelistID
|
||||||
localCfg.DisablePriceRefresh = req.DisablePriceRefresh
|
localCfg.DisablePriceRefresh = req.DisablePriceRefresh
|
||||||
localCfg.OnlyInStock = req.OnlyInStock
|
|
||||||
localCfg.UpdatedAt = time.Now()
|
localCfg.UpdatedAt = time.Now()
|
||||||
localCfg.SyncStatus = "pending"
|
localCfg.SyncStatus = "pending"
|
||||||
|
|
||||||
@@ -328,7 +326,6 @@ func (s *LocalConfigurationService) CloneToProject(configUUID string, ownerUsern
|
|||||||
WarehousePricelistID: original.WarehousePricelistID,
|
WarehousePricelistID: original.WarehousePricelistID,
|
||||||
CompetitorPricelistID: original.CompetitorPricelistID,
|
CompetitorPricelistID: original.CompetitorPricelistID,
|
||||||
DisablePriceRefresh: original.DisablePriceRefresh,
|
DisablePriceRefresh: original.DisablePriceRefresh,
|
||||||
OnlyInStock: original.OnlyInStock,
|
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,7 +563,6 @@ func (s *LocalConfigurationService) UpdateNoAuth(uuid string, req *CreateConfigR
|
|||||||
localCfg.WarehousePricelistID = req.WarehousePricelistID
|
localCfg.WarehousePricelistID = req.WarehousePricelistID
|
||||||
localCfg.CompetitorPricelistID = req.CompetitorPricelistID
|
localCfg.CompetitorPricelistID = req.CompetitorPricelistID
|
||||||
localCfg.DisablePriceRefresh = req.DisablePriceRefresh
|
localCfg.DisablePriceRefresh = req.DisablePriceRefresh
|
||||||
localCfg.OnlyInStock = req.OnlyInStock
|
|
||||||
localCfg.UpdatedAt = time.Now()
|
localCfg.UpdatedAt = time.Now()
|
||||||
localCfg.SyncStatus = "pending"
|
localCfg.SyncStatus = "pending"
|
||||||
|
|
||||||
@@ -685,7 +681,6 @@ func (s *LocalConfigurationService) CloneNoAuthToProjectFromVersion(configUUID s
|
|||||||
WarehousePricelistID: original.WarehousePricelistID,
|
WarehousePricelistID: original.WarehousePricelistID,
|
||||||
CompetitorPricelistID: original.CompetitorPricelistID,
|
CompetitorPricelistID: original.CompetitorPricelistID,
|
||||||
DisablePriceRefresh: original.DisablePriceRefresh,
|
DisablePriceRefresh: original.DisablePriceRefresh,
|
||||||
OnlyInStock: original.OnlyInStock,
|
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1648,7 +1643,6 @@ func (s *LocalConfigurationService) rollbackToVersion(configurationUUID string,
|
|||||||
current.WarehousePricelistID = rollbackData.WarehousePricelistID
|
current.WarehousePricelistID = rollbackData.WarehousePricelistID
|
||||||
current.CompetitorPricelistID = rollbackData.CompetitorPricelistID
|
current.CompetitorPricelistID = rollbackData.CompetitorPricelistID
|
||||||
current.DisablePriceRefresh = rollbackData.DisablePriceRefresh
|
current.DisablePriceRefresh = rollbackData.DisablePriceRefresh
|
||||||
current.OnlyInStock = rollbackData.OnlyInStock
|
|
||||||
current.VendorSpec = rollbackData.VendorSpec
|
current.VendorSpec = rollbackData.VendorSpec
|
||||||
if rollbackData.Line > 0 {
|
if rollbackData.Line > 0 {
|
||||||
current.Line = rollbackData.Line
|
current.Line = rollbackData.Line
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ func TestUpdateNoAuthCreatesRevisionWhenPricingSettingsChanged(t *testing.T) {
|
|||||||
Items: models.ConfigItems{{LotName: "CPU_A", Quantity: 1, UnitPrice: 1000}},
|
Items: models.ConfigItems{{LotName: "CPU_A", Quantity: 1, UnitPrice: 1000}},
|
||||||
ServerCount: 1,
|
ServerCount: 1,
|
||||||
DisablePriceRefresh: true,
|
DisablePriceRefresh: true,
|
||||||
OnlyInStock: true,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("update pricing settings: %v", err)
|
t.Fatalf("update pricing settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,13 @@ type CreateProjectRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProjectRequest struct {
|
type UpdateProjectRequest struct {
|
||||||
Code *string `json:"code,omitempty"`
|
Code *string `json:"code,omitempty"`
|
||||||
Variant *string `json:"variant,omitempty"`
|
Variant *string `json:"variant,omitempty"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
TrackerURL *string `json:"tracker_url,omitempty"`
|
TrackerURL *string `json:"tracker_url,omitempty"`
|
||||||
RentalEnabled *bool `json:"rental_enabled,omitempty"`
|
RentalEnabled *bool `json:"rental_enabled,omitempty"`
|
||||||
|
ShowStockPrices *bool `json:"show_stock_prices,omitempty"`
|
||||||
|
OnlyInStock *bool `json:"only_in_stock,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectConfigurationsResult struct {
|
type ProjectConfigurationsResult struct {
|
||||||
@@ -152,6 +154,12 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr
|
|||||||
if req.RentalEnabled != nil {
|
if req.RentalEnabled != nil {
|
||||||
localProject.RentalEnabled = *req.RentalEnabled
|
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.UpdatedAt = time.Now()
|
||||||
localProject.SyncStatus = "pending"
|
localProject.SyncStatus = "pending"
|
||||||
if err := s.localDB.SaveProject(localProject); err != nil {
|
if err := s.localDB.SaveProject(localProject); err != nil {
|
||||||
|
|||||||
@@ -218,6 +218,8 @@ func (s *Service) ImportProjectsToLocal() (*ProjectImportResult, error) {
|
|||||||
existing.IsActive = project.IsActive
|
existing.IsActive = project.IsActive
|
||||||
existing.IsSystem = project.IsSystem
|
existing.IsSystem = project.IsSystem
|
||||||
existing.RentalEnabled = project.RentalEnabled
|
existing.RentalEnabled = project.RentalEnabled
|
||||||
|
existing.ShowStockPrices = project.ShowStockPrices
|
||||||
|
existing.OnlyInStock = project.OnlyInStock
|
||||||
existing.CreatedAt = project.CreatedAt
|
existing.CreatedAt = project.CreatedAt
|
||||||
existing.UpdatedAt = project.UpdatedAt
|
existing.UpdatedAt = project.UpdatedAt
|
||||||
serverID := project.ID
|
serverID := project.ID
|
||||||
|
|||||||
+33
-22
@@ -227,7 +227,7 @@
|
|||||||
<th class="px-2 py-2 text-left border-b">LOT</th>
|
<th class="px-2 py-2 text-left border-b">LOT</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Кол-во</th>
|
<th class="px-2 py-2 text-right border-b">Кол-во</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Estimate</th>
|
<th class="px-2 py-2 text-right border-b">Estimate</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Склад</th>
|
<th class="px-2 py-2 text-right border-b stock-price-col">Склад</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Конкуренты</th>
|
<th class="px-2 py-2 text-right border-b">Конкуренты</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Ручная цена</th>
|
<th class="px-2 py-2 text-right border-b">Ручная цена</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="px-2 py-2 text-right">Итого:</td>
|
<td colspan="4" class="px-2 py-2 text-right">Итого:</td>
|
||||||
<td class="px-2 py-2 text-right" id="pricing-total-buy-estimate">—</td>
|
<td class="px-2 py-2 text-right" id="pricing-total-buy-estimate">—</td>
|
||||||
<td class="px-2 py-2 text-right" id="pricing-total-buy-warehouse">—</td>
|
<td class="px-2 py-2 text-right stock-price-col" id="pricing-total-buy-warehouse">—</td>
|
||||||
<td class="px-2 py-2 text-right" id="pricing-total-buy-competitor">—</td>
|
<td class="px-2 py-2 text-right" id="pricing-total-buy-competitor">—</td>
|
||||||
<td class="px-2 py-2 text-right font-bold" id="pricing-total-buy-vendor">—</td>
|
<td class="px-2 py-2 text-right font-bold" id="pricing-total-buy-vendor">—</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
<th class="px-2 py-2 text-left border-b">LOT</th>
|
<th class="px-2 py-2 text-left border-b">LOT</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Кол-во</th>
|
<th class="px-2 py-2 text-right border-b">Кол-во</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Estimate</th>
|
<th class="px-2 py-2 text-right border-b">Estimate</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Склад</th>
|
<th class="px-2 py-2 text-right border-b stock-price-col">Склад</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Конкуренты</th>
|
<th class="px-2 py-2 text-right border-b">Конкуренты</th>
|
||||||
<th class="px-2 py-2 text-right border-b">Ручная цена</th>
|
<th class="px-2 py-2 text-right border-b">Ручная цена</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -288,7 +288,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="px-2 py-2 text-right">Итого:</td>
|
<td colspan="4" class="px-2 py-2 text-right">Итого:</td>
|
||||||
<td class="px-2 py-2 text-right" id="pricing-total-sale-estimate">—</td>
|
<td class="px-2 py-2 text-right" id="pricing-total-sale-estimate">—</td>
|
||||||
<td class="px-2 py-2 text-right" id="pricing-total-sale-warehouse">—</td>
|
<td class="px-2 py-2 text-right stock-price-col" id="pricing-total-sale-warehouse">—</td>
|
||||||
<td class="px-2 py-2 text-right" id="pricing-total-sale-competitor">—</td>
|
<td class="px-2 py-2 text-right" id="pricing-total-sale-competitor">—</td>
|
||||||
<td class="px-2 py-2 text-right font-bold" id="pricing-total-sale-vendor">—</td>
|
<td class="px-2 py-2 text-right font-bold" id="pricing-total-sale-vendor">—</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -395,10 +395,6 @@
|
|||||||
<input id="settings-disable-price-refresh" type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
<input id="settings-disable-price-refresh" type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
||||||
<span>Не обновлять цены</span>
|
<span>Не обновлять цены</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex items-center gap-2 text-sm text-gray-700">
|
|
||||||
<input id="settings-only-in-stock" type="checkbox" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
|
||||||
<span>Только наличие</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="px-5 py-4 border-t flex justify-end gap-2">
|
<div class="px-5 py-4 border-t flex justify-end gap-2">
|
||||||
<button type="button" onclick="closePriceSettingsModal()" class="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200">Отмена</button>
|
<button type="button" onclick="closePriceSettingsModal()" class="px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200">Отмена</button>
|
||||||
@@ -411,6 +407,9 @@
|
|||||||
<div id="autocomplete-dropdown" class="hidden absolute z-50 bg-white border rounded-lg shadow-lg max-h-96 overflow-y-auto w-96"></div>
|
<div id="autocomplete-dropdown" class="hidden absolute z-50 bg-white border rounded-lg shadow-lg max-h-96 overflow-y-auto w-96"></div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
#top-section-pricing.hide-stock-prices .stock-price-col {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.autocomplete-item {
|
.autocomplete-item {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -594,6 +593,7 @@ let resolvedAutoPricelistIds = {
|
|||||||
};
|
};
|
||||||
let disablePriceRefresh = false;
|
let disablePriceRefresh = false;
|
||||||
let onlyInStock = false;
|
let onlyInStock = false;
|
||||||
|
let showStockPrices = false;
|
||||||
let activePricelistsBySource = {
|
let activePricelistsBySource = {
|
||||||
estimate: [],
|
estimate: [],
|
||||||
warehouse: [],
|
warehouse: [],
|
||||||
@@ -992,6 +992,7 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
await loadProjectIndex();
|
await loadProjectIndex();
|
||||||
updateConfigBreadcrumbs();
|
updateConfigBreadcrumbs();
|
||||||
applyRentalTabVisibility();
|
applyRentalTabVisibility();
|
||||||
|
applyStockPriceSettings();
|
||||||
|
|
||||||
rentalConditions = {};
|
rentalConditions = {};
|
||||||
(config.rental_items || []).forEach(item => {
|
(config.rental_items || []).forEach(item => {
|
||||||
@@ -1009,7 +1010,6 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
selectedPricelistIds.warehouse = config.warehouse_pricelist_id || null;
|
selectedPricelistIds.warehouse = config.warehouse_pricelist_id || null;
|
||||||
selectedPricelistIds.competitor = config.competitor_pricelist_id || null;
|
selectedPricelistIds.competitor = config.competitor_pricelist_id || null;
|
||||||
disablePriceRefresh = Boolean(config.disable_price_refresh);
|
disablePriceRefresh = Boolean(config.disable_price_refresh);
|
||||||
onlyInStock = Boolean(config.only_in_stock);
|
|
||||||
|
|
||||||
if (config.items && config.items.length > 0) {
|
if (config.items && config.items.length > 0) {
|
||||||
cart = config.items.map(item => ({
|
cart = config.items.map(item => ({
|
||||||
@@ -1203,10 +1203,6 @@ function syncPriceSettingsControls() {
|
|||||||
if (disableCheckbox) {
|
if (disableCheckbox) {
|
||||||
disableCheckbox.checked = disablePriceRefresh;
|
disableCheckbox.checked = disablePriceRefresh;
|
||||||
}
|
}
|
||||||
const inStockCheckbox = document.getElementById('settings-only-in-stock');
|
|
||||||
if (inStockCheckbox) {
|
|
||||||
inStockCheckbox.checked = onlyInStock;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPricelistVersionById(source, id) {
|
function getPricelistVersionById(source, id) {
|
||||||
@@ -1223,7 +1219,8 @@ function renderPricelistSettingsSummary() {
|
|||||||
const competitor = selectedPricelistIds.competitor ? getPricelistVersionById('competitor', selectedPricelistIds.competitor) || `ID ${selectedPricelistIds.competitor}` : '—';
|
const competitor = selectedPricelistIds.competitor ? getPricelistVersionById('competitor', selectedPricelistIds.competitor) || `ID ${selectedPricelistIds.competitor}` : '—';
|
||||||
const refreshState = disablePriceRefresh ? ' | Обновление цен: выкл' : '';
|
const refreshState = disablePriceRefresh ? ' | Обновление цен: выкл' : '';
|
||||||
const stockFilterState = onlyInStock ? ' | Только наличие: вкл' : '';
|
const stockFilterState = onlyInStock ? ' | Только наличие: вкл' : '';
|
||||||
summary.textContent = `Estimate: ${estimate}, Склад: ${warehouse}, Конкуренты: ${competitor}${refreshState}${stockFilterState}`;
|
const stockPricesState = showStockPrices ? ' | Складские цены: вкл' : '';
|
||||||
|
summary.textContent = `Estimate: ${estimate}, Склад: ${warehouse}, Конкуренты: ${competitor}${refreshState}${stockFilterState}${stockPricesState}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRefreshPricesButtonState() {
|
function updateRefreshPricesButtonState() {
|
||||||
@@ -1293,7 +1290,6 @@ function applyPriceSettings() {
|
|||||||
const warehouseVal = parseInt(document.getElementById('settings-pricelist-warehouse')?.value || '');
|
const warehouseVal = parseInt(document.getElementById('settings-pricelist-warehouse')?.value || '');
|
||||||
const competitorVal = parseInt(document.getElementById('settings-pricelist-competitor')?.value || '');
|
const competitorVal = parseInt(document.getElementById('settings-pricelist-competitor')?.value || '');
|
||||||
const disableVal = Boolean(document.getElementById('settings-disable-price-refresh')?.checked);
|
const disableVal = Boolean(document.getElementById('settings-disable-price-refresh')?.checked);
|
||||||
const inStockVal = Boolean(document.getElementById('settings-only-in-stock')?.checked);
|
|
||||||
|
|
||||||
const prevWarehouseID = currentWarehousePricelistID();
|
const prevWarehouseID = currentWarehousePricelistID();
|
||||||
if (Number.isFinite(estimateVal) && estimateVal > 0) {
|
if (Number.isFinite(estimateVal) && estimateVal > 0) {
|
||||||
@@ -1309,7 +1305,6 @@ function applyPriceSettings() {
|
|||||||
resolvedAutoPricelistIds.competitor = null;
|
resolvedAutoPricelistIds.competitor = null;
|
||||||
}
|
}
|
||||||
disablePriceRefresh = disableVal;
|
disablePriceRefresh = disableVal;
|
||||||
onlyInStock = inStockVal;
|
|
||||||
|
|
||||||
const nextWarehouseID = currentWarehousePricelistID();
|
const nextWarehouseID = currentWarehousePricelistID();
|
||||||
if (Number.isFinite(prevWarehouseID) && prevWarehouseID > 0 && prevWarehouseID !== nextWarehouseID) {
|
if (Number.isFinite(prevWarehouseID) && prevWarehouseID > 0 && prevWarehouseID !== nextWarehouseID) {
|
||||||
@@ -2631,8 +2626,7 @@ function buildSavePayload() {
|
|||||||
pricelist_id: selectedPricelistIds.estimate,
|
pricelist_id: selectedPricelistIds.estimate,
|
||||||
warehouse_pricelist_id: selectedPricelistIds.warehouse,
|
warehouse_pricelist_id: selectedPricelistIds.warehouse,
|
||||||
competitor_pricelist_id: selectedPricelistIds.competitor,
|
competitor_pricelist_id: selectedPricelistIds.competitor,
|
||||||
disable_price_refresh: disablePriceRefresh,
|
disable_price_refresh: disablePriceRefresh
|
||||||
only_in_stock: onlyInStock
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2693,7 +2687,6 @@ function restoreAutosaveDraftIfAny() {
|
|||||||
supportCode = payload.support_code || supportCode;
|
supportCode = payload.support_code || supportCode;
|
||||||
currentArticle = payload.article || currentArticle;
|
currentArticle = payload.article || currentArticle;
|
||||||
selectedPricelistIds.estimate = payload.pricelist_id || selectedPricelistIds.estimate;
|
selectedPricelistIds.estimate = payload.pricelist_id || selectedPricelistIds.estimate;
|
||||||
onlyInStock = Boolean(payload.only_in_stock);
|
|
||||||
|
|
||||||
const customPriceInput = document.getElementById('custom-price-input');
|
const customPriceInput = document.getElementById('custom-price-input');
|
||||||
if (customPriceInput) {
|
if (customPriceInput) {
|
||||||
@@ -3262,6 +3255,24 @@ function applyRentalTabVisibility() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyStockPriceSettings() {
|
||||||
|
const proj = projectUUID ? projectByUUID[projectUUID] : null;
|
||||||
|
showStockPrices = !!(proj && proj.show_stock_prices);
|
||||||
|
onlyInStock = !!(proj && proj.show_stock_prices && proj.only_in_stock);
|
||||||
|
|
||||||
|
const pricingSection = document.getElementById('top-section-pricing');
|
||||||
|
if (pricingSection) {
|
||||||
|
pricingSection.classList.toggle('hide-stock-prices', !showStockPrices);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyInStock) {
|
||||||
|
ensureWarehouseStockFilterLoaded().then(() => {
|
||||||
|
renderTab();
|
||||||
|
updateCartUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function scheduleRentalRecalc() {
|
function scheduleRentalRecalc() {
|
||||||
clearTimeout(rentalRecalcTimer);
|
clearTimeout(rentalRecalcTimer);
|
||||||
rentalRecalcTimer = setTimeout(renderRentalTab, 300);
|
rentalRecalcTimer = setTimeout(renderRentalTab, 300);
|
||||||
@@ -4670,7 +4681,7 @@ async function renderPricingTab() {
|
|||||||
<td class="px-2 py-1.5 text-xs ${borderTop}">${r.lotCell}</td>
|
<td class="px-2 py-1.5 text-xs ${borderTop}">${r.lotCell}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop}">${r.qty}</td>
|
<td class="px-2 py-1.5 text-right text-xs ${borderTop}">${r.qty}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.estWorld ? WORLD_CLS : ''}">${r.estUnit > 0 ? formatCurrency(r.estUnit) : '—'}</td>
|
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.estWorld ? WORLD_CLS : ''}">${r.estUnit > 0 ? formatCurrency(r.estUnit) : '—'}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${r.warehouseUnit != null ? formatCurrency(r.warehouseUnit) : '—'}</td>
|
<td class="px-2 py-1.5 text-right text-xs stock-price-col ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${r.warehouseUnit != null ? formatCurrency(r.warehouseUnit) : '—'}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.compWorld ? WORLD_CLS : ''}">${r.competitorUnit != null ? formatCurrency(r.competitorUnit) : '—'}</td>
|
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.compWorld ? WORLD_CLS : ''}">${r.competitorUnit != null ? formatCurrency(r.competitorUnit) : '—'}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs pricing-vendor-price-buy ${borderTop} ${r.vendorOrigUnit == null ? 'text-gray-400' : ''}">${r.vendorOrigUnit != null ? formatCurrency(r.vendorOrigUnit) : '—'}</td>
|
<td class="px-2 py-1.5 text-right text-xs pricing-vendor-price-buy ${borderTop} ${r.vendorOrigUnit == null ? 'text-gray-400' : ''}">${r.vendorOrigUnit != null ? formatCurrency(r.vendorOrigUnit) : '—'}</td>
|
||||||
`;
|
`;
|
||||||
@@ -4727,7 +4738,7 @@ async function renderPricingTab() {
|
|||||||
<td class="px-2 py-1.5 text-xs ${borderTop}">${r.lotCell}</td>
|
<td class="px-2 py-1.5 text-xs ${borderTop}">${r.lotCell}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop}">${r.qty}</td>
|
<td class="px-2 py-1.5 text-right text-xs ${borderTop}">${r.qty}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.estWorld ? WORLD_CLS : ''}">${saleEstUnit > 0 ? formatCurrency(saleEstUnit) : '—'}</td>
|
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.estWorld ? WORLD_CLS : ''}">${saleEstUnit > 0 ? formatCurrency(saleEstUnit) : '—'}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${saleWhUnit != null ? formatCurrency(saleWhUnit) : '—'}</td>
|
<td class="px-2 py-1.5 text-right text-xs stock-price-col ${borderTop} ${r.whWorld ? WORLD_CLS : ''}">${saleWhUnit != null ? formatCurrency(saleWhUnit) : '—'}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.compWorld ? WORLD_CLS : ''}">${saleCompUnit != null ? formatCurrency(saleCompUnit) : '—'}</td>
|
<td class="px-2 py-1.5 text-right text-xs ${borderTop} ${r.compWorld ? WORLD_CLS : ''}">${saleCompUnit != null ? formatCurrency(saleCompUnit) : '—'}</td>
|
||||||
<td class="px-2 py-1.5 text-right text-xs pricing-vendor-price-sale ${borderTop} text-gray-400">—</td>
|
<td class="px-2 py-1.5 text-right text-xs pricing-vendor-price-sale ${borderTop} text-gray-400">—</td>
|
||||||
`;
|
`;
|
||||||
@@ -4945,7 +4956,7 @@ async function exportPricingCSV(table) {
|
|||||||
include_lot: true,
|
include_lot: true,
|
||||||
include_bom: true,
|
include_bom: true,
|
||||||
include_estimate: true,
|
include_estimate: true,
|
||||||
include_stock: true,
|
include_stock: !!showStockPrices,
|
||||||
include_competitor: true,
|
include_competitor: true,
|
||||||
basis: basis,
|
basis: basis,
|
||||||
sale_markup: saleUplift > 0 ? saleUplift : null,
|
sale_markup: saleUplift > 0 ? saleUplift : null,
|
||||||
|
|||||||
@@ -321,6 +321,20 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="text-xs text-gray-500 mt-1">Добавляет вкладку «Аренда» (расчёт стоимости платного тестирования/аренды) на конфигурациях этого проекта.</div>
|
<div class="text-xs text-gray-500 mt-1">Добавляет вкладку «Аренда» (расчёт стоимости платного тестирования/аренды) на конфигурациях этого проекта.</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="project-settings-show-stock-prices" class="rounded border-gray-300" onchange="applyStockSettingsGating()">
|
||||||
|
<span class="text-sm font-medium text-gray-700">Складские цены</span>
|
||||||
|
</label>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">Показывает складские (Stock) цены во всех ценовых интерфейсах конфигураций этого проекта, включая экспорты CSV.</div>
|
||||||
|
</div>
|
||||||
|
<div id="project-settings-only-in-stock-wrap">
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="project-settings-only-in-stock" class="rounded border-gray-300">
|
||||||
|
<span class="text-sm font-medium text-gray-700">Только наличие</span>
|
||||||
|
</label>
|
||||||
|
<div class="text-xs text-gray-500 mt-1">При добавлении компонентов предлагает только позиции, доступные на складе. Активно только вместе со «Складские цены».</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end space-x-3 mt-6">
|
<div class="flex justify-end space-x-3 mt-6">
|
||||||
<button onclick="closeProjectSettingsModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">Отмена</button>
|
<button onclick="closeProjectSettingsModal()" class="px-4 py-2 text-gray-600 hover:text-gray-800">Отмена</button>
|
||||||
@@ -1261,10 +1275,24 @@ function openProjectSettingsModal() {
|
|||||||
document.getElementById('project-settings-name').value = project.name || '';
|
document.getElementById('project-settings-name').value = project.name || '';
|
||||||
document.getElementById('project-settings-tracker-url').value = (project.tracker_url || '').trim();
|
document.getElementById('project-settings-tracker-url').value = (project.tracker_url || '').trim();
|
||||||
document.getElementById('project-settings-rental-enabled').checked = !!project.rental_enabled;
|
document.getElementById('project-settings-rental-enabled').checked = !!project.rental_enabled;
|
||||||
|
document.getElementById('project-settings-show-stock-prices').checked = !!project.show_stock_prices;
|
||||||
|
document.getElementById('project-settings-only-in-stock').checked = !!project.only_in_stock;
|
||||||
|
applyStockSettingsGating();
|
||||||
document.getElementById('project-settings-modal').classList.remove('hidden');
|
document.getElementById('project-settings-modal').classList.remove('hidden');
|
||||||
document.getElementById('project-settings-modal').classList.add('flex');
|
document.getElementById('project-settings-modal').classList.add('flex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyStockSettingsGating() {
|
||||||
|
const showStockPrices = document.getElementById('project-settings-show-stock-prices').checked;
|
||||||
|
const onlyInStock = document.getElementById('project-settings-only-in-stock');
|
||||||
|
const wrap = document.getElementById('project-settings-only-in-stock-wrap');
|
||||||
|
onlyInStock.disabled = !showStockPrices;
|
||||||
|
if (!showStockPrices) {
|
||||||
|
onlyInStock.checked = false;
|
||||||
|
}
|
||||||
|
wrap.classList.toggle('opacity-50', !showStockPrices);
|
||||||
|
}
|
||||||
|
|
||||||
function closeProjectSettingsModal() {
|
function closeProjectSettingsModal() {
|
||||||
document.getElementById('project-settings-modal').classList.add('hidden');
|
document.getElementById('project-settings-modal').classList.add('hidden');
|
||||||
document.getElementById('project-settings-modal').classList.remove('flex');
|
document.getElementById('project-settings-modal').classList.remove('flex');
|
||||||
@@ -1277,6 +1305,8 @@ async function saveProjectSettings() {
|
|||||||
const name = document.getElementById('project-settings-name').value.trim();
|
const name = document.getElementById('project-settings-name').value.trim();
|
||||||
const trackerURL = document.getElementById('project-settings-tracker-url').value.trim();
|
const trackerURL = document.getElementById('project-settings-tracker-url').value.trim();
|
||||||
const rentalEnabled = document.getElementById('project-settings-rental-enabled').checked;
|
const rentalEnabled = document.getElementById('project-settings-rental-enabled').checked;
|
||||||
|
const showStockPrices = document.getElementById('project-settings-show-stock-prices').checked;
|
||||||
|
const onlyInStock = document.getElementById('project-settings-only-in-stock').checked;
|
||||||
if (!code) {
|
if (!code) {
|
||||||
alert('Введите код проекта');
|
alert('Введите код проекта');
|
||||||
return;
|
return;
|
||||||
@@ -1284,7 +1314,7 @@ async function saveProjectSettings() {
|
|||||||
const resp = await fetch('/api/projects/' + projectUUID, {
|
const resp = await fetch('/api/projects/' + projectUUID, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({code: code, variant: variant, name: name, tracker_url: trackerURL, rental_enabled: rentalEnabled})
|
body: JSON.stringify({code: code, variant: variant, name: name, tracker_url: trackerURL, rental_enabled: rentalEnabled, show_stock_prices: showStockPrices, only_in_stock: onlyInStock})
|
||||||
});
|
});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
if (resp.status === 409) {
|
if (resp.status === 409) {
|
||||||
@@ -1521,6 +1551,12 @@ function openExportModal() {
|
|||||||
const modal = document.getElementById('project-export-modal');
|
const modal = document.getElementById('project-export-modal');
|
||||||
if (!modal) return;
|
if (!modal) return;
|
||||||
setProjectExportStatus('', '');
|
setProjectExportStatus('', '');
|
||||||
|
const stockCheckbox = document.getElementById('export-col-stock');
|
||||||
|
const showStockPrices = !!(project && project.show_stock_prices);
|
||||||
|
stockCheckbox.disabled = !showStockPrices;
|
||||||
|
if (!showStockPrices) {
|
||||||
|
stockCheckbox.checked = false;
|
||||||
|
}
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
modal.classList.add('flex');
|
modal.classList.add('flex');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user