- LOT
PN вендора
Описание
+ LOT
Кол-во
Estimate
Склад
@@ -289,7 +289,7 @@
- Своя цена:
+ Ручная цена:
@@ -3609,6 +3609,7 @@ async function renderPricingTab() {
});
// ─── Build shared row data (unit prices for display, totals for math) ────
+ // Each BOM row is exploded into per-LOT sub-rows; grouped by vendor PN via groupStart/groupSize.
const _buildRows = () => {
const result = [];
const coveredLots = new Set();
@@ -3618,7 +3619,8 @@ async function renderPricingTab() {
const u = _getUnitPrices(pl);
const estUnit = u.estUnit > 0 ? u.estUnit : (item.unit_price || 0);
result.push({
- lotCell: escapeHtml(item.lot_name), vendorPN: null,
+ lotCell: escapeHtml(item.lot_name), lotText: item.lot_name,
+ vendorPN: null,
desc: (compMap[item.lot_name] || {}).description || '',
qty: item.quantity,
estUnit, warehouseUnit: u.warehouseUnit, competitorUnit: u.competitorUnit,
@@ -3626,6 +3628,7 @@ async function renderPricingTab() {
warehouse: u.warehouseUnit != null ? u.warehouseUnit * item.quantity : null,
competitor: u.competitorUnit != null ? u.competitorUnit * item.quantity : null,
vendorOrig: null, vendorOrigUnit: null, isEstOnly,
+ groupStart: true, groupSize: 1,
});
};
@@ -3640,42 +3643,66 @@ async function renderPricingTab() {
if (baseLot) coveredLots.add(baseLot);
allocs.forEach(a => coveredLots.add(a.lot_name));
- // Accumulate unit prices per 1 vendor PN (base + allocs)
- let rowEstUnit = 0, rowWhUnit = 0, rowCompUnit = 0;
- let hasEst = false, hasWh = false, hasComp = false;
- if (baseLot) {
- const u = _getUnitPrices(priceMap[baseLot]);
- const lotQty = _getRowLotQtyPerPN(row);
- if (u.estUnit > 0) { rowEstUnit += u.estUnit * lotQty; hasEst = true; }
- if (u.warehouseUnit != null) { rowWhUnit += u.warehouseUnit * lotQty; hasWh = true; }
- if (u.competitorUnit != null) { rowCompUnit += u.competitorUnit * lotQty; hasComp = true; }
- }
- allocs.forEach(a => {
- const u = _getUnitPrices(priceMap[a.lot_name]);
- if (u.estUnit > 0) { rowEstUnit += u.estUnit * a.quantity; hasEst = true; }
- if (u.warehouseUnit != null) { rowWhUnit += u.warehouseUnit * a.quantity; hasWh = true; }
- if (u.competitorUnit != null) { rowCompUnit += u.competitorUnit * a.quantity; hasComp = true; }
- });
-
- let lotCell = 'н/д ';
- if (baseLot && allocs.length) lotCell = `${escapeHtml(baseLot)} +${allocs.length} `;
- else if (baseLot) lotCell = escapeHtml(baseLot);
- else if (allocs.length) lotCell = `${escapeHtml(allocs[0].lot_name)}${allocs.length > 1 ? ` +${allocs.length - 1} ` : ''}`;
-
const vendorOrigUnit = row.unit_price != null ? row.unit_price
: (row.total_price != null && row.quantity > 0 ? row.total_price / row.quantity : null);
const vendorOrig = row.total_price != null ? row.total_price
: (row.unit_price != null ? row.unit_price * row.quantity : null);
const desc = row.description || (baseLot ? ((compMap[baseLot] || {}).description || '') : '');
- result.push({
- lotCell, vendorPN: row.vendor_pn, desc, qty: row.quantity,
- estUnit: hasEst ? rowEstUnit : 0,
- warehouseUnit: hasWh ? rowWhUnit : null,
- competitorUnit: hasComp ? rowCompUnit : null,
- est: hasEst ? rowEstUnit * row.quantity : 0,
- warehouse: hasWh ? rowWhUnit * row.quantity : null,
- competitor: hasComp ? rowCompUnit * row.quantity : null,
- vendorOrig, vendorOrigUnit, isEstOnly: false,
+
+ // Build per-LOT sub-rows
+ const subRows = [];
+ if (baseLot) {
+ const u = _getUnitPrices(priceMap[baseLot]);
+ const lotQty = _getRowLotQtyPerPN(row);
+ const qty = row.quantity * lotQty;
+ subRows.push({
+ lotCell: escapeHtml(baseLot), lotText: baseLot, qty,
+ estUnit: u.estUnit > 0 ? u.estUnit : 0,
+ warehouseUnit: u.warehouseUnit, competitorUnit: u.competitorUnit,
+ est: u.estUnit > 0 ? u.estUnit * qty : 0,
+ warehouse: u.warehouseUnit != null ? u.warehouseUnit * qty : null,
+ competitor: u.competitorUnit != null ? u.competitorUnit * qty : null,
+ });
+ }
+ allocs.forEach(a => {
+ const u = _getUnitPrices(priceMap[a.lot_name]);
+ const qty = row.quantity * a.quantity;
+ subRows.push({
+ lotCell: escapeHtml(a.lot_name), lotText: a.lot_name, qty,
+ estUnit: u.estUnit > 0 ? u.estUnit : 0,
+ warehouseUnit: u.warehouseUnit, competitorUnit: u.competitorUnit,
+ est: u.estUnit > 0 ? u.estUnit * qty : 0,
+ warehouse: u.warehouseUnit != null ? u.warehouseUnit * qty : null,
+ competitor: u.competitorUnit != null ? u.competitorUnit * qty : null,
+ });
+ });
+
+ if (!subRows.length) {
+ result.push({
+ lotCell: 'н/д ', lotText: '',
+ vendorPN: row.vendor_pn, desc, qty: row.quantity,
+ estUnit: 0, warehouseUnit: null, competitorUnit: null,
+ est: 0, warehouse: null, competitor: null,
+ vendorOrig, vendorOrigUnit, isEstOnly: false,
+ groupStart: true, groupSize: 1,
+ });
+ return;
+ }
+
+ const groupSize = subRows.length;
+ subRows.forEach((sub, idx) => {
+ result.push({
+ lotCell: sub.lotCell, lotText: sub.lotText,
+ vendorPN: row.vendor_pn, desc,
+ qty: sub.qty,
+ estUnit: sub.estUnit, warehouseUnit: sub.warehouseUnit, competitorUnit: sub.competitorUnit,
+ est: sub.est, warehouse: sub.warehouse, competitor: sub.competitor,
+ vendorOrig: idx === 0 ? vendorOrig : null,
+ vendorOrigUnit: idx === 0 ? vendorOrigUnit : null,
+ isEstOnly: false,
+ groupStart: idx === 0,
+ groupSize: idx === 0 ? groupSize : 0,
+ });
});
});
@@ -3708,19 +3735,28 @@ async function renderPricingTab() {
tr.dataset.qty = r.qty;
tr.dataset.vendorOrig = r.vendorOrig != null ? r.vendorOrig : '';
tr.dataset.vendorOrigUnit = r.vendorOrigUnit != null ? r.vendorOrigUnit : '';
+ tr.dataset.groupStart = r.groupStart ? 'true' : 'false';
+ tr.dataset.vendorPn = r.vendorPN || '';
+ tr.dataset.desc = r.desc;
+ tr.dataset.lot = r.lotText;
if (r.est > 0) { totEst += r.est; hasEst = true; }
if (r.warehouse != null) { totWh += r.warehouse; hasWh = true; cntWh++; }
if (r.competitor != null) { totComp += r.competitor; hasComp = true; cntComp++; }
if (r.vendorOrig != null) { totVendor += r.vendorOrig; hasVendor = true; }
+ const borderTop = r.groupStart ? 'border-t border-gray-200' : '';
+ const pnDescHtml = r.groupStart ? (() => {
+ const rs = r.groupSize > 1 ? ` rowspan="${r.groupSize}"` : '';
+ return `${r.vendorPN != null ? escapeHtml(r.vendorPN) : '—'}
+ ${escapeHtml(r.desc)} `;
+ })() : '';
tr.innerHTML = `
- ${r.lotCell}
- ${r.vendorPN != null ? escapeHtml(r.vendorPN) : '—'}
- ${escapeHtml(r.desc)}
- ${r.qty}
- ${r.estUnit > 0 ? formatCurrency(r.estUnit) : '—'}
- ${r.warehouseUnit != null ? formatCurrency(r.warehouseUnit) : '—'}
- ${r.competitorUnit != null ? formatCurrency(r.competitorUnit) : '—'}
- ${r.vendorOrigUnit != null ? formatCurrency(r.vendorOrigUnit) : '—'}
+ ${pnDescHtml}
+ ${r.lotCell}
+ ${r.qty}
+ ${r.estUnit > 0 ? formatCurrency(r.estUnit) : '—'}
+ ${r.warehouseUnit != null ? formatCurrency(r.warehouseUnit) : '—'}
+ ${r.competitorUnit != null ? formatCurrency(r.competitorUnit) : '—'}
+ ${r.vendorOrigUnit != null ? formatCurrency(r.vendorOrigUnit) : '—'}
`;
tbodyBuy.appendChild(tr);
});
@@ -3752,18 +3788,27 @@ async function renderPricingTab() {
const saleCompTotal = saleCompUnit != null ? saleCompUnit * r.qty : null;
tr.dataset.estSale = saleEstTotal;
tr.dataset.qty = r.qty;
+ tr.dataset.groupStart = r.groupStart ? 'true' : 'false';
+ tr.dataset.vendorPn = r.vendorPN || '';
+ tr.dataset.desc = r.desc;
+ tr.dataset.lot = r.lotText;
if (saleEstTotal > 0) { totEst += saleEstTotal; hasEst = true; }
if (saleWhTotal != null) { totWh += saleWhTotal; hasWh = true; cntWh++; }
if (saleCompTotal != null) { totComp += saleCompTotal; hasComp = true; cntComp++; }
+ const borderTop = r.groupStart ? 'border-t border-gray-200' : '';
+ const pnDescHtml = r.groupStart ? (() => {
+ const rs = r.groupSize > 1 ? ` rowspan="${r.groupSize}"` : '';
+ return `${r.vendorPN != null ? escapeHtml(r.vendorPN) : '—'}
+ ${escapeHtml(r.desc)} `;
+ })() : '';
tr.innerHTML = `
- ${r.lotCell}
- ${r.vendorPN != null ? escapeHtml(r.vendorPN) : '—'}
- ${escapeHtml(r.desc)}
- ${r.qty}
- ${saleEstUnit > 0 ? formatCurrency(saleEstUnit) : '—'}
- ${saleWhUnit != null ? formatCurrency(saleWhUnit) : '—'}
- ${saleCompUnit != null ? formatCurrency(saleCompUnit) : '—'}
- —
+ ${pnDescHtml}
+ ${r.lotCell}
+ ${r.qty}
+ ${saleEstUnit > 0 ? formatCurrency(saleEstUnit) : '—'}
+ ${saleWhUnit != null ? formatCurrency(saleWhUnit) : '—'}
+ ${saleCompUnit != null ? formatCurrency(saleCompUnit) : '—'}
+ —
`;
tbodySale.appendChild(tr);
});
@@ -3961,12 +4006,25 @@ function exportPricingCSV(table) {
return /[;"\n\r]/.test(s) ? `"${s}"` : s;
};
- const headers = ['Lot', 'PN вендора', 'Описание', 'Кол-во', 'Estimate', 'Склад', 'Конкуренты', 'Ручная цена'];
+ const headers = ['PN вендора', 'Описание', 'LOT', 'Кол-во', 'Estimate', 'Склад', 'Конкуренты', 'Ручная цена'];
const lines = [headers.map(csvEscape).join(csvDelimiter)];
rows.forEach(tr => {
+ // PN вендора, Описание, LOT are stored in dataset to handle rowspan correctly
+ const pn = cleanExportCell(tr.dataset.vendorPn || '');
+ const desc = cleanExportCell(tr.dataset.desc || '');
+ const lot = cleanExportCell(tr.dataset.lot || '');
+ // Qty..Ручная цена: cells at offset 2 for group-start rows, offset 0 for sub-rows
+ const isGroupStart = tr.dataset.groupStart === 'true';
const cells = tr.querySelectorAll('td');
- const cols = [0,1,2,3,4,5,6,7].map(i => cells[i] ? cleanExportCell(cells[i].textContent) : '');
+ const o = isGroupStart ? 2 : 0;
+ const cols = [pn, desc, lot,
+ cleanExportCell(cells[o]?.textContent),
+ cleanExportCell(cells[o+1]?.textContent),
+ cleanExportCell(cells[o+2]?.textContent),
+ cleanExportCell(cells[o+3]?.textContent),
+ cleanExportCell(cells[o+4]?.textContent),
+ ];
lines.push(cols.map(csvEscape).join(csvDelimiter));
});