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) -- 02.04.2026 - parser adrese: extrage APARTAMENT/SCARA/ETAJ embedded in strada (fix "Nr17 apartament 8") -- 02.04.2026 - fallback cautare PF cu permutari nume (evita duplicate la swap firstname/lastname) -- ==================================================================== -- CONSTANTS -- ==================================================================== -- ID utilizator sistem pentru toate operatiile C_ID_UTIL_SISTEM CONSTANT NUMBER := -3; -- Valori default pentru adrese incomplete C_JUD_DEFAULT CONSTANT VARCHAR2(50) := 'BUCURESTI'; N_ID_JUD_DEFAULT CONSTANT NUMBER(10) := 10; C_LOCALITATE_DEFAULT CONSTANT VARCHAR2(50) := 'BUCURESTI SECTORUL 1'; N_ID_LOCALITATE_DEFAULT CONSTANT NUMBER(10) := 1797; C_SECTOR_DEFAULT CONSTANT VARCHAR2(50) := 'SECTOR 1'; C_TARA_DEFAULT CONSTANT VARCHAR2(50) := 'ROMANIA'; N_ID_TARA_DEFAULT CONSTANT NUMBER(10) := 1; -- Lungimi maxime pentru validari C_MIN_COD_FISCAL CONSTANT NUMBER := 3; C_CUI_PERS_FIZICA CONSTANT NUMBER := 13; -- CNP are 13 cifre -- Variabila package pentru ultima eroare (pentru orchestrator VFP) g_last_error VARCHAR2(4000); -- ==================================================================== -- CUSTOM EXCEPTIONS -- ==================================================================== partener_invalid_exception EXCEPTION; PRAGMA EXCEPTION_INIT(partener_invalid_exception, -20001); adresa_invalid_exception EXCEPTION; PRAGMA EXCEPTION_INIT(adresa_invalid_exception, -20002); integrare_pack_def_exception EXCEPTION; PRAGMA EXCEPTION_INIT(integrare_pack_def_exception, -20003); -- ==================================================================== -- PUBLIC FUNCTIONS -- ==================================================================== /** * Procedura principala pentru cautarea sau crearea unui partener * SCHIMBAT din FUNCTION in PROCEDURE pentru compatibilitate cu DML operations * * Algoritm: * 1. Cauta dupa cod_fiscal (daca > 3 caractere) * 2. Cauta dupa denumire exacta * 3. Creeaza partener nou cu pack_def.adauga_partener() * 4. Adauga adresa cu pack_def.adauga_adresa_partener2() * * @param p_cod_fiscal Cod fiscal/CUI/CNP partener * @param p_denumire Denumirea partenerului (companie sau nume complet) * @param p_adresa Adresa in format: "JUD:Bucuresti;BUCURESTI;Str.Victoriei;10" * @param p_telefon Numar de telefon * @param p_email Adresa de email * @param p_is_persoana_juridica 1=persoana juridica, 0=persoana fizica, NULL=auto-detect prin CNP * @param p_id_partener OUT ID_PART al partenerului gasit sau creat */ PROCEDURE cauta_sau_creeaza_partener(p_cod_fiscal IN VARCHAR2, 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, p_adresa IN VARCHAR2, p_phone IN VARCHAR2, p_email IN VARCHAR2, p_id_adresa OUT NUMBER); /** * Parseaza o adresa din format semicolon in componentele individuale * * Format input: "JUD:Bucuresti;BUCURESTI;Str.Victoriei;10" * sau: "BUCURESTI;Str.Victoriei;10" * sau: "Str.Victoriei;10" * * @param p_adresa_text Textul adresei de parseat * @param p_judet OUT Judetul extras (default: Bucuresti) * @param p_localitate OUT Localitatea extrasa (default: BUCURESTI) * @param p_strada OUT Strada si numarul * @param p_sector OUT Sectorul (default: Sectorul 1) */ PROCEDURE parseaza_adresa_semicolon(p_adresa_text IN VARCHAR2, p_judet OUT VARCHAR2, p_localitate OUT VARCHAR2, p_strada OUT VARCHAR2, p_numar OUT VARCHAR2, p_sector OUT VARCHAR2, p_bloc OUT VARCHAR2, p_scara OUT VARCHAR2, p_apart OUT VARCHAR2, p_etaj OUT VARCHAR2); -- ==================================================================== -- UTILITY FUNCTIONS (PUBLIC pentru testare) -- ==================================================================== /** * Cauta partener dupa cod fiscal * @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, p_strict_search IN NUMBER DEFAULT NULL) RETURN NUMBER; /** * Cauta partener dupa denumire exacta * @param p_denumire Denumirea de cautat * @return ID_PART sau NULL daca nu gaseste */ FUNCTION cauta_partener_dupa_denumire(p_denumire IN VARCHAR2) RETURN NUMBER; /** * Verifica daca un cod fiscal apartine unei persoane fizice (CNP) * @param p_cod_fiscal Codul fiscal de verificat * @return 1 daca este persoana fizica, 0 daca este companie */ FUNCTION este_persoana_fizica(p_cod_fiscal IN VARCHAR2) RETURN NUMBER; /** * Separa numele complet in nume si prenume pentru persoane fizice * @param p_denumire_completa Numele complet * @param p_nume OUT Numele de familie * @param p_prenume OUT Prenumele */ PROCEDURE separa_nume_prenume(p_denumire_completa IN VARCHAR2, p_nume OUT VARCHAR2, p_prenume OUT VARCHAR2); -- ==================================================================== -- ERROR MANAGEMENT FUNCTIONS (similar cu PACK_JSON) -- ==================================================================== /** * Returneaza ultima eroare pentru orchestrator VFP */ FUNCTION get_last_error RETURN VARCHAR2; /** * Reseteaza eroarea */ PROCEDURE clear_error; FUNCTION strip_diacritics(p_text IN VARCHAR2) RETURN VARCHAR2; END PACK_IMPORT_PARTENERI; / CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS -- 01.04.2026 - strip_diacritics la stocare adrese si parteneri FUNCTION strip_diacritics(p_text IN VARCHAR2) RETURN VARCHAR2 IS BEGIN IF p_text IS NULL THEN RETURN NULL; END IF; RETURN TRANSLATE( UPPER(TRIM(p_text)), 'ĂăÂâÎîȘșȚțŞşŢţ', 'AAAAIISSTTSSTT' ); END strip_diacritics; -- ================================================================ -- ERROR MANAGEMENT FUNCTIONS IMPLEMENTATION -- ================================================================ FUNCTION get_last_error RETURN VARCHAR2 IS BEGIN RETURN g_last_error; END get_last_error; PROCEDURE clear_error IS BEGIN g_last_error := NULL; END clear_error; -- ==================================================================== -- PRIVATE FUNCTIONS -- ==================================================================== /** * Valideaza datele unui partener inainte de creare */ FUNCTION valideaza_date_partener(p_cod_fiscal IN VARCHAR2, p_denumire IN VARCHAR2) RETURN BOOLEAN IS BEGIN -- Verificari obligatorii IF p_denumire IS NULL THEN g_last_error := 'Denumirea partenerului nu poate fi goala'; RETURN FALSE; END IF; -- Cod fiscal optional, dar daca exista trebuie sa aiba minim 3 caractere IF p_cod_fiscal IS NOT NULL AND LENGTH(TRIM(p_cod_fiscal)) > 0 THEN IF LENGTH(TRIM(p_cod_fiscal)) < C_MIN_COD_FISCAL THEN g_last_error := 'Codul fiscal trebuie sa aiba minim ' || C_MIN_COD_FISCAL || ' caractere'; RETURN FALSE; END IF; END IF; RETURN TRUE; EXCEPTION WHEN OTHERS THEN g_last_error := 'ERROR in valideaza_date_partener: ' || SQLERRM; RETURN FALSE; END valideaza_date_partener; /** * Curata si standardizeaza textul pentru cautare */ FUNCTION curata_text_cautare(p_text IN VARCHAR2) RETURN VARCHAR2 IS BEGIN IF p_text IS NULL THEN RETURN NULL; END IF; RETURN UPPER(TRIM(p_text)); END curata_text_cautare; -- ==================================================================== -- PUBLIC FUNCTIONS IMPLEMENTATION -- ==================================================================== -- 01.04.2026 - cautare duala cod_fiscal cu/fara prefix RO (anti-duplicare parteneri) -- 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); v_bare_cui VARCHAR2(50); v_ro_cui VARCHAR2(52); BEGIN IF p_cod_fiscal IS NULL OR LENGTH(TRIM(p_cod_fiscal)) < C_MIN_COD_FISCAL THEN RETURN NULL; END IF; v_cod_fiscal_curat := UPPER(TRIM(p_cod_fiscal)); -- Extract bare CUI (without RO prefix) IF REGEXP_LIKE(v_cod_fiscal_curat, '^RO\s*\d') THEN v_bare_cui := TRIM(REGEXP_REPLACE(v_cod_fiscal_curat, '^RO\s*', '')); ELSE v_bare_cui := v_cod_fiscal_curat; END IF; v_ro_cui := 'RO' || v_bare_cui; BEGIN 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; EXCEPTION WHEN NO_DATA_FOUND THEN RETURN NULL; END; EXCEPTION WHEN OTHERS THEN pINFO('ERROR in cauta_partener_dupa_cod_fiscal: ' || SQLERRM, 'IMPORT_PARTENERI'); RAISE; END cauta_partener_dupa_cod_fiscal; FUNCTION cauta_partener_dupa_denumire(p_denumire IN VARCHAR2) RETURN NUMBER IS v_id_part NUMBER; v_denumire_curata VARCHAR2(200); BEGIN -- Validare input IF p_denumire IS NULL THEN RETURN NULL; END IF; v_denumire_curata := curata_text_cautare(p_denumire); -- pINFO('Cautare partener dupa denumire: ' || v_denumire_curata, 'IMPORT_PARTENERI'); -- Cautare in NOM_PARTENERI BEGIN SELECT id_part INTO v_id_part FROM nom_parteneri WHERE UPPER(TRIM(denumire)) = v_denumire_curata AND ROWNUM = 1; -- In caz de duplicate, luam primul -- pINFO('Gasit partener cu denumirea ' || v_denumire_curata || ': ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); RETURN v_id_part; EXCEPTION WHEN NO_DATA_FOUND THEN -- pINFO('Nu s-a gasit partener cu denumirea: ' || v_denumire_curata, 'IMPORT_PARTENERI'); RETURN NULL; WHEN TOO_MANY_ROWS THEN -- Luam primul gasit SELECT id_part INTO v_id_part FROM (SELECT id_part FROM nom_parteneri WHERE UPPER(TRIM(denumire)) = v_denumire_curata ORDER BY id_part) WHERE ROWNUM = 1; pINFO('WARNING: Multiple parteneri cu aceeasi denumire ' || v_denumire_curata || '. Selectat ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); RETURN v_id_part; END; EXCEPTION WHEN OTHERS THEN pINFO('ERROR in cauta_partener_dupa_denumire: ' || SQLERRM, 'IMPORT_PARTENERI'); RAISE; END cauta_partener_dupa_denumire; FUNCTION este_persoana_fizica(p_cod_fiscal IN VARCHAR2) RETURN NUMBER IS v_cod_curat VARCHAR2(50); BEGIN IF p_cod_fiscal IS NULL THEN RETURN 0; END IF; v_cod_curat := TRIM(p_cod_fiscal); -- CNP-ul are exact 13 cifre IF LENGTH(v_cod_curat) = C_CUI_PERS_FIZICA AND REGEXP_LIKE(v_cod_curat, '^[0-9]{13}$') THEN RETURN 1; END IF; RETURN 0; EXCEPTION WHEN OTHERS THEN -- pINFO('ERROR in este_persoana_fizica: ' || SQLERRM, 'IMPORT_PARTENERI'); RETURN 0; END este_persoana_fizica; PROCEDURE separa_nume_prenume(p_denumire_completa IN VARCHAR2, p_nume OUT VARCHAR2, p_prenume OUT VARCHAR2) IS v_pozitie_spatiu NUMBER; v_denumire_curata VARCHAR2(200); BEGIN IF p_denumire_completa IS NULL THEN p_nume := NULL; p_prenume := NULL; RETURN; END IF; v_denumire_curata := TRIM(p_denumire_completa); -- Cauta primul spatiu v_pozitie_spatiu := INSTR(v_denumire_curata, ' '); IF v_pozitie_spatiu > 0 THEN -- Numele = prima parte p_nume := TRIM(SUBSTR(v_denumire_curata, 1, v_pozitie_spatiu - 1)); -- Prenumele = restul p_prenume := TRIM(SUBSTR(v_denumire_curata, v_pozitie_spatiu + 1)); ELSE -- Nu exista spatiu, totul este nume p_nume := v_denumire_curata; p_prenume := NULL; END IF; -- Validare lungimi maxime (sa nu depaseasca limitele tabelei) IF LENGTH(p_nume) > 50 THEN p_nume := SUBSTR(p_nume, 1, 50); END IF; IF LENGTH(p_prenume) > 50 THEN p_prenume := SUBSTR(p_prenume, 1, 50); END IF; EXCEPTION WHEN OTHERS THEN -- pINFO('ERROR in separa_nume_prenume: ' || SQLERRM, 'IMPORT_PARTENERI'); p_nume := SUBSTR(p_denumire_completa, 1, 50); -- fallback p_prenume := NULL; END separa_nume_prenume; -- 31.03.2026 - parser inteligent: split numar in bloc/scara/apart/etaj (fix ORA-12899 pe NUMAR max 10 chars) PROCEDURE parseaza_adresa_semicolon(p_adresa_text IN VARCHAR2, p_judet OUT VARCHAR2, p_localitate OUT VARCHAR2, p_strada OUT VARCHAR2, p_numar OUT VARCHAR2, p_sector OUT VARCHAR2, p_bloc OUT VARCHAR2, p_scara OUT VARCHAR2, p_apart OUT VARCHAR2, p_etaj OUT VARCHAR2) IS v_adresa_curata VARCHAR2(500); v_componente SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(); v_count NUMBER; v_temp_judet VARCHAR2(100); v_pozitie NUMBER; v_strada VARCHAR2(100); -- variabile pentru parsarea inteligenta a numarului v_raw_numar VARCHAR2(500); v_token VARCHAR2(200); v_token_upper VARCHAR2(200); v_rest_parts VARCHAR2(500); v_tok_pos NUMBER; v_tok_idx NUMBER; BEGIN -- p_adresa_text: JUD: JUDET;LOCALITATE;STRADA, NR -- Initializare cu valori default p_judet := C_JUD_DEFAULT; p_localitate := C_LOCALITATE_DEFAULT; p_strada := NULL; p_numar := NULL; p_sector := C_SECTOR_DEFAULT; p_bloc := NULL; p_scara := NULL; p_apart := NULL; p_etaj := NULL; -- Validare input IF p_adresa_text IS NULL THEN RETURN; END IF; v_adresa_curata := TRIM(p_adresa_text); -- Split dupa semicolon SELECT TRIM(REGEXP_SUBSTR(v_adresa_curata, '[^;]+', 1, LEVEL)) BULK COLLECT INTO v_componente FROM DUAL CONNECT BY REGEXP_SUBSTR(v_adresa_curata, '[^;]+', 1, LEVEL) IS NOT NULL; v_count := v_componente.COUNT; IF v_count = 0 THEN RETURN; END IF; -- Parsare in functie de numarul de componente IF v_count = 1 THEN -- Doar strada p_strada := SUBSTR(v_componente(1), 1, 100); ELSIF v_count = 2 THEN -- Localitate;Strada p_localitate := SUBSTR(v_componente(1), 1, 100); p_strada := SUBSTR(v_componente(2), 1, 100); ELSIF v_count >= 3 THEN -- Verifica daca prima componenta contine "JUD:" v_temp_judet := v_componente(1); IF UPPER(v_temp_judet) LIKE 'JUD:%' THEN -- Format: JUD:Bucuresti;BUCURESTI;Strada,Numar p_judet := SUBSTR(REPLACE(v_temp_judet, 'JUD:', ''), 1, 100); p_localitate := SUBSTR(v_componente(2), 1, 100); p_strada := SUBSTR(v_componente(3), 1, 100); v_strada := p_strada; -- Separa strada de tot ce e dupa prima virgula v_pozitie := INSTR(v_strada, ','); IF v_pozitie > 0 THEN p_strada := TRIM(SUBSTR(v_strada, 1, v_pozitie - 1)); v_raw_numar := TRIM(SUBSTR(v_strada, v_pozitie + 1)); END IF; ELSE -- Format: Localitate;Strada;Altceva p_localitate := SUBSTR(v_componente(1), 1, 100); p_strada := SUBSTR(v_componente(2) || ' ' || v_componente(3), 1, 100); END IF; END IF; -- Pre-processing: extrage NR/BLOC embedded in p_strada (spatiu-separate, fara virgula) -- Ex: "STR.DACIA NR.15 BLOC Z2" → strada="STR.DACIA", numar="15", bloc="Z2" -- Trebuie facut INAINTE de parsarea tokenilor din v_raw_numar IF p_strada IS NOT NULL THEN v_token_upper := UPPER(p_strada); -- Extrage NR din strada IF REGEXP_LIKE(v_token_upper, '(\s)(NUMARUL|NUMAR|NR\.?)\s*(\S+)') THEN p_numar := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(NUMARUL|NUMAR|NR\.?)\s*(\S+).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(NUMARUL|NUMAR|NR\.?)\s*\S+', '', 1, 1, 'i')); END IF; -- Extrage BLOC din strada IF REGEXP_LIKE(v_token_upper, '(\s)(BLOC|BL\.?)\s*(\S+)') THEN p_bloc := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(BLOC|BL\.?)\s*(\S+).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(BLOC|BL\.?)\s*\S+', '', 1, 1, 'i')); END IF; -- Re-read v_token_upper after BLOC removal may have changed p_strada v_token_upper := UPPER(p_strada); -- Extrage APARTAMENT din strada (ex: "George Enescu apartament 8") -- Separator [\s.:] obligatoriu dupa prefix scurt (AP) pt a evita false-positives (ex: "APATEULUI") IF REGEXP_LIKE(v_token_upper, '(\s)(APARTAMENT|APART\.?|AP\.?)[\s.:]+(\S+)') THEN p_apart := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(APARTAMENT|APART\.?|AP\.?)[\s.:]+(\S+).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(APARTAMENT|APART\.?|AP\.?)[\s.:]+\S+', '', 1, 1, 'i')); -- Fallback: "apart14" sau "ap14" — keyword lipit direct de cifra (sigur, nu exista cuvinte AP+cifra) ELSIF REGEXP_LIKE(v_token_upper, '(\s)(APART|AP)(\d\S*)') THEN p_apart := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(APART|AP)(\d\S*).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(APART|AP)\d\S*', '', 1, 1, 'i')); END IF; v_token_upper := UPPER(p_strada); -- Extrage SCARA din strada (ex: "Str Dacia Nr5 scara B") IF REGEXP_LIKE(v_token_upper, '(\s)(SCARA|SC\.?)[\s.:]+(\S+)') THEN p_scara := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(SCARA|SC\.?)[\s.:]+(\S+).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(SCARA|SC\.?)[\s.:]+\S+', '', 1, 1, 'i')); -- Fallback: "scara3" sau "sc1" — keyword lipit direct de cifra ELSIF REGEXP_LIKE(v_token_upper, '(\s)(SCARA|SC)(\d\S*)') THEN p_scara := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(SCARA|SC)(\d\S*).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(SCARA|SC)\d\S*', '', 1, 1, 'i')); END IF; v_token_upper := UPPER(p_strada); -- Extrage ETAJ din strada (ex: "Str Dacia Nr5 etaj 2") IF REGEXP_LIKE(v_token_upper, '(\s)(ETAJ|ET\.?)[\s.:]+(\S+)') THEN p_etaj := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(ETAJ|ET\.?)[\s.:]+(\S+).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(ETAJ|ET\.?)[\s.:]+\S+', '', 1, 1, 'i')); -- Fallback: "etaj2" sau "et2" — keyword lipit direct de cifra ELSIF REGEXP_LIKE(v_token_upper, '(\s)(ETAJ|ET)(\d\S*)') THEN p_etaj := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(ETAJ|ET)(\d\S*).*', '\3', 1, 1)); p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(ETAJ|ET)\d\S*', '', 1, 1, 'i')); END IF; END IF; -- ================================================================ -- Parser inteligent: split v_raw_numar in numar/bloc/scara/apart/etaj -- Tokenii sunt separati prin virgula -- Patterns: NR/NUMAR, BL/BLOC, SC/SCARA, AP/APART, ET/ETAJ -- ================================================================ IF v_raw_numar IS NOT NULL THEN -- Loop prin tokeni separati de virgula (fara BULK COLLECT — compatibil Oracle 11) v_rest_parts := NULL; v_tok_idx := 0; v_raw_numar := v_raw_numar || ','; -- sentinel pentru ultimul token LOOP v_tok_pos := INSTR(v_raw_numar, ','); EXIT WHEN v_tok_pos = 0 OR v_raw_numar IS NULL; v_token := TRIM(SUBSTR(v_raw_numar, 1, v_tok_pos - 1)); v_raw_numar := SUBSTR(v_raw_numar, v_tok_pos + 1); v_tok_idx := v_tok_idx + 1; IF v_token IS NULL THEN CONTINUE; END IF; v_token_upper := UPPER(v_token); -- Longer match first; (\s|\.) handles both "BL A2" and "BL.A2" and "AP.7" IF REGEXP_LIKE(v_token_upper, '^(BLOC|BL\.?)(\s|\.)') THEN p_bloc := TRIM(REGEXP_REPLACE(v_token, '^(BLOC|BL\.?)(\s|\.)*', '', 1, 1, 'i')); ELSIF REGEXP_LIKE(v_token_upper, '^(SCARA|SC\.?)(\s|\.)') THEN p_scara := TRIM(REGEXP_REPLACE(v_token, '^(SCARA|SC\.?)(\s|\.)*', '', 1, 1, 'i')); ELSIF REGEXP_LIKE(v_token_upper, '^(APARTAMENT|APART\.?|AP\.?)(\s|\.)') THEN p_apart := TRIM(REGEXP_REPLACE(v_token, '^(APARTAMENT|APART\.?|AP\.?)(\s|\.)*', '', 1, 1, 'i')); ELSIF REGEXP_LIKE(v_token_upper, '^(ETAJ|ET\.?)(\s|\.)') THEN p_etaj := TRIM(REGEXP_REPLACE(v_token, '^(ETAJ|ET\.?)(\s|\.)*', '', 1, 1, 'i')); ELSIF REGEXP_LIKE(v_token_upper, '^(NUMARUL|NUMAR|NR\.?)(\s|\.)') THEN p_numar := TRIM(REGEXP_REPLACE(v_token, '^(NUMARUL|NUMAR|NR\.?)(\s|\.)*', '', 1, 1, 'i')); ELSE -- Primul token necunoscut devine numar (daca numar e inca gol) IF p_numar IS NULL AND v_tok_idx = 1 THEN p_numar := v_token; ELSE -- Restul (cartier, sat, indicatii) se adauga la strada IF v_rest_parts IS NOT NULL THEN v_rest_parts := v_rest_parts || ', ' || v_token; ELSE v_rest_parts := v_token; END IF; END IF; END IF; END LOOP; -- Adauga restul la strada IF v_rest_parts IS NOT NULL THEN p_strada := SUBSTR(p_strada || ', ' || v_rest_parts, 1, 100); END IF; END IF; -- Curatare finala p_judet := UPPER(TRIM(p_judet)); p_localitate := UPPER(TRIM(p_localitate)); p_strada := UPPER(TRIM(p_strada)); p_numar := UPPER(TRIM(p_numar)); p_sector := UPPER(TRIM(p_sector)); p_bloc := UPPER(TRIM(p_bloc)); p_scara := UPPER(TRIM(p_scara)); p_apart := UPPER(TRIM(p_apart)); p_etaj := UPPER(TRIM(p_etaj)); -- Truncare de siguranta (limita coloanelor Oracle) p_numar := SUBSTR(p_numar, 1, 10); p_bloc := SUBSTR(p_bloc, 1, 30); p_scara := SUBSTR(p_scara, 1, 10); p_apart := SUBSTR(p_apart, 1, 10); p_etaj := SUBSTR(p_etaj, 1, 20); -- Fallback pentru campuri goale IF p_judet IS NULL THEN p_judet := C_JUD_DEFAULT; END IF; IF p_localitate IS NULL THEN p_localitate := C_LOCALITATE_DEFAULT; END IF; IF p_sector IS NULL THEN p_sector := C_SECTOR_DEFAULT; END IF; EXCEPTION WHEN OTHERS THEN g_last_error := 'ERROR in parseaza_adresa_semicolon: ' || SQLERRM; -- Pastram valorile default in caz de eroare p_judet := C_JUD_DEFAULT; p_localitate := C_LOCALITATE_DEFAULT; p_sector := C_SECTOR_DEFAULT; END parseaza_adresa_semicolon; PROCEDURE cauta_sau_creeaza_partener(p_cod_fiscal IN VARCHAR2, 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; v_este_persoana_fizica NUMBER; v_nume VARCHAR2(50); v_prenume VARCHAR2(50); -- Date pentru pack_def v_cod_fiscal_curat VARCHAR2(50); v_denumire_curata VARCHAR2(200); -- Permutari nume PF (Step 2b) v_word1 VARCHAR2(100); v_word2 VARCHAR2(100); v_word3 VARCHAR2(100); v_pos1 NUMBER; v_pos2 NUMBER; BEGIN -- Resetare eroare la inceputul procesarii clear_error; -- pINFO('=== INCEPUT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); -- pINFO('Input: cod_fiscal=' || NVL(p_cod_fiscal, 'NULL') || -- ', denumire=' || NVL(p_denumire, 'NULL') || -- ', adresa=' || NVL(p_adresa, 'NULL'), 'IMPORT_PARTENERI'); -- Validare date input IF NOT valideaza_date_partener(p_cod_fiscal, p_denumire) THEN g_last_error := 'Date partener invalide - validare esuata'; p_id_partener := -1; RETURN; END IF; v_cod_fiscal_curat := TRIM(p_cod_fiscal); v_denumire_curata := UPPER(TRIM(p_denumire)); -- 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, p_strict_search); IF v_id_part IS NOT NULL THEN -- pINFO('Partener gasit dupa cod_fiscal. 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 2: Cautare dupa denumire exacta (prioritate 2) -- 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 2b: Cautare cu permutari nume (doar persoane fizice, 2-3 cuvinte) -- Rezolva cazul cand clientul inverseaza firstname/lastname in GoMag IF p_strict_search IS NULL AND (p_is_persoana_juridica IS NOT NULL AND p_is_persoana_juridica = 0) THEN v_pos1 := INSTR(v_denumire_curata, ' '); IF v_pos1 > 0 THEN v_word1 := TRIM(SUBSTR(v_denumire_curata, 1, v_pos1 - 1)); v_pos2 := INSTR(v_denumire_curata, ' ', v_pos1 + 1); IF v_pos2 = 0 THEN -- 2 cuvinte: incearca inversarea "WORD2 WORD1" v_word2 := TRIM(SUBSTR(v_denumire_curata, v_pos1 + 1)); v_id_part := cauta_partener_dupa_denumire(v_word2 || ' ' || v_word1); IF v_id_part IS NOT NULL THEN pINFO('Partener PF gasit prin inversare nume: ' || v_denumire_curata || ' -> ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); p_id_partener := v_id_part; RETURN; END IF; ELSE -- 3 cuvinte: incearca toate permutatiile (5 ramase, originala deja incercata) v_word2 := TRIM(SUBSTR(v_denumire_curata, v_pos1 + 1, v_pos2 - v_pos1 - 1)); v_word3 := TRIM(SUBSTR(v_denumire_curata, v_pos2 + 1)); -- Permutari: W1 W3 W2, W2 W1 W3, W2 W3 W1, W3 W1 W2, W3 W2 W1 FOR i IN 1..5 LOOP v_id_part := cauta_partener_dupa_denumire( CASE i WHEN 1 THEN v_word1 || ' ' || v_word3 || ' ' || v_word2 WHEN 2 THEN v_word2 || ' ' || v_word1 || ' ' || v_word3 WHEN 3 THEN v_word2 || ' ' || v_word3 || ' ' || v_word1 WHEN 4 THEN v_word3 || ' ' || v_word1 || ' ' || v_word2 WHEN 5 THEN v_word3 || ' ' || v_word2 || ' ' || v_word1 END ); IF v_id_part IS NOT NULL THEN pINFO('Partener PF gasit prin permutare nume: ' || v_denumire_curata || ' -> ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); p_id_partener := v_id_part; RETURN; END IF; END LOOP; END IF; END IF; END IF; -- STEP 3: Creare partener nou -- pINFO('Nu s-a gasit partener existent. Se creeaza unul nou...', 'IMPORT_PARTENERI'); -- Verifica tipul partenerului -- Prioritate: parametru explicit > detectie prin CNP IF p_is_persoana_juridica IS NOT NULL THEN -- Foloseste informatia explicita din GoMag orders v_este_persoana_fizica := CASE WHEN p_is_persoana_juridica = 1 THEN 0 ELSE 1 END; ELSE -- Auto-detect prin CNP (comportament original) v_este_persoana_fizica := este_persoana_fizica(v_cod_fiscal_curat); END IF; IF v_este_persoana_fizica = 1 THEN -- pINFO('Detectata persoana fizica (CUI 13 cifre)', 'IMPORT_PARTENERI'); separa_nume_prenume(v_denumire_curata, v_nume, v_prenume); v_nume := UPPER(v_nume); v_prenume := UPPER(v_prenume); -- pINFO('Nume separat: NUME=' || NVL(v_nume, 'NULL') || ', PRENUME=' || NVL(v_prenume, 'NULL'), 'IMPORT_PARTENERI'); END IF; -- Strip diacritics from partner name before storage v_denumire_curata := strip_diacritics(v_denumire_curata); -- Creare partener prin pack_def BEGIN IF v_este_persoana_fizica = 1 THEN -- Pentru persoane fizice pack_def.adauga_partener(tcDenumire => v_nume || ' ' || v_prenume, tcNume => v_nume, tcPrenume => v_prenume, tcCod_fiscal => v_cod_fiscal_curat, tcReg_comert => p_registru, tnId_loc => NULL, tnId_categorie_entitate => NULL, tcPrefix => '', tcSufix => '', tnTip_persoana => 2, -- persoana fizica tcBanca => '', -- nu avem info bancara tcCont_banca => '', -- nu avem info bancara tnInactiv => 0, tcMotiv_inactiv => '', tnId_util => C_ID_UTIL_SISTEM, tcSir_id_tipPart => '16;17', tcSir_id_part_del => '', tnId_Part => v_id_part); ELSE -- Pentru companii pack_def.adauga_partener(tcDenumire => v_denumire_curata, tcNume => v_denumire_curata, tcPrenume => '', tcCod_fiscal => v_cod_fiscal_curat, tcReg_comert => p_registru, tnId_loc => NULL, tnId_categorie_entitate => NULL, tcPrefix => '', tcSufix => '', tnTip_persoana => 1, -- persoana juridica tcBanca => '', -- nu avem info bancara tcCont_banca => '', -- nu avem info bancara tnInactiv => 0, tcMotiv_inactiv => '', tnId_util => C_ID_UTIL_SISTEM, tcSir_id_tipPart => '16;17', tcSir_id_part_del => '', tnId_Part => v_id_part); END IF; IF v_id_part IS NULL OR v_id_part <= 0 THEN g_last_error := 'pack_def.adauga_partener a returnat ID invalid'; p_id_partener := -1; RETURN; END IF; -- pINFO('Partener creat cu succes. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); EXCEPTION WHEN OTHERS THEN g_last_error := 'ERROR la crearea partenerului prin pack_def: ' || SQLERRM; p_id_partener := -1; RETURN; END; -- pINFO('Partener creat complet. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); -- pINFO('=== SFARSIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); p_id_partener := v_id_part; EXCEPTION WHEN OTHERS THEN g_last_error := 'ERROR NEASTEPTAT in cauta_sau_creeaza_partener: ' || SQLERRM; p_id_partener := -1; END cauta_sau_creeaza_partener; procedure cauta_sau_creeaza_adresa(p_id_part IN NUMBER, p_adresa IN VARCHAR2, p_phone IN VARCHAR2, p_email IN VARCHAR2, p_id_adresa OUT NUMBER) is v_judet VARCHAR2(200); v_id_judet NUMBER(10); v_localitate VARCHAR2(200); v_id_localitate NUMBER(10); v_strada VARCHAR2(1000); v_numar VARCHAR2(1000); v_sector VARCHAR2(100); v_bloc VARCHAR2(30); v_scara VARCHAR2(10); v_apart VARCHAR2(10); v_etaj VARCHAR2(20); v_id_tara NUMBER(10); v_principala NUMBER(1); begin -- Resetare eroare la inceputul procesarii clear_error; IF p_id_part is null OR p_adresa IS NULL THEN GOTO sfarsit; END IF; -- pINFO('Se adauga adresa pentru partenerul nou creat...', 'IMPORT_PARTENERI'); -- Verific daca exista o adresa principala SELECT DECODE(nr, 0, 1, 0) INTO v_principala FROM (SELECT count(id_adresa) nr from vadrese_parteneri where id_part = p_id_part and principala = 1); -- Parseaza adresa (cu split inteligent numar/bloc/scara/apart/etaj) parseaza_adresa_semicolon(p_adresa, v_judet, v_localitate, v_strada, v_numar, v_sector, v_bloc, v_scara, v_apart, v_etaj); -- 01.04.2026 - cautare adresa pe strada + diacritics + id_loc validation -- TIER 1: county + city + street (diacritics normalized) + valid id_loc begin select id_adresa into p_id_adresa from ( select id_adresa from vadrese_parteneri where id_part = p_id_part and judet = v_judet and localitate = v_localitate and strip_diacritics(strada) = strip_diacritics(v_strada) and id_loc IS NOT NULL order by principala desc, id_adresa desc ) where rownum = 1; exception when NO_DATA_FOUND then p_id_adresa := null; end; -- TIER 2: county + city (no street) but ONLY with valid id_loc if p_id_adresa is null then begin select id_adresa into p_id_adresa from ( select id_adresa from vadrese_parteneri where id_part = p_id_part and judet = v_judet and localitate = v_localitate and id_loc IS NOT NULL order by principala desc, id_adresa desc ) where rownum = 1; exception when NO_DATA_FOUND then p_id_adresa := null; end; end if; -- Adaug o adresa if p_id_adresa is null then -- caut judetul begin select id_judet into v_id_judet from syn_nom_judete where judet = v_judet and sters = 0; exception when NO_DATA_FOUND then v_id_judet := N_ID_JUD_DEFAULT; end; -- caut localitatea begin select id_loc, id_judet, id_tara into v_id_localitate, v_id_judet, v_id_tara from (select id_loc, id_judet, id_tara, rownum rn from syn_nom_localitati l where id_judet = v_id_judet and localitate = v_localitate and inactiv = 0 and sters = 0 order by localitate) where rn = 1; exception when NO_DATA_FOUND then begin select id_loc, id_judet, id_tara into v_id_localitate, v_id_judet, v_id_tara from (select id_loc, id_judet, id_tara, rownum rn from syn_nom_localitati l where id_judet = v_id_judet and inactiv = 0 and sters = 0 order by localitate) where rn = 1; exception when NO_DATA_FOUND then v_id_localitate := N_ID_LOCALITATE_DEFAULT; v_id_judet := N_ID_JUD_DEFAULT; v_id_tara := N_ID_TARA_DEFAULT; end; end; -- 01.04.2026 - strip_diacritics la stocare adrese v_strada := strip_diacritics(v_strada); v_localitate := strip_diacritics(v_localitate); v_numar := strip_diacritics(v_numar); v_bloc := strip_diacritics(v_bloc); BEGIN pack_def.adauga_adresa_partener2(tnId_part => p_id_part, tcDenumire_adresa => NULL, tnDA_apare => 0, tcStrada => v_strada, tcNumar => v_numar, tcBloc => v_bloc, tcScara => v_scara, tcApart => v_apart, tnEtaj => v_etaj, tnId_loc => v_id_localitate, tcLocalitate => v_localitate, tnId_judet => v_id_judet, tnCodpostal => NULL, tnId_tara => v_id_tara, tcTelefon1 => p_phone, tcTelefon2 => NULL, tcFax => NULL, tcEmail => p_email, tcWeb => NULL, tnPrincipala => to_char(v_principala), tnInactiv => 0, tnId_util => C_ID_UTIL_SISTEM, tnIdAdresa => p_id_adresa); IF p_id_adresa IS NOT NULL AND p_id_adresa > 0 THEN -- pINFO('Adresa adaugata cu succes. ID_ADRESA=' || p_id_adresa, 'IMPORT_PARTENERI'); NULL; ELSE g_last_error := 'WARNING: pack_def.adauga_adresa_partener2 a returnat ID invalid: ' || NVL(TO_CHAR(p_id_adresa), 'NULL'); -- pINFO('WARNING: pack_def.adauga_adresa_partener2 a returnat ID invalid: ' || NVL(TO_CHAR(p_id_adresa), 'NULL'), 'IMPORT_PARTENERI'); END IF; EXCEPTION WHEN OTHERS THEN g_last_error := 'ERROR la adaugarea adresei prin pack_def: ' || SQLERRM; -- pINFO('ERROR la adaugarea adresei prin pack_def: ' || SQLERRM, 'IMPORT_PARTENERI'); -- Nu raisam exceptia pentru adresa, partenerii pot exista fara adresa -- pINFO('Partenerul a fost creat, dar adresa nu a putut fi adaugata', 'IMPORT_PARTENERI'); END; END IF; <> null; end; END PACK_IMPORT_PARTENERI; /