Doua root cause-uri pentru ORA-12899 la importul comenzii #485841056: 1. Oracle ORA-06502: v_apart/v_scara/v_bloc/v_etaj in cauta_sau_creeaza_adresa declarate VARCHAR2(10/20/30) → Oracle mostenea constrangerea pe OUT parametrii din parseaza_adresa_semicolon → crash INAINTE de fix-ul overflow de la linia 521. Fix: marite la VARCHAR2(100). 2. Python format_address_for_oracle stripuia doar city exact, nu si 'city region' sau 'region city'. GoMag trimite adresa cu suffix 'Municipiul Bucuresti Bucuresti' (city+region) → token urias pentru apartament → declansa ORA-06502 de mai sus. Fix: incearca toate combinatiile city+region, region+city, city, region. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1157 lines
49 KiB
Plaintext
1157 lines
49 KiB
Plaintext
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)
|
|
-- 06.04.2026 - eliminat TIER 2 cautare adresa (judet+loc fara strada) — creeaza adresa noua cand strada difera
|
|
-- 06.04.2026 - fix strip_diacritics: UNISTR encoding-safe (TRANSLATE cu UTF-8 literal se corupea pe Windows)
|
|
-- 06.04.2026 - fix TIER 1: strip_diacritics si pe localitate (nu doar strada)
|
|
-- 07.04.2026 - fix parser adrese: inserare virgule inaintea keywords, tokeni lipiti (Ap78), strip localitate din strada
|
|
-- 07.04.2026 - fix duplicate: normalize localitate + resolve id_localitate inainte de TIER 1 (match pe id_loc)
|
|
-- 07.04.2026 - fix localitate necunoscuta: SOUNDEX fuzzy match (TIER L2) + pastreaza judetul in L3
|
|
-- 08.04.2026 - fix parser: inserare virgule in strada inainte de comma-split (sc/ap/et nu se extrageau fara virgula)
|
|
-- 15.04.2026 - fix cauta_partener_dupa_denumire: exclude sters=1, prioritizeaza inactiv=0 (bug GoMag #484668145)
|
|
-- 16.04.2026 - fix cauta_partener_dupa_cod_fiscal strict mode: regex detectie RO tolereaza spatiu (^RO\s*\d),
|
|
-- IN-set foloseste v_ro_cui (canonic) in loc de v_cod_fiscal_curat. Regula business platitor/
|
|
-- neplatitor pastrata. Bug anterior: input "RO 34963277" cadea pe branch neplatitor, rata partener
|
|
-- existent "RO34963277" → duplicat FG COFFE #485065210.
|
|
-- 22.04.2026 - fix numar overflow: prima componenta ramane numar; "SAT X" → p_localitate (satul
|
|
-- = localitate, TIER L1/L2/L3 existent rezolva id_loc); landmark → strada;
|
|
-- COM/ORAS/MUN ignorate (deja in p_localitate din GoMag city)
|
|
-- 23.04.2026 - hardening: SUBSTR(1,10) neconditionat dupa split, blocheaza
|
|
-- overflow rezidual pe prefix lung fara spatiu in primii 10 char.
|
|
-- 28.04.2026 - fix ORA-06502: v_bloc/scara/apart/etaj in cauta_sau_creeaza_adresa
|
|
-- marite la VARCHAR2(100) — Oracle OUT param mostenea constrangerea
|
|
-- VARCHAR2(10) si cadea pe "apartament 140 interfon 140 Municipiul..."
|
|
|
|
-- ====================================================================
|
|
-- 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
|
|
-- 06.04.2026 - fix: UNISTR encoding-safe (TRANSLATE cu UTF-8 literal se corupea pe Windows sqlplus)
|
|
-- Hybrid: REPLACE comma-below Ș/Ț → cedilla Ş/Ţ, apoi CONVERT US7ASCII (strips Ă/Â/Î/Ş/Ţ)
|
|
FUNCTION strip_diacritics(p_text IN VARCHAR2) RETURN VARCHAR2 IS
|
|
BEGIN
|
|
IF p_text IS NULL THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
RETURN CONVERT(
|
|
UPPER(TRIM(
|
|
REPLACE(REPLACE(REPLACE(REPLACE(
|
|
p_text,
|
|
UNISTR('\0218'), UNISTR('\015E')), -- Ș → Ş (comma-below → cedilla)
|
|
UNISTR('\0219'), UNISTR('\015F')), -- ș → ş
|
|
UNISTR('\021A'), UNISTR('\0162')), -- Ț → Ţ
|
|
UNISTR('\021B'), UNISTR('\0163')) -- ț → ţ
|
|
)),
|
|
'US7ASCII', 'AL32UTF8'
|
|
);
|
|
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: regula business ANAF platitor/neplatitor TVA
|
|
-- Platitor (prefix RO) → cauta doar RO<bare> si RO <bare> (cu spatiu)
|
|
-- Neplatitor (fara RO) → cauta doar <bare>
|
|
-- Nu cross-match intre platitor si neplatitor (entitati fiscal distincte).
|
|
IF REGEXP_LIKE(v_cod_fiscal_curat, '^RO\s*\d') THEN
|
|
-- Input "RO123" sau "RO 123" (platitor TVA) → cauta RO<bare> si RO <bare>
|
|
SELECT id_part INTO v_id_part FROM (
|
|
SELECT id_part
|
|
FROM nom_parteneri
|
|
WHERE UPPER(TRIM(cod_fiscal)) IN (v_ro_cui, '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);
|
|
|
|
-- Cautare in NOM_PARTENERI - exclude sters=1, prioritizeaza active (inactiv=0)
|
|
BEGIN
|
|
SELECT id_part INTO v_id_part FROM (
|
|
SELECT id_part
|
|
FROM nom_parteneri
|
|
WHERE UPPER(TRIM(denumire)) = v_denumire_curata
|
|
AND NVL(sters, 0) = 0
|
|
ORDER BY NVL(inactiv, 0) ASC, id_part DESC
|
|
) WHERE ROWNUM = 1;
|
|
|
|
RETURN v_id_part;
|
|
|
|
EXCEPTION
|
|
WHEN NO_DATA_FOUND THEN
|
|
RETURN NULL;
|
|
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)
|
|
-- 08.04.2026 - fix: inserare virgule in strada inainte de comma-split (sc/ap/et nu se extrageau fara virgula)
|
|
-- 08.04.2026 - fix: BLOC/NR regex require separator (fix false-positive on BLOCURI neighborhood name)
|
|
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;
|
|
|
|
-- 08.04.2026 - insert commas before address keywords so comma-split always fires
|
|
-- Reuses same regex as v_raw_numar comma insertion (lines below)
|
|
-- Ex: "Str X nr 26 bl 6 sc 2 ap 36" → "Str X,nr 26,bl 6,sc 2,ap 36"
|
|
v_strada := REGEXP_REPLACE(v_strada,
|
|
'(\s)(BLOC|BL|SCARA|SC|APARTAMENT|APART|AP|ETAJ|ET|NUMARUL|NUMAR|NR)(\s|\.|\d)',
|
|
',\2\3', 1, 0, 'i');
|
|
|
|
-- 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
|
|
-- ================================================================
|
|
-- Insert commas before address keywords to create proper tokens
|
|
-- No guard on existing commas — double commas produce empty tokens (harmless)
|
|
IF v_raw_numar IS NOT NULL THEN
|
|
v_raw_numar := REGEXP_REPLACE(v_raw_numar,
|
|
'(\s)(BLOC|BL|SCARA|SC|APARTAMENT|APART|AP|ETAJ|ET|NUMARUL|NUMAR|NR)(\s|\.|\d)',
|
|
',\2\3', 1, 0, 'i');
|
|
v_raw_numar := LTRIM(v_raw_numar, ', ');
|
|
END IF;
|
|
|
|
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'));
|
|
-- Glued tokens: Ap78, BL30, SC2, ET3, NR15 (no separator between keyword and digit)
|
|
ELSIF REGEXP_LIKE(v_token_upper, '^(BLOC|BL)(\d)') THEN
|
|
p_bloc := TRIM(REGEXP_REPLACE(v_token, '^(BLOC|BL)', '', 1, 1, 'i'));
|
|
ELSIF REGEXP_LIKE(v_token_upper, '^(SCARA|SC)(\d)') THEN
|
|
p_scara := TRIM(REGEXP_REPLACE(v_token, '^(SCARA|SC)', '', 1, 1, 'i'));
|
|
ELSIF REGEXP_LIKE(v_token_upper, '^(APARTAMENT|APART|AP)(\d)') THEN
|
|
p_apart := TRIM(REGEXP_REPLACE(v_token, '^(APARTAMENT|APART|AP)', '', 1, 1, 'i'));
|
|
ELSIF REGEXP_LIKE(v_token_upper, '^(ETAJ|ET)(\d)') THEN
|
|
p_etaj := TRIM(REGEXP_REPLACE(v_token, '^(ETAJ|ET)', '', 1, 1, 'i'));
|
|
ELSIF REGEXP_LIKE(v_token_upper, '^(NUMARUL|NUMAR|NR)(\d)') THEN
|
|
p_numar := TRIM(REGEXP_REPLACE(v_token, '^(NUMARUL|NUMAR|NR)', '', 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));
|
|
|
|
-- Strip localitate from end of strada (users type city into address)
|
|
IF p_strada IS NOT NULL AND p_localitate IS NOT NULL THEN
|
|
IF p_strada LIKE '%' || p_localitate THEN
|
|
v_token := RTRIM(SUBSTR(p_strada, 1, LENGTH(p_strada) - LENGTH(p_localitate)));
|
|
IF v_token IS NOT NULL THEN
|
|
p_strada := v_token;
|
|
END IF;
|
|
END IF;
|
|
END IF;
|
|
|
|
-- Truncare de siguranta (limita coloanelor Oracle)
|
|
-- 22.04.2026 - numar overflow fix:
|
|
-- prima componenta ramane numar
|
|
-- "SAT X ..." → X ... devine p_localitate (satul = localitate, TIER L1/L2/L3 rezolva)
|
|
-- "COM X"/"ORAS X"/"MUN X" → ignorat (deja in p_localitate din GoMag)
|
|
-- altceva (landmark) → strada
|
|
IF LENGTH(p_numar) > 10 THEN
|
|
v_pozitie := INSTR(p_numar, ' ');
|
|
IF v_pozitie > 1 THEN
|
|
v_rest_parts := TRIM(SUBSTR(p_numar, v_pozitie + 1));
|
|
p_numar := SUBSTR(p_numar, 1, v_pozitie - 1);
|
|
IF v_rest_parts IS NOT NULL THEN
|
|
IF UPPER(v_rest_parts) LIKE 'SAT %' THEN
|
|
-- Satul = localitate → overwrite p_localitate cu tot ce urmeaza dupa "SAT "
|
|
p_localitate := UPPER(TRIM(REGEXP_REPLACE(v_rest_parts, '^SAT\s+', '', 1, 1, 'i')));
|
|
ELSIF UPPER(v_rest_parts) NOT LIKE 'COM %'
|
|
AND UPPER(v_rest_parts) NOT LIKE 'ORAS %'
|
|
AND UPPER(v_rest_parts) NOT LIKE 'MUN %' THEN
|
|
-- Landmark (ex: "LA NON STOP") → strada
|
|
p_strada := SUBSTR(TRIM(p_strada || ' ' || v_rest_parts), 1, 100);
|
|
END IF;
|
|
-- COM/ORAS/MUN aruncat (deja in p_localitate din GoMag)
|
|
END IF;
|
|
ELSE
|
|
p_numar := SUBSTR(p_numar, 1, 10);
|
|
END IF;
|
|
END IF;
|
|
-- Safety net: daca split-ul de mai sus a lasat >10 char (ex: prefixul
|
|
-- inaintea primului spatiu era el insusi >10), forteaza limita coloanei.
|
|
-- 23.04.2026 - hardening overflow rezidual
|
|
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(100);
|
|
v_scara VARCHAR2(100);
|
|
v_apart VARCHAR2(100);
|
|
v_etaj VARCHAR2(100);
|
|
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);
|
|
|
|
-- 07.04.2026 - normalize MUNICIPIUL BUCURESTI → BUCURESTI SECTORUL X before TIER 1
|
|
IF UPPER(TRIM(v_localitate)) IN ('MUNICIPIUL BUCURESTI', 'MUN BUCURESTI', 'MUN. BUCURESTI', 'BUCURESTI') THEN
|
|
IF v_sector IS NOT NULL AND TRIM(v_sector) IS NOT NULL THEN
|
|
v_localitate := 'BUCURESTI SECTORUL ' || TRIM(v_sector);
|
|
END IF;
|
|
END IF;
|
|
|
|
-- Resolve id_judet inainte de TIER 1
|
|
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;
|
|
|
|
-- Resolve id_localitate inainte de TIER 1
|
|
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 strip_diacritics(localitate) = strip_diacritics(v_localitate)
|
|
AND inactiv = 0
|
|
AND sters = 0
|
|
ORDER BY localitate)
|
|
WHERE rn = 1;
|
|
EXCEPTION
|
|
WHEN NO_DATA_FOUND THEN
|
|
-- TIER L2: fuzzy match prin SOUNDEX (ex: CRAMPOIA → CRAMPOAIA, edit distance 1)
|
|
-- Aplica si pentru localitati scurte (< 5 chars) — SOUNDEX e suficient de specific pe judet
|
|
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
|
|
FROM syn_nom_localitati
|
|
WHERE id_judet = v_id_judet
|
|
AND SOUNDEX(strip_diacritics(localitate)) = SOUNDEX(strip_diacritics(v_localitate))
|
|
AND inactiv = 0 AND sters = 0
|
|
ORDER BY LENGTH(localitate) ASC) -- cel mai scurt = cel mai apropiat
|
|
WHERE ROWNUM = 1;
|
|
pINFO('WARNING addr fuzzy match: ' || v_localitate || ' -> SOUNDEX in judet ' || v_judet,
|
|
'IMPORT_PARTENERI');
|
|
EXCEPTION
|
|
WHEN NO_DATA_FOUND THEN
|
|
-- TIER L3: localitate cu totul necunoscuta — pastreaza judetul corect deja rezolvat
|
|
-- Prima localitate alfabetic din judet (v_id_judet ramas din lookup reusit)
|
|
BEGIN
|
|
SELECT id_loc, id_tara INTO v_id_localitate, v_id_tara
|
|
FROM (SELECT id_loc, id_tara FROM syn_nom_localitati
|
|
WHERE id_judet = v_id_judet AND inactiv = 0 AND sters = 0
|
|
ORDER BY localitate)
|
|
WHERE ROWNUM = 1;
|
|
EXCEPTION
|
|
WHEN NO_DATA_FOUND THEN
|
|
-- Judet fara localitati (imposibil in practica) — fallback global
|
|
v_id_localitate := N_ID_LOCALITATE_DEFAULT;
|
|
v_id_judet := N_ID_JUD_DEFAULT;
|
|
v_id_tara := N_ID_TARA_DEFAULT;
|
|
END;
|
|
pINFO('WARNING addr localitate necunoscuta: ' || v_localitate || ', judet=' || v_judet ||
|
|
' -> prima din judet', 'IMPORT_PARTENERI');
|
|
END;
|
|
END;
|
|
|
|
-- 07.04.2026 - fix duplicate: normalize localitate + resolve id_localitate inainte de TIER 1 (match pe id_loc)
|
|
-- TIER 1: match pe id_loc + strada (evita duplicate MUNICIPIUL BUCURESTI vs BUCURESTI SECTORUL X)
|
|
begin
|
|
select id_adresa into p_id_adresa from (
|
|
select id_adresa
|
|
from vadrese_parteneri
|
|
where id_part = p_id_part
|
|
and id_loc = v_id_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;
|
|
|
|
-- Adaug o adresa
|
|
if p_id_adresa is null then
|
|
-- 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;
|
|
|
|
<<sfarsit>>
|
|
null;
|
|
end;
|
|
|
|
END PACK_IMPORT_PARTENERI;
|
|
/
|