diff --git a/cmd/qfs/main.go b/cmd/qfs/main.go
index dccec4c..8d3a490 100644
--- a/cmd/qfs/main.go
+++ b/cmd/qfs/main.go
@@ -1126,7 +1126,12 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
configs.POST("/:uuid/refresh-prices", func(c *gin.Context) {
uuid := c.Param("uuid")
- config, err := configService.RefreshPricesNoAuth(uuid)
+ var req struct {
+ PricelistID *uint `json:"pricelist_id"`
+ }
+ // Ignore bind error — pricelist_id is optional
+ _ = c.ShouldBindJSON(&req)
+ config, err := configService.RefreshPricesNoAuth(uuid, req.PricelistID)
if err != nil {
respondError(c, http.StatusInternalServerError, "internal server error", err)
return
diff --git a/internal/services/local_configuration.go b/internal/services/local_configuration.go
index 6e86d5a..e1bf6b3 100644
--- a/internal/services/local_configuration.go
+++ b/internal/services/local_configuration.go
@@ -399,17 +399,29 @@ func (s *LocalConfigurationService) RefreshPrices(uuid string, ownerUsername str
return nil, ErrConfigForbidden
}
- // Refresh local pricelists when online and use latest active/local pricelist for recalculation.
+ // Refresh local pricelists when online.
if s.isOnline() {
_ = s.syncService.SyncPricelistsIfNeeded()
}
- latestPricelist, latestErr := s.localDB.GetLatestLocalPricelist()
+
+ // Use the pricelist stored in the config; fall back to latest if unavailable.
+ var pricelist *localdb.LocalPricelist
+ if localCfg.PricelistID != nil && *localCfg.PricelistID > 0 {
+ if pl, err := s.localDB.GetLocalPricelistByServerID(*localCfg.PricelistID); err == nil {
+ pricelist = pl
+ }
+ }
+ if pricelist == nil {
+ if pl, err := s.localDB.GetLatestLocalPricelist(); err == nil {
+ pricelist = pl
+ }
+ }
// Update prices for all items from pricelist
updatedItems := make(localdb.LocalConfigItems, len(localCfg.Items))
for i, item := range localCfg.Items {
- if latestErr == nil && latestPricelist != nil {
- price, err := s.localDB.GetLocalPriceForLot(latestPricelist.ID, item.LotName)
+ if pricelist != nil {
+ price, err := s.localDB.GetLocalPriceForLot(pricelist.ID, item.LotName)
if err == nil && price > 0 {
updatedItems[i] = localdb.LocalConfigItem{
LotName: item.LotName,
@@ -434,8 +446,8 @@ func (s *LocalConfigurationService) RefreshPrices(uuid string, ownerUsername str
}
localCfg.TotalPrice = &total
- if latestErr == nil && latestPricelist != nil {
- localCfg.PricelistID = &latestPricelist.ServerID
+ if pricelist != nil {
+ localCfg.PricelistID = &pricelist.ServerID
}
// Set price update timestamp and mark for sync
@@ -762,8 +774,10 @@ func (s *LocalConfigurationService) ListTemplates(page, perPage int) ([]models.C
return templates[start:end], total, nil
}
-// RefreshPricesNoAuth updates all component prices in the configuration without ownership check
-func (s *LocalConfigurationService) RefreshPricesNoAuth(uuid string) (*models.Configuration, error) {
+// RefreshPricesNoAuth updates all component prices in the configuration without ownership check.
+// pricelistServerID optionally specifies which pricelist to use; if nil, the config's stored
+// pricelist is used; if that is also absent, the latest local pricelist is used as a fallback.
+func (s *LocalConfigurationService) RefreshPricesNoAuth(uuid string, pricelistServerID *uint) (*models.Configuration, error) {
// Get configuration from local SQLite
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
if err != nil {
@@ -773,13 +787,36 @@ func (s *LocalConfigurationService) RefreshPricesNoAuth(uuid string) (*models.Co
if s.isOnline() {
_ = s.syncService.SyncPricelistsIfNeeded()
}
- latestPricelist, latestErr := s.localDB.GetLatestLocalPricelist()
+
+ // Resolve which pricelist to use:
+ // 1. Explicitly requested pricelist (from UI selection)
+ // 2. Pricelist stored in the configuration
+ // 3. Latest local pricelist as last-resort fallback
+ var targetServerID *uint
+ if pricelistServerID != nil && *pricelistServerID > 0 {
+ targetServerID = pricelistServerID
+ } else if localCfg.PricelistID != nil && *localCfg.PricelistID > 0 {
+ targetServerID = localCfg.PricelistID
+ }
+
+ var pricelist *localdb.LocalPricelist
+ if targetServerID != nil {
+ if pl, err := s.localDB.GetLocalPricelistByServerID(*targetServerID); err == nil {
+ pricelist = pl
+ }
+ }
+ if pricelist == nil {
+ // Fallback: use latest local pricelist
+ if pl, err := s.localDB.GetLatestLocalPricelist(); err == nil {
+ pricelist = pl
+ }
+ }
// Update prices for all items from pricelist
updatedItems := make(localdb.LocalConfigItems, len(localCfg.Items))
for i, item := range localCfg.Items {
- if latestErr == nil && latestPricelist != nil {
- price, err := s.localDB.GetLocalPriceForLot(latestPricelist.ID, item.LotName)
+ if pricelist != nil {
+ price, err := s.localDB.GetLocalPriceForLot(pricelist.ID, item.LotName)
if err == nil && price > 0 {
updatedItems[i] = localdb.LocalConfigItem{
LotName: item.LotName,
@@ -804,8 +841,8 @@ func (s *LocalConfigurationService) RefreshPricesNoAuth(uuid string) (*models.Co
}
localCfg.TotalPrice = &total
- if latestErr == nil && latestPricelist != nil {
- localCfg.PricelistID = &latestPricelist.ServerID
+ if pricelist != nil {
+ localCfg.PricelistID = &pricelist.ServerID
}
// Set price update timestamp and mark for sync
diff --git a/web/templates/index.html b/web/templates/index.html
index fc58cb1..7a9c025 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -925,14 +925,9 @@ async function loadActivePricelists(force = false) {
const resp = await fetch(`/api/pricelists?active_only=true&source=${source}&per_page=200`);
const data = await resp.json();
activePricelistsBySource[source] = data.pricelists || [];
- const existing = selectedPricelistIds[source];
- if (existing && activePricelistsBySource[source].some(pl => Number(pl.id) === Number(existing))) {
- return;
- }
- selectedPricelistIds[source] = null;
+ // Do not reset the stored pricelist — it may be inactive but must be preserved
} catch (e) {
activePricelistsBySource[source] = [];
- selectedPricelistIds[source] = null;
}
}));
activePricelistsLoadedAt = Date.now();
@@ -954,11 +949,25 @@ function renderPricelistSelectOptions(selectId, source) {
select.value = '';
return;
}
- select.innerHTML = `` + pricelists.map(pl => {
+ select.innerHTML = pricelists.map(pl => {
return ``;
}).join('');
const current = selectedPricelistIds[source];
- select.value = current ? String(current) : '';
+ if (current) {
+ select.value = String(current);
+ // Stored pricelist may be inactive — add it as a virtual option if not found
+ if (!select.value) {
+ const opt = document.createElement('option');
+ opt.value = String(current);
+ opt.textContent = `ID ${current} (неактивный)`;
+ select.prepend(opt);
+ select.value = String(current);
+ }
+ } else if (pricelists.length > 0) {
+ // New config: pre-select the first (latest) pricelist
+ selectedPricelistIds[source] = Number(pricelists[0].id);
+ select.value = String(pricelists[0].id);
+ }
}
function syncPriceSettingsControls() {
@@ -984,9 +993,9 @@ function getPricelistVersionById(source, id) {
function renderPricelistSettingsSummary() {
const summary = document.getElementById('pricelist-settings-summary');
if (!summary) return;
- const estimate = selectedPricelistIds.estimate ? getPricelistVersionById('estimate', selectedPricelistIds.estimate) || `ID ${selectedPricelistIds.estimate}` : 'авто';
- const warehouse = selectedPricelistIds.warehouse ? getPricelistVersionById('warehouse', selectedPricelistIds.warehouse) || `ID ${selectedPricelistIds.warehouse}` : 'авто';
- const competitor = selectedPricelistIds.competitor ? getPricelistVersionById('competitor', selectedPricelistIds.competitor) || `ID ${selectedPricelistIds.competitor}` : 'авто';
+ const estimate = selectedPricelistIds.estimate ? getPricelistVersionById('estimate', selectedPricelistIds.estimate) || `ID ${selectedPricelistIds.estimate}` : '—';
+ const warehouse = selectedPricelistIds.warehouse ? getPricelistVersionById('warehouse', selectedPricelistIds.warehouse) || `ID ${selectedPricelistIds.warehouse}` : '—';
+ const competitor = selectedPricelistIds.competitor ? getPricelistVersionById('competitor', selectedPricelistIds.competitor) || `ID ${selectedPricelistIds.competitor}` : '—';
const refreshState = disablePriceRefresh ? ' | Обновление цен: выкл' : '';
const stockFilterState = onlyInStock ? ' | Только наличие: вкл' : '';
summary.textContent = `Estimate: ${estimate}, Склад: ${warehouse}, Конкуренты: ${competitor}${refreshState}${stockFilterState}`;
@@ -1062,16 +1071,16 @@ function applyPriceSettings() {
const inStockVal = Boolean(document.getElementById('settings-only-in-stock')?.checked);
const prevWarehouseID = currentWarehousePricelistID();
- selectedPricelistIds.estimate = Number.isFinite(estimateVal) && estimateVal > 0 ? estimateVal : null;
- selectedPricelistIds.warehouse = Number.isFinite(warehouseVal) && warehouseVal > 0 ? warehouseVal : null;
- selectedPricelistIds.competitor = Number.isFinite(competitorVal) && competitorVal > 0 ? competitorVal : null;
- if (selectedPricelistIds.estimate) {
+ if (Number.isFinite(estimateVal) && estimateVal > 0) {
+ selectedPricelistIds.estimate = estimateVal;
resolvedAutoPricelistIds.estimate = null;
}
- if (selectedPricelistIds.warehouse) {
+ if (Number.isFinite(warehouseVal) && warehouseVal > 0) {
+ selectedPricelistIds.warehouse = warehouseVal;
resolvedAutoPricelistIds.warehouse = null;
}
- if (selectedPricelistIds.competitor) {
+ if (Number.isFinite(competitorVal) && competitorVal > 0) {
+ selectedPricelistIds.competitor = competitorVal;
resolvedAutoPricelistIds.competitor = null;
}
disablePriceRefresh = disableVal;
@@ -2508,11 +2517,14 @@ async function refreshPrices() {
}
try {
+ const refreshPayload = {};
+ if (selectedPricelistIds.estimate) {
+ refreshPayload.pricelist_id = selectedPricelistIds.estimate;
+ }
const resp = await fetch('/api/configs/' + configUUID + '/refresh-prices', {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- }
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(refreshPayload)
});
if (!resp.ok) {