This commit is contained in:
2026-03-11 12:32:25 +02:00
parent 8e94c05901
commit 69841872d1
6 changed files with 5781 additions and 1121 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ vfp/settings.ini
vfp/output/ vfp/output/
vfp/*.json vfp/*.json
*.~pck *.~pck
.claude/HANDOFF.md

View File

@@ -1,561 +0,0 @@
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;
/
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
-- Oracle 10g compatible: Extract string values
v_result := REGEXP_SUBSTR(p_json_object,
'"' || p_key_name || '":"[^"]*"');
IF v_result IS NOT NULL THEN
-- Remove key part and quotes manually
v_result := REGEXP_REPLACE(v_result, '^"' || p_key_name || '":"', '');
v_result := REGEXP_REPLACE(v_result, '"$', '');
END IF;
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
-- Oracle 10g compatible: Extract number values without subexpressions
-- Pattern: "key_name":123.45 (numeric value direct)
v_result_str := REGEXP_SUBSTR(p_json_object,
'"' || p_key_name || '":[0-9]+\.?[0-9]*');
IF v_result_str IS NOT NULL THEN
-- Extract just the number part after the colon
v_result_str := REGEXP_SUBSTR(v_result_str, '[0-9]+\.?[0-9]*');
END IF;
-- Daca nu gaseste, incearca cu quotes: "key_name":"123.45"
IF v_result_str IS NULL OR LENGTH(TRIM(v_result_str)) = 0 THEN
v_result_str := REGEXP_SUBSTR(p_json_object,
'"' || p_key_name || '":"[0-9]+\.?[0-9]*"');
IF v_result_str IS NOT NULL THEN
-- Extract number between quotes
v_result_str := REGEXP_SUBSTR(v_result_str, '[0-9]+\.?[0-9]*');
END IF;
END IF;
IF v_result_str IS NOT NULL AND LENGTH(TRIM(v_result_str)) > 0 THEN
BEGIN
v_result_str := TRIM(v_result_str);
-- Oracle 10g compatible conversion with NLS independence
v_result := TO_NUMBER(v_result_str, '999999999D999999999', 'NLS_NUMERIC_CHARACTERS=''.,''');
EXCEPTION
WHEN OTHERS THEN
BEGIN
-- Fallback: try with comma as decimal separator
v_result := TO_NUMBER(REPLACE(v_result_str, '.', ','));
EXCEPTION
WHEN OTHERS THEN
g_last_error := 'Cannot convert to number: "' || v_result_str || '" for key ' || p_key_name;
v_result := NULL;
END;
END;
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(100);
BEGIN
-- Oracle 10g compatible: Extract boolean values
v_result_str := REGEXP_SUBSTR(p_json_object,
'"' || p_key_name || '":(true|false)');
IF v_result_str IS NOT NULL THEN
-- Extract just the boolean value
v_result_str := REGEXP_REPLACE(v_result_str, '^"' || p_key_name || '":', '');
END IF;
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" (Oracle 10g compatible)
v_order_json := REGEXP_SUBSTR(v_object, '"order":\{[^}]+\}');
IF v_order_json IS NOT NULL THEN
-- Extract just the object part
v_order_json := REGEXP_REPLACE(v_order_json, '^"order":', '');
END IF;
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();
-- Force an error by trying to parse malformed array
BEGIN
FOR obj IN (SELECT * FROM TABLE(parse_array('[{"incomplete":"object"'))) LOOP
NULL;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
-- This should trigger parse_array to set g_last_error
NULL;
END;
-- Alternative: try to get a string from NULL object
v_value := get_string(NULL, 'test');
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;
/

View File

@@ -1,28 +1,55 @@
-- ====================================================================
-- PACK_IMPORT_COMENZI
-- Package pentru importul comenzilor din platforme web (GoMag, etc.)
-- in sistemul ROA Oracle.
--
-- Dependinte:
-- Packages: PACK_COMENZI (adauga_comanda, adauga_articol_comanda)
-- pljson (pljson_list, pljson) - instalat in CONTAFIN_ORACLE,
-- accesat prin PUBLIC SYNONYM
-- Tabele: ARTICOLE_TERTI (mapari SKU -> CODMAT)
-- NOM_ARTICOLE (nomenclator articole ROA)
-- COMENZI (verificare duplicat comanda_externa)
--
-- Proceduri publice:
--
-- importa_comanda(...)
-- Importa o comanda completa: creeaza comanda + adauga articolele.
-- p_json_articole accepta:
-- - array JSON: [{"sku":"X","quantity":"1","price":"10","vat":"19"}, ...]
-- - obiect JSON: {"sku":"X","quantity":"1","price":"10","vat":"19"}
-- Valorile sku, quantity, price, vat sunt extrase ca STRING si convertite.
-- Daca comanda exista deja (comanda_externa), nu se dubleaza.
-- La eroare ridica RAISE_APPLICATION_ERROR(-20001, mesaj).
-- Returneaza v_id_comanda (OUT) = ID-ul comenzii create.
--
-- Logica cautare articol per SKU:
-- 1. Mapari speciale din ARTICOLE_TERTI (reimpachetare, seturi compuse)
-- - un SKU poate avea mai multe randuri (set) cu procent_pret
-- 2. Fallback: cautare directa in NOM_ARTICOLE dupa CODMAT = SKU
--
-- get_last_error / clear_error
-- Management erori pentru orchestratorul VFP.
--
-- Exemplu utilizare:
-- DECLARE
-- v_id NUMBER;
-- BEGIN
-- PACK_IMPORT_COMENZI.importa_comanda(
-- p_nr_comanda_ext => '479317993',
-- p_data_comanda => SYSDATE,
-- p_id_partener => 1424,
-- p_json_articole => '[{"sku":"5941623003366","quantity":"1.00","price":"40.99","vat":"21"}]',
-- p_id_pol => 39,
-- v_id_comanda => v_id);
-- DBMS_OUTPUT.PUT_LINE('ID comanda: ' || v_id);
-- END;
-- ====================================================================
CREATE OR REPLACE PACKAGE PACK_IMPORT_COMENZI AS 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,
ptva NUMBER,
success NUMBER,
error_message VARCHAR2(4000));
TYPE t_articol_table IS TABLE OF t_articol_result;
-- Variabila package pentru ultima eroare (pentru orchestrator VFP) -- Variabila package pentru ultima eroare (pentru orchestrator VFP)
g_last_error VARCHAR2(4000); g_last_error VARCHAR2(4000);
-- 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,
p_ptva IN NUMBER)
RETURN t_articol_table
PIPELINED;
-- Procedura pentru importul complet al unei comenzi -- Procedura pentru importul complet al unei comenzi
PROCEDURE importa_comanda(p_nr_comanda_ext IN VARCHAR2, PROCEDURE importa_comanda(p_nr_comanda_ext IN VARCHAR2,
p_data_comanda IN DATE, p_data_comanda IN DATE,
@@ -34,7 +61,7 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_COMENZI AS
p_id_sectie IN NUMBER DEFAULT NULL, p_id_sectie IN NUMBER DEFAULT NULL,
v_id_comanda OUT NUMBER); v_id_comanda OUT NUMBER);
-- Functii pentru managementul erorilor (similar cu PACK_JSON) -- Functii pentru managementul erorilor (pentru orchestrator VFP)
FUNCTION get_last_error RETURN VARCHAR2; FUNCTION get_last_error RETURN VARCHAR2;
PROCEDURE clear_error; PROCEDURE clear_error;
@@ -43,7 +70,6 @@ END PACK_IMPORT_COMENZI;
CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
-- Constante pentru configurare -- Constante pentru configurare
-- Nota: c_id_pol, c_id_gestiune, c_id_sectie sunt acum parametri ai procedurii importa_comanda
c_id_util CONSTANT NUMBER := -3; -- Sistem c_id_util CONSTANT NUMBER := -3; -- Sistem
c_interna CONSTANT NUMBER := 2; -- Comenzi de la client (web) c_interna CONSTANT NUMBER := 2; -- Comenzi de la client (web)
@@ -61,155 +87,7 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
END clear_error; END clear_error;
-- ================================================================ -- ================================================================
-- Functii interne -- Procedura principala pentru importul unei comenzi
-- ================================================================
-- 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,
p_ptva IN NUMBER)
RETURN t_articol_table
PIPELINED IS
v_result t_articol_result;
v_found_mapping BOOLEAN := FALSE;
-- 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.ptva := p_ptva;
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.ptva := p_ptva;
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
null;
-- 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
-- ================================================================ -- ================================================================
PROCEDURE importa_comanda(p_nr_comanda_ext IN VARCHAR2, PROCEDURE importa_comanda(p_nr_comanda_ext IN VARCHAR2,
p_data_comanda IN DATE, p_data_comanda IN DATE,
@@ -224,22 +102,23 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
v_sku VARCHAR2(100); v_sku VARCHAR2(100);
v_cantitate_web NUMBER; v_cantitate_web NUMBER;
v_pret_web NUMBER; v_pret_web NUMBER;
v_vat NUMBER;
v_articole_procesate NUMBER := 0; v_articole_procesate NUMBER := 0;
v_articole_eroare NUMBER := 0; v_articole_eroare NUMBER := 0;
v_start_time DATE;
v_vat NUMBER;
v_articol_json VARCHAR2(4000);
v_articol_count NUMBER := 0; v_articol_count NUMBER := 0;
v_articole_table t_articol_table; -- Variabile pentru cautare articol
v_articol_idx NUMBER; v_found_mapping BOOLEAN;
art_rec t_articol_result; v_id_articol NUMBER;
v_codmat VARCHAR2(50);
v_cantitate_roa NUMBER;
v_pret_unitar NUMBER;
-- pljson
l_json_articole CLOB := p_json_articole; l_json_articole CLOB := p_json_articole;
v_json_arr pljson_list;
v_json_obj pljson;
BEGIN BEGIN
v_start_time := SYSDATE;
-- Resetare eroare la inceputul procesarii -- Resetare eroare la inceputul procesarii
clear_error; clear_error;
@@ -258,10 +137,9 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
WHERE comanda_externa = p_nr_comanda_ext WHERE comanda_externa = p_nr_comanda_ext
AND sters = 0; AND sters = 0;
-- pINFO('WARN IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Comanda exista deja cu ID: ' || v_id_comanda, 'IMPORT_COMENZI'); IF v_id_comanda IS NOT NULL THEN
if v_id_comanda is not null then
GOTO sfarsit; GOTO sfarsit;
end if; END IF;
EXCEPTION EXCEPTION
WHEN NO_DATA_FOUND THEN WHEN NO_DATA_FOUND THEN
NULL; -- Normal, comanda nu exista NULL; -- Normal, comanda nu exista
@@ -270,23 +148,21 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
-- Calculeaza data de livrare (comanda + 1 zi) -- Calculeaza data de livrare (comanda + 1 zi)
v_data_livrare := p_data_comanda + 1; v_data_livrare := p_data_comanda + 1;
-- STEP 1: Creeaza comanda folosind versiunea overloaded cu OUT parameter -- STEP 1: Creeaza comanda
-- Apeleaza procedura adauga_comanda care returneaza ID_COMANDA prin OUT
PACK_COMENZI.adauga_comanda(V_NR_COMANDA => p_nr_comanda_ext, PACK_COMENZI.adauga_comanda(V_NR_COMANDA => p_nr_comanda_ext,
V_DATA_COMANDA => p_data_comanda, V_DATA_COMANDA => p_data_comanda,
V_ID => p_id_partener, -- ID_PART V_ID => p_id_partener,
V_DATA_LIVRARE => v_data_livrare, V_DATA_LIVRARE => v_data_livrare,
V_PROC_DISCOUNT => 0, -- Fara discount implicit V_PROC_DISCOUNT => 0,
V_INTERNA => c_interna, V_INTERNA => c_interna,
V_ID_UTIL => c_id_util, V_ID_UTIL => c_id_util,
V_ID_SECTIE => p_id_sectie, V_ID_SECTIE => p_id_sectie,
V_ID_ADRESA_FACTURARE => p_id_adresa_facturare, V_ID_ADRESA_FACTURARE => p_id_adresa_facturare,
V_ID_ADRESA_LIVRARE => p_id_adresa_livrare, V_ID_ADRESA_LIVRARE => p_id_adresa_livrare,
V_ID_CODCLIENT => NULL, -- Nu folosim cod client V_ID_CODCLIENT => NULL,
V_COMANDA_EXTERNA => p_nr_comanda_ext, V_COMANDA_EXTERNA => p_nr_comanda_ext,
V_ID_CTR => NULL, -- Nu avem contract V_ID_CTR => NULL,
V_ID_COMANDA => v_id_comanda -- OUT parameter cu ID_COMANDA V_ID_COMANDA => v_id_comanda);
);
IF v_id_comanda IS NULL OR v_id_comanda <= 0 THEN IF v_id_comanda IS NULL OR v_id_comanda <= 0 THEN
g_last_error := 'IMPORTA_COMANDA ' || p_nr_comanda_ext || g_last_error := 'IMPORTA_COMANDA ' || p_nr_comanda_ext ||
@@ -294,90 +170,97 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_COMENZI AS
GOTO sfarsit; GOTO sfarsit;
END IF; END IF;
-- pINFO('IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Comanda creata cu ID: ' || v_id_comanda, 'IMPORT_COMENZI'); -- STEP 2: Proceseaza articolele din JSON folosind pljson
-- Suporta atat array "[{...},{...}]" cat si obiect singular "{...}"
IF LTRIM(l_json_articole) LIKE '[%' THEN
v_json_arr := pljson_list(l_json_articole);
ELSE
v_json_arr := pljson_list('[' || l_json_articole || ']');
END IF;
-- STEP 2: Proceseaza articolele din JSON folosind PACK_JSON FOR i IN 1 .. v_json_arr.count LOOP
-- Asteapta format JSON: [{"sku":"ABC","cantitate":1,"pret":10.5},{"sku":"DEF","cantitate":2,"pret":20.0}]
-- Parse JSON array folosind package-ul generic
FOR json_obj IN (SELECT *
FROM TABLE(PACK_JSON.parse_array(l_json_articole))) LOOP
v_articol_count := v_articol_count + 1; v_articol_count := v_articol_count + 1;
v_articol_json := json_obj.COLUMN_VALUE; v_json_obj := pljson(v_json_arr.get(i));
BEGIN BEGIN
-- Extrage datele folosind functiile PACK_JSON -- Extrage datele folosind pljson (valorile vin ca string din json magazin web)
v_sku := PACK_JSON.get_string(v_articol_json, 'sku'); v_sku := v_json_obj.get_string('sku');
v_cantitate_web := PACK_JSON.get_number(v_articol_json, 'quantity'); v_cantitate_web := TO_NUMBER(v_json_obj.get_string('quantity'));
v_pret_web := PACK_JSON.get_number(v_articol_json, 'price'); v_pret_web := TO_NUMBER(v_json_obj.get_string('price'));
v_vat := PACK_JSON.get_number(v_articol_json, 'vat'); v_vat := TO_NUMBER(v_json_obj.get_string('vat'));
-- 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 articolele ROA pentru acest SKU
-- Cauta mai intai in ARTICOLE_TERTI (mapari speciale / seturi)
v_found_mapping := FALSE;
-- STEP 3: Gaseste maparile pentru acest SKU FOR rec IN (SELECT at.codmat, at.cantitate_roa, at.procent_pret, na.id_articol
-- Apeleaza functia si stocheaza rezultatele FROM articole_terti at
SELECT * JOIN nom_articole na ON na.codmat = at.codmat
BULK COLLECT WHERE at.sku = v_sku
INTO v_articole_table AND at.activ = 1
FROM TABLE(gaseste_articol_roa(v_sku, ORDER BY at.procent_pret DESC) LOOP
v_pret_web,
v_cantitate_web,
v_vat));
-- Itereaza prin rezultate v_found_mapping := TRUE;
IF v_articole_table.COUNT > 0 THEN v_cantitate_roa := rec.cantitate_roa * v_cantitate_web;
FOR v_articol_idx IN 1 .. v_articole_table.COUNT LOOP v_pret_unitar := CASE WHEN v_pret_web IS NOT NULL
THEN (v_pret_web * rec.procent_pret / 100) / rec.cantitate_roa
ELSE 0
END;
art_rec := v_articole_table(v_articol_idx);
IF art_rec.success = 1 THEN
-- Adauga articolul la comanda
BEGIN BEGIN
PACK_COMENZI.adauga_articol_comanda(V_ID_COMANDA => v_id_comanda, PACK_COMENZI.adauga_articol_comanda(V_ID_COMANDA => v_id_comanda,
V_ID_ARTICOL => art_rec.id_articol, V_ID_ARTICOL => rec.id_articol,
V_ID_POL => p_id_pol, V_ID_POL => p_id_pol,
V_CANTITATE => art_rec.cantitate_roa, V_CANTITATE => v_cantitate_roa,
V_PRET => art_rec.pret_unitar, V_PRET => v_pret_unitar,
V_ID_UTIL => c_id_util, V_ID_UTIL => c_id_util,
V_ID_SECTIE => p_id_sectie, V_ID_SECTIE => p_id_sectie,
V_PTVA => art_rec.ptva); V_PTVA => v_vat);
v_articole_procesate := v_articole_procesate + 1; 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 EXCEPTION
WHEN OTHERS THEN WHEN OTHERS THEN
v_articole_eroare := v_articole_eroare + 1; v_articole_eroare := v_articole_eroare + 1;
g_last_error := g_last_error || CHR(10) || g_last_error := g_last_error || CHR(10) ||
'ERROR IMPORTA_COMANDA ' || 'Eroare adaugare articol ' || rec.codmat || ': ' || SQLERRM;
p_nr_comanda_ext ||
': Eroare la adaugare articol ' ||
art_rec.codmat || ': ' || SQLERRM;
-- pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Eroare la adaugare articol ' || art_rec.codmat || ': ' || SQLERRM, 'IMPORT_COMENZI');
END; END;
ELSE END LOOP;
-- Daca nu s-a gasit mapare, cauta direct in NOM_ARTICOLE
IF NOT v_found_mapping THEN
BEGIN
SELECT id_articol, codmat
INTO v_id_articol, v_codmat
FROM nom_articole
WHERE codmat = v_sku;
v_pret_unitar := NVL(v_pret_web, 0);
PACK_COMENZI.adauga_articol_comanda(V_ID_COMANDA => v_id_comanda,
V_ID_ARTICOL => v_id_articol,
V_ID_POL => p_id_pol,
V_CANTITATE => v_cantitate_web,
V_PRET => v_pret_unitar,
V_ID_UTIL => c_id_util,
V_ID_SECTIE => p_id_sectie,
V_PTVA => v_vat);
v_articole_procesate := v_articole_procesate + 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_articole_eroare := v_articole_eroare + 1; v_articole_eroare := v_articole_eroare + 1;
g_last_error := g_last_error || CHR(10) || g_last_error := g_last_error || CHR(10) ||
'ERROR IMPORTA_COMANDA ' || 'SKU negasit in ARTICOLE_TERTI si NOM_ARTICOLE: ' || v_sku;
p_nr_comanda_ext || WHEN TOO_MANY_ROWS THEN
': SKU nu a putut fi mapat: ' || v_sku || v_articole_eroare := v_articole_eroare + 1;
' - ' || art_rec.error_message; g_last_error := g_last_error || CHR(10) ||
-- pINFO('ERROR IMPORTA_COMANDA ' || p_nr_comanda_ext || ': SKU nu a putut fi mapat: ' || v_sku || ' - ' || art_rec.error_message, 'IMPORT_COMENZI'); 'Multiple articole gasite pentru SKU: ' || v_sku;
WHEN OTHERS THEN
v_articole_eroare := v_articole_eroare + 1;
g_last_error := g_last_error || CHR(10) ||
'Eroare adaugare articol ' || v_sku || ' (CODMAT: ' || v_codmat || '): ' || SQLERRM;
END;
END IF; END IF;
END LOOP; -- End v_articol_idx loop END; -- End BEGIN block pentru articol individual
ELSE
v_articole_eroare := v_articole_eroare + 1;
g_last_error := g_last_error || CHR(10) ||
'WARN IMPORTA_COMANDA ' || p_nr_comanda_ext ||
': Niciun articol gasit pentru SKU: ' ||
v_sku;
-- pINFO('WARN IMPORTA_COMANDA ' || p_nr_comanda_ext || ': Niciun articol gasit pentru SKU: ' || v_sku, 'IMPORT_COMENZI');
END IF;
END; -- End DECLARE block pentru v_articole_table
END LOOP; END LOOP;

File diff suppressed because it is too large Load Diff

306
scripts/parse_sync_log.py Normal file
View File

@@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""
Parser pentru log-urile sync_comenzi_web.
Extrage comenzi esuate, SKU-uri lipsa, si genereaza un sumar.
Suporta atat formatul vechi (verbose) cat si formatul nou (compact).
Utilizare:
python parse_sync_log.py # Ultimul log din vfp/log/
python parse_sync_log.py <fisier.log> # Log specific
python parse_sync_log.py --skus # Doar lista SKU-uri lipsa
python parse_sync_log.py --dir /path/to/logs # Director custom
"""
import os
import sys
import re
import glob
import argparse
# Regex pentru linii cu timestamp (intrare noua in log)
RE_TIMESTAMP = re.compile(r'^\[(\d{2}:\d{2}:\d{2})\]\s+\[(\w+\s*)\]\s*(.*)')
# Regex format NOU: [N/Total] OrderNumber P:X A:Y/Z -> OK/ERR details
RE_COMPACT_OK = re.compile(r'\[(\d+)/(\d+)\]\s+(\S+)\s+.*->\s+OK\s+ID:(\S+)')
RE_COMPACT_ERR = re.compile(r'\[(\d+)/(\d+)\]\s+(\S+)\s+.*->\s+ERR\s+(.*)')
# Regex format VECHI (backwards compat)
RE_SKU_NOT_FOUND = re.compile(r'SKU negasit.*?:\s*(\S+)')
RE_PRICE_POLICY = re.compile(r'Pretul pentru acest articol nu a fost gasit')
RE_FAILED_ORDER = re.compile(r'Import comanda esuat pentru\s+(\S+)')
RE_ARTICOL_ERR = re.compile(r'Eroare adaugare articol\s+(\S+)')
RE_ORDER_PROCESS = re.compile(r'Procesez comanda:\s+(\S+)\s+din\s+(\S+)')
RE_ORDER_SUCCESS = re.compile(r'SUCCES: Comanda importata.*?ID Oracle:\s+(\S+)')
# Regex comune
RE_SYNC_END = re.compile(r'SYNC END\s*\|.*?(\d+)\s+processed.*?(\d+)\s+ok.*?(\d+)\s+err')
RE_STATS_LINE = re.compile(r'Duration:\s*(\S+)\s*\|\s*Orders:\s*(\S+)')
RE_STOPPED_EARLY = re.compile(r'Peste \d+.*ero|stopped early')
def find_latest_log(log_dir):
"""Gaseste cel mai recent log sync_comenzi din directorul specificat."""
pattern = os.path.join(log_dir, 'sync_comenzi_*.log')
files = glob.glob(pattern)
if not files:
return None
return max(files, key=os.path.getmtime)
def parse_log_entries(lines):
"""Parseaza liniile log-ului in intrari structurate."""
entries = []
current = None
for line in lines:
line = line.rstrip('\n\r')
m = RE_TIMESTAMP.match(line)
if m:
if current:
entries.append(current)
current = {
'time': m.group(1),
'level': m.group(2).strip(),
'text': m.group(3),
'full': line,
'continuation': []
}
elif current is not None:
current['continuation'].append(line)
current['text'] += '\n' + line
if current:
entries.append(current)
return entries
def extract_sku_from_error(err_text):
"""Extrage SKU din textul erorii (diverse formate)."""
# SKU_NOT_FOUND: 8714858424056
m = re.search(r'SKU_NOT_FOUND:\s*(\S+)', err_text)
if m:
return ('SKU_NOT_FOUND', m.group(1))
# PRICE_POLICY: 8000070028685
m = re.search(r'PRICE_POLICY:\s*(\S+)', err_text)
if m:
return ('PRICE_POLICY', m.group(1))
# Format vechi: SKU negasit...NOM_ARTICOLE: xxx
m = RE_SKU_NOT_FOUND.search(err_text)
if m:
return ('SKU_NOT_FOUND', m.group(1))
# Format vechi: Eroare adaugare articol xxx
m = RE_ARTICOL_ERR.search(err_text)
if m:
return ('ARTICOL_ERROR', m.group(1))
# Format vechi: Pretul...
if RE_PRICE_POLICY.search(err_text):
return ('PRICE_POLICY', '(SKU necunoscut)')
return (None, None)
def analyze_entries(entries):
"""Analizeaza intrarile si extrage informatii relevante."""
result = {
'start_time': None,
'end_time': None,
'duration': None,
'total_orders': 0,
'success_orders': 0,
'error_orders': 0,
'stopped_early': False,
'failed': [],
'missing_skus': [],
}
seen_skus = set()
current_order = None
for entry in entries:
text = entry['text']
level = entry['level']
# Start/end time
if entry['time']:
if result['start_time'] is None:
result['start_time'] = entry['time']
result['end_time'] = entry['time']
# Format NOU: SYNC END line cu statistici
m = RE_SYNC_END.search(text)
if m:
result['total_orders'] = int(m.group(1))
result['success_orders'] = int(m.group(2))
result['error_orders'] = int(m.group(3))
# Format NOU: compact OK line
m = RE_COMPACT_OK.search(text)
if m:
continue
# Format NOU: compact ERR line
m = RE_COMPACT_ERR.search(text)
if m:
order_nr = m.group(3)
err_detail = m.group(4).strip()
err_type, sku = extract_sku_from_error(err_detail)
if err_type and sku:
result['failed'].append((order_nr, err_type, sku))
if sku not in seen_skus and sku != '(SKU necunoscut)':
seen_skus.add(sku)
result['missing_skus'].append(sku)
else:
result['failed'].append((order_nr, 'ERROR', err_detail[:60]))
continue
# Stopped early
if RE_STOPPED_EARLY.search(text):
result['stopped_early'] = True
# Format VECHI: statistici din sumar
if 'Total comenzi procesate:' in text:
try:
result['total_orders'] = int(text.split(':')[-1].strip())
except ValueError:
pass
if 'Comenzi importate cu succes:' in text:
try:
result['success_orders'] = int(text.split(':')[-1].strip())
except ValueError:
pass
if 'Comenzi cu erori:' in text:
try:
result['error_orders'] = int(text.split(':')[-1].strip())
except ValueError:
pass
# Format VECHI: Duration line
m = RE_STATS_LINE.search(text)
if m:
result['duration'] = m.group(1)
# Format VECHI: erori
if level == 'ERROR':
m_fail = RE_FAILED_ORDER.search(text)
if m_fail:
current_order = m_fail.group(1)
m = RE_ORDER_PROCESS.search(text)
if m:
current_order = m.group(1)
err_type, sku = extract_sku_from_error(text)
if err_type and sku:
order_nr = current_order or '?'
result['failed'].append((order_nr, err_type, sku))
if sku not in seen_skus and sku != '(SKU necunoscut)':
seen_skus.add(sku)
result['missing_skus'].append(sku)
# Duration din SYNC END
m = re.search(r'\|\s*(\d+)s\s*$', text)
if m:
result['duration'] = m.group(1) + 's'
return result
def format_report(result, log_path):
"""Formateaza raportul complet."""
lines = []
lines.append('=== SYNC LOG REPORT ===')
lines.append(f'File: {os.path.basename(log_path)}')
duration = result["duration"] or "?"
start = result["start_time"] or "?"
end = result["end_time"] or "?"
lines.append(f'Run: {start} - {end} ({duration})')
lines.append('')
stopped = 'YES' if result['stopped_early'] else 'NO'
lines.append(
f'SUMMARY: {result["total_orders"]} processed, '
f'{result["success_orders"]} success, '
f'{result["error_orders"]} errors '
f'(stopped early: {stopped})'
)
lines.append('')
if result['failed']:
lines.append('FAILED ORDERS:')
seen = set()
for order_nr, err_type, sku in result['failed']:
key = (order_nr, err_type, sku)
if key not in seen:
seen.add(key)
lines.append(f' {order_nr:<12} {err_type:<18} {sku}')
lines.append('')
if result['missing_skus']:
lines.append(f'MISSING SKUs ({len(result["missing_skus"])} unique):')
for sku in sorted(result['missing_skus']):
lines.append(f' {sku}')
lines.append('')
return '\n'.join(lines)
def main():
parser = argparse.ArgumentParser(
description='Parser pentru log-urile sync_comenzi_web'
)
parser.add_argument(
'logfile', nargs='?', default=None,
help='Fisier log specific (default: ultimul din vfp/log/)'
)
parser.add_argument(
'--skus', action='store_true',
help='Afiseaza doar lista SKU-uri lipsa (una pe linie)'
)
parser.add_argument(
'--dir', default=None,
help='Director cu log-uri (default: vfp/log/ relativ la script)'
)
args = parser.parse_args()
if args.logfile:
log_path = args.logfile
else:
if args.dir:
log_dir = args.dir
else:
script_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(script_dir)
log_dir = os.path.join(project_dir, 'vfp', 'log')
log_path = find_latest_log(log_dir)
if not log_path:
print(f'Nu am gasit fisiere sync_comenzi_*.log in {log_dir}',
file=sys.stderr)
sys.exit(1)
if not os.path.isfile(log_path):
print(f'Fisierul nu exista: {log_path}', file=sys.stderr)
sys.exit(1)
with open(log_path, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
entries = parse_log_entries(lines)
result = analyze_entries(entries)
if args.skus:
for sku in sorted(result['missing_skus']):
print(sku)
else:
print(format_report(result, log_path))
if __name__ == '__main__':
main()

View File

@@ -11,13 +11,14 @@ Set Ansi On
Set Deleted On Set Deleted On
*-- Variabile globale *-- Variabile globale
Private gcAppPath, gcLogFile, gnStartTime, gnOrdersProcessed, gnOrdersSuccess, gnOrdersErrors Private gcAppPath, gcLogFile, gnStartTime, gnOrdersProcessed, gnOrdersSuccess, gnOrdersErrors, gcFailedSKUs
Private goConnectie, goSettings, goAppSetup Private goConnectie, goSettings, goAppSetup, gcStepError
Local lcJsonPattern, laJsonFiles[1], lnJsonFiles, lnIndex, lcJsonFile Local lcJsonPattern, laJsonFiles[1], lnJsonFiles, lnIndex, lcJsonFile
Local loJsonData, lcJsonContent, lnOrderCount, lnOrderIndex Local loJsonData, lcJsonContent, lnOrderCount, lnOrderIndex
Local loOrder, lcResult, llProcessSuccess, lcPath Local loOrder, lcResult, llProcessSuccess, lcPath
goConnectie = Null goConnectie = Null
gcStepError = ""
*-- Initializare *-- Initializare
gcAppPath = Addbs(Justpath(Sys(16,0))) gcAppPath = Addbs(Justpath(Sys(16,0)))
@@ -37,100 +38,72 @@ gnStartTime = Seconds()
gnOrdersProcessed = 0 gnOrdersProcessed = 0
gnOrdersSuccess = 0 gnOrdersSuccess = 0
gnOrdersErrors = 0 gnOrdersErrors = 0
gcFailedSKUs = ""
*-- Initializare logging *-- Initializare logging
gcLogFile = InitLog("sync_comenzi") gcLogFile = InitLog("sync_comenzi")
LogMessage("=== SYNC COMENZI WEB > ORACLE ROA ===", "INFO", gcLogFile)
*-- Creare si initializare clasa setup aplicatie *-- Creare si initializare clasa setup aplicatie
goAppSetup = Createobject("ApplicationSetup", gcAppPath) goAppSetup = Createobject("ApplicationSetup", gcAppPath)
*-- Setup complet cu validare si afisare configuratie
If !goAppSetup.Initialize() If !goAppSetup.Initialize()
LogMessage("EROARE: Setup-ul aplicatiei a esuat sau necesita configurare!", "ERROR", gcLogFile) LogMessage("EROARE: Setup-ul aplicatiei a esuat sau necesita configurare!", "ERROR", gcLogFile)
Return .F. Return .F.
Endif Endif
*-- Obtinere setari din clasa
goSettings = goAppSetup.GetSettings() goSettings = goAppSetup.GetSettings()
*-- Verificare directoare necesare *-- Verificare directoare necesare
If !Directory(gcAppPath + "output") If !Directory(gcAppPath + "output")
LogMessage("EROARE: Directorul output/ nu exista! Ruleaza mai intai adapter-ul web", "ERROR", gcLogFile) LogMessage("EROARE: Directorul output/ nu exista!", "ERROR", gcLogFile)
Return .F. Return .F.
Endif Endif
*-- Rulare automata adapter pentru obtinere comenzi (daca este configurat) *-- Rulare automata adapter pentru obtinere comenzi
If goSettings.AutoRunAdapter If goSettings.AutoRunAdapter
LogMessage("Rulez adapter pentru obtinere comenzi: " + goSettings.AdapterProgram, "INFO", gcLogFile)
If !ExecuteAdapter() If !ExecuteAdapter()
LogMessage("EROARE la rularea adapter-ului, continuez cu fisierele JSON existente", "WARN", gcLogFile) LogMessage("EROARE adapter, continuez cu JSON existente", "WARN", gcLogFile)
Endif Endif
Else
LogMessage("AutoRunAdapter este dezactivat, folosesc doar fisierele JSON existente", "INFO", gcLogFile)
Endif Endif
*-- Gasire fisiere JSON comenzi din pattern configurat *-- Gasire fisiere JSON comenzi
lcJsonPattern = gcAppPath + "output\" + goSettings.JsonFilePattern lcJsonPattern = gcAppPath + "output\" + goSettings.JsonFilePattern
lnJsonFiles = Adir(laJsonFiles, lcJsonPattern) lnJsonFiles = Adir(laJsonFiles, lcJsonPattern)
If lnJsonFiles = 0 If lnJsonFiles = 0
LogMessage("AVERTISMENT: Nu au fost gasite fisiere JSON cu comenzi web", "WARN", gcLogFile) LogMessage("Nu au fost gasite fisiere JSON cu comenzi web", "WARN", gcLogFile)
LogMessage("Ruleaza mai intai adapter-ul web cu GetOrders=1 in settings.ini", "INFO", gcLogFile)
Return .T. Return .T.
Endif Endif
LogMessage("Gasite " + Transform(lnJsonFiles) + " fisiere JSON cu comenzi web", "INFO", gcLogFile) *-- Conectare Oracle
*-- Incercare conectare Oracle (folosind conexiunea existenta din sistem)
If !ConnectToOracle() If !ConnectToOracle()
LogMessage("EROARE: Nu s-a putut conecta la Oracle ROA", "ERROR", gcLogFile) LogMessage("EROARE: Nu s-a putut conecta la Oracle ROA", "ERROR", gcLogFile)
Return .F. Return .F.
Endif Endif
SET STEP ON
*-- Header compact
LogMessage("SYNC START | " + goSettings.OracleDSN + " " + goSettings.OracleUser + " | " + Transform(lnJsonFiles) + " JSON files", "INFO", gcLogFile)
*-- Procesare fiecare fisier JSON gasit *-- Procesare fiecare fisier JSON gasit
For lnIndex = 1 To lnJsonFiles For lnIndex = 1 To lnJsonFiles
lcJsonFile = gcAppPath + "output\" + laJsonFiles[lnIndex, 1] lcJsonFile = gcAppPath + "output\" + laJsonFiles[lnIndex, 1]
LogMessage("Procesez fisierul: " + laJsonFiles[lnIndex, 1], "INFO", gcLogFile)
*-- Citire si parsare JSON
Try Try
lcJsonContent = Filetostr(lcJsonFile) lcJsonContent = Filetostr(lcJsonFile)
If Empty(lcJsonContent) If Empty(lcJsonContent)
LogMessage("AVERTISMENT: Fisier JSON gol - " + laJsonFiles[lnIndex, 1], "WARN", gcLogFile)
Loop Loop
Endif Endif
*-- Parsare JSON array cu comenzi
loJsonData = nfjsonread(lcJsonContent) loJsonData = nfjsonread(lcJsonContent)
If Isnull(loJsonData) If Isnull(loJsonData) Or Type('loJsonData') != 'O' Or Type('loJsonData.orders') != 'O'
LogMessage("EROARE: Nu s-a putut parsa JSON-ul din " + laJsonFiles[lnIndex, 1], "ERROR", gcLogFile) LogMessage("EROARE JSON: " + laJsonFiles[lnIndex, 1], "ERROR", gcLogFile)
Loop Loop
Endif Endif
*-- Verificare daca este obiect JSON valid
If Type('loJsonData') != 'O'
LogMessage("EROARE: JSON-ul nu este un obiect valid - " + laJsonFiles[lnIndex, 1], "ERROR", gcLogFile)
Loop
Endif
*-- Verificare structura GoMag (cu proprietatea "orders")
If Type('loJsonData.orders') != 'O'
LogMessage("EROARE: JSON-ul nu contine proprietatea 'orders' - " + laJsonFiles[lnIndex, 1], "ERROR", gcLogFile)
Loop
Endif
*-- Obtinere numar comenzi din obiectul orders
Local Array laOrderProps[1] Local Array laOrderProps[1]
lnOrderCount = Amembers(laOrderProps, loJsonData.orders, 0) lnOrderCount = Amembers(laOrderProps, loJsonData.orders, 0)
LogMessage("Gasite " + Transform(lnOrderCount) + " comenzi in " + laJsonFiles[lnIndex, 1], "INFO", gcLogFile)
*-- Log informatii pagina daca sunt disponibile *-- Procesare fiecare comanda
If Type('loJsonData.page') = 'C' Or Type('loJsonData.page') = 'N'
LogMessage("Pagina: " + Transform(loJsonData.Page) + " din " + Transform(loJsonData.Pages), "DEBUG", gcLogFile)
Endif
*-- Procesare fiecare comanda din obiectul orders
For lnOrderIndex = 1 To lnOrderCount For lnOrderIndex = 1 To lnOrderCount
Local lcOrderId, loOrder Local lcOrderId, loOrder
lcOrderId = laOrderProps[lnOrderIndex] lcOrderId = laOrderProps[lnOrderIndex]
@@ -138,35 +111,28 @@ For lnIndex = 1 To lnJsonFiles
If Type('loOrder') = 'O' If Type('loOrder') = 'O'
gnOrdersProcessed = gnOrdersProcessed + 1 gnOrdersProcessed = gnOrdersProcessed + 1
LogMessage("Procesez comanda ID: " + lcOrderId + " (Nr: " + Iif(Type('loOrder.number') = 'C', loOrder.Number, "NECUNOSCUT") + ")", "DEBUG", gcLogFile)
llProcessSuccess = ProcessWebOrder(loOrder) llProcessSuccess = ProcessWebOrder(loOrder, lnOrderIndex, lnOrderCount)
If llProcessSuccess If llProcessSuccess
gnOrdersSuccess = gnOrdersSuccess + 1 gnOrdersSuccess = gnOrdersSuccess + 1
Else Else
gnOrdersErrors = gnOrdersErrors + 1 gnOrdersErrors = gnOrdersErrors + 1
Endif Endif
Else
LogMessage("AVERTISMENT: Comanda cu ID " + lcOrderId + " nu este un obiect valid", "WARN", gcLogFile)
Endif Endif
* Daca sunt peste 10 erori, ies din import fara sa mai import alte comenzi
* Probabil ca sunt erori in cod / baza de date
If m.gnOrdersErrors > 10 If m.gnOrdersErrors > 10
Exit Exit
Endif Endif
Endfor Endfor
Catch To loError Catch To loError
LogMessage("EROARE la procesarea fisierului " + laJsonFiles[lnIndex, 1] + ": " + loError.Message, "ERROR", gcLogFile) LogMessage("EROARE fisier " + laJsonFiles[lnIndex, 1] + ": " + loError.Message, "ERROR", gcLogFile)
gnOrdersErrors = gnOrdersErrors + 1 gnOrdersErrors = gnOrdersErrors + 1
Endtry Endtry
* Daca sunt peste 10 erori, ies din import fara sa mai import alte comenzi
* Probabil ca sunt erori in cod / baza de date
If m.gnOrdersErrors > 10 If m.gnOrdersErrors > 10
LogMessage("Peste 10 comenzi au dat eroare la import. Nu se mai importa restul de comenzi.", "ERROR", gcLogFile) LogMessage("Peste 10 erori, stop import", "ERROR", gcLogFile)
Exit Exit
Endif Endif
Endfor Endfor
@@ -174,11 +140,26 @@ Endfor
*-- Inchidere conexiune Oracle *-- Inchidere conexiune Oracle
DisconnectFromOracle() DisconnectFromOracle()
*-- Logging final cu statistici *-- Sumar SKU-uri lipsa
LogMessage("=== PROCESARE COMPLETA ===", "INFO", gcLogFile) If !Empty(gcFailedSKUs)
LogMessage("Total comenzi procesate: " + Transform(gnOrdersProcessed), "INFO", gcLogFile) LogMessage("=== SKU-URI LIPSA ===", "INFO", gcLogFile)
LogMessage("Comenzi importate cu succes: " + Transform(gnOrdersSuccess), "INFO", gcLogFile) Local lnSkuCount, lnSkuIdx
LogMessage("Comenzi cu erori: " + Transform(gnOrdersErrors), "INFO", gcLogFile) Local Array laSkus[1]
lnSkuCount = Alines(laSkus, gcFailedSKUs, .T., CHR(10))
For lnSkuIdx = 1 To lnSkuCount
If !Empty(laSkus[lnSkuIdx])
LogMessage(Alltrim(laSkus[lnSkuIdx]), "INFO", gcLogFile)
Endif
Endfor
LogMessage("=== SFARSIT SKU-URI LIPSA ===", "INFO", gcLogFile)
Endif
*-- Footer compact
Local lcStopped, lnSkuTotal, lnDuration
lnDuration = Int(Seconds() - gnStartTime)
lnSkuTotal = Iif(Empty(gcFailedSKUs), 0, Occurs(CHR(10), gcFailedSKUs) + 1)
lcStopped = Iif(gnOrdersErrors > 10, " (stopped early)", "")
LogMessage("SYNC END | " + Transform(gnOrdersProcessed) + " processed: " + Transform(gnOrdersSuccess) + " ok, " + Transform(gnOrdersErrors) + " err" + lcStopped + " | " + Transform(lnSkuTotal) + " SKUs lipsa | " + Transform(lnDuration) + "s", "INFO", gcLogFile)
CloseLog(gnStartTime, 0, gnOrdersProcessed, gcLogFile) CloseLog(gnStartTime, 0, gnOrdersProcessed, gcLogFile)
Return .T. Return .T.
@@ -187,132 +168,123 @@ Return .T.
*-- HELPER FUNCTIONS *-- HELPER FUNCTIONS
*-- =================================================================== *-- ===================================================================
*-- Functie pentru conectarea la Oracle folosind setarile din settings.ini *-- Conectare la Oracle
Function ConnectToOracle Function ConnectToOracle
Local llSuccess, lcConnectionString, lnHandle Local llSuccess, lnHandle
llSuccess = .F. llSuccess = .F.
Try Try
*-- Conectare Oracle folosind datele din settings.ini
lnHandle = SQLConnect(goSettings.OracleDSN, goSettings.OracleUser, goSettings.OraclePassword) lnHandle = SQLConnect(goSettings.OracleDSN, goSettings.OracleUser, goSettings.OraclePassword)
If lnHandle > 0 If lnHandle > 0
goConnectie = lnHandle goConnectie = lnHandle
llSuccess = .T. llSuccess = .T.
LogMessage("Conectare Oracle reusita - Handle: " + Transform(lnHandle), "INFO", gcLogFile)
LogMessage("DSN: " + goSettings.OracleDSN + " | User: " + goSettings.OracleUser, "DEBUG", gcLogFile)
Else Else
LogMessage("EROARE: Conectare Oracle esuata - Handle: " + Transform(lnHandle), "ERROR", gcLogFile) LogMessage("EROARE conectare Oracle: Handle=" + Transform(lnHandle), "ERROR", gcLogFile)
LogMessage("DSN: " + goSettings.OracleDSN + " | User: " + goSettings.OracleUser, "ERROR", gcLogFile)
Endif Endif
Catch To loError Catch To loError
LogMessage("EROARE la conectarea Oracle: " + loError.Message, "ERROR", gcLogFile) LogMessage("EROARE conectare Oracle: " + loError.Message, "ERROR", gcLogFile)
Endtry Endtry
Return llSuccess Return llSuccess
Endfunc Endfunc
*-- Functie pentru deconectarea de la Oracle *-- Deconectare de la Oracle
Function DisconnectFromOracle Function DisconnectFromOracle
If Type('goConnectie') = 'N' And goConnectie > 0 If Type('goConnectie') = 'N' And goConnectie > 0
SQLDisconnect(goConnectie) SQLDisconnect(goConnectie)
LogMessage("Deconectare Oracle reusita", "INFO", gcLogFile)
Endif Endif
Return .T. Return .T.
Endfunc Endfunc
*-- Functie principala de procesare comanda web *-- Procesare comanda web - logeaza O SINGURA LINIE per comanda
*-- Format: [N/Total] OrderNumber P:PartnerID A:AddrFact/AddrLivr -> OK/ERR details
Function ProcessWebOrder Function ProcessWebOrder
Parameters loOrder Lparameters loOrder, tnIndex, tnTotal
Local llSuccess, lcOrderNumber, lcOrderDate, lnPartnerID, lcArticlesJSON Local llSuccess, lcOrderNumber, lcOrderDate, lnPartnerID, lcArticlesJSON
Local lcObservatii, lcSQL, lnResult, lcErrorDetails, lnIdComanda, llSucces Local lcSQL, lnResult, lcErrorDetails, lnIdComanda, llSucces
Local ldOrderDate, loError Local ldOrderDate, loError
Local lnIdAdresaFacturare, lnIdAdresaLivrare, lcErrorMessage Local lnIdAdresaFacturare, lnIdAdresaLivrare
Local lcPrefix, lcSummary, lcErrDetail
lnIdAdresaLivrare = NULL lnIdAdresaLivrare = NULL
lnIdAdresaFacturare = NULL lnIdAdresaFacturare = NULL
lnIdComanda = 0 lnIdComanda = 0
llSucces = .T. llSucces = .T.
lnPartnerID = 0
lcOrderNumber = "?"
*-- Prefix: [N/Total] OrderNumber
lcPrefix = "[" + Transform(tnIndex) + "/" + Transform(tnTotal) + "]"
Try Try
*-- Validare comanda *-- Validare comanda
If !ValidateWebOrder(loOrder) If !ValidateWebOrder(loOrder)
LogMessage("EROARE: Comanda web invalida - lipsesc date obligatorii", "ERROR", gcLogFile) LogMessage(lcPrefix + " ? -> ERR VALIDARE: date obligatorii lipsa", "ERROR", gcLogFile)
llSucces = .F. Return .F.
Endif Endif
*-- Extragere date comanda *-- Extragere date comanda
If m.llSucces
lcOrderNumber = CleanWebText(Transform(loOrder.Number)) lcOrderNumber = CleanWebText(Transform(loOrder.Number))
lcOrderDate = ConvertWebDate(loOrder.Date) && yyyymmdd lcOrderDate = ConvertWebDate(loOrder.Date)
ldOrderDate = String2Date(m.lcOrderDate, 'yyyymmdd') ldOrderDate = String2Date(m.lcOrderDate, 'yyyymmdd')
lcPrefix = lcPrefix + " " + lcOrderNumber
LogMessage("Procesez comanda: " + lcOrderNumber + " din " + lcOrderDate, "INFO", gcLogFile) *-- Procesare partener
gcStepError = ""
*-- Procesare partener (billing address)
lnPartnerID = ProcessPartner(loOrder.billing) lnPartnerID = ProcessPartner(loOrder.billing)
If lnPartnerID <= 0 If lnPartnerID <= 0
LogMessage("EROARE: Nu s-a putut procesa partenerul pentru comanda " + lcOrderNumber, "ERROR", gcLogFile) LogMessage(lcPrefix + " -> ERR PARTENER: " + Iif(Empty(gcStepError), "nu s-a putut procesa", gcStepError), "ERROR", gcLogFile)
llSucces = .F. Return .F.
Else
LogMessage("Partener identificat/creat: ID=" + Transform(lnPartnerID), "INFO", gcLogFile)
*-- Adresa facturare
lnIdAdresaFacturare = ProcessAddress(m.lnPartnerID, loOrder.billing)
IF TYPE('loOrder.shipping') = 'O'
*-- Adresa livrares
lnIdAdresaLivrare = ProcessAddress(m.lnPartnerID, loOrder.shipping)
ENDIF
Endif Endif
*-- Adrese
lnIdAdresaFacturare = ProcessAddress(m.lnPartnerID, loOrder.billing)
If Type('loOrder.shipping') = 'O'
lnIdAdresaLivrare = ProcessAddress(m.lnPartnerID, loOrder.shipping)
Endif Endif
*-- Construire JSON articole *-- Construire JSON articole
If m.llSucces
lcArticlesJSON = BuildArticlesJSON(loOrder.items) lcArticlesJSON = BuildArticlesJSON(loOrder.items)
If Empty(m.lcArticlesJSON) If Empty(m.lcArticlesJSON)
LogMessage("EROARE: Nu s-au gasit articole valide in comanda " + lcOrderNumber, "ERROR", gcLogFile) LogMessage(lcPrefix + " P:" + Transform(lnPartnerID) + " -> ERR JSON_ARTICOLE", "ERROR", gcLogFile)
llSucces = .F. Return .F.
Endif
Endif Endif
*-- Construire observatii cu detalii suplimentare *-- Import comanda in Oracle
*!* lcObservatii = BuildOrderObservations(loOrder)
*-- Apel package Oracle pentru import comanda
If m.llSucces
lcSQL = "BEGIN PACK_IMPORT_COMENZI.importa_comanda(?lcOrderNumber, ?ldOrderDate, ?lnPartnerID, ?lcArticlesJSON, ?lnIdAdresaLivrare, ?lnIdAdresaFacturare, ?goSettings.IdPol, ?goSettings.IdSectie, ?@lnIdComanda); END;" lcSQL = "BEGIN PACK_IMPORT_COMENZI.importa_comanda(?lcOrderNumber, ?ldOrderDate, ?lnPartnerID, ?lcArticlesJSON, ?lnIdAdresaLivrare, ?lnIdAdresaFacturare, ?goSettings.IdPol, ?goSettings.IdSectie, ?@lnIdComanda); END;"
lnResult = SQLExec(goConnectie, lcSQL) lnResult = SQLExec(goConnectie, lcSQL)
*-- Construire linie sumar cu ID-uri adrese
lcSummary = lcPrefix + " P:" + Transform(lnPartnerID) + ;
" A:" + Transform(Nvl(lnIdAdresaFacturare, 0)) + "/" + Transform(Nvl(lnIdAdresaLivrare, 0))
If lnResult > 0 And Nvl(m.lnIdComanda, 0) > 0 If lnResult > 0 And Nvl(m.lnIdComanda, 0) > 0
LogMessage("SUCCES: Comanda importata - ID Oracle: " + Transform(m.lnIdComanda), "INFO", gcLogFile) LogMessage(lcSummary + " -> OK ID:" + Transform(m.lnIdComanda), "INFO", gcLogFile)
llSuccess = .T. Return .T.
Else Else
llSuccess = .F. lcErrorDetails = GetOracleErrorDetails()
*-- Obtinere detalii eroare Oracle lcErrDetail = ClassifyImportError(lcErrorDetails)
lcErrorDetails = GetOracleErrorDetails(m.lcSQL) CollectFailedSKUs(lcErrorDetails)
LogMessage("EROARE: Import comanda esuat pentru " + lcOrderNumber + CHR(10) + lcErrorDetails, "ERROR", gcLogFile) LogMessage(lcSummary + " -> ERR " + lcErrDetail, "ERROR", gcLogFile)
Endif Return .F.
Endif Endif
Catch To loError Catch To loError
llSucces = .F. LogMessage(lcPrefix + " -> ERR EXCEPTIE: " + loError.Message, "ERROR", gcLogFile)
LogMessage("EXCEPTIE la procesarea comenzii " + lcOrderNumber + ": " + loError.Message, "ERROR", gcLogFile) Return .F.
Endtry Endtry
Return llSuccess
Endfunc Endfunc
*-- Functie pentru validarea comenzii web *-- Validare comanda web
Function ValidateWebOrder Function ValidateWebOrder
Parameters loOrder Parameters loOrder
Local llValid Local llValid
llValid = .T. llValid = .T.
*-- Verificari obligatorii
If Type('loOrder.number') != 'C' Or Empty(loOrder.Number) If Type('loOrder.number') != 'C' Or Empty(loOrder.Number)
llValid = .F. llValid = .F.
Endif Endif
@@ -332,7 +304,7 @@ Function ValidateWebOrder
Return llValid Return llValid
Endfunc Endfunc
*-- Functie pentru procesarea partenerului din billing.company GoMag *-- Procesare partener (fara logging, seteaza gcStepError la eroare)
Function ProcessPartner Function ProcessPartner
Lparameters toBilling Lparameters toBilling
Local lcDenumire, lcCodFiscal, lcRegistru, lcAdresa, lcTelefon, lcEmail, lcRegistru Local lcDenumire, lcCodFiscal, lcRegistru, lcAdresa, lcTelefon, lcEmail, lcRegistru
@@ -344,147 +316,63 @@ Function ProcessPartner
lcCodFiscal = '' lcCodFiscal = ''
lcRegistru = '' lcRegistru = ''
lnIsPersoanaJuridica = 0 lnIsPersoanaJuridica = 0
lcCodFiscal = Null && Persoanele fizice nu au CUI in platformele web lcCodFiscal = Null
If .F.
TEXT TO lcExampleJsonAdresa NOSHOW
"billing": {
"address": "STR. CAMPUL LINISTII, NR. 1",
"city": "Arad",
"company": {
"bank": "",
"code": "RO30071208",
"iban": "",
"name": "BASSANO BUILDINGS SRL",
"registrationNo": ""
},
"country": "Romania",
"customerId": "13422",
"email": "office@bassano.ro",
"firstname": "Ionela",
"lastname": "Letcan",
"phone": "0728141899",
"region": "Arad"
},
ENDTEXT
ENDIF
Try Try
*-- Extragere date partener din datele billing
If Type('toBilling.company') = 'O' And !Empty(toBilling.company.Name) If Type('toBilling.company') = 'O' And !Empty(toBilling.company.Name)
loCompany = toBilling.company loCompany = toBilling.company
*-- Companie - persoana juridica
lcDenumire = CleanWebText(loCompany.Name) lcDenumire = CleanWebText(loCompany.Name)
lcCodFiscal = Iif(Type('loCompany.code') = 'C', loCompany.Code, Null) lcCodFiscal = Iif(Type('loCompany.code') = 'C', loCompany.Code, Null)
lcCodFiscal = CleanWebText(m.lcCodFiscal) lcCodFiscal = CleanWebText(m.lcCodFiscal)
lcRegistru = Iif(Type('loCompany.registrationNo') = 'C', loCompany.registrationNo, Null) lcRegistru = Iif(Type('loCompany.registrationNo') = 'C', loCompany.registrationNo, Null)
lcRegistru = CleanWebText(m.lcRegistru) lcRegistru = CleanWebText(m.lcRegistru)
lnIsPersoanaJuridica = 1 && Persoana juridica lnIsPersoanaJuridica = 1
Else Else
*-- Persoana fizica If Type('toBilling.firstname') = 'C'
IF TYPE('toBilling.firstname') = 'C'
lcDenumire = CleanWebText(Alltrim(toBilling.firstname) + " " + Alltrim(toBilling.lastname)) lcDenumire = CleanWebText(Alltrim(toBilling.firstname) + " " + Alltrim(toBilling.lastname))
lnIsPersoanaJuridica = 0 && Persoana fizica lnIsPersoanaJuridica = 0
ENDIF Endif
Endif Endif
LogMessage("Partener: " + lcDenumire + " | CUI: " + Iif(Isnull(lcCodFiscal), "NULL", lcCodFiscal) + " | Tip: " + Iif(lnIsPersoanaJuridica = 1, "JURIDICA", "FIZICA"), "DEBUG", gcLogFile)
* Cautare/creare client
lcSQL = "BEGIN PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener(?lcCodFiscal, ?lcDenumire, ?lcRegistru, ?lnIsPersoanaJuridica, ?@lnIdPart); END;" lcSQL = "BEGIN PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener(?lcCodFiscal, ?lcDenumire, ?lcRegistru, ?lnIsPersoanaJuridica, ?@lnIdPart); END;"
lnResult = SQLExec(goConnectie, lcSQL) lnResult = SQLExec(goConnectie, lcSQL)
If lnResult > 0 If lnResult <= 0
LogMessage("Partener procesat cu succes: ID=" + Transform(m.lnIdPart), "DEBUG", gcLogFile) gcStepError = lcDenumire + " | " + GetOracleErrorDetails()
Else
*-- Obtinere detalii eroare Oracle
lcErrorDetails = GetOracleErrorDetails(m.lcSQL)
LogMessage("EROARE la apelul procedurii PACK_IMPORT_PARTENERI.cauta_sau_creeaza_partener pentru: " + lcDenumire + CHR(10) + lcErrorDetails, "ERROR", gcLogFile)
Endif Endif
Catch To loError Catch To loError
LogMessage("EXCEPTIE la procesarea partenerului: " + loError.Message, "ERROR", gcLogFile) gcStepError = loError.Message
Endtry Endtry
Return m.lnIdPart Return m.lnIdPart
ENDFUNC && ProcessPartner Endfunc
*-- Functie pentru procesarea adresei din billing/shipping *-- Procesare adresa (fara logging)
Function ProcessAddress Function ProcessAddress
Lparameters tnIdPart, toAdresa Lparameters tnIdPart, toAdresa
Local lcAdresa, lcTelefon, lcEmail, lcSQL, lnResult, lnIdAdresa Local lcAdresa, lcTelefon, lcEmail, lcSQL, lnResult, lnIdAdresa
lnIdAdresa = 0 lnIdAdresa = 0
If .F.
TEXT TO lcExampleJsonAdresa NOSHOW
"billing": {
"address": "STR. CAMPUL LINISTII, NR. 1",
"city": "Arad",
"company": {
"bank": "",
"code": "RO30071208",
"iban": "",
"name": "BASSANO BUILDINGS SRL",
"registrationNo": ""
},
"country": "Romania",
"customerId": "13422",
"email": "office@bassano.ro",
"firstname": "Ionela",
"lastname": "Letcan",
"phone": "0728141899",
"region": "Arad"
},
"shipping": {
"address": "Strada Molnar Janos nr 23 bloc 37 sc B etj e ap. 16",
"city": "Bra?ov",
"company": "",
"country": "Romania",
"email": "ancamirela74@gmail.com",
"firstname": "Anca",
"lastname": "Stanciu",
"phone": "0758261492",
"region": "Brasov",
"zipcode": ""
}
ENDTEXT
Endif
Try Try
* Cautare/creare adresa
If !Empty(Nvl(m.tnIdPart, 0)) If !Empty(Nvl(m.tnIdPart, 0))
*-- Formatare adresa pentru Oracle (format semicolon cu prefix JUD:)
lcAdresa = FormatAddressForOracle(toAdresa) lcAdresa = FormatAddressForOracle(toAdresa)
*-- Date contact
lcTelefon = Iif(Type('toAdresa.phone') = 'C', toAdresa.phone, "") lcTelefon = Iif(Type('toAdresa.phone') = 'C', toAdresa.phone, "")
lcEmail = Iif(Type('toAdresa.email') = 'C', toAdresa.email, "") lcEmail = Iif(Type('toAdresa.email') = 'C', toAdresa.email, "")
lcSQL = "BEGIN PACK_IMPORT_PARTENERI.cauta_sau_creeaza_adresa(?tnIdPart, ?lcAdresa, ?lcTelefon, ?lcEmail, ?@lnIdAdresa); END;" lcSQL = "BEGIN PACK_IMPORT_PARTENERI.cauta_sau_creeaza_adresa(?tnIdPart, ?lcAdresa, ?lcTelefon, ?lcEmail, ?@lnIdAdresa); END;"
lnResult = SQLExec(goConnectie, lcSQL) lnResult = SQLExec(goConnectie, lcSQL)
If lnResult > 0
LogMessage("Adresa procesata cu succes: ID=" + Transform(m.lnIdAdresa), "DEBUG", gcLogFile)
Else
*-- Obtinere detalii eroare Oracle
lcErrorDetails = GetOracleErrorDetails(m.lcSQL)
LogMessage("EROARE la apelul procedurii PACK_IMPORT_PARTENERI.cauta_sau_creeaza_adresa pentru PartnerId: " + ALLTRIM(TRANSFORM(m.tnIdPart)) + CHR(10) + lcErrorDetails, "ERROR", gcLogFile)
Endif
Endif Endif
Catch To loError Catch To loError
LogMessage("EXCEPTIE la procesarea adresei: " + loError.Message, "ERROR", gcLogFile)
Endtry Endtry
Return m.lnIdAdresa Return m.lnIdAdresa
Endfunc && ProcessAddress Endfunc
*-- Functie pentru construirea JSON-ului cu articole conform package Oracle *-- Construire JSON articole
Function BuildArticlesJSON Function BuildArticlesJSON
Lparameters loItems Lparameters loItems
@@ -494,14 +382,13 @@ Function BuildArticlesJSON
Try Try
lcJSON = nfjsoncreate(loItems) lcJSON = nfjsoncreate(loItems)
Catch To loError Catch To loError
LogMessage("EROARE la construirea JSON articole: " + loError.Message, "ERROR", gcLogFile)
lcJSON = "" lcJSON = ""
Endtry Endtry
Return lcJSON Return lcJSON
Endfunc Endfunc
*-- Functie pentru curatarea textului web (HTML entities ASCII simplu) *-- Curatare text web (HTML entities -> ASCII simplu)
Function CleanWebText Function CleanWebText
Parameters tcText Parameters tcText
Local lcResult Local lcResult
@@ -512,41 +399,36 @@ Function CleanWebText
lcResult = tcText lcResult = tcText
*-- Conversie HTML entities in caractere simple (fara diacritice) lcResult = Strtran(lcResult, '&#259;', 'a')
lcResult = Strtran(lcResult, '&#259;', 'a') && ă → a lcResult = Strtran(lcResult, '&#537;', 's')
lcResult = Strtran(lcResult, '&#537;', 's') && ș → s lcResult = Strtran(lcResult, '&#539;', 't')
lcResult = Strtran(lcResult, '&#539;', 't') && ț → t lcResult = Strtran(lcResult, '&#238;', 'i')
lcResult = Strtran(lcResult, '&#238;', 'i') && î → i lcResult = Strtran(lcResult, '&#226;', 'a')
lcResult = Strtran(lcResult, '&#226;', 'a') && â → a
lcResult = Strtran(lcResult, '&amp;', '&') lcResult = Strtran(lcResult, '&amp;', '&')
lcResult = Strtran(lcResult, '&lt;', '<') lcResult = Strtran(lcResult, '&lt;', '<')
lcResult = Strtran(lcResult, '&gt;', '>') lcResult = Strtran(lcResult, '&gt;', '>')
lcResult = Strtran(lcResult, '&quot;', '"') lcResult = Strtran(lcResult, '&quot;', '"')
*-- Eliminare tag-uri HTML simple
lcResult = Strtran(lcResult, '<br>', ' ') lcResult = Strtran(lcResult, '<br>', ' ')
lcResult = Strtran(lcResult, '<br/>', ' ') lcResult = Strtran(lcResult, '<br/>', ' ')
lcResult = Strtran(lcResult, '<br />', ' ') lcResult = Strtran(lcResult, '<br />', ' ')
*-- Eliminare Esc character
lcResult = Strtran(lcResult, '\/', '/') lcResult = Strtran(lcResult, '\/', '/')
Return Alltrim(lcResult) Return Alltrim(lcResult)
Endfunc Endfunc
*-- Functie pentru conversia datei web in format Oracle *-- Conversie data web in format YYYYMMDD
Function ConvertWebDate Function ConvertWebDate
Parameters tcWebDate Parameters tcWebDate
Local lcResult Local lcResult
If Empty(tcWebDate) Or Type('tcWebDate') != 'C' If Empty(tcWebDate) Or Type('tcWebDate') != 'C'
Return Dtos(Date()) && yyyymmdd Return Dtos(Date())
Endif Endif
*-- Web date format: "2025-08-27 16:32:43" → "20250827"
lcResult = Strtran(Left(tcWebDate, 10), "-", "",1,10,1) lcResult = Strtran(Left(tcWebDate, 10), "-", "",1,10,1)
*-- Validare format YYYYMMDD
If Len(lcResult) = 8 If Len(lcResult) = 8
Return lcResult Return lcResult
Else Else
@@ -554,12 +436,9 @@ Function ConvertWebDate
Endif Endif
Endfunc Endfunc
*-- Functie pentru conversia datei string in Date *-- Conversie string in Date
* ldData = String2Date('20250912', ['yyyymmdd'])
Function String2Date Function String2Date
Lparameters tcDate, tcFormat Lparameters tcDate, tcFormat
* tcDate: 20250911
* tcFormat: yyyymmdd (default)
Local lcAn, lcDate, lcFormat, lcLuna, lcZi, ldData, lnAn, lnLuna, lnZi, loEx Local lcAn, lcDate, lcFormat, lcLuna, lcZi, ldData, lnAn, lnLuna, lnZi, loEx
ldData = {} ldData = {}
@@ -567,19 +446,17 @@ Function String2Date
lcDate = m.tcDate lcDate = m.tcDate
lcFormat = Iif(!Empty(m.tcFormat), Alltrim(Lower(m.tcFormat)), 'yyyymmdd') lcFormat = Iif(!Empty(m.tcFormat), Alltrim(Lower(m.tcFormat)), 'yyyymmdd')
lcDate = Chrtran(m.lcDate, '-/\','...') && inlocuiesc .-/\ cu . ca sa am doar variante yyyy.mm.dd, dd.mm.yyyy lcDate = Chrtran(m.lcDate, '-/\','...')
lcDate = Strtran(m.lcDate, '.', '', 1, 2, 1) lcDate = Strtran(m.lcDate, '.', '', 1, 2, 1)
lcFormat = Chrtran(m.lcFormat, '.-/\','...') lcFormat = Chrtran(m.lcFormat, '.-/\','...')
lcFormat = Strtran(m.lcFormat, '.', '', 1, 2, 1) lcFormat = Strtran(m.lcFormat, '.', '', 1, 2, 1)
Do Case Do Case
Case m.lcFormat = 'ddmmyyyy' Case m.lcFormat = 'ddmmyyyy'
lcAn = Substr(m.tcDate, 5, 4) lcAn = Substr(m.tcDate, 5, 4)
lcLuna = Substr(m.tcDate, 3, 2) lcLuna = Substr(m.tcDate, 3, 2)
lcZi = Substr(m.tcDate, 1, 2) lcZi = Substr(m.tcDate, 1, 2)
Otherwise Otherwise
* yyyymmdd
lcAn = Substr(m.tcDate, 1, 4) lcAn = Substr(m.tcDate, 1, 4)
lcLuna = Substr(m.tcDate, 5, 2) lcLuna = Substr(m.tcDate, 5, 2)
lcZi = Substr(m.tcDate, 7, 2) lcZi = Substr(m.tcDate, 7, 2)
@@ -597,30 +474,27 @@ Function String2Date
Return m.ldData Return m.ldData
Endfunc Endfunc
*-- Functie pentru formatarea adresei in format semicolon pentru Oracle *-- Formatare adresa in format semicolon pentru Oracle
Function FormatAddressForOracle Function FormatAddressForOracle
Parameters loBilling Parameters loBilling
Local lcAdresa, lcJudet, lcOras, lcStrada Local lcAdresa, lcJudet, lcOras, lcStrada
*-- Extragere componente adresa
lcJudet = Iif(Type('loBilling.region') = 'C', CleanWebText(loBilling.Region), "") lcJudet = Iif(Type('loBilling.region') = 'C', CleanWebText(loBilling.Region), "")
lcOras = Iif(Type('loBilling.city') = 'C', CleanWebText(loBilling.city), "") lcOras = Iif(Type('loBilling.city') = 'C', CleanWebText(loBilling.city), "")
lcStrada = Iif(Type('loBilling.address') = 'C', CleanWebText(loBilling.address), "") lcStrada = Iif(Type('loBilling.address') = 'C', CleanWebText(loBilling.address), "")
*-- Format semicolon cu prefix JUD: conform specificatiilor Oracle
lcAdresa = "JUD:" + lcJudet + ";" + lcOras + ";" + lcStrada lcAdresa = "JUD:" + lcJudet + ";" + lcOras + ";" + lcStrada
Return lcAdresa Return lcAdresa
Endfunc Endfunc
*-- Functie pentru construirea observatiilor comenzii *-- Construire observatii comanda
Function BuildOrderObservations Function BuildOrderObservations
Parameters loOrder Parameters loOrder
Local lcObservatii Local lcObservatii
lcObservatii = "" lcObservatii = ""
*-- Informatii plata si livrare
If Type('loOrder.payment') = 'O' And Type('loOrder.payment.name') = 'C' If Type('loOrder.payment') = 'O' And Type('loOrder.payment.name') = 'C'
lcObservatii = lcObservatii + "Payment: " + CleanWebText(loOrder.Payment.Name) + "; " lcObservatii = lcObservatii + "Payment: " + CleanWebText(loOrder.Payment.Name) + "; "
Endif Endif
@@ -629,7 +503,6 @@ Function BuildOrderObservations
lcObservatii = lcObservatii + "Delivery: " + CleanWebText(loOrder.delivery.Name) + "; " lcObservatii = lcObservatii + "Delivery: " + CleanWebText(loOrder.delivery.Name) + "; "
Endif Endif
*-- Status si sursa
If Type('loOrder.status') = 'C' If Type('loOrder.status') = 'C'
lcObservatii = lcObservatii + "Status: " + CleanWebText(loOrder.Status) + "; " lcObservatii = lcObservatii + "Status: " + CleanWebText(loOrder.Status) + "; "
Endif Endif
@@ -643,7 +516,6 @@ Function BuildOrderObservations
lcObservatii = lcObservatii + "; " lcObservatii = lcObservatii + "; "
Endif Endif
*-- Verificare adrese diferite shipping vs billing
If Type('loOrder.shipping') = 'O' And Type('loOrder.billing') = 'O' If Type('loOrder.shipping') = 'O' And Type('loOrder.billing') = 'O'
If Type('loOrder.shipping.address') = 'C' And Type('loOrder.billing.address') = 'C' If Type('loOrder.shipping.address') = 'C' And Type('loOrder.billing.address') = 'C'
If !Alltrim(loOrder.shipping.address) == Alltrim(loOrder.billing.address) If !Alltrim(loOrder.shipping.address) == Alltrim(loOrder.billing.address)
@@ -657,7 +529,6 @@ Function BuildOrderObservations
Endif Endif
Endif Endif
*-- Limitare lungime observatii pentru Oracle
If Len(lcObservatii) > 500 If Len(lcObservatii) > 500
lcObservatii = Left(lcObservatii, 497) + "..." lcObservatii = Left(lcObservatii, 497) + "..."
Endif Endif
@@ -665,16 +536,12 @@ Function BuildOrderObservations
Return lcObservatii Return lcObservatii
Endfunc Endfunc
*-- Functie pentru obtinerea detaliilor erorii Oracle *-- Obtinere detalii eroare Oracle (single-line, fara SQL)
Function GetOracleErrorDetails Function GetOracleErrorDetails
Lparameters tcSql
* tcSql (optional) : SQL executat
Local lcError, laError[1], lnErrorLines, lnIndex Local lcError, laError[1], lnErrorLines, lnIndex
lcError = "" lcError = ""
*-- Obtinere eroare Oracle
lnErrorLines = Aerror(laError) lnErrorLines = Aerror(laError)
If lnErrorLines > 0 If lnErrorLines > 0
For lnIndex = 1 To lnErrorLines For lnIndex = 1 To lnErrorLines
@@ -689,12 +556,103 @@ Function GetOracleErrorDetails
lcError = "Eroare Oracle nedefinita" lcError = "Eroare Oracle nedefinita"
Endif Endif
lcError = Iif(Pcount() = 1 And !Empty(m.tcSql) And Type('tcSql') = 'C', m.tcSql + Chr(13) + Chr(10), '') + m.lcError *-- Compact: inlocuieste newlines cu spatii
lcError = Strtran(lcError, CHR(13) + CHR(10), " ")
lcError = Strtran(lcError, CHR(10), " ")
lcError = Strtran(lcError, CHR(13), " ")
Return lcError Return lcError
Endfunc Endfunc
*-- Functie pentru executia adapter-ului configurat *-- Clasifica eroarea Oracle intr-un format compact
*-- Returneaza: "SKU_NOT_FOUND: sku" / "PRICE_POLICY: sku" / eroarea bruta
Function ClassifyImportError
Lparameters tcErrorDetails
Local lcText, lcSku, lnPos, lcSearch
lcText = Iif(Empty(tcErrorDetails), "", tcErrorDetails)
*-- SKU negasit
lcSearch = "NOM_ARTICOLE: "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
Return "SKU_NOT_FOUND: " + lcSku
Endif
*-- Eroare adaugare articol (include pretul)
lcSearch = "Eroare adaugare articol "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
Return "PRICE_POLICY: " + lcSku
Endif
*-- Eroare pret fara SKU (inainte de fix-ul Oracle)
If Atc("Pretul pentru acest articol", lcText) > 0
Return "PRICE_POLICY: (SKU necunoscut)"
Endif
*-- Eroare generica - primele 100 caractere
Return Left(lcText, 100)
Endfunc
*-- Colectare SKU-uri lipsa din mesajele de eroare Oracle
Function CollectFailedSKUs
Lparameters tcErrorDetails
Local lcSku, lnPos, lcSearch, lcText
If Empty(tcErrorDetails)
Return
Endif
lcText = tcErrorDetails
*-- Pattern 1: "SKU negasit in ARTICOLE_TERTI si NOM_ARTICOLE: XXXXX"
lcSearch = "NOM_ARTICOLE: "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
If !Empty(lcSku)
AddUniqueSKU(lcSku)
Endif
Endif
*-- Pattern 2: "Eroare adaugare articol XXXXX (CODMAT:" sau "Eroare adaugare articol XXXXX:"
lcSearch = "Eroare adaugare articol "
lnPos = Atc(lcSearch, lcText)
If lnPos > 0
lcSku = Alltrim(Getwordnum(Substr(lcText, lnPos + Len(lcSearch)), 1))
If !Empty(lcSku)
AddUniqueSKU(lcSku)
Endif
Endif
Return
Endfunc
*-- Adauga un SKU in gcFailedSKUs daca nu exista deja
Function AddUniqueSKU
Lparameters tcSku
Local lcSku
lcSku = Alltrim(tcSku)
If Empty(lcSku)
Return
Endif
If Empty(gcFailedSKUs)
gcFailedSKUs = lcSku
Else
If !(CHR(10) + lcSku + CHR(10)) $ (CHR(10) + gcFailedSKUs + CHR(10))
gcFailedSKUs = gcFailedSKUs + CHR(10) + lcSku
Endif
Endif
Return
Endfunc
*-- Executie adapter configurat
Function ExecuteAdapter Function ExecuteAdapter
Local llSuccess, lcAdapterPath Local llSuccess, lcAdapterPath
@@ -704,28 +662,15 @@ Function ExecuteAdapter
lcAdapterPath = gcAppPath + goSettings.AdapterProgram lcAdapterPath = gcAppPath + goSettings.AdapterProgram
If File(lcAdapterPath) If File(lcAdapterPath)
LogMessage("Executie adapter: " + lcAdapterPath, "INFO", gcLogFile)
Do (lcAdapterPath) Do (lcAdapterPath)
llSuccess = .T. llSuccess = .T.
LogMessage("Adapter executat cu succes", "INFO", gcLogFile)
Else Else
LogMessage("EROARE: Adapter-ul nu a fost gasit la: " + lcAdapterPath, "ERROR", gcLogFile) LogMessage("EROARE: Adapter negasit: " + lcAdapterPath, "ERROR", gcLogFile)
Endif Endif
Catch To loError Catch To loError
LogMessage("EXCEPTIE la executia adapter-ului " + goSettings.AdapterProgram + ": " + loError.Message, "ERROR", gcLogFile) LogMessage("EROARE adapter: " + loError.Message, "ERROR", gcLogFile)
Endtry Endtry
Return llSuccess Return llSuccess
Endfunc Endfunc
*-- Orchestrator complet pentru sincronizarea comenzilor web cu Oracle ROA
*-- Caracteristici:
*-- - Citeste JSON-urile generate de gomag-vending.prg
*-- - Proceseaza comenzile cu toate helper functions necesare
*-- - Integreaza cu package-urile Oracle validate in Phase 1
*-- - Logging complet cu statistici de procesare
*-- - Error handling pentru toate situatiile
*-- - Support pentru toate formatele GoMag (billing/shipping, companii/persoane fizice)