fix(import): resolve duplicate article + order_items repopulation on retry
Two production bugs from VENDING (order 485224762, 2026-04-22): 1. Oracle: ORA-20000 when a GoMag order contains a kit SKU whose expansion includes CODMAT X plus a second item with SKU=X. Two article-insert call-sites in PACK_IMPORT_COMENZI bypassed merge_or_insert_articol — line 622 (NOM_ARTICOLE fallback) and line 538 (kit discount line). Both now use merge_or_insert_articol for consistent dedup semantics. Regression test added in test_complete_import.py covering the exact kit-plus-direct scenario. 2. SQLite: retry_service._download_and_reimport refreshed orders row but never repopulated order_items. Combined with mark_order_deleted_in_roa (which wipes items), any retry/resync left the UI showing "Niciun articol" despite successful Oracle import. Retry now rebuilds items from the fresh GoMag download on both success and error paths, mirroring sync_service. Includes scripts/backfill_order_items.py — one-shot recovery for orders already in this bad state. Reads settings, re-fetches from GoMag, rewrites order_items without touching Oracle or order status. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -901,6 +901,98 @@ def test_duplicate_codmat_different_prices():
|
||||
return False
|
||||
|
||||
|
||||
def test_kit_component_plus_direct_sku_merge():
|
||||
"""Regression (prod VENDING 2026-04-22, order 485224762):
|
||||
Kit SKU expanding to CODMAT X + direct SKU = X in same order used to crash
|
||||
with ORA-20000 because NOM_ARTICOLE fallback bypassed merge_or_insert_articol.
|
||||
|
||||
After fix, both inserts succeed (merged if price/vat match, separate rows otherwise).
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("TEST: Kit component + direct SKU same CODMAT (no ORA-20000)")
|
||||
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)
|
||||
|
||||
# Add extra kit mapping: KIT_DUP expands to CAF01 (2 units) + LAV001 (1 unit)
|
||||
# CAF01 is also importable as direct SKU (CODMAT match in NOM_ARTICOLE)
|
||||
cur.execute("DELETE FROM ARTICOLE_TERTI WHERE sku = 'KIT_DUP'")
|
||||
cur.execute("""INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ)
|
||||
VALUES ('KIT_DUP', 'CAF01', 2, 100, 1)""")
|
||||
cur.execute("""INSERT INTO ARTICOLE_TERTI (sku, codmat, cantitate_roa, procent_pret, activ)
|
||||
VALUES ('KIT_DUP', 'LAV001', 1, 100, 1)""")
|
||||
# Price for LAV001 in id_pol=1 (CAF01 already set by setup_test_data)
|
||||
cur.execute("""MERGE INTO crm_politici_pret_art dst
|
||||
USING (SELECT 1 AS id_pol, 9999002 AS id_articol FROM DUAL) src
|
||||
ON (dst.id_pol = src.id_pol AND dst.id_articol = src.id_articol)
|
||||
WHEN NOT MATCHED THEN INSERT (id_pol, id_articol, pret, proc_tvav)
|
||||
VALUES (src.id_pol, src.id_articol, 30.00, 19)""")
|
||||
conn.commit()
|
||||
|
||||
try:
|
||||
# Order contains BOTH the kit (which expands to CAF01 + LAV001)
|
||||
# AND direct SKU 'CAF01' — the exact production bug scenario.
|
||||
articles_json = (
|
||||
'[{"sku": "KIT_DUP", "quantity": "1", "price": "200", "vat": "19"},'
|
||||
' {"sku": "CAF01", "quantity": "3", "price": "50", "vat": "19"}]'
|
||||
)
|
||||
order_id = _import_order(
|
||||
cur, f'TEST-BIZ-KITDUP-{suffix}', partner_id, articles_json
|
||||
)
|
||||
|
||||
# Pre-fix: order_id was 0/NULL because RAISE_APPLICATION_ERROR
|
||||
# rolled back the transaction. Post-fix: order is created successfully.
|
||||
assert order_id and order_id > 0, (
|
||||
f"REGRESSION: order import failed (id={order_id}). "
|
||||
"Line 622 NOM_ARTICOLE fallback likely still bypasses merge_or_insert_articol."
|
||||
)
|
||||
|
||||
rows = _get_order_lines(cur, order_id)
|
||||
print(f" Order {order_id}, {len(rows)} lines:")
|
||||
for r in rows:
|
||||
print(f" qty={r[0]}, pret={r[1]:.2f}, codmat={r[2]}, ptva={r[3]}")
|
||||
|
||||
# CAF01 must appear with summed quantity (kit: 2x1=2) + (direct: 3) = 5
|
||||
# when price+vat align, OR as multiple rows summing to 5 when they don't.
|
||||
caf_positive = [r for r in rows if r[2] == 'CAF01' and r[0] > 0]
|
||||
total_caf_qty = sum(r[0] for r in caf_positive)
|
||||
assert len(caf_positive) >= 1, \
|
||||
f"Expected >=1 positive CAF01 line, got {len(caf_positive)}"
|
||||
assert total_caf_qty == 5, \
|
||||
f"Expected total CAF01 qty=5 (2 from kit + 3 direct), got {total_caf_qty}"
|
||||
print(f" PASS: CAF01 qty={total_caf_qty} across {len(caf_positive)} line(s), no ORA-20000")
|
||||
|
||||
conn.commit()
|
||||
finally:
|
||||
cur.execute("DELETE FROM ARTICOLE_TERTI WHERE sku = 'KIT_DUP'")
|
||||
cur.execute("DELETE FROM crm_politici_pret_art WHERE id_articol = 9999002 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:
|
||||
cur.execute("DELETE FROM ARTICOLE_TERTI WHERE sku = 'KIT_DUP'")
|
||||
cur.execute("DELETE FROM crm_politici_pret_art WHERE id_articol = 9999002 AND id_pol = 1")
|
||||
teardown_test_data(cur)
|
||||
conn.commit()
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def test_pf_reverse_name_dedup():
|
||||
"""Test that PF partner with reversed name order is found, not duplicated.
|
||||
Creates partner 'POPESCU ION', then searches for 'ION POPESCU' — should return same id_part.
|
||||
@@ -1017,6 +1109,7 @@ if __name__ == "__main__":
|
||||
("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),
|
||||
("Kit component + direct SKU same CODMAT", test_kit_component_plus_direct_sku_merge),
|
||||
]
|
||||
biz_passed = 0
|
||||
for name, test_fn in biz_tests:
|
||||
|
||||
Reference in New Issue
Block a user