diff --git a/TODOS.md b/TODOS.md index 8022878..685aff7 100644 --- a/TODOS.md +++ b/TODOS.md @@ -27,10 +27,3 @@ **Effort:** S (human: ~2h / CC: ~10min) **Context:** TIER 2 matched county+city without street, reusing VFP-era addresses with wrong streets. After removal (2026-04-06), new imports create correct addresses. Old wrong addresses stay. Could identify them by: address has id_loc but no linked order rows, and was last modified before 2026-04-06. **Depends on:** TIER 2 removal deployed and verified. - -## P3: Extract match-column badge styles to CSS classes -**What:** Replace inline styles on Kit and Disc. badges (in shared.js) with CSS classes (e.g., `.match-badge-kit`, `.match-badge-disc`). -**Why:** Currently both badges use identical inline `style="background:var(--X-light);color:var(--X-text);font-size:10px;padding:2px 6px"`. If a 3rd badge type appears, inline styles become a maintenance burden. -**Effort:** XS (human: ~30min / CC: ~5min) -**Context:** Low priority. Two inline-styled badges is fine. Trigger: when a 3rd badge type is needed in the price match column. -**Depends on:** Quantity discount feature shipped. diff --git a/api/app/routers/sync.py b/api/app/routers/sync.py index 335ff72..b41513b 100644 --- a/api/app/routers/sync.py +++ b/api/app/routers/sync.py @@ -46,6 +46,9 @@ async def backfill_price_match(): from ..database import get_sqlite db = await get_sqlite() try: + # Reset all cached price_match to re-evaluate with current logic + await db.execute("UPDATE orders SET price_match = NULL WHERE price_match IS NOT NULL") + await db.commit() cursor = await db.execute(""" SELECT order_number FROM orders WHERE status IN ('IMPORTED', 'ALREADY_IMPORTED') @@ -465,9 +468,6 @@ async def order_detail(order_number: str): item["price_match"] = pi.get("match") if pi.get("kit"): item["kit"] = True - if pi.get("quantity_discount"): - item["quantity_discount"] = True - item["baseprice"] = pi.get("baseprice") order_price_check = price_data.get("summary", {}) # Cache price_match in SQLite if changed if order_price_check.get("oracle_available") is not False: diff --git a/api/app/services/validation_service.py b/api/app/services/validation_service.py index bca7f02..b70733d 100644 --- a/api/app/services/validation_service.py +++ b/api/app/services/validation_service.py @@ -714,13 +714,6 @@ def get_prices_for_order(items: list[dict], app_settings: dict, conn=None) -> di result_items[idx]["kit"] = True continue - # Quantity discount: baseprice > price means GoMag applied a volume discount - baseprice = float(item.get("baseprice") or 0) - if baseprice > 0 and baseprice > pret_gomag + 0.01: - result_items[idx]["quantity_discount"] = True - result_items[idx]["baseprice"] = baseprice - continue - pret_roa_total = 0.0 all_resolved = True @@ -766,7 +759,7 @@ def get_prices_for_order(items: list[dict], app_settings: dict, conn=None) -> di continue pret_roa = round(pret_roa_total, 4) - match = abs(pret_gomag - pret_roa) < 0.01 + match = pret_gomag <= pret_roa + 0.01 result_items[idx]["pret_roa"] = pret_roa result_items[idx]["match"] = match checked += 1 diff --git a/api/app/static/js/dashboard.js b/api/app/static/js/dashboard.js index 974f70b..773ec74 100644 --- a/api/app/static/js/dashboard.js +++ b/api/app/static/js/dashboard.js @@ -510,7 +510,7 @@ function diffDots(o, mobile) { if (o.address_mismatch===1) d += ``; if (o.price_match===false) - d += ``; + d += ``; return d; } diff --git a/api/app/static/js/shared.js b/api/app/static/js/shared.js index 99bc467..0a4829d 100644 --- a/api/app/static/js/shared.js +++ b/api/app/static/js/shared.js @@ -614,11 +614,9 @@ async function renderOrderDetailModal(orderNumber, opts) { const valoare = (Number(item.price || 0) * Number(item.quantity || 0)); const clickAttr = opts.onQuickMap ? `onclick="_sharedModalQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}',${idx})"` : ''; const priceInfo = { pret_roa: item.pret_roa, match: item.price_match }; - const priceMismatchHtml = item.quantity_discount - ? `
Disc. ${fmtNum(item.baseprice)} ${fmtNum(item.price)} lei
` - : (priceInfo.match === false - ? `
ROA: ${fmtNum(priceInfo.pret_roa)} lei
` - : ''); + const priceMismatchHtml = priceInfo.match === false + ? `
ROA: ${fmtNum(priceInfo.pret_roa)} lei
` + : ''; return `
${esc(item.sku)} @@ -690,10 +688,6 @@ async function renderOrderDetailModal(orderNumber, opts) { if (item.kit) { matchDot = 'Kit'; rowStyle = ''; - } else if (item.quantity_discount) { - const bpTitle = item.baseprice ? `Catalog: ${fmtNum(item.baseprice)} lei` : 'Discount GoMag'; - matchDot = `Disc.`; - rowStyle = ''; } else if (priceInfo.pret_roa == null && priceInfo.match == null) { matchDot = ''; rowStyle = ''; @@ -709,7 +703,7 @@ async function renderOrderDetailModal(orderNumber, opts) { ${esc(item.product_name || '-')} ${renderCodmatCell(item)} ${item.quantity || 0} - ${item.quantity_discount && item.baseprice ? `${fmtNum(item.baseprice)} ${fmtNum(item.price)}` : (item.price != null ? fmtNum(item.price) : '-')} + ${item.price != null ? fmtNum(item.price) : '-'} ${pretRoaHtml} ${item.vat != null ? Number(item.vat) : '-'} ${fmtNum(valoare)} diff --git a/api/app/templates/base.html b/api/app/templates/base.html index 0ac16f9..10d34bf 100644 --- a/api/app/templates/base.html +++ b/api/app/templates/base.html @@ -168,7 +168,7 @@ - + + {% endblock %} diff --git a/api/tests/test_business_rules.py b/api/tests/test_business_rules.py index b9a23cd..4e15105 100644 --- a/api/tests/test_business_rules.py +++ b/api/tests/test_business_rules.py @@ -636,49 +636,39 @@ class TestGetPricesForOrderCantitateRoa: assert result["summary"]["mismatches"] == 0 -class TestGetPricesForOrderQuantityDiscount: - """baseprice > price means GoMag applied a discount — skip price comparison.""" +class TestGetPricesDirectionalMatch: + """Price match is directional: gomag <= roa is OK, gomag > roa is mismatch.""" - def test_discount_detected(self): - """baseprice > price: quantity_discount=True, match=None, mismatches=0.""" + def test_gomag_below_roa_is_match(self): + """GoMag price lower than ROA (promo/volume discount) → match=True.""" items = [{"sku": "SKU-DISC", "price": 28.59, "baseprice": 33.0, "quantity": 48, "codmat_details": [{"codmat": "COD1", "cantitate_roa": 1, "id_articol": 100, "cont": "345"}]}] conn = _mock_oracle_conn(pol_cu_tva=True, price_map={100: (28.99, 1.19)}) result = get_prices_for_order(items, {"id_pol": "1"}, conn=conn) - assert result["items"][0].get("quantity_discount") is True - assert result["items"][0]["match"] is None + assert result["items"][0]["match"] is True + assert result["items"][0]["pret_roa"] == 28.99 assert result["summary"]["mismatches"] == 0 - def test_no_discount_when_baseprice_equals_price(self): - """baseprice == price: normal comparison.""" - items = [{"sku": "SKU-FULL", "price": 28.99, "baseprice": 28.99, "quantity": 1, + def test_gomag_above_roa_is_mismatch(self): + """GoMag price higher than ROA → match=False, mismatch counted.""" + items = [{"sku": "SKU-HIGH", "price": 30.00, "quantity": 1, "codmat_details": [{"codmat": "COD2", "cantitate_roa": 1, "id_articol": 200, "cont": "345"}]}] conn = _mock_oracle_conn(pol_cu_tva=True, price_map={200: (28.99, 1.19)}) result = get_prices_for_order(items, {"id_pol": "1"}, conn=conn) - assert result["items"][0].get("quantity_discount") is not True - assert result["items"][0]["match"] is True + assert result["items"][0]["match"] is False + assert result["summary"]["mismatches"] == 1 - def test_no_discount_when_baseprice_missing(self): - """baseprice=0 (missing): normal comparison.""" - items = [{"sku": "SKU-OLD", "price": 28.99, "quantity": 1, + def test_gomag_equals_roa_is_match(self): + """GoMag price equals ROA → match=True.""" + items = [{"sku": "SKU-FULL", "price": 28.99, "quantity": 1, "codmat_details": [{"codmat": "COD3", "cantitate_roa": 1, "id_articol": 300, "cont": "345"}]}] conn = _mock_oracle_conn(pol_cu_tva=True, price_map={300: (28.99, 1.19)}) result = get_prices_for_order(items, {"id_pol": "1"}, conn=conn) - assert result["items"][0].get("quantity_discount") is not True assert result["items"][0]["match"] is True - - def test_kit_takes_precedence_over_discount(self): - """Kit check runs before discount check — kit wins.""" - items = [{"sku": "SKU-KITDISC", "price": 20.0, "baseprice": 25.0, "quantity": 10, - "codmat_details": [{"codmat": "COD4", "cantitate_roa": 2, - "id_articol": 400, "cont": "345"}]}] - conn = _mock_oracle_conn(pol_cu_tva=True, price_map={400: (10.0, 1.19)}) - result = get_prices_for_order(items, {"id_pol": "1"}, conn=conn) - assert result["items"][0].get("kit") is True - assert result["items"][0].get("quantity_discount") is not True + assert result["summary"]["mismatches"] == 0 # ── normalize_company_name (II, PFA, INTREPRINDERE INDIVIDUALA) ──