pljson
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,3 +25,4 @@ vfp/settings.ini
|
|||||||
vfp/output/
|
vfp/output/
|
||||||
vfp/*.json
|
vfp/*.json
|
||||||
*.~pck
|
*.~pck
|
||||||
|
.claude/HANDOFF.md
|
||||||
|
|||||||
@@ -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;
|
|
||||||
/
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
5086
api/database-scripts/co_2026_03_10_02_COMUN_PLJSON.sql
Normal file
5086
api/database-scripts/co_2026_03_10_02_COMUN_PLJSON.sql
Normal file
File diff suppressed because it is too large
Load Diff
306
scripts/parse_sync_log.py
Normal file
306
scripts/parse_sync_log.py
Normal 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()
|
||||||
@@ -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)
|
||||||
|
|
||||||
If lnResult > 0 And Nvl(m.lnIdComanda,0) > 0
|
*-- Construire linie sumar cu ID-uri adrese
|
||||||
LogMessage("SUCCES: Comanda importata - ID Oracle: " + Transform(m.lnIdComanda), "INFO", gcLogFile)
|
lcSummary = lcPrefix + " P:" + Transform(lnPartnerID) + ;
|
||||||
llSuccess = .T.
|
" A:" + Transform(Nvl(lnIdAdresaFacturare, 0)) + "/" + Transform(Nvl(lnIdAdresaLivrare, 0))
|
||||||
|
|
||||||
|
If lnResult > 0 And Nvl(m.lnIdComanda, 0) > 0
|
||||||
|
LogMessage(lcSummary + " -> OK ID:" + Transform(m.lnIdComanda), "INFO", gcLogFile)
|
||||||
|
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, 'ă', 'a')
|
||||||
lcResult = Strtran(lcResult, 'ă', 'a') && ă → a
|
lcResult = Strtran(lcResult, 'ș', 's')
|
||||||
lcResult = Strtran(lcResult, 'ș', 's') && ș → s
|
lcResult = Strtran(lcResult, 'ț', 't')
|
||||||
lcResult = Strtran(lcResult, 'ț', 't') && ț → t
|
lcResult = Strtran(lcResult, 'î', 'i')
|
||||||
lcResult = Strtran(lcResult, 'î', 'i') && î → i
|
lcResult = Strtran(lcResult, 'â', 'a')
|
||||||
lcResult = Strtran(lcResult, 'â', 'a') && â → a
|
|
||||||
lcResult = Strtran(lcResult, '&', '&')
|
lcResult = Strtran(lcResult, '&', '&')
|
||||||
lcResult = Strtran(lcResult, '<', '<')
|
lcResult = Strtran(lcResult, '<', '<')
|
||||||
lcResult = Strtran(lcResult, '>', '>')
|
lcResult = Strtran(lcResult, '>', '>')
|
||||||
lcResult = Strtran(lcResult, '"', '"')
|
lcResult = Strtran(lcResult, '"', '"')
|
||||||
|
|
||||||
*-- 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)
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user