feat(plsql): CUI strict search + country filter for ANAF

PL/SQL cauta_partener_dupa_cod_fiscal gains p_strict_search param:
- strict (=1): search only exact CUI form (ANAF-determined)
- dual (NULL): search all forms (existing anti-dedup behavior)
Skip denomination fallback when strict to force new partner creation.

Python: country filter excludes foreign companies from ANAF batch,
anaf_strict flag threaded sync→import→PL/SQL, normalize RO-space
in cod_fiscal_adjusted comparison to eliminate false positives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-02 13:01:34 +00:00
parent 74209ed266
commit 219c821df4
3 changed files with 74 additions and 28 deletions

View File

@@ -201,7 +201,7 @@ def build_articles_json(items, order=None, settings=None) -> str:
return json.dumps(articles) 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. """Import a single order into Oracle ROA.
Returns dict with: 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 is_pj = 0
cur.callproc("PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener", [ 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() partner_id = id_partener.getvalue()

View File

@@ -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}") _log_line(run_id, f"ANAF pre-populare eroare: {e}")
logger.warning(f"ANAF cache pre-population failed: {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() company_cuis = set()
for order in truly_importable: 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 "" raw_cf = import_service.clean_web_text(order.billing.company_code) or ""
bare = anaf_service.strip_ro_prefix(raw_cf) bare = anaf_service.strip_ro_prefix(raw_cf)
if anaf_service.validate_cui(bare): 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}") _log_line(run_id, f"#{order.number} CUI corectat: {raw_cf}{correct_cf}")
cod_fiscal_override = 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( result = await asyncio.to_thread(
import_service.import_single_order, import_service.import_single_order,
order, id_pol=id_pol, id_sectie=id_sectie, order, id_pol=id_pol, id_sectie=id_sectie,
app_settings=app_settings, id_gestiuni=id_gestiuni, 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) # 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"), "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_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_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_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_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, "adresa_livrare_roa": json.dumps(result.get("adresa_livrare_roa")) if result.get("adresa_livrare_roa") else None,

View File

@@ -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 -- 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) -- 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 -- 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 -- CONSTANTS
@@ -66,6 +67,7 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS
p_denumire IN VARCHAR2, p_denumire IN VARCHAR2,
p_registru IN VARCHAR2, p_registru IN VARCHAR2,
p_is_persoana_juridica IN NUMBER DEFAULT NULL, p_is_persoana_juridica IN NUMBER DEFAULT NULL,
p_strict_search IN NUMBER DEFAULT NULL,
p_id_partener OUT NUMBER); p_id_partener OUT NUMBER);
procedure cauta_sau_creeaza_adresa(p_id_part IN 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 * @param p_cod_fiscal Codul fiscal de cautat
* @return ID_PART sau NULL daca nu gaseste * @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; 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) -- 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 RETURN NUMBER IS
v_id_part NUMBER; v_id_part NUMBER;
v_cod_fiscal_curat VARCHAR2(50); v_cod_fiscal_curat VARCHAR2(50);
@@ -250,19 +255,42 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
END IF; END IF;
v_ro_cui := 'RO' || v_bare_cui; 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 BEGIN
SELECT id_part INTO v_id_part FROM ( IF p_strict_search = 1 THEN
SELECT id_part -- Cautare STRICT: doar forma primita + varianta cu spatiu
FROM nom_parteneri IF REGEXP_LIKE(v_cod_fiscal_curat, '^RO\d') THEN
WHERE UPPER(TRIM(cod_fiscal)) IN (v_bare_cui, v_ro_cui, 'RO ' || v_bare_cui) -- Input "RO123" → cauta si "RO 123"
AND NVL(sters, 0) = 0 SELECT id_part INTO v_id_part FROM (
ORDER BY NVL(inactiv, 0) ASC, SELECT id_part
CASE WHEN UPPER(TRIM(cod_fiscal)) = v_cod_fiscal_curat THEN 0 ELSE 1 END ASC, FROM nom_parteneri
id_part DESC WHERE UPPER(TRIM(cod_fiscal)) IN (v_cod_fiscal_curat, 'RO ' || v_bare_cui)
) WHERE ROWNUM = 1; 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; RETURN v_id_part;
@@ -609,6 +637,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
p_denumire IN VARCHAR2, p_denumire IN VARCHAR2,
p_registru IN VARCHAR2, p_registru IN VARCHAR2,
p_is_persoana_juridica IN NUMBER DEFAULT NULL, p_is_persoana_juridica IN NUMBER DEFAULT NULL,
p_strict_search IN NUMBER DEFAULT NULL,
p_id_partener OUT NUMBER) IS p_id_partener OUT NUMBER) IS
v_id_part NUMBER; 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) -- STEP 1: Cautare dupa cod fiscal (prioritate 1)
IF v_cod_fiscal_curat IS NOT NULL AND IF v_cod_fiscal_curat IS NOT NULL AND
LENGTH(v_cod_fiscal_curat) >= C_MIN_COD_FISCAL THEN 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 IF v_id_part IS NOT NULL THEN
-- pINFO('Partener gasit dupa cod_fiscal. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); -- 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; END IF;
-- STEP 2: Cautare dupa denumire exacta (prioritate 2) -- STEP 2: Cautare dupa denumire exacta (prioritate 2)
v_id_part := cauta_partener_dupa_denumire(v_denumire_curata); -- Skip cand cautare stricta ANAF — vrem partener nou cu CUI corect
IF p_strict_search IS NULL THEN
IF v_id_part IS NOT NULL THEN v_id_part := cauta_partener_dupa_denumire(v_denumire_curata);
-- pINFO('Partener gasit dupa denumire. ID_PART=' || v_id_part, 'IMPORT_PARTENERI');
-- pINFO('=== SFARSIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); IF v_id_part IS NOT NULL THEN
p_id_partener := v_id_part; -- pINFO('Partener gasit dupa denumire. ID_PART=' || v_id_part, 'IMPORT_PARTENERI');
RETURN; -- pINFO('=== SFARSIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI');
p_id_partener := v_id_part;
RETURN;
END IF;
END IF; END IF;
-- STEP 3: Creare partener nou -- STEP 3: Creare partener nou