diff --git a/internal/handlers/pricelist.go b/internal/handlers/pricelist.go
index 6807006..cbb7e03 100644
--- a/internal/handlers/pricelist.go
+++ b/internal/handlers/pricelist.go
@@ -181,15 +181,39 @@ func (h *PricelistHandler) GetItems(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
+ lotNames := make([]string, len(items))
+ for i, item := range items {
+ lotNames[i] = item.LotName
+ }
+ type compRow struct {
+ LotName string
+ LotDescription string
+ }
+ var comps []compRow
+ if len(lotNames) > 0 {
+ h.localDB.DB().Table("local_components").
+ Select("lot_name, lot_description").
+ Where("lot_name IN ?", lotNames).
+ Scan(&comps)
+ }
+ descMap := make(map[string]string, len(comps))
+ for _, c := range comps {
+ descMap[c.LotName] = c.LotDescription
+ }
+
resultItems := make([]gin.H, 0, len(items))
for _, item := range items {
resultItems = append(resultItems, gin.H{
- "id": item.ID,
- "lot_name": item.LotName,
- "price": item.Price,
- "category": item.LotCategory,
- "available_qty": item.AvailableQty,
- "partnumbers": []string(item.Partnumbers),
+ "id": item.ID,
+ "lot_name": item.LotName,
+ "lot_description": descMap[item.LotName],
+ "price": item.Price,
+ "category": item.LotCategory,
+ "available_qty": item.AvailableQty,
+ "partnumbers": []string(item.Partnumbers),
+ "partnumber_qtys": map[string]interface{}{},
+ "competitor_names": []string{},
+ "price_spread_pct": nil,
})
}
diff --git a/web/templates/pricelist_detail.html b/web/templates/pricelist_detail.html
index 169f793..564582a 100644
--- a/web/templates/pricelist_detail.html
+++ b/web/templates/pricelist_detail.html
@@ -60,8 +60,9 @@
Описание |
Доступно |
Partnumbers |
+ Поставщик |
Цена, $ |
- Настройки |
+ Настройки |
@@ -150,18 +151,25 @@
}
}
+ function isStockSource() {
+ const src = (currentSource || '').toLowerCase();
+ return src === 'warehouse' || src === 'competitor';
+ }
+
function isWarehouseSource() {
return (currentSource || '').toLowerCase() === 'warehouse';
}
function itemsColspan() {
- return isWarehouseSource() ? 7 : 5;
+ return isStockSource() ? 6 : 5;
}
function toggleWarehouseColumns() {
- const visible = isWarehouseSource();
- document.getElementById('th-qty').classList.toggle('hidden', !visible);
- document.getElementById('th-partnumbers').classList.toggle('hidden', !visible);
+ const stock = isStockSource();
+ document.getElementById('th-qty').classList.toggle('hidden', true);
+ document.getElementById('th-partnumbers').classList.toggle('hidden', !stock);
+ document.getElementById('th-competitors').classList.toggle('hidden', !stock);
+ document.getElementById('th-settings').classList.toggle('hidden', stock);
}
function formatQty(qty) {
@@ -234,27 +242,69 @@
return;
}
- const showWarehouse = isWarehouseSource();
+ const stock = isStockSource();
+ const p = stock ? 'px-3 py-2' : 'px-6 py-3';
+ const descMax = stock ? 30 : 60;
+
const html = items.map(item => {
const price = item.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const description = item.lot_description || '-';
- const truncatedDesc = description.length > 60 ? description.substring(0, 60) + '...' : description;
- const qty = formatQty(item.available_qty);
- const partnumbers = Array.isArray(item.partnumbers) && item.partnumbers.length > 0 ? item.partnumbers.join(', ') : '—';
+ const truncatedDesc = description.length > descMax ? description.substring(0, descMax) + '...' : description;
+
+ // Partnumbers cell (stock sources only)
+ let pnHtml = '—';
+ if (stock) {
+ const qtys = item.partnumber_qtys || {};
+ const allPNs = Array.isArray(item.partnumbers) ? item.partnumbers : [];
+ const withQty = allPNs.filter(pn => qtys[pn] > 0);
+ const list = withQty.length > 0 ? withQty : allPNs;
+ if (list.length === 0) {
+ pnHtml = '—';
+ } else {
+ const shown = list.slice(0, 4);
+ const rest = list.length - shown.length;
+ const formatPN = pn => {
+ const q = qtys[pn];
+ const qStr = (q > 0) ? ` (${formatQty(q)} шт.)` : '';
+ return `${escapeHtml(pn)}${qStr}
`;
+ };
+ pnHtml = shown.map(formatPN).join('');
+ if (rest > 0) pnHtml += `+${rest} ещё
`;
+ }
+ }
+
+ // Supplier cell (stock sources only)
+ let supplierHtml = '';
+ if (stock) {
+ if (isWarehouseSource()) {
+ supplierHtml = `склад`;
+ } else {
+ const names = Array.isArray(item.competitor_names) && item.competitor_names.length > 0
+ ? item.competitor_names
+ : ['конкурент'];
+ supplierHtml = names.map(n => `${escapeHtml(n)}`).join(' ');
+ }
+ }
+
+ // Price cell — add spread badge for competitor
+ let priceHtml = price;
+ if (!isWarehouseSource() && item.price_spread_pct > 0) {
+ priceHtml += ` ±${item.price_spread_pct.toFixed(0)}%`;
+ }
return `
- |
- ${item.lot_name}
+ |
+ ${escapeHtml(item.lot_name)}
|
-
- ${item.category || '-'}
+ |
+ ${escapeHtml(item.category || '-')}
|
- ${truncatedDesc} |
- ${showWarehouse ? `${qty} | ` : ''}
- ${showWarehouse ? `${escapeHtml(partnumbers)} | ` : ''}
- ${price} |
- ${formatPriceSettings(item)} |
+ ${escapeHtml(truncatedDesc)} |
+ ${stock ? `${pnHtml} | ` : ''}
+ ${stock ? `${supplierHtml} | ` : ''}
+ ${priceHtml} |
+ ${!stock ? `${formatPriceSettings(item)} | ` : ''}
`;
}).join('');