test(business-rules): add 44 regression tests for kit pricing, discount, and SKU mapping

38 unit tests (no Oracle) covering: discount VAT split, build_articles_json,
kit detection pattern, sync_prices skip logic, VAT included normalization,
validate_kit_component_prices (pret=0 allowed), dual policy assignment,
and resolve_codmat_ids deduplication.

6 Oracle integration tests covering: multi-kit discount merge, per-kit
discount placement, distributed mode total, markup no negative discount,
price=0 component import, and duplicate CODMAT different prices.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-26 13:30:52 +00:00
parent f07946b489
commit 61193b793f
2 changed files with 888 additions and 5 deletions

View File

@@ -528,6 +528,379 @@ def test_repackaging_kit_pricing():
return False
# ===========================================================================
# Group 10: Business Rule Regression Tests (Oracle integration)
# ===========================================================================
def _create_test_partner(cur, suffix):
"""Helper: create a test partner and return its ID."""
partner_var = cur.var(oracledb.NUMBER)
name = f'Test BizRule {suffix}'
cur.execute("""
DECLARE v_id NUMBER;
BEGIN
v_id := PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener(
NULL, :name, 'JUD:Bucuresti;BUCURESTI;Str Test;1',
'0720000000', 'bizrule@test.com');
:result := v_id;
END;
""", {'name': name, 'result': partner_var})
return partner_var.getvalue()
def _import_order(cur, order_number, partner_id, articles_json, kit_mode='separate_line', id_pol=1):
"""Helper: call importa_comanda and return order ID."""
result_var = cur.var(oracledb.NUMBER)
cur.execute("""
DECLARE v_id NUMBER;
BEGIN
PACK_IMPORT_COMENZI.importa_comanda(
:order_number, SYSDATE, :partner_id,
:articles_json,
NULL, NULL,
:id_pol, NULL, NULL,
:kit_mode,
NULL, NULL, NULL,
v_id);
:result := v_id;
END;
""", {
'order_number': order_number,
'partner_id': partner_id,
'articles_json': articles_json,
'id_pol': id_pol,
'kit_mode': kit_mode,
'result': result_var
})
return result_var.getvalue()
def _get_order_lines(cur, order_id):
"""Helper: fetch COMENZI_ELEMENTE rows for an order."""
cur.execute("""
SELECT ce.CANTITATE, ce.PRET, na.CODMAT, ce.PTVA
FROM COMENZI_ELEMENTE ce
JOIN NOM_ARTICOLE na ON ce.ID_ARTICOL = na.ID_ARTICOL
WHERE ce.ID_COMANDA = :oid
ORDER BY ce.CANTITATE DESC, ce.PRET DESC
""", {'oid': order_id})
return cur.fetchall()
def test_multi_kit_discount_merge():
"""Regression (0666d6b): 2 identical kits at same VAT must merge discount lines,
not crash on duplicate check collision."""
print("\n" + "=" * 60)
print("TEST: Multi-kit discount merge (separate_line)")
print("=" * 60)
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
suffix = f'{datetime.now().strftime("%H%M%S")}-{random.randint(1000, 9999)}'
setup_test_data(cur)
partner_id = _create_test_partner(cur, suffix)
# 2 identical CAFE100 kits: total web = 2 * 450 = 900
articles_json = '[{"sku": "CAFE100", "cantitate": 2, "pret": 450}]'
order_id = _import_order(cur, f'TEST-BIZ-MERGE-{suffix}', partner_id, articles_json)
assert order_id and order_id > 0, "Order import failed"
rows = _get_order_lines(cur, order_id)
art_lines = [r for r in rows if r[0] > 0]
disc_lines = [r for r in rows if r[0] < 0]
assert len(art_lines) >= 1, f"Expected article line(s), got {len(art_lines)}"
assert len(disc_lines) >= 1, f"Expected discount line(s), got {len(disc_lines)}"
total = sum(r[0] * r[1] for r in rows)
expected = 900.0
print(f" Total: {total:.2f} (expected: {expected:.2f})")
assert abs(total - expected) < 0.02, f"Total {total:.2f} != expected {expected:.2f}"
print(" PASS")
conn.commit()
teardown_test_data(cur)
conn.commit()
return True
except Exception as e:
print(f" FAIL: {e}")
import traceback
traceback.print_exc()
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
teardown_test_data(cur)
conn.commit()
except:
pass
return False
def test_kit_discount_per_kit_placement():
"""Regression (580ca59): discount lines must appear after article lines (both present)."""
print("\n" + "=" * 60)
print("TEST: Kit discount per-kit placement")
print("=" * 60)
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
suffix = f'{datetime.now().strftime("%H%M%S")}-{random.randint(1000, 9999)}'
setup_test_data(cur)
partner_id = _create_test_partner(cur, suffix)
articles_json = '[{"sku": "CAFE100", "cantitate": 1, "pret": 450}]'
order_id = _import_order(cur, f'TEST-BIZ-PLACE-{suffix}', partner_id, articles_json)
assert order_id and order_id > 0, "Order import failed"
rows = _get_order_lines(cur, order_id)
art_lines = [r for r in rows if r[0] > 0]
disc_lines = [r for r in rows if r[0] < 0]
print(f" Article lines: {len(art_lines)}, Discount lines: {len(disc_lines)}")
assert len(art_lines) >= 1, "No article line found"
assert len(disc_lines) >= 1, "No discount line found — kit pricing did not activate"
print(" PASS")
conn.commit()
teardown_test_data(cur)
conn.commit()
return True
except Exception as e:
print(f" FAIL: {e}")
import traceback
traceback.print_exc()
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
teardown_test_data(cur)
conn.commit()
except:
pass
return False
def test_repackaging_distributed_total_matches_web():
"""Regression (61ae58e): distributed mode total must match web price exactly."""
print("\n" + "=" * 60)
print("TEST: Repackaging distributed total matches web")
print("=" * 60)
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
suffix = f'{datetime.now().strftime("%H%M%S")}-{random.randint(1000, 9999)}'
setup_test_data(cur)
partner_id = _create_test_partner(cur, suffix)
# 3 packs @ 400 lei => total web = 1200
articles_json = '[{"sku": "CAFE100", "cantitate": 3, "pret": 400}]'
order_id = _import_order(cur, f'TEST-BIZ-DIST-{suffix}', partner_id,
articles_json, kit_mode='distributed')
assert order_id and order_id > 0, "Order import failed"
rows = _get_order_lines(cur, order_id)
# Distributed: single line with adjusted price
positive_lines = [r for r in rows if r[0] > 0]
assert len(positive_lines) == 1, f"Expected 1 line in distributed mode, got {len(positive_lines)}"
total = positive_lines[0][0] * positive_lines[0][1]
expected = 1200.0
print(f" Line: qty={positive_lines[0][0]}, price={positive_lines[0][1]:.2f}")
print(f" Total: {total:.2f} (expected: {expected:.2f})")
assert abs(total - expected) < 0.02, f"Total {total:.2f} != expected {expected:.2f}"
print(" PASS")
conn.commit()
teardown_test_data(cur)
conn.commit()
return True
except Exception as e:
print(f" FAIL: {e}")
import traceback
traceback.print_exc()
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
teardown_test_data(cur)
conn.commit()
except:
pass
return False
def test_kit_markup_no_negative_discount():
"""Regression (47b5723): when web price > list price (markup), no discount line should be inserted."""
print("\n" + "=" * 60)
print("TEST: Kit markup — no negative discount")
print("=" * 60)
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
suffix = f'{datetime.now().strftime("%H%M%S")}-{random.randint(1000, 9999)}'
setup_test_data(cur)
partner_id = _create_test_partner(cur, suffix)
# CAF01 list price = 51.50/unit, 10 units = 515
# Web price 600 > 515 => markup, no discount line
articles_json = '[{"sku": "CAFE100", "cantitate": 1, "pret": 600}]'
order_id = _import_order(cur, f'TEST-BIZ-MARKUP-{suffix}', partner_id, articles_json)
assert order_id and order_id > 0, "Order import failed"
rows = _get_order_lines(cur, order_id)
disc_lines = [r for r in rows if r[0] < 0]
print(f" Total lines: {len(rows)}, Discount lines: {len(disc_lines)}")
assert len(disc_lines) == 0, f"Expected 0 discount lines for markup, got {len(disc_lines)}"
print(" PASS")
conn.commit()
teardown_test_data(cur)
conn.commit()
return True
except Exception as e:
print(f" FAIL: {e}")
import traceback
traceback.print_exc()
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
teardown_test_data(cur)
conn.commit()
except:
pass
return False
def test_kit_component_price_zero_import():
"""Regression (1703232): kit components with pret=0 should import successfully."""
print("\n" + "=" * 60)
print("TEST: Kit component price=0 import")
print("=" * 60)
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
suffix = f'{datetime.now().strftime("%H%M%S")}-{random.randint(1000, 9999)}'
setup_test_data(cur)
partner_id = _create_test_partner(cur, suffix)
# Temporarily set CAF01 price to 0
cur.execute("""
UPDATE crm_politici_pret_art SET PRET = 0
WHERE id_articol = 9999001 AND id_pol = 1
""")
conn.commit()
try:
# Import with pret=0 — should succeed (discount = full web price)
articles_json = '[{"sku": "CAFE100", "cantitate": 1, "pret": 100}]'
order_id = _import_order(cur, f'TEST-BIZ-PRET0-{suffix}', partner_id, articles_json)
print(f" Order ID: {order_id}")
assert order_id and order_id > 0, "Order import failed with pret=0"
print(" PASS: Order imported successfully with pret=0")
conn.commit()
finally:
# Restore original price
cur.execute("""
UPDATE crm_politici_pret_art SET PRET = 51.50
WHERE id_articol = 9999001 AND id_pol = 1
""")
conn.commit()
teardown_test_data(cur)
conn.commit()
return True
except Exception as e:
print(f" FAIL: {e}")
import traceback
traceback.print_exc()
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
# Restore price on error
cur.execute("""
UPDATE crm_politici_pret_art SET PRET = 51.50
WHERE id_articol = 9999001 AND id_pol = 1
""")
conn.commit()
teardown_test_data(cur)
conn.commit()
except:
pass
return False
def test_duplicate_codmat_different_prices():
"""Regression (95565af): same CODMAT at different prices should create separate lines,
discriminated by PRET + SIGN(CANTITATE)."""
print("\n" + "=" * 60)
print("TEST: Duplicate CODMAT different prices")
print("=" * 60)
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
suffix = f'{datetime.now().strftime("%H%M%S")}-{random.randint(1000, 9999)}'
setup_test_data(cur)
partner_id = _create_test_partner(cur, suffix)
# Two articles both mapping to CAF01 but at different prices
# CAFE100 -> CAF01 via ARTICOLE_TERTI (kit pricing)
# We use separate_line mode so article gets list price 51.50
# Then a second article at a different price on the same CODMAT
# For this test, we import 2 separate orders to same CODMAT with different prices
# The real scenario: kit article line + discount line on same id_articol
articles_json = '[{"sku": "CAFE100", "cantitate": 1, "pret": 450}]'
order_id = _import_order(cur, f'TEST-BIZ-DUP-{suffix}', partner_id, articles_json)
assert order_id and order_id > 0, "Order import failed"
rows = _get_order_lines(cur, order_id)
# separate_line mode: article at list price + discount at negative qty
# Both reference same CODMAT (CAF01) but different PRET and SIGN(CANTITATE)
codmats = [r[2] for r in rows]
print(f" Lines: {len(rows)}")
for r in rows:
print(f" qty={r[0]}, pret={r[1]:.2f}, codmat={r[2]}")
# Should have at least 2 lines with same CODMAT but different qty sign
caf_lines = [r for r in rows if r[2] == 'CAF01']
assert len(caf_lines) >= 2, f"Expected 2+ CAF01 lines (article + discount), got {len(caf_lines)}"
signs = {1 if r[0] > 0 else -1 for r in caf_lines}
assert len(signs) == 2, "Expected both positive and negative quantity lines for same CODMAT"
print(" PASS: Same CODMAT with different PRET/SIGN coexist")
conn.commit()
teardown_test_data(cur)
conn.commit()
return True
except Exception as e:
print(f" FAIL: {e}")
import traceback
traceback.print_exc()
try:
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
with conn.cursor() as cur:
teardown_test_data(cur)
conn.commit()
except:
pass
return False
if __name__ == "__main__":
print("Starting complete order import test...")
print(f"Timestamp: {datetime.now()}")
@@ -536,16 +909,32 @@ if __name__ == "__main__":
print(f"\nTest completed at: {datetime.now()}")
if success:
print("🎯 PHASE 1 VALIDATION: SUCCESSFUL")
print("PHASE 1 VALIDATION: SUCCESSFUL")
else:
print("🔧 PHASE 1 VALIDATION: NEEDS ATTENTION")
print("PHASE 1 VALIDATION: NEEDS ATTENTION")
# Run repackaging kit pricing test
print("\n")
repack_success = test_repackaging_kit_pricing()
if repack_success:
print("🎯 REPACKAGING KIT PRICING: SUCCESSFUL")
print("REPACKAGING KIT PRICING: SUCCESSFUL")
else:
print("🔧 REPACKAGING KIT PRICING: NEEDS ATTENTION")
print("REPACKAGING KIT PRICING: NEEDS ATTENTION")
# Run business rule regression tests
print("\n")
biz_tests = [
("Multi-kit discount merge", test_multi_kit_discount_merge),
("Kit discount per-kit placement", test_kit_discount_per_kit_placement),
("Distributed total matches web", test_repackaging_distributed_total_matches_web),
("Markup no negative discount", test_kit_markup_no_negative_discount),
("Component price=0 import", test_kit_component_price_zero_import),
("Duplicate CODMAT different prices", test_duplicate_codmat_different_prices),
]
biz_passed = 0
for name, test_fn in biz_tests:
if test_fn():
biz_passed += 1
print(f"\nBusiness rule tests: {biz_passed}/{len(biz_tests)} passed")
exit(0 if success else 1)