diff --git a/api/app/services/import_service.py b/api/app/services/import_service.py index 58afdef..52e586b 100644 --- a/api/app/services/import_service.py +++ b/api/app/services/import_service.py @@ -201,7 +201,7 @@ def build_articles_json(items, order=None, settings=None) -> str: return json.dumps(articles) -def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_settings: dict = None, id_gestiuni: list[int] = None, cod_fiscal_override: str = None) -> dict: +def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_settings: dict = None, id_gestiuni: list[int] = None, cod_fiscal_override: str = None, anaf_strict: int = None) -> dict: """Import a single order into Oracle ROA. Returns dict with: @@ -257,7 +257,7 @@ def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_se is_pj = 0 cur.callproc("PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener", [ - cod_fiscal, denumire, registru, is_pj, id_partener + cod_fiscal, denumire, registru, is_pj, anaf_strict, id_partener ]) partner_id = id_partener.getvalue() diff --git a/api/app/services/sync_service.py b/api/app/services/sync_service.py index c7a15f2..83cb90e 100644 --- a/api/app/services/sync_service.py +++ b/api/app/services/sync_service.py @@ -653,10 +653,11 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None _log_line(run_id, f"ANAF pre-populare eroare: {e}") logger.warning(f"ANAF cache pre-population failed: {e}") - # Step 4: ANAF batch verification for company CUIs + # Step 4: ANAF batch verification for company CUIs (RO companies only) company_cuis = set() for order in truly_importable: - if order.billing.is_company and order.billing.company_code: + is_ro = (order.billing.country or "").strip().lower() == "romania" + if order.billing.is_company and order.billing.company_code and is_ro: raw_cf = import_service.clean_web_text(order.billing.company_code) or "" bare = anaf_service.strip_ro_prefix(raw_cf) if anaf_service.validate_cui(bare): @@ -709,11 +710,19 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None _log_line(run_id, f"#{order.number} CUI corectat: {raw_cf} → {correct_cf}") cod_fiscal_override = correct_cf + # Determine strict search mode: only when RO company + ANAF data available + is_ro_company = (order.billing.is_company + and (order.billing.country or "").strip().lower() == "romania") + anaf_strict = None + if is_ro_company and anaf_data_for_order and anaf_data_for_order.get("scpTVA") is not None: + anaf_strict = 1 # ANAF data available → strict search + result = await asyncio.to_thread( import_service.import_single_order, order, id_pol=id_pol, id_sectie=id_sectie, app_settings=app_settings, id_gestiuni=id_gestiuni, - cod_fiscal_override=cod_fiscal_override + cod_fiscal_override=cod_fiscal_override, + anaf_strict=anaf_strict ) # Build order items data for storage (R9) @@ -770,7 +779,12 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None "denumire_roa": result.get("denumire_roa"), "anaf_platitor_tva": (1 if anaf_data_for_order.get("scpTVA") else 0) if anaf_data_for_order and anaf_data_for_order.get("scpTVA") is not None else None, "anaf_checked_at": anaf_data_for_order.get("checked_at") if anaf_data_for_order else None, - "anaf_cod_fiscal_adjusted": 1 if cod_fiscal_override and cod_fiscal_override != raw_cf else 0, + "anaf_cod_fiscal_adjusted": 1 if ( + cod_fiscal_override + and result.get("cod_fiscal_roa") + and anaf_service.strip_ro_prefix(result["cod_fiscal_roa"]) == anaf_service.strip_ro_prefix(raw_cf) + and result["cod_fiscal_roa"].strip().upper().replace("RO ", "RO") != raw_cf.strip().upper().replace("RO ", "RO") + ) else 0, "adresa_livrare_gomag": json.dumps({"address": order.shipping.address, "city": order.shipping.city, "region": order.shipping.region}) if order.shipping else None, "adresa_facturare_gomag": json.dumps({"address": order.billing.address, "city": order.billing.city, "region": order.billing.region}), "adresa_livrare_roa": json.dumps(result.get("adresa_livrare_roa")) if result.get("adresa_livrare_roa") else None, diff --git a/api/database-scripts/05_pack_import_parteneri.pck b/api/database-scripts/05_pack_import_parteneri.pck index 628fdaf..ddcd419 100644 --- a/api/database-scripts/05_pack_import_parteneri.pck +++ b/api/database-scripts/05_pack_import_parteneri.pck @@ -3,6 +3,7 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS -- 20.03.2026 - import parteneri GoMag: PJ/PF, shipping/billing, cautare/creare automata -- 31.03.2026 - parser inteligent adrese: split numar in bloc/scara/apart/etaj (fix ORA-12899 pe NUMAR max 10 chars) -- 01.04.2026 - ANAF dedup: cautare duala CUI, adrese pe strada+diacritics, strip diacritics la stocare + -- 02.04.2026 - cautare CUI strict (p_strict_search=1) sau dual anti-dedup (NULL) -- ==================================================================== -- CONSTANTS @@ -66,6 +67,7 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS p_denumire IN VARCHAR2, p_registru IN VARCHAR2, p_is_persoana_juridica IN NUMBER DEFAULT NULL, + p_strict_search IN NUMBER DEFAULT NULL, p_id_partener OUT NUMBER); procedure cauta_sau_creeaza_adresa(p_id_part IN NUMBER, @@ -106,7 +108,8 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS * @param p_cod_fiscal Codul fiscal de cautat * @return ID_PART sau NULL daca nu gaseste */ - FUNCTION cauta_partener_dupa_cod_fiscal(p_cod_fiscal IN VARCHAR2) + FUNCTION cauta_partener_dupa_cod_fiscal(p_cod_fiscal IN VARCHAR2, + p_strict_search IN NUMBER DEFAULT NULL) RETURN NUMBER; /** @@ -229,7 +232,9 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS -- ==================================================================== -- 01.04.2026 - cautare duala cod_fiscal cu/fara prefix RO (anti-duplicare parteneri) - FUNCTION cauta_partener_dupa_cod_fiscal(p_cod_fiscal IN VARCHAR2) + -- 02.04.2026 - p_strict_search=1: cautare doar forma exacta (+ varianta cu spatiu pt RO) + FUNCTION cauta_partener_dupa_cod_fiscal(p_cod_fiscal IN VARCHAR2, + p_strict_search IN NUMBER DEFAULT NULL) RETURN NUMBER IS v_id_part NUMBER; v_cod_fiscal_curat VARCHAR2(50); @@ -250,19 +255,42 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS END IF; v_ro_cui := 'RO' || v_bare_cui; - -- 01.04.2026 - cautare duala cod_fiscal cu/fara prefix RO (anti-duplicare parteneri) - -- Search 3 forms: bare, RO+bare, RO+space+bare (index-friendly) - -- Priority: active + exact form > active + alternate > inactive BEGIN - SELECT id_part INTO v_id_part FROM ( - SELECT id_part - FROM nom_parteneri - WHERE UPPER(TRIM(cod_fiscal)) IN (v_bare_cui, v_ro_cui, 'RO ' || v_bare_cui) - AND NVL(sters, 0) = 0 - ORDER BY NVL(inactiv, 0) ASC, - CASE WHEN UPPER(TRIM(cod_fiscal)) = v_cod_fiscal_curat THEN 0 ELSE 1 END ASC, - id_part DESC - ) WHERE ROWNUM = 1; + IF p_strict_search = 1 THEN + -- Cautare STRICT: doar forma primita + varianta cu spatiu + IF REGEXP_LIKE(v_cod_fiscal_curat, '^RO\d') THEN + -- Input "RO123" → cauta si "RO 123" + SELECT id_part INTO v_id_part FROM ( + SELECT id_part + FROM nom_parteneri + WHERE UPPER(TRIM(cod_fiscal)) IN (v_cod_fiscal_curat, 'RO ' || v_bare_cui) + AND NVL(sters, 0) = 0 + ORDER BY NVL(inactiv, 0) ASC, id_part DESC + ) WHERE ROWNUM = 1; + ELSE + -- Input "123" → cauta doar "123" + SELECT id_part INTO v_id_part FROM ( + SELECT id_part + FROM nom_parteneri + WHERE UPPER(TRIM(cod_fiscal)) = v_bare_cui + AND NVL(sters, 0) = 0 + ORDER BY NVL(inactiv, 0) ASC, id_part DESC + ) WHERE ROWNUM = 1; + END IF; + ELSE + -- Cautare DUALA anti-dedup: toate formele (comportament original) + -- Search 3 forms: bare, RO+bare, RO+space+bare (index-friendly) + -- Priority: active + exact form > active + alternate > inactive + SELECT id_part INTO v_id_part FROM ( + SELECT id_part + FROM nom_parteneri + WHERE UPPER(TRIM(cod_fiscal)) IN (v_bare_cui, v_ro_cui, 'RO ' || v_bare_cui) + AND NVL(sters, 0) = 0 + ORDER BY NVL(inactiv, 0) ASC, + CASE WHEN UPPER(TRIM(cod_fiscal)) = v_cod_fiscal_curat THEN 0 ELSE 1 END ASC, + id_part DESC + ) WHERE ROWNUM = 1; + END IF; RETURN v_id_part; @@ -609,6 +637,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS p_denumire IN VARCHAR2, p_registru IN VARCHAR2, p_is_persoana_juridica IN NUMBER DEFAULT NULL, + p_strict_search IN NUMBER DEFAULT NULL, p_id_partener OUT NUMBER) IS v_id_part NUMBER; @@ -642,7 +671,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS -- STEP 1: Cautare dupa cod fiscal (prioritate 1) IF v_cod_fiscal_curat IS NOT NULL AND LENGTH(v_cod_fiscal_curat) >= C_MIN_COD_FISCAL THEN - v_id_part := cauta_partener_dupa_cod_fiscal(v_cod_fiscal_curat); + v_id_part := cauta_partener_dupa_cod_fiscal(v_cod_fiscal_curat, p_strict_search); IF v_id_part IS NOT NULL THEN -- pINFO('Partener gasit dupa cod_fiscal. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); @@ -653,13 +682,16 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS END IF; -- STEP 2: Cautare dupa denumire exacta (prioritate 2) - v_id_part := cauta_partener_dupa_denumire(v_denumire_curata); - - IF v_id_part IS NOT NULL THEN - -- pINFO('Partener gasit dupa denumire. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); - -- pINFO('=== SFARSIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); - p_id_partener := v_id_part; - RETURN; + -- Skip cand cautare stricta ANAF — vrem partener nou cu CUI corect + IF p_strict_search IS NULL THEN + v_id_part := cauta_partener_dupa_denumire(v_denumire_curata); + + IF v_id_part IS NOT NULL THEN + -- pINFO('Partener gasit dupa denumire. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); + -- pINFO('=== SFARSIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); + p_id_partener := v_id_part; + RETURN; + END IF; END IF; -- STEP 3: Creare partener nou