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>
940 lines
39 KiB
Python
940 lines
39 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Complete end-to-end test for order import functionality
|
|
Tests: Partner creation, Article mapping, Order import with full workflow
|
|
"""
|
|
|
|
import oracledb
|
|
import os
|
|
from dotenv import load_dotenv
|
|
import random
|
|
from datetime import datetime
|
|
|
|
load_dotenv('.env')
|
|
|
|
user = os.environ['ORACLE_USER']
|
|
password = os.environ['ORACLE_PASSWORD']
|
|
dsn = os.environ['ORACLE_DSN']
|
|
|
|
try:
|
|
instantclient_path = os.environ.get('INSTANTCLIENTPATH', '/opt/oracle/instantclient_23_9')
|
|
oracledb.init_oracle_client(lib_dir=instantclient_path)
|
|
except Exception as e:
|
|
pass
|
|
|
|
def setup_test_data(cur):
|
|
"""Setup test data by running SQL script"""
|
|
print("🔧 Setting up test data...")
|
|
|
|
# Read and execute setup script
|
|
with open('/app/tests/setup_test_data.sql', 'r') as f:
|
|
setup_sql = f.read()
|
|
|
|
# Split by statements and execute each
|
|
statements = [stmt.strip() for stmt in setup_sql.split(';') if stmt.strip() and not stmt.strip().startswith('--')]
|
|
|
|
for stmt in statements:
|
|
if stmt.upper().startswith(('INSERT', 'DELETE', 'COMMIT')):
|
|
try:
|
|
cur.execute(stmt)
|
|
if stmt.upper().startswith('COMMIT'):
|
|
print(" ✅ Test data setup committed")
|
|
except Exception as e:
|
|
if "unique constraint" not in str(e).lower():
|
|
print(f" ⚠️ Setup warning: {e}")
|
|
|
|
def teardown_test_data(cur):
|
|
"""Cleanup test data by running teardown script"""
|
|
print("🧹 Cleaning up test data...")
|
|
|
|
try:
|
|
# Read and execute teardown script
|
|
with open('/app/tests/teardown_test_data.sql', 'r') as f:
|
|
teardown_sql = f.read()
|
|
|
|
# Split by statements and execute each
|
|
statements = [stmt.strip() for stmt in teardown_sql.split(';') if stmt.strip() and not stmt.strip().startswith('--')]
|
|
|
|
for stmt in statements:
|
|
if stmt.upper().startswith(('DELETE', 'COMMIT')):
|
|
try:
|
|
cur.execute(stmt)
|
|
if stmt.upper().startswith('COMMIT'):
|
|
print(" ✅ Test data cleanup committed")
|
|
except Exception as e:
|
|
print(f" ⚠️ Cleanup warning: {e}")
|
|
|
|
except Exception as e:
|
|
print(f" ❌ Teardown error: {e}")
|
|
|
|
def test_complete_import():
|
|
"""
|
|
Complete test of order import workflow:
|
|
1. Setup test data
|
|
2. Test individual components
|
|
3. Create partner if doesn't exist
|
|
4. Import complete order with articles
|
|
5. Verify results
|
|
6. Cleanup test data
|
|
"""
|
|
print("🎯 COMPLETE ORDER IMPORT TEST")
|
|
print("=" * 60)
|
|
|
|
success_count = 0
|
|
total_tests = 0
|
|
|
|
try:
|
|
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
|
|
with conn.cursor() as cur:
|
|
unique_suffix = random.randint(1000, 9999)
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
# ========================================
|
|
# SETUP: Initialize test data
|
|
# ========================================
|
|
setup_test_data(cur)
|
|
|
|
# ========================================
|
|
# TEST 1: Component Validation
|
|
# ========================================
|
|
print("\n📋 TEST 1: Individual Component Validation")
|
|
print("-" * 40)
|
|
|
|
# Test article mapping
|
|
total_tests += 1
|
|
print("1.1 Testing article mapping...")
|
|
cur.execute("SELECT * FROM TABLE(PACK_IMPORT_COMENZI.gaseste_articol_roa('CAFE100'))")
|
|
article_results = cur.fetchall()
|
|
if len(article_results) > 0:
|
|
print(f" ✅ Article mapping: Found {len(article_results)} mappings for CAFE100")
|
|
success_count += 1
|
|
else:
|
|
print(" ❌ Article mapping: No results for CAFE100")
|
|
|
|
# Test JSON parsing
|
|
total_tests += 1
|
|
print("1.2 Testing JSON parsing...")
|
|
test_json = '[{"sku": "CAFE100", "cantitate": 1, "pret": 25.0}]'
|
|
cur.execute("SELECT * FROM TABLE(PACK_JSON.parse_array(:json))", {'json': test_json})
|
|
json_results = cur.fetchall()
|
|
if len(json_results) > 0:
|
|
print(f" ✅ JSON parsing: Successfully parsed {len(json_results)} items")
|
|
success_count += 1
|
|
else:
|
|
print(" ❌ JSON parsing: Failed to parse JSON")
|
|
|
|
# ========================================
|
|
# TEST 2: Partner Management
|
|
# ========================================
|
|
print("\n👥 TEST 2: Partner Creation/Retrieval")
|
|
print("-" * 40)
|
|
|
|
total_tests += 1
|
|
partner_name = f'Test Client {timestamp}-{unique_suffix}'
|
|
partner_address = 'JUD:Bucuresti;BUCURESTI;Str. Test;12'
|
|
partner_phone = f'072{unique_suffix:04d}000'
|
|
partner_email = f'test{unique_suffix}@example.com'
|
|
|
|
print(f"2.1 Creating/finding partner: {partner_name}")
|
|
|
|
partner_var = cur.var(oracledb.NUMBER)
|
|
cur.execute("""
|
|
DECLARE
|
|
v_partner_id NUMBER;
|
|
BEGIN
|
|
v_partner_id := PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener(
|
|
NULL, -- cod_fiscal (NULL for individuals)
|
|
:partner_name,
|
|
:partner_address,
|
|
:partner_phone,
|
|
:partner_email
|
|
);
|
|
:result := v_partner_id;
|
|
END;
|
|
""", {
|
|
'partner_name': partner_name,
|
|
'partner_address': partner_address,
|
|
'partner_phone': partner_phone,
|
|
'partner_email': partner_email,
|
|
'result': partner_var
|
|
})
|
|
|
|
partner_id = partner_var.getvalue()
|
|
if partner_id and partner_id > 0:
|
|
print(f" ✅ Partner management: ID {partner_id}")
|
|
success_count += 1
|
|
else:
|
|
print(" ❌ Partner management: Failed to create/find partner")
|
|
return False
|
|
|
|
# ========================================
|
|
# TEST 3: Complete Order Import
|
|
# ========================================
|
|
print("\n📦 TEST 3: Complete Order Import")
|
|
print("-" * 40)
|
|
|
|
total_tests += 1
|
|
order_number = f'COMPLETE-{timestamp}-{unique_suffix}'
|
|
|
|
# Test with multiple articles including real GoMag SKU
|
|
test_articles = [
|
|
{"sku": "CAFE100", "cantitate": 2, "pret": 25.0}, # Mapped article
|
|
{"sku": "8000070028685", "cantitate": 1, "pret": 69.79} # Real GoMag SKU
|
|
]
|
|
articles_json = str(test_articles).replace("'", '"')
|
|
|
|
print(f"3.1 Importing order: {order_number}")
|
|
print(f" Articles: {articles_json}")
|
|
|
|
result_var = cur.var(oracledb.NUMBER)
|
|
cur.execute("""
|
|
DECLARE
|
|
v_order_id NUMBER;
|
|
BEGIN
|
|
v_order_id := PACK_IMPORT_COMENZI.importa_comanda(
|
|
:order_number,
|
|
SYSDATE,
|
|
:partner_id,
|
|
:articles_json,
|
|
NULL, -- id_adresa_livrare
|
|
NULL, -- id_adresa_facturare
|
|
'Complete end-to-end test order'
|
|
);
|
|
:result := v_order_id;
|
|
END;
|
|
""", {
|
|
'order_number': order_number,
|
|
'partner_id': partner_id,
|
|
'articles_json': articles_json,
|
|
'result': result_var
|
|
})
|
|
|
|
order_id = result_var.getvalue()
|
|
|
|
# Get detailed error information
|
|
cur.execute("SELECT PACK_IMPORT_COMENZI.get_last_error FROM DUAL")
|
|
error_msg = cur.fetchone()[0]
|
|
|
|
if order_id and order_id > 0:
|
|
print(f" ✅ Order import: SUCCESS! ID {order_id}")
|
|
success_count += 1
|
|
|
|
# ========================================
|
|
# TEST 4: Result Verification
|
|
# ========================================
|
|
print("\n🔍 TEST 4: Result Verification")
|
|
print("-" * 40)
|
|
|
|
total_tests += 1
|
|
# Verify order details
|
|
cur.execute("""
|
|
SELECT
|
|
c.NR_COMANDA,
|
|
c.DATA_COMANDA,
|
|
c.INTERNA,
|
|
c.ID_PART,
|
|
c.ID_GESTIUNE,
|
|
c.ID_SECTIE,
|
|
np.DENUMIRE as PARTNER_NAME
|
|
FROM COMENZI c
|
|
LEFT JOIN NOM_PARTENERI np ON c.ID_PART = np.ID_PART
|
|
WHERE c.ID_COMANDA = :order_id
|
|
""", {'order_id': order_id})
|
|
|
|
order_details = cur.fetchone()
|
|
if order_details:
|
|
print(f"4.1 Order verification:")
|
|
print(f" Number: {order_details[0]}")
|
|
print(f" Date: {order_details[1]}")
|
|
print(f" Type (INTERNA): {order_details[2]}")
|
|
print(f" Partner: {order_details[6]} (ID: {order_details[3]})")
|
|
print(f" Gestiune: {order_details[4]}")
|
|
print(f" Sectie: {order_details[5]}")
|
|
|
|
# Verify articles in order
|
|
cur.execute("""
|
|
SELECT
|
|
ce.CANTITATE,
|
|
ce.PRET,
|
|
na.CODMAT,
|
|
na.DENUMIRE
|
|
FROM COMENZI_ELEMENTE ce
|
|
JOIN NOM_ARTICOLE na ON ce.ID_ARTICOL = na.ID_ARTICOL
|
|
WHERE ce.ID_COMANDA = :order_id
|
|
ORDER BY na.CODMAT
|
|
""", {'order_id': order_id})
|
|
|
|
order_articles = cur.fetchall()
|
|
if order_articles:
|
|
print(f"4.2 Articles in order ({len(order_articles)} items):")
|
|
for art in order_articles:
|
|
print(f" - Qty: {art[0]:>3}, Price: {art[1]:>8.2f}, Code: {art[2]:>10} - {art[3]}")
|
|
success_count += 1
|
|
|
|
# Calculate totals
|
|
total_qty = sum(art[0] for art in order_articles)
|
|
total_value = sum(art[0] * art[1] for art in order_articles)
|
|
print(f" TOTAL: Qty={total_qty}, Value={total_value:.2f} RON")
|
|
|
|
else:
|
|
print(" ❌ No articles found in order")
|
|
else:
|
|
print(" ❌ Order verification failed")
|
|
|
|
else:
|
|
print(f" ❌ Order import: FAILED")
|
|
if error_msg:
|
|
print(f" Error: {error_msg}")
|
|
else:
|
|
print(f" No specific error message, ID returned: {order_id}")
|
|
|
|
conn.commit()
|
|
|
|
# ========================================
|
|
# FINAL RESULTS
|
|
# ========================================
|
|
print("\n" + "=" * 60)
|
|
print(f"📊 FINAL RESULTS: {success_count}/{total_tests} tests passed")
|
|
print("=" * 60)
|
|
|
|
# ========================================
|
|
# TEARDOWN: Cleanup test data
|
|
# ========================================
|
|
teardown_test_data(cur)
|
|
conn.commit()
|
|
|
|
if success_count == total_tests:
|
|
print("🎉 ALL TESTS PASSED! Order import system is fully functional.")
|
|
return True
|
|
elif success_count >= total_tests - 1:
|
|
print("⚠️ MOSTLY SUCCESSFUL: Core components working, minor issues remain.")
|
|
return True
|
|
else:
|
|
print("❌ SIGNIFICANT ISSUES: Multiple components need attention.")
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"❌ CRITICAL ERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
# Attempt cleanup even on error
|
|
try:
|
|
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
|
|
with conn.cursor() as cur:
|
|
print("\n🧹 Attempting cleanup after error...")
|
|
teardown_test_data(cur)
|
|
conn.commit()
|
|
except:
|
|
print(" ⚠️ Cleanup after error also failed")
|
|
|
|
return False
|
|
|
|
def test_repackaging_kit_pricing():
|
|
"""
|
|
Test single-component repackaging with kit pricing.
|
|
CAFE100 -> CAF01 with cantitate_roa=10 (1 web package = 10 ROA units).
|
|
Verifies that kit pricing applies: list price per unit + discount line.
|
|
"""
|
|
print("\n" + "=" * 60)
|
|
print("🎯 REPACKAGING KIT PRICING TEST")
|
|
print("=" * 60)
|
|
|
|
success_count = 0
|
|
total_tests = 0
|
|
|
|
try:
|
|
with oracledb.connect(user=user, password=password, dsn=dsn) as conn:
|
|
with conn.cursor() as cur:
|
|
unique_suffix = random.randint(1000, 9999)
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
setup_test_data(cur)
|
|
|
|
# Create a test partner
|
|
partner_var = cur.var(oracledb.NUMBER)
|
|
partner_name = f'Test Repack {timestamp}-{unique_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', 'repack@test.com');
|
|
:result := v_id;
|
|
END;
|
|
""", {'name': partner_name, 'result': partner_var})
|
|
partner_id = partner_var.getvalue()
|
|
if not partner_id or partner_id <= 0:
|
|
print(" SKIP: Could not create test partner")
|
|
return False
|
|
|
|
# ---- Test separate_line mode ----
|
|
total_tests += 1
|
|
order_number = f'TEST-REPACK-SEP-{timestamp}-{unique_suffix}'
|
|
# Web price: 2 packages * 10 units * some_price = total
|
|
# With list price 51.50/unit, 2 packs of 10 = 20 units
|
|
# Web price per package = 450 lei => total web = 900
|
|
# Expected: 20 units @ 51.50 = 1030, discount = 130
|
|
web_price_per_pack = 450.0
|
|
articles_json = f'[{{"sku": "CAFE100", "cantitate": 2, "pret": {web_price_per_pack}}}]'
|
|
|
|
print(f"\n1. Testing separate_line mode: {order_number}")
|
|
print(f" CAFE100 x2 @ {web_price_per_pack} lei/pack, cantitate_roa=10")
|
|
|
|
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,
|
|
1, -- id_pol (default price policy)
|
|
NULL, NULL,
|
|
'separate_line', -- kit_mode
|
|
NULL, NULL, NULL,
|
|
v_id);
|
|
:result := v_id;
|
|
END;
|
|
""", {
|
|
'order_number': order_number,
|
|
'partner_id': partner_id,
|
|
'articles_json': articles_json,
|
|
'result': result_var
|
|
})
|
|
|
|
order_id = result_var.getvalue()
|
|
if order_id and order_id > 0:
|
|
print(f" Order created: ID {order_id}")
|
|
|
|
cur.execute("""
|
|
SELECT ce.CANTITATE, ce.PRET, na.CODMAT, na.DENUMIRE
|
|
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
|
|
""", {'oid': order_id})
|
|
rows = cur.fetchall()
|
|
|
|
if len(rows) >= 2:
|
|
# Should have article line + discount line
|
|
art_line = [r for r in rows if r[0] > 0]
|
|
disc_line = [r for r in rows if r[0] < 0]
|
|
|
|
if art_line and disc_line:
|
|
print(f" Article: qty={art_line[0][0]}, price={art_line[0][1]:.2f} ({art_line[0][2]})")
|
|
print(f" Discount: qty={disc_line[0][0]}, price={disc_line[0][1]:.2f}")
|
|
total = sum(r[0] * r[1] for r in rows)
|
|
expected_total = web_price_per_pack * 2
|
|
print(f" Total: {total:.2f} (expected: {expected_total:.2f})")
|
|
if abs(total - expected_total) < 0.02:
|
|
print(" PASS: Total matches web price")
|
|
success_count += 1
|
|
else:
|
|
print(" FAIL: Total mismatch")
|
|
else:
|
|
print(f" FAIL: Expected article + discount lines, got {len(art_line)} art / {len(disc_line)} disc")
|
|
elif len(rows) == 1:
|
|
print(f" FAIL: Only 1 line (no discount). qty={rows[0][0]}, price={rows[0][1]:.2f}")
|
|
print(" Kit pricing did NOT activate for single-component repackaging")
|
|
else:
|
|
print(" FAIL: No order lines found")
|
|
else:
|
|
cur.execute("SELECT PACK_IMPORT_COMENZI.get_last_error FROM DUAL")
|
|
err = cur.fetchone()[0]
|
|
print(f" FAIL: Order import failed: {err}")
|
|
|
|
conn.commit()
|
|
|
|
# ---- Test distributed mode ----
|
|
total_tests += 1
|
|
order_number2 = f'TEST-REPACK-DIST-{timestamp}-{unique_suffix}'
|
|
print(f"\n2. Testing distributed mode: {order_number2}")
|
|
|
|
result_var2 = 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,
|
|
1, NULL, NULL,
|
|
'distributed',
|
|
NULL, NULL, NULL,
|
|
v_id);
|
|
:result := v_id;
|
|
END;
|
|
""", {
|
|
'order_number': order_number2,
|
|
'partner_id': partner_id,
|
|
'articles_json': articles_json,
|
|
'result': result_var2
|
|
})
|
|
|
|
order_id2 = result_var2.getvalue()
|
|
if order_id2 and order_id2 > 0:
|
|
print(f" Order created: ID {order_id2}")
|
|
|
|
cur.execute("""
|
|
SELECT ce.CANTITATE, ce.PRET, na.CODMAT
|
|
FROM COMENZI_ELEMENTE ce
|
|
JOIN NOM_ARTICOLE na ON ce.ID_ARTICOL = na.ID_ARTICOL
|
|
WHERE ce.ID_COMANDA = :oid
|
|
""", {'oid': order_id2})
|
|
rows2 = cur.fetchall()
|
|
|
|
if len(rows2) == 1:
|
|
# Distributed: single line with adjusted price
|
|
total = rows2[0][0] * rows2[0][1]
|
|
expected_total = web_price_per_pack * 2
|
|
print(f" Line: qty={rows2[0][0]}, price={rows2[0][1]:.2f}, total={total:.2f}")
|
|
if abs(total - expected_total) < 0.02:
|
|
print(" PASS: Distributed price correct")
|
|
success_count += 1
|
|
else:
|
|
print(f" FAIL: Total {total:.2f} != expected {expected_total:.2f}")
|
|
else:
|
|
print(f" INFO: Got {len(rows2)} lines (expected 1 for distributed)")
|
|
for r in rows2:
|
|
print(f" qty={r[0]}, price={r[1]:.2f}, codmat={r[2]}")
|
|
else:
|
|
cur.execute("SELECT PACK_IMPORT_COMENZI.get_last_error FROM DUAL")
|
|
err = cur.fetchone()[0]
|
|
print(f" FAIL: Order import failed: {err}")
|
|
|
|
conn.commit()
|
|
|
|
# Cleanup
|
|
teardown_test_data(cur)
|
|
conn.commit()
|
|
|
|
print(f"\n{'=' * 60}")
|
|
print(f"RESULTS: {success_count}/{total_tests} tests passed")
|
|
print('=' * 60)
|
|
return success_count == total_tests
|
|
|
|
except Exception as e:
|
|
print(f"CRITICAL ERROR: {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
|
|
|
|
|
|
# ===========================================================================
|
|
# 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()}")
|
|
|
|
success = test_complete_import()
|
|
|
|
print(f"\nTest completed at: {datetime.now()}")
|
|
if success:
|
|
print("PHASE 1 VALIDATION: SUCCESSFUL")
|
|
else:
|
|
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")
|
|
else:
|
|
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) |