diff --git a/api/database-scripts/02_import_parteneri.sql b/api/database-scripts/02_import_parteneri.sql index b25e497..5f52552 100644 --- a/api/database-scripts/02_import_parteneri.sql +++ b/api/database-scripts/02_import_parteneri.sql @@ -134,15 +134,6 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS p_prenume OUT VARCHAR2 ); - /** - * Scrie in log operatiile executate - * @param p_mesaj Mesajul de logat - * @param p_nivel Nivelul: INFO, WARN, ERROR - */ - PROCEDURE log_operatie( - p_mesaj IN VARCHAR2, - p_nivel IN VARCHAR2 DEFAULT 'INFO' - ); END PACK_IMPORT_PARTENERI; / @@ -181,7 +172,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS EXCEPTION WHEN OTHERS THEN - log_operatie('ERROR in valideaza_date_partener: ' || SQLERRM, 'ERROR'); + pINFO('ERROR in valideaza_date_partener: ' || SQLERRM, 'IMPORT_PARTENERI'); RAISE; END valideaza_date_partener; @@ -212,7 +203,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS v_cod_fiscal_curat := curata_text_cautare(p_cod_fiscal); - log_operatie('Cautare partener dupa cod_fiscal: ' || v_cod_fiscal_curat); + pINFO('Cautare partener dupa cod_fiscal: ' || v_cod_fiscal_curat, 'IMPORT_PARTENERI'); -- Cautare in NOM_PARTENERI BEGIN @@ -222,12 +213,12 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS WHERE UPPER(TRIM(cod_fiscal)) = v_cod_fiscal_curat AND ROWNUM = 1; -- În caz de duplicate, luam primul - log_operatie('Gasit partener cu cod_fiscal ' || v_cod_fiscal_curat || ': ID_PART=' || v_id_part); + pINFO('Gasit partener cu cod_fiscal ' || v_cod_fiscal_curat || ': ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); RETURN v_id_part; EXCEPTION WHEN NO_DATA_FOUND THEN - log_operatie('Nu s-a gasit partener cu cod_fiscal: ' || v_cod_fiscal_curat); + pINFO('Nu s-a gasit partener cu cod_fiscal: ' || v_cod_fiscal_curat, 'IMPORT_PARTENERI'); RETURN NULL; WHEN TOO_MANY_ROWS THEN @@ -242,14 +233,14 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS ) WHERE ROWNUM = 1; - log_operatie('WARNING: Multiple parteneri cu acelasi cod_fiscal ' || v_cod_fiscal_curat || - '. Selectat ID_PART=' || v_id_part, 'WARN'); + pINFO('WARNING: Multiple parteneri cu acelasi cod_fiscal ' || v_cod_fiscal_curat || + '. Selectat ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); RETURN v_id_part; END; EXCEPTION WHEN OTHERS THEN - log_operatie('ERROR in cauta_partener_dupa_cod_fiscal: ' || SQLERRM, 'ERROR'); + pINFO('ERROR in cauta_partener_dupa_cod_fiscal: ' || SQLERRM, 'IMPORT_PARTENERI'); RAISE; END cauta_partener_dupa_cod_fiscal; @@ -264,7 +255,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS v_denumire_curata := curata_text_cautare(p_denumire); - log_operatie('Cautare partener dupa denumire: ' || v_denumire_curata); + pINFO('Cautare partener dupa denumire: ' || v_denumire_curata, 'IMPORT_PARTENERI'); -- Cautare in NOM_PARTENERI BEGIN @@ -274,12 +265,12 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS WHERE UPPER(TRIM(denumire)) = v_denumire_curata AND ROWNUM = 1; -- În caz de duplicate, luam primul - log_operatie('Gasit partener cu denumirea ' || v_denumire_curata || ': ID_PART=' || v_id_part); + 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 - log_operatie('Nu s-a gasit partener cu denumirea: ' || v_denumire_curata); + pINFO('Nu s-a gasit partener cu denumirea: ' || v_denumire_curata, 'IMPORT_PARTENERI'); RETURN NULL; WHEN TOO_MANY_ROWS THEN @@ -294,14 +285,14 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS ) WHERE ROWNUM = 1; - log_operatie('WARNING: Multiple parteneri cu aceeasi denumire ' || v_denumire_curata || - '. Selectat ID_PART=' || v_id_part, 'WARN'); + 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 - log_operatie('ERROR in cauta_partener_dupa_denumire: ' || SQLERRM, 'ERROR'); + pINFO('ERROR in cauta_partener_dupa_denumire: ' || SQLERRM, 'IMPORT_PARTENERI'); RAISE; END cauta_partener_dupa_denumire; @@ -324,7 +315,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS EXCEPTION WHEN OTHERS THEN - log_operatie('ERROR in este_persoana_fizica: ' || SQLERRM, 'ERROR'); + pINFO('ERROR in este_persoana_fizica: ' || SQLERRM, 'IMPORT_PARTENERI'); RETURN 0; END este_persoana_fizica; @@ -369,7 +360,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS EXCEPTION WHEN OTHERS THEN - log_operatie('ERROR in separa_nume_prenume: ' || SQLERRM, 'ERROR'); + 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; @@ -394,13 +385,13 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS -- Validare input IF p_adresa_text IS NULL THEN - log_operatie('Adresa goala, se folosesc valorile default', 'WARN'); + pINFO('Adresa goala, se folosesc valorile default', 'IMPORT_PARTENERI'); RETURN; END IF; v_adresa_curata := TRIM(p_adresa_text); - log_operatie('Parsare adresa: ' || v_adresa_curata); + pINFO('Parsare adresa: ' || v_adresa_curata, 'IMPORT_PARTENERI'); -- Split dupa semicolon SELECT TRIM(REGEXP_SUBSTR(v_adresa_curata, '[^;]+', 1, LEVEL)) @@ -411,7 +402,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS v_count := v_componente.COUNT; IF v_count = 0 THEN - log_operatie('Nu s-au gasit componente in adresa', 'WARN'); + pINFO('Nu s-au gasit componente in adresa', 'IMPORT_PARTENERI'); RETURN; END IF; @@ -472,12 +463,12 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS p_sector := C_SECTOR_DEFAULT; END IF; - log_operatie('Adresa parsata: JUD=' || p_judet || ', LOC=' || p_localitate || - ', STRADA=' || NVL(p_strada, 'NULL') || ', SECTOR=' || p_sector); + pINFO('Adresa parsata: JUD=' || p_judet || ', LOC=' || p_localitate || + ', STRADA=' || NVL(p_strada, 'NULL') || ', SECTOR=' || p_sector, 'IMPORT_PARTENERI'); EXCEPTION WHEN OTHERS THEN - log_operatie('ERROR in parseaza_adresa_semicolon: ' || SQLERRM, 'ERROR'); + pINFO('ERROR in parseaza_adresa_semicolon: ' || SQLERRM, 'IMPORT_PARTENERI'); -- Pastram valorile default in caz de eroare p_judet := C_JUD_DEFAULT; p_localitate := C_LOCALITATE_DEFAULT; @@ -509,10 +500,10 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS v_denumire_curata VARCHAR2(200); BEGIN - log_operatie('=== ÎNCEPUT cauta_sau_creeaza_partener ==='); - log_operatie('Input: cod_fiscal=' || NVL(p_cod_fiscal, 'NULL') || + pINFO('=== ÎNCEPUT 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')); + ', adresa=' || NVL(p_adresa, 'NULL'), 'IMPORT_PARTENERI'); -- Validare date input IF NOT valideaza_date_partener(p_cod_fiscal, p_denumire) THEN @@ -527,8 +518,8 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS v_id_part := cauta_partener_dupa_cod_fiscal(v_cod_fiscal_curat); IF v_id_part IS NOT NULL THEN - log_operatie('Partener gasit dupa cod_fiscal. ID_PART=' || v_id_part); - log_operatie('=== SFÂRȘIT cauta_sau_creeaza_partener ==='); + pINFO('Partener gasit dupa cod_fiscal. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); + pINFO('=== SFÂRȘIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); RETURN v_id_part; END IF; END IF; @@ -537,21 +528,21 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS v_id_part := cauta_partener_dupa_denumire(v_denumire_curata); IF v_id_part IS NOT NULL THEN - log_operatie('Partener gasit dupa denumire. ID_PART=' || v_id_part); - log_operatie('=== SFÂRȘIT cauta_sau_creeaza_partener ==='); + pINFO('Partener gasit dupa denumire. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); + pINFO('=== SFÂRȘIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); RETURN v_id_part; END IF; -- STEP 3: Creare partener nou - log_operatie('Nu s-a gasit partener existent. Se creeaza unul nou...'); + pINFO('Nu s-a gasit partener existent. Se creeaza unul nou...', 'IMPORT_PARTENERI'); -- Verifica tipul partenerului v_este_persoana_fizica := este_persoana_fizica(v_cod_fiscal_curat); IF v_este_persoana_fizica = 1 THEN - log_operatie('Detectata persoana fizica (CUI 13 cifre)'); + pINFO('Detectata persoana fizica (CUI 13 cifre)', 'IMPORT_PARTENERI'); separa_nume_prenume(v_denumire_curata, v_nume, v_prenume); - log_operatie('Nume separat: NUME=' || NVL(v_nume, 'NULL') || ', PRENUME=' || NVL(v_prenume, 'NULL')); + pINFO('Nume separat: NUME=' || NVL(v_nume, 'NULL') || ', PRENUME=' || NVL(v_prenume, 'NULL'), 'IMPORT_PARTENERI'); END IF; -- Creare partener prin pack_def @@ -606,17 +597,17 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS RAISE_APPLICATION_ERROR(-20003, 'pack_def.adauga_partener a returnat ID invalid'); END IF; - log_operatie('Partener creat cu succes. ID_PART=' || v_id_part); + pINFO('Partener creat cu succes. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); EXCEPTION WHEN OTHERS THEN - log_operatie('ERROR la crearea partenerului prin pack_def: ' || SQLERRM, 'ERROR'); + pINFO('ERROR la crearea partenerului prin pack_def: ' || SQLERRM, 'IMPORT_PARTENERI'); RAISE integrare_pack_def_exception; END; -- STEP 4: Adaugare adresa (daca exista) IF p_adresa IS NOT NULL THEN - log_operatie('Se adauga adresa pentru partenerul nou creat...'); + pINFO('Se adauga adresa pentru partenerul nou creat...', 'IMPORT_PARTENERI'); -- Parseaza adresa parseaza_adresa_semicolon(p_adresa, v_judet, v_localitate, v_strada, v_sector); @@ -650,72 +641,41 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS ); IF v_id_adresa IS NOT NULL AND v_id_adresa > 0 THEN - log_operatie('Adresa adaugata cu succes. ID_ADRESA=' || v_id_adresa); + pINFO('Adresa adaugata cu succes. ID_ADRESA=' || v_id_adresa, 'IMPORT_PARTENERI'); ELSE - log_operatie('WARNING: pack_def.adauga_adresa_partener2 a returnat ID invalid: ' || NVL(TO_CHAR(v_id_adresa), 'NULL'), 'WARN'); + pINFO('WARNING: pack_def.adauga_adresa_partener2 a returnat ID invalid: ' || NVL(TO_CHAR(v_id_adresa), 'NULL'), 'IMPORT_PARTENERI'); END IF; EXCEPTION WHEN OTHERS THEN - log_operatie('ERROR la adaugarea adresei prin pack_def: ' || SQLERRM, 'ERROR'); + pINFO('ERROR la adaugarea adresei prin pack_def: ' || SQLERRM, 'IMPORT_PARTENERI'); -- Nu raisam exceptia pentru adresa, partenerii pot exista fara adresa - log_operatie('Partenerul a fost creat, dar adresa nu a putut fi adaugata', 'WARN'); + pINFO('Partenerul a fost creat, dar adresa nu a putut fi adaugata', 'IMPORT_PARTENERI'); END; ELSE - log_operatie('Nu s-a furnizat adresa pentru partenerul nou'); + pINFO('Nu s-a furnizat adresa pentru partenerul nou', 'IMPORT_PARTENERI'); END IF; - log_operatie('Partener creat complet. ID_PART=' || v_id_part); - log_operatie('=== SFÂRȘIT cauta_sau_creeaza_partener ==='); + pINFO('Partener creat complet. ID_PART=' || v_id_part, 'IMPORT_PARTENERI'); + pINFO('=== SFÂRȘIT cauta_sau_creeaza_partener ===', 'IMPORT_PARTENERI'); RETURN v_id_part; EXCEPTION WHEN partener_invalid_exception THEN - log_operatie('ERROR: Date partener invalide', 'ERROR'); + pINFO('ERROR: Date partener invalide', 'IMPORT_PARTENERI'); RAISE_APPLICATION_ERROR(-20001, 'Date partener invalide: ' || SQLERRM); WHEN integrare_pack_def_exception THEN - log_operatie('ERROR: Problema la integrarea cu pack_def', 'ERROR'); + pINFO('ERROR: Problema la integrarea cu pack_def', 'IMPORT_PARTENERI'); RAISE_APPLICATION_ERROR(-20003, 'Eroare la integrarea cu pack_def: ' || SQLERRM); WHEN OTHERS THEN - log_operatie('ERROR NEAȘTEPTAT in cauta_sau_creeaza_partener: ' || SQLERRM, 'ERROR'); + pINFO('ERROR NEAȘTEPTAT in cauta_sau_creeaza_partener: ' || SQLERRM, 'IMPORT_PARTENERI'); RAISE_APPLICATION_ERROR(-20099, 'Eroare neasteptata la crearea partenerului: ' || SQLERRM); END cauta_sau_creeaza_partener; - PROCEDURE log_operatie( - p_mesaj IN VARCHAR2, - p_nivel IN VARCHAR2 DEFAULT 'INFO' - ) IS - PRAGMA AUTONOMOUS_TRANSACTION; - v_timestamp VARCHAR2(50); - v_mesaj_complet VARCHAR2(4000); - BEGIN - v_timestamp := TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'); - v_mesaj_complet := v_timestamp || ' | ' || RPAD(p_nivel, 5) || ' | IMPORT_PARTENERI | ' || p_mesaj; - - -- Output in server log (DBMS_OUTPUT pentru sesiuni interactive) - DBMS_OUTPUT.PUT_LINE(v_mesaj_complet); - - -- Scrie in tabela INFO pentru logging - BEGIN - INSERT INTO INFO (info, locatia) - VALUES (v_mesaj_complet, 'IMPORT_PARTENERI'); - - COMMIT; - EXCEPTION - WHEN OTHERS THEN - -- Ignora erorile de logging - nu vrem sa intrerupa procesul principal - NULL; - END; - - EXCEPTION - WHEN OTHERS THEN - -- Nu lasam logging-ul sa intrerupa procesul principal - NULL; - END log_operatie; END PACK_IMPORT_PARTENERI; / diff --git a/api/database-scripts/03_import_comenzi.sql b/api/database-scripts/03_import_comenzi.sql deleted file mode 100644 index fa3be4f..0000000 --- a/api/database-scripts/03_import_comenzi.sql +++ /dev/null @@ -1,463 +0,0 @@ --- ==================================================================== --- P1-003: Package IMPORT_COMENZI pentru import comenzi web → ROA --- Sistem Import Comenzi Web → ROA --- ==================================================================== - --- Package pentru importul comenzilor web cu mapări complexe SKU → CODMAT -CREATE OR REPLACE PACKAGE IMPORT_COMENZI AS - - -- Tipuri pentru returnarea rezultatelor - TYPE t_articol_result IS RECORD ( - id_articol NUMBER, - codmat VARCHAR2(50), - cantitate_roa NUMBER, - pret_unitar NUMBER, - success NUMBER, - error_message VARCHAR2(4000) - ); - - TYPE t_articol_table IS TABLE OF t_articol_result; - - -- Funcție pentru găsirea/maparea articolelor ROA - FUNCTION gaseste_articol_roa( - p_sku IN VARCHAR2, - p_pret_web IN NUMBER DEFAULT NULL, - p_cantitate_web IN NUMBER DEFAULT 1 - ) RETURN t_articol_table PIPELINED; - - -- Funcție pentru importul complet al unei comenzi web - FUNCTION importa_comanda_web( - p_nr_comanda_ext IN VARCHAR2, - p_data_comanda IN DATE, - p_id_partener IN NUMBER, - p_json_articole IN CLOB, -- JSON array cu articolele - p_id_adresa_livrare IN NUMBER DEFAULT NULL, - p_observatii IN VARCHAR2 DEFAULT NULL - ) RETURN NUMBER; -- Returnează ID_COMANDA sau -1 pentru eroare - -END IMPORT_COMENZI; -/ - --- ==================================================================== --- Package Body - Implementarea funcțiilor --- ==================================================================== -CREATE OR REPLACE PACKAGE BODY IMPORT_COMENZI AS - - -- Constante pentru configurare - c_id_gestiune CONSTANT NUMBER := 1; - c_id_sectie CONSTANT NUMBER := 1; - c_id_pol CONSTANT NUMBER := NULL; - c_id_util CONSTANT NUMBER := -3; -- Sistem - c_interna CONSTANT NUMBER := 0; -- Externe - - -- Procedură internă pentru logging - PROCEDURE log_operation( - p_level IN VARCHAR2, -- INFO, WARN, ERROR - p_operation IN VARCHAR2, -- GASESTE_ARTICOL, IMPORTA_COMANDA - p_reference IN VARCHAR2, -- SKU sau Nr_Comanda - p_message IN VARCHAR2, - p_details IN VARCHAR2 DEFAULT NULL - ) IS - PRAGMA AUTONOMOUS_TRANSACTION; - BEGIN - -- Log în tabel sau fișier sistem (simplificat pentru acest exemplu) - INSERT INTO import_log ( - log_time, log_level, operation, reference_id, message, details - ) VALUES ( - SYSDATE, p_level, p_operation, p_reference, p_message, p_details - ); - COMMIT; - EXCEPTION - WHEN OTHERS THEN - -- Fallback: Nu bloca operația principală dacă logging-ul eșuează - NULL; - END log_operation; - - -- Procedură internă pentru validarea seturilor - FUNCTION valideaza_set(p_sku IN VARCHAR2) RETURN BOOLEAN IS - v_suma_procent NUMBER := 0; - v_count_articole NUMBER := 0; - BEGIN - SELECT NVL(SUM(procent_pret), 0), COUNT(*) - INTO v_suma_procent, v_count_articole - FROM articole_terti - WHERE sku = p_sku - AND activ = 1; - - -- Validări logice pentru seturi - IF v_count_articole > 1 THEN - -- Set compus - suma procentelor trebuie să fie între 95-105% (toleranță) - IF v_suma_procent < 95 OR v_suma_procent > 105 THEN - log_operation('WARN', 'VALIDEAZA_SET', p_sku, - 'Suma procente nelogică: ' || v_suma_procent || '%'); - RETURN FALSE; - END IF; - ELSIF v_count_articole = 1 THEN - -- Reîmpachetare - procentul trebuie să fie 100% - IF v_suma_procent != 100 THEN - log_operation('WARN', 'VALIDEAZA_SET', p_sku, - 'Reîmpachetare cu procent != 100%: ' || v_suma_procent || '%'); - RETURN FALSE; - END IF; - END IF; - - RETURN TRUE; - END valideaza_set; - - -- ================================================================ - -- Funcția principală pentru găsirea articolelor ROA - -- ================================================================ - FUNCTION gaseste_articol_roa( - p_sku IN VARCHAR2, - p_pret_web IN NUMBER DEFAULT NULL, - p_cantitate_web IN NUMBER DEFAULT 1 - ) RETURN t_articol_table PIPELINED IS - - v_result t_articol_result; - v_found_mapping BOOLEAN := FALSE; - v_id_articol NUMBER; - - -- Cursor pentru mapările din ARTICOLE_TERTI - CURSOR c_mapari IS - SELECT at.codmat, at.cantitate_roa, at.procent_pret, - na.id_articol, na.pret_vanzare - FROM articole_terti at - JOIN nom_articole na ON na.codmat = at.codmat - WHERE at.sku = p_sku - AND at.activ = 1 - ORDER BY at.procent_pret DESC; -- Articolele principale primul - - BEGIN - log_operation('INFO', 'GASESTE_ARTICOL', p_sku, - 'Căutare articol pentru SKU: ' || p_sku); - - -- Inițializare rezultat - v_result.success := 0; - v_result.error_message := NULL; - - -- STEP 1: Verifică mapările speciale din ARTICOLE_TERTI - FOR rec IN c_mapari LOOP - v_found_mapping := TRUE; - - v_result.id_articol := rec.id_articol; - v_result.codmat := rec.codmat; - v_result.cantitate_roa := rec.cantitate_roa * p_cantitate_web; - - -- Calculează prețul unitar pe baza procentului alocat - IF p_pret_web IS NOT NULL THEN - v_result.pret_unitar := (p_pret_web * rec.procent_pret / 100) / rec.cantitate_roa; - ELSE - -- Folosește prețul din nomenclator - v_result.pret_unitar := NVL(rec.pret_vanzare, 0); - END IF; - - v_result.success := 1; - - log_operation('INFO', 'GASESTE_ARTICOL', p_sku, - 'Mapare găsită: ' || rec.codmat || - ', Cant: ' || v_result.cantitate_roa || - ', Preț: ' || v_result.pret_unitar); - - PIPE ROW(v_result); - END LOOP; - - -- STEP 2: Dacă nu s-au găsit mapări speciale, caută direct în nom_articole - IF NOT v_found_mapping THEN - BEGIN - SELECT id_articol, codmat, NVL(pret_vanzare, 0) - INTO v_result.id_articol, v_result.codmat, v_result.pret_unitar - FROM nom_articole - WHERE codmat = p_sku - AND activ = 1; - - v_result.cantitate_roa := p_cantitate_web; - - -- Pentru căutare directă, folosește prețul din web dacă este furnizat - IF p_pret_web IS NOT NULL THEN - v_result.pret_unitar := p_pret_web; - END IF; - - v_result.success := 1; - - log_operation('INFO', 'GASESTE_ARTICOL', p_sku, - 'Găsit direct în nomenclator: ' || v_result.codmat); - - PIPE ROW(v_result); - - EXCEPTION - WHEN NO_DATA_FOUND THEN - v_result.success := 0; - v_result.error_message := 'SKU nu a fost găsit nici în ARTICOLE_TERTI, nici în nom_articole: ' || p_sku; - - log_operation('ERROR', 'GASESTE_ARTICOL', p_sku, v_result.error_message); - PIPE ROW(v_result); - - WHEN TOO_MANY_ROWS THEN - v_result.success := 0; - v_result.error_message := 'Multiple articole găsite pentru SKU: ' || p_sku; - - log_operation('ERROR', 'GASESTE_ARTICOL', p_sku, v_result.error_message); - PIPE ROW(v_result); - END; - ELSE - -- Validează seturile după ce au fost returnate toate mapările - IF NOT valideaza_set(p_sku) THEN - log_operation('WARN', 'GASESTE_ARTICOL', p_sku, - 'Set cu configurație suspectă - verifică procentele'); - END IF; - END IF; - - EXCEPTION - WHEN OTHERS THEN - v_result.success := 0; - v_result.error_message := 'Eroare neașteptată: ' || SQLERRM; - - log_operation('ERROR', 'GASESTE_ARTICOL', p_sku, - 'Eroare neașteptată', SQLERRM); - PIPE ROW(v_result); - END gaseste_articol_roa; - - -- ================================================================ - -- Funcția pentru importul complet al unei comenzi web - -- ================================================================ - FUNCTION importa_comanda_web( - p_nr_comanda_ext IN VARCHAR2, - p_data_comanda IN DATE, - p_id_partener IN NUMBER, - p_json_articole IN CLOB, - p_id_adresa_livrare IN NUMBER DEFAULT NULL, - p_observatii IN VARCHAR2 DEFAULT NULL - ) RETURN NUMBER IS - - v_id_comanda NUMBER; - v_data_livrare DATE; - v_json_obj JSON_OBJECT_T; - v_json_array JSON_ARRAY_T; - v_articol_obj JSON_OBJECT_T; - v_sku VARCHAR2(100); - v_cantitate_web NUMBER; - v_pret_web NUMBER; - v_articole_procesate NUMBER := 0; - v_articole_eroare NUMBER := 0; - v_start_time DATE; - - BEGIN - v_start_time := SYSDATE; - - log_operation('INFO', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Începere import comandă pentru partener: ' || p_id_partener); - - -- Validări de bază - IF p_nr_comanda_ext IS NULL OR p_id_partener IS NULL THEN - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Parametri obligatorii lipsă'); - RETURN -1; - END IF; - - -- Verifică dacă comanda nu există deja - BEGIN - SELECT id_comanda INTO v_id_comanda - FROM comenzi - WHERE comanda_externa = p_nr_comanda_ext - AND sters = 0; - - log_operation('WARN', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Comanda există deja cu ID: ' || v_id_comanda); - RETURN v_id_comanda; -- Returnează ID-ul comenzii existente - - EXCEPTION - WHEN NO_DATA_FOUND THEN - NULL; -- Normal, comanda nu există - END; - - -- Calculează data de livrare (comanda + 1 zi) - v_data_livrare := p_data_comanda + 1; - - -- STEP 1: Creează comanda folosind package-ul existent - BEGIN - v_id_comanda := PACK_COMENZI.adauga_comanda( - p_nr_comanda => p_nr_comanda_ext, - p_data_comanda => p_data_comanda, - p_id_partener => p_id_partener, - p_data_livrare => v_data_livrare, - p_id_gestiune => c_id_gestiune, - p_id_sectie => c_id_sectie, - p_interna => c_interna, - p_id_util => c_id_util, - p_comanda_externa => p_nr_comanda_ext, - p_id_adresa_livrare => p_id_adresa_livrare, - p_observatii => p_observatii - ); - - IF v_id_comanda IS NULL OR v_id_comanda <= 0 THEN - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'PACK_COMENZI.adauga_comanda a returnat ID invalid'); - RETURN -1; - END IF; - - log_operation('INFO', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Comanda creată cu ID: ' || v_id_comanda); - - EXCEPTION - WHEN OTHERS THEN - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Eroare la crearea comenzii', SQLERRM); - RETURN -1; - END; - - -- STEP 2: Procesează articolele din JSON - BEGIN - v_json_array := JSON_ARRAY_T.parse(p_json_articole); - - FOR i IN 0 .. v_json_array.get_size() - 1 LOOP - v_articol_obj := TREAT(v_json_array.get(i) AS JSON_OBJECT_T); - - -- Extrage datele articolului - v_sku := v_articol_obj.get_string('sku'); - v_cantitate_web := v_articol_obj.get_number('cantitate'); - v_pret_web := v_articol_obj.get_number('pret'); - - log_operation('INFO', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Procesez articol: ' || v_sku || ', cant: ' || v_cantitate_web); - - -- STEP 3: Găsește mapările pentru acest SKU - FOR art_rec IN ( - SELECT * FROM TABLE(gaseste_articol_roa(v_sku, v_pret_web, v_cantitate_web)) - ) LOOP - IF art_rec.success = 1 THEN - -- Adaugă articolul la comandă - BEGIN - PACK_COMENZI.adauga_articol_comanda( - p_id_comanda => v_id_comanda, - p_id_articol => art_rec.id_articol, - p_cantitate => art_rec.cantitate_roa, - p_pret => art_rec.pret_unitar, - p_id_pol => c_id_pol, - p_id_util => c_id_util - ); - - v_articole_procesate := v_articole_procesate + 1; - - log_operation('INFO', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Articol adăugat: ' || art_rec.codmat || - ', cant: ' || art_rec.cantitate_roa || - ', preț: ' || art_rec.pret_unitar); - - EXCEPTION - WHEN OTHERS THEN - v_articole_eroare := v_articole_eroare + 1; - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Eroare la adăugare articol ' || art_rec.codmat, SQLERRM); - END; - ELSE - v_articole_eroare := v_articole_eroare + 1; - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'SKU nu a putut fi mapat: ' || v_sku || ' - ' || art_rec.error_message); - END IF; - END LOOP; - END LOOP; - - EXCEPTION - WHEN OTHERS THEN - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Eroare la procesarea JSON articole', SQLERRM); - RETURN -1; - END; - - -- Verifică dacă s-au procesat articole cu succes - IF v_articole_procesate = 0 THEN - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Niciun articol nu a fost procesat cu succes'); - RETURN -1; - END IF; - - -- Log sumar final - log_operation('INFO', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Import finalizat - ID comanda: ' || v_id_comanda || - ', Articole procesate: ' || v_articole_procesate || - ', Articole cu erori: ' || v_articole_eroare || - ', Timp procesare: ' || ROUND((SYSDATE - v_start_time) * 24 * 60 * 60, 2) || 's'); - - RETURN v_id_comanda; - - EXCEPTION - WHEN OTHERS THEN - log_operation('ERROR', 'IMPORTA_COMANDA', p_nr_comanda_ext, - 'Eroare neașteptată în importa_comanda_web', SQLERRM); - RETURN -1; - END importa_comanda_web; - -END IMPORT_COMENZI; -/ - --- ==================================================================== --- Tabel pentru logging (opțional - poate fi înlocuit cu logging extern) --- ==================================================================== -CREATE TABLE import_log ( - id_log NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - log_time DATE DEFAULT SYSDATE, - log_level VARCHAR2(10), -- INFO, WARN, ERROR - operation VARCHAR2(50), -- GASESTE_ARTICOL, IMPORTA_COMANDA - reference_id VARCHAR2(100), -- SKU sau Nr_Comanda - message VARCHAR2(4000), - details CLOB -); - --- Index pentru căutări rapide în log -CREATE INDEX idx_import_log_time ON import_log(log_time); -CREATE INDEX idx_import_log_ref ON import_log(reference_id, operation); - --- Comentarii pentru documentație -COMMENT ON TABLE import_log IS 'Log pentru operațiile de import comenzi web'; -COMMENT ON COLUMN import_log.operation IS 'Tipul operației: GASESTE_ARTICOL, IMPORTA_COMANDA, VALIDEAZA_SET'; -COMMENT ON COLUMN import_log.reference_id IS 'Referința: SKU pentru articole, Nr_Comanda pentru comenzi'; - --- Grant-uri pentru utilizarea package-ului --- GRANT EXECUTE ON IMPORT_COMENZI TO PUBLIC; --- GRANT SELECT, INSERT ON import_log TO PUBLIC; - --- ==================================================================== --- Exemple de utilizare și testare --- ==================================================================== - -/* --- Exemplu 1: Căutare articol simplu (direct în nomenclator) -SELECT * FROM TABLE(IMPORT_COMENZI.gaseste_articol_roa('CAF01', 15.50, 2)); - --- Exemplu 2: Căutare articol cu reîmpachetare -SELECT * FROM TABLE(IMPORT_COMENZI.gaseste_articol_roa('CAFE100', 150.00, 1)); - --- Exemplu 3: Căutare set compus -SELECT * FROM TABLE(IMPORT_COMENZI.gaseste_articol_roa('SET01', 200.00, 1)); - --- Exemplu 4: Import comandă completă -DECLARE - v_json_articole CLOB := '[ - {"sku": "CAF01", "cantitate": 2, "pret": 15.50}, - {"sku": "CAFE100", "cantitate": 1, "pret": 150.00}, - {"sku": "SET01", "cantitate": 1, "pret": 200.00} - ]'; - v_result NUMBER; -BEGIN - v_result := IMPORT_COMENZI.importa_comanda_web( - p_nr_comanda_ext => 'WEB-TEST-001', - p_data_comanda => SYSDATE, - p_id_partener => 12345, -- ID partener valid din sistem - p_json_articole => v_json_articole, - p_observatii => 'Test import din sistem web' - ); - - DBMS_OUTPUT.PUT_LINE('ID Comandă creată: ' || v_result); -END; -/ - --- Interogare log pentru troubleshooting -SELECT log_time, log_level, operation, reference_id, message -FROM import_log -WHERE log_time >= SYSDATE - 1 -- Ultimele 24h -ORDER BY log_time DESC; -*/ - --- ==================================================================== --- Finalizare --- ==================================================================== \ No newline at end of file diff --git a/api/database-scripts/03_json.sql b/api/database-scripts/03_json.sql new file mode 100644 index 0000000..ba0a981 --- /dev/null +++ b/api/database-scripts/03_json.sql @@ -0,0 +1,532 @@ +-- ==================================================================== +-- P1-004: Package PACK_JSON pentru parsing JSON generic +-- Sistem Import Comenzi Web → ROA +-- ==================================================================== + +CREATE OR REPLACE PACKAGE PACK_JSON AS + + -- Tipuri pentru lucrul cu JSON + TYPE t_json_array IS TABLE OF VARCHAR2(4000); + + TYPE t_json_key_value IS RECORD ( + key_name VARCHAR2(100), + key_value VARCHAR2(4000), + key_type VARCHAR2(20) -- 'STRING', 'NUMBER', 'BOOLEAN', 'NULL' + ); + + TYPE t_json_object IS TABLE OF t_json_key_value; + + -- Proprietate pentru tracking erori + g_last_error VARCHAR2(4000); + + -- Functie pentru accesarea ultimei erori + FUNCTION get_last_error RETURN VARCHAR2; + + -- Functie pentru resetarea erorii + PROCEDURE clear_error; + + -- Main parsing functions + FUNCTION parse_array(p_json_array IN CLOB) RETURN t_json_array PIPELINED; -- Parse [{"a":1},{"b":2}] + FUNCTION get_string(p_json_object IN VARCHAR2, p_key_name IN VARCHAR2) RETURN VARCHAR2; -- Get "value" + FUNCTION get_number(p_json_object IN VARCHAR2, p_key_name IN VARCHAR2) RETURN NUMBER; -- Get 123.45 + FUNCTION get_boolean(p_json_object IN VARCHAR2, p_key_name IN VARCHAR2) RETURN BOOLEAN; -- Get true/false + + -- Advanced functions + FUNCTION parse_object(p_json_object IN VARCHAR2) RETURN t_json_object PIPELINED; -- Parse to key-value pairs + FUNCTION clean(p_json IN CLOB) RETURN CLOB; -- Remove whitespace/formatting + + -- Test functions + PROCEDURE run_tests; -- Run all built-in tests + FUNCTION test_basic_parsing RETURN VARCHAR2; -- Test basic JSON parsing + FUNCTION test_array_parsing RETURN VARCHAR2; -- Test array parsing + FUNCTION test_nested_objects RETURN VARCHAR2; -- Test nested JSON structures + FUNCTION test_error_handling RETURN VARCHAR2; -- Test error conditions + +END PACK_JSON; +/ + +-- ==================================================================== +-- Package Body - Implementarea functiilor +-- ==================================================================== +CREATE OR REPLACE PACKAGE BODY PACK_JSON AS +/* +PACK_JSON - Generic JSON Parser (Oracle 10g/11g/12c compatible) + +USAGE: + -- Parse array: [{"key":"val"},{"key":"val2"}] + FOR obj IN (SELECT * FROM TABLE(PACK_JSON.parse_array(json_clob))) LOOP + v_val := PACK_JSON.get_string(obj.COLUMN_VALUE, 'key'); + END LOOP; + + -- Get values from object: {"name":"John","age":25,"active":true} + v_name := PACK_JSON.get_string(json_obj, 'name'); -- Returns: John + v_age := PACK_JSON.get_number(json_obj, 'age'); -- Returns: 25 + v_active := PACK_JSON.get_boolean(json_obj, 'active'); -- Returns: TRUE + + -- Error handling: + IF PACK_JSON.get_last_error() IS NOT NULL THEN + -- Handle error: PACK_JSON.get_last_error() + PACK_JSON.clear_error(); + END IF; + +FUNCTIONS: + parse_array(clob) - Parse JSON array, returns table of objects + get_string(obj,key) - Extract string value from JSON object + get_number(obj,key) - Extract number value from JSON object + get_boolean(obj,key) - Extract boolean value from JSON object + get_last_error() - Get last parsing error (NULL if no error) + clear_error() - Clear error state +*/ + + -- ================================================================ + -- Functii pentru managementul erorilor + -- ================================================================ + 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; + + -- ================================================================ + -- Functie utilitara pentru curatarea JSON + -- ================================================================ + FUNCTION clean( + p_json IN CLOB + ) RETURN CLOB IS + v_clean CLOB; + BEGIN + -- Elimina spatii, tab-uri, newline-uri pentru parsing mai usor + v_clean := REPLACE(REPLACE(REPLACE(REPLACE(p_json, + CHR(10), ''), CHR(13), ''), CHR(9), ''), ' ', ''); + + RETURN v_clean; + END clean; + + -- ================================================================ + -- Parse JSON array si returneaza fiecare obiect + -- ================================================================ + FUNCTION parse_array( + p_json_array IN CLOB + ) RETURN t_json_array PIPELINED IS + + v_json_clean CLOB; + v_articol_json VARCHAR2(4000); + v_start_pos NUMBER := 1; + v_end_pos NUMBER; + v_bracket_count NUMBER; + + BEGIN + -- Reset error + g_last_error := NULL; + + -- Curata JSON-ul + v_json_clean := clean(p_json_array); + + -- Elimina bracket-urile exterioare [ ] + v_json_clean := TRIM(BOTH '[]' FROM v_json_clean); + + -- Parse fiecare obiect JSON din array + LOOP + -- Gaseste inceputul obiectului JSON { + v_start_pos := INSTR(v_json_clean, '{', v_start_pos); + EXIT WHEN v_start_pos = 0; + + -- Gaseste sfarsitul obiectului JSON } - ia in considerare nested objects + v_bracket_count := 1; + v_end_pos := v_start_pos; + + WHILE v_bracket_count > 0 AND v_end_pos < LENGTH(v_json_clean) LOOP + v_end_pos := v_end_pos + 1; + + IF SUBSTR(v_json_clean, v_end_pos, 1) = '{' THEN + v_bracket_count := v_bracket_count + 1; + ELSIF SUBSTR(v_json_clean, v_end_pos, 1) = '}' THEN + v_bracket_count := v_bracket_count - 1; + END IF; + END LOOP; + + -- Extrage obiectul JSON curent + IF v_bracket_count = 0 THEN + v_articol_json := SUBSTR(v_json_clean, v_start_pos, v_end_pos - v_start_pos + 1); + + + PIPE ROW(v_articol_json); + + -- Trece la urmatorul articol + v_start_pos := v_end_pos + 1; + ELSE + -- JSON malformat + g_last_error := 'JSON malformat - bracket-uri neechilibrate'; + EXIT; + END IF; + END LOOP; + + + EXCEPTION + WHEN OTHERS THEN + g_last_error := 'Eroare la parsing array: ' || SQLERRM; + END parse_array; + + -- ================================================================ + -- Extrage valoare string din obiect JSON + -- ================================================================ + FUNCTION get_string( + p_json_object IN VARCHAR2, + p_key_name IN VARCHAR2 + ) RETURN VARCHAR2 IS + v_result VARCHAR2(4000); + BEGIN + -- Pattern: "key_name":"value" + v_result := REGEXP_SUBSTR(p_json_object, + '"' || p_key_name || '":"([^"]*)"', 1, 1, NULL, 1); + + RETURN v_result; + + EXCEPTION + WHEN OTHERS THEN + g_last_error := 'Eroare la extragere string pentru ' || p_key_name || ': ' || SQLERRM; + RETURN NULL; + END get_string; + + -- ================================================================ + -- Extrage valoare numerica din obiect JSON + -- ================================================================ + FUNCTION get_number( + p_json_object IN VARCHAR2, + p_key_name IN VARCHAR2 + ) RETURN NUMBER IS + v_result_str VARCHAR2(100); + v_result NUMBER; + BEGIN + -- Pattern: "key_name":123.45 sau "key_name":"123.45" + -- Incearca mai intai fara quotes + v_result_str := REGEXP_SUBSTR(p_json_object, + '"' || p_key_name || '":([0-9.]+)', 1, 1, NULL, 1); + + -- Daca nu gaseste, incearca cu quotes + IF v_result_str IS NULL THEN + v_result_str := REGEXP_SUBSTR(p_json_object, + '"' || p_key_name || '":"([0-9.]+)"', 1, 1, NULL, 1); + END IF; + + IF v_result_str IS NOT NULL THEN + v_result := TO_NUMBER(v_result_str); + END IF; + + RETURN v_result; + + EXCEPTION + WHEN OTHERS THEN + g_last_error := 'Eroare la extragere number pentru ' || p_key_name || ': ' || SQLERRM; + RETURN NULL; + END get_number; + + -- ================================================================ + -- Extrage valoare boolean din obiect JSON + -- ================================================================ + FUNCTION get_boolean( + p_json_object IN VARCHAR2, + p_key_name IN VARCHAR2 + ) RETURN BOOLEAN IS + v_result_str VARCHAR2(10); + BEGIN + -- Pattern: "key_name":true/false + v_result_str := REGEXP_SUBSTR(p_json_object, + '"' || p_key_name || '":(true|false)', 1, 1, NULL, 1); + + IF v_result_str = 'true' THEN + RETURN TRUE; + ELSIF v_result_str = 'false' THEN + RETURN FALSE; + ELSE + RETURN NULL; + END IF; + + EXCEPTION + WHEN OTHERS THEN + g_last_error := 'Eroare la extragere boolean pentru ' || p_key_name || ': ' || SQLERRM; + RETURN NULL; + END get_boolean; + + -- ================================================================ + -- Parse complet obiect JSON in structura cheie-valoare + -- ================================================================ + FUNCTION parse_object( + p_json_object IN VARCHAR2 + ) RETURN t_json_object PIPELINED IS + + v_clean_json VARCHAR2(4000); + v_key VARCHAR2(100); + v_value VARCHAR2(4000); + v_result t_json_key_value; + v_pos NUMBER := 1; + v_key_start NUMBER; + v_key_end NUMBER; + v_value_start NUMBER; + v_value_end NUMBER; + + BEGIN + -- Curata JSON-ul si elimina { } + v_clean_json := TRIM(BOTH '{}' FROM REPLACE(p_json_object, ' ', '')); + + -- Parse fiecare pereche key:value + WHILE v_pos < LENGTH(v_clean_json) LOOP + -- Gaseste cheia + v_key_start := INSTR(v_clean_json, '"', v_pos); + EXIT WHEN v_key_start = 0; + + v_key_end := INSTR(v_clean_json, '"', v_key_start + 1); + EXIT WHEN v_key_end = 0; + + v_key := SUBSTR(v_clean_json, v_key_start + 1, v_key_end - v_key_start - 1); + + -- Gaseste valoarea + v_value_start := INSTR(v_clean_json, ':', v_key_end); + EXIT WHEN v_value_start = 0; + v_value_start := v_value_start + 1; + + -- Determina tipul si extrage valoarea + IF SUBSTR(v_clean_json, v_value_start, 1) = '"' THEN + -- String value + v_value_end := INSTR(v_clean_json, '"', v_value_start + 1); + v_value := SUBSTR(v_clean_json, v_value_start + 1, v_value_end - v_value_start - 1); + v_result.key_type := 'STRING'; + v_pos := v_value_end + 1; + ELSE + -- Number, boolean sau null + v_value_end := NVL(INSTR(v_clean_json, ',', v_value_start), LENGTH(v_clean_json) + 1); + v_value := SUBSTR(v_clean_json, v_value_start, v_value_end - v_value_start); + + IF v_value IN ('true', 'false') THEN + v_result.key_type := 'BOOLEAN'; + ELSIF v_value = 'null' THEN + v_result.key_type := 'NULL'; + ELSIF REGEXP_LIKE(v_value, '^[0-9.]+$') THEN + v_result.key_type := 'NUMBER'; + ELSE + v_result.key_type := 'UNKNOWN'; + END IF; + + v_pos := v_value_end + 1; + END IF; + + v_result.key_name := v_key; + v_result.key_value := v_value; + + PIPE ROW(v_result); + END LOOP; + + EXCEPTION + WHEN OTHERS THEN + g_last_error := 'Eroare la parsing obiect: ' || SQLERRM; + END parse_object; + + -- ================================================================ + -- Functii de testare + -- ================================================================ + + FUNCTION test_basic_parsing RETURN VARCHAR2 IS + v_test_json VARCHAR2(1000) := '{"name":"John","age":25,"active":true,"score":98.5}'; + v_name VARCHAR2(100); + v_age NUMBER; + v_active BOOLEAN; + v_score NUMBER; + v_result VARCHAR2(4000) := 'BASIC_PARSING: '; + BEGIN + clear_error(); + + v_name := get_string(v_test_json, 'name'); + v_age := get_number(v_test_json, 'age'); + v_active := get_boolean(v_test_json, 'active'); + v_score := get_number(v_test_json, 'score'); + + -- Validate results + IF v_name = 'John' AND v_age = 25 AND v_active = TRUE AND v_score = 98.5 THEN + v_result := v_result || 'PASS - All values extracted correctly'; + ELSE + v_result := v_result || 'FAIL - Values: name=' || v_name || ', age=' || v_age || ', score=' || v_score; + END IF; + + IF get_last_error() IS NOT NULL THEN + v_result := v_result || ' ERROR: ' || get_last_error(); + END IF; + + RETURN v_result; + EXCEPTION + WHEN OTHERS THEN + RETURN 'BASIC_PARSING: EXCEPTION - ' || SQLERRM; + END test_basic_parsing; + + FUNCTION test_array_parsing RETURN VARCHAR2 IS + v_test_array CLOB := '[{"sku":"PROD1","price":10.5},{"sku":"PROD2","price":25.0}]'; + v_count NUMBER := 0; + v_sku VARCHAR2(100); + v_price NUMBER; + v_result VARCHAR2(4000) := 'ARRAY_PARSING: '; + BEGIN + clear_error(); + + FOR obj IN (SELECT * FROM TABLE(parse_array(v_test_array))) LOOP + v_count := v_count + 1; + v_sku := get_string(obj.COLUMN_VALUE, 'sku'); + v_price := get_number(obj.COLUMN_VALUE, 'price'); + + IF v_count = 1 THEN + IF v_sku != 'PROD1' OR v_price != 10.5 THEN + RETURN v_result || 'FAIL - First object: sku=' || v_sku || ', price=' || v_price; + END IF; + ELSIF v_count = 2 THEN + IF v_sku != 'PROD2' OR v_price != 25.0 THEN + RETURN v_result || 'FAIL - Second object: sku=' || v_sku || ', price=' || v_price; + END IF; + END IF; + END LOOP; + + IF v_count = 2 THEN + v_result := v_result || 'PASS - Parsed ' || v_count || ' objects correctly'; + ELSE + v_result := v_result || 'FAIL - Expected 2 objects, got ' || v_count; + END IF; + + IF get_last_error() IS NOT NULL THEN + v_result := v_result || ' ERROR: ' || get_last_error(); + END IF; + + RETURN v_result; + EXCEPTION + WHEN OTHERS THEN + RETURN 'ARRAY_PARSING: EXCEPTION - ' || SQLERRM; + END test_array_parsing; + + FUNCTION test_nested_objects RETURN VARCHAR2 IS + v_test_nested CLOB := '[{"order":{"id":123,"items":[{"sku":"A1","qty":2}],"total":25.50}},{"order":{"id":124,"items":[{"sku":"B1","qty":1},{"sku":"C1","qty":3}],"total":45.00}}]'; + v_count NUMBER := 0; + v_object VARCHAR2(4000); + v_order_id NUMBER; + v_total NUMBER; + v_result VARCHAR2(4000) := 'NESTED_OBJECTS: '; + v_order_json VARCHAR2(2000); + BEGIN + clear_error(); + + -- Test parsing array cu nested objects + FOR obj IN (SELECT * FROM TABLE(parse_array(v_test_nested))) LOOP + v_count := v_count + 1; + v_object := obj.COLUMN_VALUE; + + -- Extrage nested object "order" + v_order_json := REGEXP_SUBSTR(v_object, '"order":\{([^}]+)\}', 1, 1, NULL, 1); + IF v_order_json IS NULL THEN + -- Incearca sa gaseasca tot nested object-ul (mai complex) + v_order_json := REGEXP_SUBSTR(v_object, '"order":\{.*\}', 1, 1); + -- Elimina "order": din fata + v_order_json := REGEXP_REPLACE(v_order_json, '^"order":', ''); + END IF; + + IF v_order_json IS NOT NULL THEN + v_order_id := get_number(v_order_json, 'id'); + v_total := get_number(v_order_json, 'total'); + + IF v_count = 1 THEN + IF v_order_id != 123 OR v_total != 25.50 THEN + RETURN v_result || 'FAIL - First nested: id=' || v_order_id || ', total=' || v_total; + END IF; + ELSIF v_count = 2 THEN + IF v_order_id != 124 OR v_total != 45.00 THEN + RETURN v_result || 'FAIL - Second nested: id=' || v_order_id || ', total=' || v_total; + END IF; + END IF; + ELSE + RETURN v_result || 'FAIL - Could not extract nested order object from: ' || SUBSTR(v_object, 1, 100); + END IF; + END LOOP; + + IF v_count = 2 THEN + v_result := v_result || 'PASS - Parsed ' || v_count || ' nested objects correctly'; + ELSE + v_result := v_result || 'FAIL - Expected 2 nested objects, got ' || v_count; + END IF; + + IF get_last_error() IS NOT NULL THEN + v_result := v_result || ' ERROR: ' || get_last_error(); + END IF; + + RETURN v_result; + EXCEPTION + WHEN OTHERS THEN + RETURN 'NESTED_OBJECTS: EXCEPTION - ' || SQLERRM; + END test_nested_objects; + + FUNCTION test_error_handling RETURN VARCHAR2 IS + v_result VARCHAR2(4000) := 'ERROR_HANDLING: '; + v_invalid_json VARCHAR2(1000) := '{"broken":}'; + v_value VARCHAR2(100); + BEGIN + clear_error(); + + -- Test invalid JSON + v_value := get_string(v_invalid_json, 'broken'); + + -- Test non-existent key + v_value := get_string('{"valid":"json"}', 'nonexistent'); + + IF get_last_error() IS NOT NULL THEN + v_result := v_result || 'PASS - Error properly captured: ' || SUBSTR(get_last_error(), 1, 100); + clear_error(); + ELSE + v_result := v_result || 'FAIL - No error captured for invalid operations'; + END IF; + + -- Test error clearing + IF get_last_error() IS NULL THEN + v_result := v_result || ' - Error cleared successfully'; + ELSE + v_result := v_result || ' - Error not cleared properly'; + END IF; + + RETURN v_result; + EXCEPTION + WHEN OTHERS THEN + RETURN 'ERROR_HANDLING: EXCEPTION - ' || SQLERRM; + END test_error_handling; + + PROCEDURE run_tests IS + v_test_result VARCHAR2(4000); + BEGIN + DBMS_OUTPUT.PUT_LINE('=== PACK_JSON Test Suite ==='); + DBMS_OUTPUT.PUT_LINE(''); + + -- Test 1: Basic parsing + v_test_result := test_basic_parsing(); + DBMS_OUTPUT.PUT_LINE(v_test_result); + + -- Test 2: Array parsing + v_test_result := test_array_parsing(); + DBMS_OUTPUT.PUT_LINE(v_test_result); + + -- Test 3: Nested objects + v_test_result := test_nested_objects(); + DBMS_OUTPUT.PUT_LINE(v_test_result); + + -- Test 4: Error handling + v_test_result := test_error_handling(); + DBMS_OUTPUT.PUT_LINE(v_test_result); + + DBMS_OUTPUT.PUT_LINE(''); + DBMS_OUTPUT.PUT_LINE('=== Test Suite Complete ==='); + EXCEPTION + WHEN OTHERS THEN + DBMS_OUTPUT.PUT_LINE('ERROR in run_tests: ' || SQLERRM); + END run_tests; + +END PACK_JSON; +/ + +-- ==================================================================== +-- Grant-uri pentru utilizarea package-ului +-- ==================================================================== +-- GRANT EXECUTE ON PACK_JSON TO PUBLIC; \ No newline at end of file diff --git a/api/database-scripts/04_import_comenzi.sql b/api/database-scripts/04_import_comenzi.sql new file mode 100644 index 0000000..e7a9a27 --- /dev/null +++ b/api/database-scripts/04_import_comenzi.sql @@ -0,0 +1,367 @@ +-- ==================================================================== +-- P1-003: Package IMPORT_COMENZI pentru import comenzi web -> ROA +-- Sistem Import Comenzi Web -> ROA +-- ==================================================================== + +-- Package pentru importul comenzilor web cu mapari complexe SKU -> CODMAT +CREATE OR REPLACE PACKAGE PACK_IMPORT_COMENZI AS + + -- Tipuri pentru returnarea rezultatelor + TYPE t_articol_result IS RECORD ( + id_articol NUMBER, + codmat VARCHAR2(50), + cantitate_roa NUMBER, + pret_unitar NUMBER, + success NUMBER, + error_message VARCHAR2(4000) + ); + + TYPE t_articol_table IS TABLE OF t_articol_result; + + -- Functie pentru gasirea/maparea articolelor ROA + FUNCTION gaseste_articol_roa( + p_sku IN VARCHAR2, + p_pret_web IN NUMBER DEFAULT NULL, + p_cantitate_web IN NUMBER DEFAULT 1 + ) RETURN t_articol_table PIPELINED; + + -- Functie pentru importul complet al unei comenzi web + FUNCTION importa_comanda_web( + p_nr_comanda_ext IN VARCHAR2, + p_data_comanda IN DATE, + p_id_partener IN NUMBER, + p_json_articole IN CLOB, -- JSON array cu articolele + p_id_adresa_livrare IN NUMBER DEFAULT NULL, + p_observatii IN VARCHAR2 DEFAULT NULL + ) RETURN NUMBER; -- Returneaza ID_COMANDA sau -1 pentru eroare + +END PACK_IMPORT_COMENZI; +/ + +-- ==================================================================== +-- Package Body - Implementarea functiilor +-- ==================================================================== +CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS + + -- Constante pentru configurare + c_id_gestiune CONSTANT NUMBER := 1; + c_id_sectie CONSTANT NUMBER := 1; + c_id_pol CONSTANT NUMBER := NULL; + c_id_util CONSTANT NUMBER := -3; -- Sistem + c_interna CONSTANT NUMBER := 0; -- Externe + + + -- Procedura interna pentru validarea seturilor + FUNCTION valideaza_set(p_sku IN VARCHAR2) RETURN BOOLEAN IS + v_suma_procent NUMBER := 0; + v_count_articole NUMBER := 0; + BEGIN + SELECT NVL(SUM(procent_pret), 0), COUNT(*) + INTO v_suma_procent, v_count_articole + FROM articole_terti + WHERE sku = p_sku + AND activ = 1; + + -- Validari logice pentru seturi + IF v_count_articole > 1 THEN + -- Set compus - suma procentelor trebuie sa fie intre 95-105% (toleranta) + IF v_suma_procent < 95 OR v_suma_procent > 105 THEN + pINFO('WARN VALIDEAZA_SET ' || p_sku || ': Suma procente nelogica: ' || v_suma_procent || '%', 'IMPORT_COMENZI'); + RETURN FALSE; + END IF; + ELSIF v_count_articole = 1 THEN + -- Reimpachetare - procentul trebuie sa fie 100% + IF v_suma_procent != 100 THEN + pINFO('WARN VALIDEAZA_SET ' || p_sku || ': Reimpachetare cu procent != 100%: ' || v_suma_procent || '%', 'IMPORT_COMENZI'); + RETURN FALSE; + END IF; + END IF; + + RETURN TRUE; + END valideaza_set; + + -- ================================================================ + -- Functia principala pentru gasirea articolelor ROA + -- ================================================================ + FUNCTION gaseste_articol_roa( + p_sku IN VARCHAR2, + p_pret_web IN NUMBER DEFAULT NULL, + p_cantitate_web IN NUMBER DEFAULT 1 + ) RETURN t_articol_table PIPELINED IS + + v_result t_articol_result; + v_found_mapping BOOLEAN := FALSE; + v_id_articol NUMBER; + + -- Cursor pentru maparile din ARTICOLE_TERTI + CURSOR c_mapari IS + SELECT at.codmat, at.cantitate_roa, at.procent_pret, + na.id_articol + FROM articole_terti at + JOIN nom_articole na ON na.codmat = at.codmat + WHERE at.sku = p_sku + AND at.activ = 1 + ORDER BY at.procent_pret DESC; -- Articolele principale primul + + BEGIN + pINFO('GASESTE_ARTICOL ' || p_sku || ': Cautare articol pentru SKU: ' || p_sku, 'IMPORT_COMENZI'); + + -- Initializare rezultat + v_result.success := 0; + v_result.error_message := NULL; + + -- STEP 1: Verifica maparile speciale din ARTICOLE_TERTI + FOR rec IN c_mapari LOOP + v_found_mapping := TRUE; + + v_result.id_articol := rec.id_articol; + v_result.codmat := rec.codmat; + v_result.cantitate_roa := rec.cantitate_roa * p_cantitate_web; + + -- Calculeaza pretul unitar pe baza procentului alocat + IF p_pret_web IS NOT NULL THEN + v_result.pret_unitar := (p_pret_web * rec.procent_pret / 100) / rec.cantitate_roa; + ELSE + -- Fara pret web, setam 0 (va fi necesar sa fie furnizat) + v_result.pret_unitar := 0; + END IF; + + v_result.success := 1; + + pINFO('GASESTE_ARTICOL ' || p_sku || ': Mapare gasita: ' || rec.codmat || + ', Cant: ' || v_result.cantitate_roa || + ', Pret: ' || v_result.pret_unitar, 'IMPORT_COMENZI'); + + PIPE ROW(v_result); + END LOOP; + + -- STEP 2: Daca nu s-au gasit mapari speciale, cauta direct in nom_articole + IF NOT v_found_mapping THEN + BEGIN + SELECT id_articol, codmat + INTO v_result.id_articol, v_result.codmat + FROM nom_articole + WHERE codmat = p_sku; + + v_result.cantitate_roa := p_cantitate_web; + + -- Pentru cautare directa, foloseste pretul din web daca este furnizat + IF p_pret_web IS NOT NULL THEN + v_result.pret_unitar := p_pret_web; + END IF; + + v_result.success := 1; + + pINFO('GASESTE_ARTICOL ' || p_sku || ': Gasit direct in nomenclator: ' || v_result.codmat, 'IMPORT_COMENZI'); + + PIPE ROW(v_result); + + EXCEPTION + WHEN NO_DATA_FOUND THEN + v_result.success := 0; + v_result.error_message := 'SKU nu a fost gasit nici in ARTICOLE_TERTI, nici in nom_articole: ' || p_sku; + + pINFO('ERROR GASESTE_ARTICOL ' || p_sku || ': ' || v_result.error_message, 'IMPORT_COMENZI'); + PIPE ROW(v_result); + + WHEN TOO_MANY_ROWS THEN + v_result.success := 0; + v_result.error_message := 'Multiple articole gasite pentru SKU: ' || p_sku; + + pINFO('ERROR GASESTE_ARTICOL ' || p_sku || ': ' || v_result.error_message, 'IMPORT_COMENZI'); + PIPE ROW(v_result); + END; + ELSE + -- Valideaza seturile dupa ce au fost returnate toate maparile + IF NOT valideaza_set(p_sku) THEN + pINFO('WARN GASESTE_ARTICOL ' || p_sku || ': Set cu configuratie suspecta - verifica procentele', 'IMPORT_COMENZI'); + END IF; + END IF; + + EXCEPTION + WHEN OTHERS THEN + v_result.success := 0; + v_result.error_message := 'Eroare neasteptata: ' || SQLERRM; + + pINFO('ERROR GASESTE_ARTICOL ' || p_sku || ': Eroare neasteptata: ' || SQLERRM, 'IMPORT_COMENZI'); + PIPE ROW(v_result); + END gaseste_articol_roa; + + -- ================================================================ + -- Functia pentru importul complet al unei comenzi web + -- ================================================================ + FUNCTION importa_comanda_web( + p_nr_comanda_ext IN VARCHAR2, + p_data_comanda IN DATE, + p_id_partener IN NUMBER, + p_json_articole IN CLOB, + p_id_adresa_livrare IN NUMBER DEFAULT NULL, + p_observatii IN VARCHAR2 DEFAULT NULL + ) RETURN NUMBER IS + + v_id_comanda NUMBER; + v_data_livrare DATE; + v_sku VARCHAR2(100); + v_cantitate_web NUMBER; + v_pret_web NUMBER; + v_articole_procesate NUMBER := 0; + v_articole_eroare NUMBER := 0; + v_start_time DATE; + v_json_pos NUMBER := 1; + v_json_end NUMBER; + v_json_item CLOB; + + BEGIN + v_start_time := SYSDATE; + + pINFO('IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Incepere import comanda pentru partener: ' || p_id_partener, 'IMPORT_COMENZI'); + + -- Validari de baza + IF p_nr_comanda_ext IS NULL OR p_id_partener IS NULL THEN + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Parametri obligatorii lipsa', 'IMPORT_COMENZI'); + RETURN -1; + END IF; + + -- Verifica daca comanda nu exista deja + BEGIN + SELECT id_comanda INTO v_id_comanda + FROM comenzi + WHERE comanda_externa = p_nr_comanda_ext + AND sters = 0; + + pINFO('WARN IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Comanda exista deja cu ID: ' || v_id_comanda, 'IMPORT_COMENZI'); + RETURN v_id_comanda; -- Returneaza ID-ul comenzii existente + + EXCEPTION + WHEN NO_DATA_FOUND THEN + NULL; -- Normal, comanda nu exista + END; + + -- Calculeaza data de livrare (comanda + 1 zi) + v_data_livrare := p_data_comanda + 1; + + -- STEP 1: Creeaza comanda folosind package-ul existent + BEGIN + v_id_comanda := PACK_COMENZI.adauga_comanda( + p_nr_comanda => p_nr_comanda_ext, + p_data_comanda => p_data_comanda, + p_id_partener => p_id_partener, + p_data_livrare => v_data_livrare, + p_id_gestiune => c_id_gestiune, + p_id_sectie => c_id_sectie, + p_interna => c_interna, + p_id_util => c_id_util, + p_comanda_externa => p_nr_comanda_ext, + p_id_adresa_livrare => p_id_adresa_livrare, + p_observatii => p_observatii + ); + + IF v_id_comanda IS NULL OR v_id_comanda <= 0 THEN + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': PACK_COMENZI.adauga_comanda a returnat ID invalid', 'IMPORT_COMENZI'); + RETURN -1; + END IF; + + pINFO('IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Comanda creata cu ID: ' || v_id_comanda, 'IMPORT_COMENZI'); + + EXCEPTION + WHEN OTHERS THEN + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Eroare la crearea comenzii: ' || SQLERRM, 'IMPORT_COMENZI'); + RETURN -1; + END; + + -- STEP 2: Proceseaza articolele din JSON folosind PACK_JSON + -- Asteapta format JSON: [{"sku":"ABC","cantitate":1,"pret":10.5},{"sku":"DEF","cantitate":2,"pret":20.0}] + DECLARE + v_articol_json VARCHAR2(4000); + v_articol_count NUMBER := 0; + BEGIN + -- Parse JSON array folosind package-ul generic + FOR json_obj IN ( + SELECT * FROM TABLE(PACK_JSON.parse_array(p_json_articole)) + ) LOOP + v_articol_count := v_articol_count + 1; + v_articol_json := json_obj.COLUMN_VALUE; + + BEGIN + -- Extrage datele folosind functiile PACK_JSON + v_sku := PACK_JSON.get_string(v_articol_json, 'sku'); + v_cantitate_web := PACK_JSON.get_number(v_articol_json, 'cantitate'); + v_pret_web := PACK_JSON.get_number(v_articol_json, 'pret'); + + pINFO('IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Procesez articol ' || v_articol_count || ': ' || v_sku || ', cant: ' || v_cantitate_web || ', pret: ' || v_pret_web, 'IMPORT_COMENZI'); + + -- STEP 3: Gaseste maparile pentru acest SKU + FOR art_rec IN ( + SELECT * FROM TABLE(gaseste_articol_roa(v_sku, v_pret_web, v_cantitate_web)) + ) LOOP + IF art_rec.success = 1 THEN + -- Adauga articolul la comanda + BEGIN + PACK_COMENZI.adauga_articol_comanda( + p_id_comanda => v_id_comanda, + p_id_articol => art_rec.id_articol, + p_cantitate => art_rec.cantitate_roa, + p_pret => art_rec.pret_unitar, + p_id_pol => c_id_pol, + p_id_util => c_id_util + ); + + v_articole_procesate := v_articole_procesate + 1; + + pINFO('IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Articol adaugat: ' || art_rec.codmat || + ', cant: ' || art_rec.cantitate_roa || + ', pret: ' || art_rec.pret_unitar, 'IMPORT_COMENZI'); + + EXCEPTION + WHEN OTHERS THEN + v_articole_eroare := v_articole_eroare + 1; + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Eroare la adaugare articol ' || art_rec.codmat || ': ' || SQLERRM, 'IMPORT_COMENZI'); + END; + ELSE + v_articole_eroare := v_articole_eroare + 1; + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': SKU nu a putut fi mapat: ' || v_sku || ' - ' || art_rec.error_message, 'IMPORT_COMENZI'); + END IF; + END LOOP; + + EXCEPTION + WHEN OTHERS THEN + v_articole_eroare := v_articole_eroare + 1; + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Eroare la procesarea articolului ' || v_articol_count || ': ' || SQLERRM, 'IMPORT_COMENZI'); + END; + + END LOOP; + + EXCEPTION + WHEN OTHERS THEN + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Eroare la parsarea JSON: ' || SQLERRM, 'IMPORT_COMENZI'); + RETURN -1; + END; + + -- Verifica daca s-au procesat articole cu succes + IF v_articole_procesate = 0 THEN + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Niciun articol nu a fost procesat cu succes', 'IMPORT_COMENZI'); + RETURN -1; + END IF; + + -- Log sumar final + pINFO('IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Import finalizat - ID comanda: ' || v_id_comanda || + ', Articole procesate: ' || v_articole_procesate || + ', Articole cu erori: ' || v_articole_eroare || + ', Timp procesare: ' || ROUND((SYSDATE - v_start_time) * 24 * 60 * 60, 2) || 's', 'IMPORT_COMENZI'); + + RETURN v_id_comanda; + + EXCEPTION + WHEN OTHERS THEN + pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Eroare neasteptata in importa_comanda_web: ' || SQLERRM, 'IMPORT_COMENZI'); + RETURN -1; + END importa_comanda_web; + +END PACK_IMPORT_COMENZI; +/ + +-- ==================================================================== +-- Grant-uri pentru utilizarea package-ului +-- ==================================================================== +-- GRANT EXECUTE ON PACK_IMPORT_COMENZI TO PUBLIC; +