Fix competitor price display and pricelist item deduplication

- Render competitor prices in Pricing tab (all three row branches)
- Add footer total accumulation for competitor column
- Deduplicate local_pricelist_items via migration + unique index
- Use ON CONFLICT DO NOTHING in SaveLocalPricelistItems to prevent duplicates on concurrent sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-03-13 10:33:04 +03:00
parent 06397a6bd1
commit e2da8b4253
3 changed files with 57 additions and 9 deletions

View File

@@ -225,7 +225,7 @@
<td class="px-3 py-2 text-right" id="pricing-total-estimate"></td>
<td class="px-3 py-2 text-right font-bold" id="pricing-total-vendor"></td>
<td class="px-3 py-2 text-right" id="pricing-total-warehouse"></td>
<td class="px-3 py-2 text-right"></td>
<td class="px-3 py-2 text-right" id="pricing-total-competitor"></td>
</tr>
</tfoot>
</table>
@@ -3546,8 +3546,8 @@ async function renderPricingTab() {
} catch(e) { /* silent — pricing tab renders with available data */ }
}
let totalVendor = 0, totalEstimate = 0, totalWarehouse = 0;
let hasVendor = false, hasEstimate = false, hasWarehouse = false;
let totalVendor = 0, totalEstimate = 0, totalWarehouse = 0, totalCompetitor = 0;
let hasVendor = false, hasEstimate = false, hasWarehouse = false, hasCompetitor = false;
tbody.innerHTML = '';
@@ -3563,10 +3563,13 @@ async function renderPricingTab() {
const pl = priceMap[item.lot_name];
const estUnit = (pl && pl.estimate_price > 0) ? pl.estimate_price : (item.unit_price || 0);
const warehouseUnit = (pl && pl.warehouse_price > 0) ? pl.warehouse_price : null;
const competitorUnit = (pl && pl.competitor_price > 0) ? pl.competitor_price : null;
const estimateTotal = estUnit * item.quantity;
const warehouseTotal = warehouseUnit != null ? warehouseUnit * item.quantity : null;
const competitorTotal = competitorUnit != null ? competitorUnit * item.quantity : null;
if (estimateTotal > 0) { totalEstimate += estimateTotal; hasEstimate = true; }
if (warehouseTotal != null && warehouseTotal > 0) { totalWarehouse += warehouseTotal; hasWarehouse = true; }
if (competitorTotal != null && competitorTotal > 0) { totalCompetitor += competitorTotal; hasCompetitor = true; }
tr.dataset.est = estimateTotal;
const desc = (compMap[item.lot_name] || {}).description || '';
tr.dataset.vendorOrig = '';
@@ -3578,7 +3581,7 @@ async function renderPricingTab() {
<td class="px-3 py-1.5 text-right text-xs">${estimateTotal > 0 ? formatCurrency(estimateTotal) : '—'}</td>
<td class="px-3 py-1.5 text-right text-xs text-gray-400 pricing-vendor-price">—</td>
<td class="px-3 py-1.5 text-right text-xs">${warehouseTotal != null && warehouseTotal > 0 ? formatCurrency(warehouseTotal) : '—'}</td>
<td class="px-3 py-1.5 text-right text-xs text-gray-400">—</td>
<td class="px-3 py-1.5 text-right text-xs">${competitorTotal != null && competitorTotal > 0 ? formatCurrency(competitorTotal) : '—'}</td>
`;
tbody.appendChild(tr);
});
@@ -3598,10 +3601,13 @@ async function renderPricingTab() {
let hasEstimateForRow = false;
let rowWarehouse = 0;
let hasWarehouseForRow = false;
let rowCompetitor = 0;
let hasCompetitorForRow = false;
if (baseLot) {
const pl = priceMap[baseLot];
const estimateUnit = (pl && pl.estimate_price > 0) ? pl.estimate_price : null;
const warehouseUnit = (pl && pl.warehouse_price > 0) ? pl.warehouse_price : null;
const competitorUnit = (pl && pl.competitor_price > 0) ? pl.competitor_price : null;
if (estimateUnit != null) {
rowEst += estimateUnit * row.quantity * _getRowLotQtyPerPN(row);
hasEstimateForRow = true;
@@ -3610,11 +3616,16 @@ async function renderPricingTab() {
rowWarehouse += warehouseUnit * row.quantity * _getRowLotQtyPerPN(row);
hasWarehouseForRow = true;
}
if (competitorUnit != null) {
rowCompetitor += competitorUnit * row.quantity * _getRowLotQtyPerPN(row);
hasCompetitorForRow = true;
}
}
allocs.forEach(a => {
const pl = priceMap[a.lot_name];
const estimateUnit = (pl && pl.estimate_price > 0) ? pl.estimate_price : null;
const warehouseUnit = (pl && pl.warehouse_price > 0) ? pl.warehouse_price : null;
const competitorUnit = (pl && pl.competitor_price > 0) ? pl.competitor_price : null;
if (estimateUnit != null) {
rowEst += estimateUnit * row.quantity * a.quantity;
hasEstimateForRow = true;
@@ -3623,12 +3634,17 @@ async function renderPricingTab() {
rowWarehouse += warehouseUnit * row.quantity * a.quantity;
hasWarehouseForRow = true;
}
if (competitorUnit != null) {
rowCompetitor += competitorUnit * row.quantity * a.quantity;
hasCompetitorForRow = true;
}
});
const vendorTotal = row.total_price != null ? row.total_price : (row.unit_price != null ? row.unit_price * row.quantity : null);
if (vendorTotal != null) { totalVendor += vendorTotal; hasVendor = true; }
if (hasEstimateForRow) { totalEstimate += rowEst; hasEstimate = true; }
if (hasWarehouseForRow) { totalWarehouse += rowWarehouse; hasWarehouse = true; }
if (hasCompetitorForRow) { totalCompetitor += rowCompetitor; hasCompetitor = true; }
tr.dataset.est = rowEst;
tr.dataset.vendorOrig = vendorTotal != null ? vendorTotal : '';
@@ -3649,7 +3665,7 @@ async function renderPricingTab() {
<td class="px-3 py-1.5 text-right text-xs">${hasEstimateForRow ? formatCurrency(rowEst) : '—'}</td>
<td class="px-3 py-1.5 text-right text-xs pricing-vendor-price">${vendorTotal != null ? formatCurrency(vendorTotal) : '—'}</td>
<td class="px-3 py-1.5 text-right text-xs">${hasWarehouseForRow ? formatCurrency(rowWarehouse) : '—'}</td>
<td class="px-3 py-1.5 text-right text-xs text-gray-400">—</td>
<td class="px-3 py-1.5 text-right text-xs">${hasCompetitorForRow ? formatCurrency(rowCompetitor) : '—'}</td>
`;
tbody.appendChild(tr);
});
@@ -3663,10 +3679,13 @@ async function renderPricingTab() {
const pl = priceMap[item.lot_name];
const estUnit = (pl && pl.estimate_price > 0) ? pl.estimate_price : (item.unit_price || 0);
const warehouseUnit = (pl && pl.warehouse_price > 0) ? pl.warehouse_price : null;
const competitorUnit = (pl && pl.competitor_price > 0) ? pl.competitor_price : null;
const estimateTotal = estUnit * item.quantity;
const warehouseTotal = warehouseUnit != null ? warehouseUnit * item.quantity : null;
const competitorTotal = competitorUnit != null ? competitorUnit * item.quantity : null;
if (estimateTotal > 0) { totalEstimate += estimateTotal; hasEstimate = true; }
if (warehouseTotal != null && warehouseTotal > 0) { totalWarehouse += warehouseTotal; hasWarehouse = true; }
if (competitorTotal != null && competitorTotal > 0) { totalCompetitor += competitorTotal; hasCompetitor = true; }
tr.dataset.est = estimateTotal;
tr.dataset.vendorOrig = '';
const desc = (compMap[item.lot_name] || {}).description || '';
@@ -3678,7 +3697,7 @@ async function renderPricingTab() {
<td class="px-3 py-1.5 text-right text-xs">${estimateTotal > 0 ? formatCurrency(estimateTotal) : '—'}</td>
<td class="px-3 py-1.5 text-right text-xs text-gray-400 pricing-vendor-price">—</td>
<td class="px-3 py-1.5 text-right text-xs">${warehouseTotal != null && warehouseTotal > 0 ? formatCurrency(warehouseTotal) : '—'}</td>
<td class="px-3 py-1.5 text-right text-xs text-gray-400">—</td>
<td class="px-3 py-1.5 text-right text-xs">${competitorTotal != null && competitorTotal > 0 ? formatCurrency(competitorTotal) : '—'}</td>
`;
tbody.appendChild(tr);
});
@@ -3688,6 +3707,7 @@ async function renderPricingTab() {
document.getElementById('pricing-total-vendor').textContent = hasVendor ? formatCurrency(totalVendor) : '—';
document.getElementById('pricing-total-estimate').textContent = hasEstimate ? formatCurrency(totalEstimate) : '—';
document.getElementById('pricing-total-warehouse').textContent = hasWarehouse ? formatCurrency(totalWarehouse) : '—';
document.getElementById('pricing-total-competitor').textContent = hasCompetitor ? formatCurrency(totalCompetitor) : '—';
tfoot.classList.remove('hidden');
// Update custom price proportional breakdown