fix(address): smart parser splits numar into bloc/scara/apart/etaj (ORA-12899)
Addresses with commas after street (e.g. "Str.Dacia NR.15 BLOC Z2,SCARA A,AP.7") caused NUMAR column overflow (max 10 chars). Parser now tokenizes by comma and routes BL/SC/AP/ET/NR prefixes to proper columns. Also extracts NR/BLOC embedded in street text. Import service now blocks orders when address creation fails (returns ERROR instead of silently importing without address). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
7
TODOS.md
7
TODOS.md
@@ -13,3 +13,10 @@
|
||||
**Effort:** M (human: ~1 sapt / CC: ~1h)
|
||||
**Context:** Depinde de infrastructura email/webhook disponibila la client. Implementare: SMTP simplu sau webhook URL configurabil in Settings.
|
||||
**Depends on:** Lansare in productie + infrastructura email la client.
|
||||
|
||||
## P3: Fix script — handle missing orders in GoMag API
|
||||
**What:** Fix script for 17 address-less orders should check if GoMag API returns data for each order, and report which orders couldn't be fixed.
|
||||
**Why:** Old orders may be deleted or expired from GoMag API. Without this check, the fix script fails silently and the operator thinks all 17 were fixed.
|
||||
**Effort:** S (human: ~10min / CC: ~2min)
|
||||
**Context:** Part of the address overflow fix (Pas 5). The fix script re-downloads from GoMag API to get original address text, but doesn't verify the API response. Add empty-response check + report.
|
||||
**Depends on:** Address parser fix (Pas 1-2) deployed.
|
||||
|
||||
@@ -307,6 +307,16 @@ def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_se
|
||||
])
|
||||
addr_livr_id = id_adresa_livr.getvalue()
|
||||
|
||||
if addr_livr_id is None:
|
||||
cur.execute("SELECT PACK_IMPORT_PARTENERI.get_last_error FROM dual")
|
||||
plsql_err = cur.fetchone()[0]
|
||||
err_msg = f"Shipping address creation failed for partner {partner_id}"
|
||||
if plsql_err:
|
||||
err_msg += f": {plsql_err}"
|
||||
logger.error(f"Order {order_number}: {err_msg}")
|
||||
result["error"] = err_msg
|
||||
return result
|
||||
|
||||
# Step 3: Process billing address
|
||||
if different_person:
|
||||
# Different person: use shipping address for BOTH billing and shipping in ROA
|
||||
@@ -325,6 +335,16 @@ def import_single_order(order, id_pol: int = None, id_sectie: int = None, app_se
|
||||
])
|
||||
addr_fact_id = id_adresa_fact.getvalue()
|
||||
|
||||
if addr_fact_id is None:
|
||||
cur.execute("SELECT PACK_IMPORT_PARTENERI.get_last_error FROM dual")
|
||||
plsql_err = cur.fetchone()[0]
|
||||
err_msg = f"Billing address creation failed for partner {partner_id}"
|
||||
if plsql_err:
|
||||
err_msg += f": {plsql_err}"
|
||||
logger.error(f"Order {order_number}: {err_msg}")
|
||||
result["error"] = err_msg
|
||||
return result
|
||||
|
||||
if addr_fact_id is not None:
|
||||
result["id_adresa_facturare"] = int(addr_fact_id)
|
||||
if addr_livr_id is not None:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS
|
||||
|
||||
-- 20.03.2026 - import parteneri GoMag: PJ/PF, shipping/billing, cautare/creare automata
|
||||
-- 31.03.2026 - parser inteligent adrese: split numar in bloc/scara/apart/etaj (fix ORA-12899 pe NUMAR max 10 chars)
|
||||
|
||||
-- ====================================================================
|
||||
-- CONSTANTS
|
||||
@@ -89,7 +90,11 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS
|
||||
p_localitate OUT VARCHAR2,
|
||||
p_strada OUT VARCHAR2,
|
||||
p_numar OUT VARCHAR2,
|
||||
p_sector OUT VARCHAR2);
|
||||
p_sector OUT VARCHAR2,
|
||||
p_bloc OUT VARCHAR2,
|
||||
p_scara OUT VARCHAR2,
|
||||
p_apart OUT VARCHAR2,
|
||||
p_etaj OUT VARCHAR2);
|
||||
|
||||
-- ====================================================================
|
||||
-- UTILITY FUNCTIONS (PUBLIC pentru testare)
|
||||
@@ -380,91 +385,91 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
|
||||
p_prenume := NULL;
|
||||
END separa_nume_prenume;
|
||||
|
||||
-- 31.03.2026 - parser inteligent: split numar in bloc/scara/apart/etaj (fix ORA-12899 pe NUMAR max 10 chars)
|
||||
PROCEDURE parseaza_adresa_semicolon(p_adresa_text IN VARCHAR2,
|
||||
p_judet OUT VARCHAR2,
|
||||
p_localitate OUT VARCHAR2,
|
||||
p_strada OUT VARCHAR2,
|
||||
p_numar OUT VARCHAR2,
|
||||
p_sector OUT VARCHAR2) IS
|
||||
p_sector OUT VARCHAR2,
|
||||
p_bloc OUT VARCHAR2,
|
||||
p_scara OUT VARCHAR2,
|
||||
p_apart OUT VARCHAR2,
|
||||
p_etaj OUT VARCHAR2) IS
|
||||
v_adresa_curata VARCHAR2(500);
|
||||
v_componente SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
|
||||
v_count NUMBER;
|
||||
v_temp_judet VARCHAR2(100);
|
||||
v_pozitie NUMBER;
|
||||
v_strada VARCHAR2(100);
|
||||
-- variabile pentru parsarea inteligenta a numarului
|
||||
v_raw_numar VARCHAR2(500);
|
||||
v_token VARCHAR2(200);
|
||||
v_token_upper VARCHAR2(200);
|
||||
v_rest_parts VARCHAR2(500);
|
||||
v_tok_pos NUMBER;
|
||||
v_tok_idx NUMBER;
|
||||
BEGIN
|
||||
-- p_adresa_text: JUD: JUDET;LOCALITATE;STRADA, NR
|
||||
-- Initializare cu valori default
|
||||
p_judet := C_JUD_DEFAULT;
|
||||
p_localitate := C_LOCALITATE_DEFAULT;
|
||||
p_strada := NULL;
|
||||
p_numar := NULL;
|
||||
p_sector := C_SECTOR_DEFAULT;
|
||||
|
||||
p_bloc := NULL;
|
||||
p_scara := NULL;
|
||||
p_apart := NULL;
|
||||
p_etaj := NULL;
|
||||
|
||||
-- Validare input
|
||||
IF p_adresa_text IS NULL THEN
|
||||
-- pINFO('Adresa goala, se folosesc valorile default', 'IMPORT_PARTENERI');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
|
||||
v_adresa_curata := TRIM(p_adresa_text);
|
||||
|
||||
-- pINFO('Parsare adresa: ' || v_adresa_curata, 'IMPORT_PARTENERI');
|
||||
|
||||
|
||||
-- Split dupa semicolon
|
||||
SELECT TRIM(REGEXP_SUBSTR(v_adresa_curata, '[^;]+', 1, LEVEL))
|
||||
BULK COLLECT
|
||||
INTO v_componente
|
||||
FROM DUAL
|
||||
CONNECT BY REGEXP_SUBSTR(v_adresa_curata, '[^;]+', 1, LEVEL) IS NOT NULL;
|
||||
|
||||
|
||||
v_count := v_componente.COUNT;
|
||||
|
||||
|
||||
IF v_count = 0 THEN
|
||||
-- pINFO('Nu s-au gasit componente in adresa', 'IMPORT_PARTENERI');
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
|
||||
-- Parsare in functie de numarul de componente
|
||||
IF v_count = 1 THEN
|
||||
-- Doar strada
|
||||
p_strada := SUBSTR(v_componente(1), 1, 100);
|
||||
|
||||
|
||||
ELSIF v_count = 2 THEN
|
||||
-- Localitate;Strada
|
||||
p_localitate := SUBSTR(v_componente(1), 1, 100);
|
||||
p_strada := SUBSTR(v_componente(2), 1, 100);
|
||||
|
||||
|
||||
ELSIF v_count >= 3 THEN
|
||||
-- Verifica daca prima componenta contine "JUD:"
|
||||
v_temp_judet := v_componente(1);
|
||||
|
||||
|
||||
IF UPPER(v_temp_judet) LIKE 'JUD:%' THEN
|
||||
-- Format: JUD:Bucuresti;BUCURESTI;Strada,Numar
|
||||
p_judet := SUBSTR(REPLACE(v_temp_judet, 'JUD:', ''), 1, 100);
|
||||
p_localitate := SUBSTR(v_componente(2), 1, 100);
|
||||
p_strada := SUBSTR(v_componente(3), 1, 100);
|
||||
v_strada := p_strada;
|
||||
|
||||
-- Combina strada si numarul
|
||||
|
||||
-- Separa strada de tot ce e dupa prima virgula
|
||||
v_pozitie := INSTR(v_strada, ',');
|
||||
IF v_pozitie > 0 THEN
|
||||
p_strada := TRIM(SUBSTR(v_strada, 1, v_pozitie - 1));
|
||||
p_numar := TRIM(SUBSTR(v_strada, v_pozitie + 1));
|
||||
|
||||
-- Elimina prefixele din numele strazii (STR., STRADA, BD., BDUL., etc.)
|
||||
/* v_nume_strada := TRIM(REGEXP_REPLACE(v_nume_strada,
|
||||
'^(STR\.|STRADA|BD\.|BDUL\.|CALEA|PIATA|PTA\.|AL\.|ALEEA|SOS\.|SOSEA|INTR\.|INTRAREA)\s*',
|
||||
'', 1, 1, 'i')); */
|
||||
|
||||
-- Elimina prefixele din numarul strazii (NR., NUMARUL, etc.)
|
||||
p_numar := TRIM(REGEXP_REPLACE(p_numar,
|
||||
'^(NR\.|NUMARUL|NUMAR)\s*',
|
||||
'',
|
||||
1,
|
||||
1,
|
||||
'i'));
|
||||
p_strada := TRIM(SUBSTR(v_strada, 1, v_pozitie - 1));
|
||||
v_raw_numar := TRIM(SUBSTR(v_strada, v_pozitie + 1));
|
||||
END IF;
|
||||
|
||||
|
||||
ELSE
|
||||
-- Format: Localitate;Strada;Altceva
|
||||
p_localitate := SUBSTR(v_componente(1), 1, 100);
|
||||
@@ -473,35 +478,116 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
|
||||
100);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
|
||||
-- Pre-processing: extrage NR/BLOC embedded in p_strada (spatiu-separate, fara virgula)
|
||||
-- Ex: "STR.DACIA NR.15 BLOC Z2" → strada="STR.DACIA", numar="15", bloc="Z2"
|
||||
-- Trebuie facut INAINTE de parsarea tokenilor din v_raw_numar
|
||||
IF p_strada IS NOT NULL THEN
|
||||
v_token_upper := UPPER(p_strada);
|
||||
-- Extrage NR din strada
|
||||
IF REGEXP_LIKE(v_token_upper, '(\s)(NUMARUL|NUMAR|NR\.?)\s*(\S+)') THEN
|
||||
p_numar := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(NUMARUL|NUMAR|NR\.?)\s*(\S+).*', '\3', 1, 1));
|
||||
p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(NUMARUL|NUMAR|NR\.?)\s*\S+', '', 1, 1, 'i'));
|
||||
END IF;
|
||||
-- Extrage BLOC din strada
|
||||
IF REGEXP_LIKE(v_token_upper, '(\s)(BLOC|BL\.?)\s*(\S+)') THEN
|
||||
p_bloc := TRIM(REGEXP_REPLACE(v_token_upper, '.*(\s)(BLOC|BL\.?)\s*(\S+).*', '\3', 1, 1));
|
||||
p_strada := TRIM(REGEXP_REPLACE(p_strada, '(\s)(BLOC|BL\.?)\s*\S+', '', 1, 1, 'i'));
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- ================================================================
|
||||
-- Parser inteligent: split v_raw_numar in numar/bloc/scara/apart/etaj
|
||||
-- Tokenii sunt separati prin virgula
|
||||
-- Patterns: NR/NUMAR, BL/BLOC, SC/SCARA, AP/APART, ET/ETAJ
|
||||
-- ================================================================
|
||||
IF v_raw_numar IS NOT NULL THEN
|
||||
-- Loop prin tokeni separati de virgula (fara BULK COLLECT — compatibil Oracle 11)
|
||||
v_rest_parts := NULL;
|
||||
v_tok_idx := 0;
|
||||
v_raw_numar := v_raw_numar || ','; -- sentinel pentru ultimul token
|
||||
|
||||
LOOP
|
||||
v_tok_pos := INSTR(v_raw_numar, ',');
|
||||
EXIT WHEN v_tok_pos = 0 OR v_raw_numar IS NULL;
|
||||
|
||||
v_token := TRIM(SUBSTR(v_raw_numar, 1, v_tok_pos - 1));
|
||||
v_raw_numar := SUBSTR(v_raw_numar, v_tok_pos + 1);
|
||||
v_tok_idx := v_tok_idx + 1;
|
||||
|
||||
IF v_token IS NULL THEN
|
||||
CONTINUE;
|
||||
END IF;
|
||||
|
||||
v_token_upper := UPPER(v_token);
|
||||
|
||||
-- Longer match first; (\s|\.) handles both "BL A2" and "BL.A2" and "AP.7"
|
||||
IF REGEXP_LIKE(v_token_upper, '^(BLOC|BL\.?)(\s|\.)') THEN
|
||||
p_bloc := TRIM(REGEXP_REPLACE(v_token, '^(BLOC|BL\.?)(\s|\.)*', '', 1, 1, 'i'));
|
||||
ELSIF REGEXP_LIKE(v_token_upper, '^(SCARA|SC\.?)(\s|\.)') THEN
|
||||
p_scara := TRIM(REGEXP_REPLACE(v_token, '^(SCARA|SC\.?)(\s|\.)*', '', 1, 1, 'i'));
|
||||
ELSIF REGEXP_LIKE(v_token_upper, '^(APARTAMENT|APART\.?|AP\.?)(\s|\.)') THEN
|
||||
p_apart := TRIM(REGEXP_REPLACE(v_token, '^(APARTAMENT|APART\.?|AP\.?)(\s|\.)*', '', 1, 1, 'i'));
|
||||
ELSIF REGEXP_LIKE(v_token_upper, '^(ETAJ|ET\.?)(\s|\.)') THEN
|
||||
p_etaj := TRIM(REGEXP_REPLACE(v_token, '^(ETAJ|ET\.?)(\s|\.)*', '', 1, 1, 'i'));
|
||||
ELSIF REGEXP_LIKE(v_token_upper, '^(NUMARUL|NUMAR|NR\.?)(\s|\.)') THEN
|
||||
p_numar := TRIM(REGEXP_REPLACE(v_token, '^(NUMARUL|NUMAR|NR\.?)(\s|\.)*', '', 1, 1, 'i'));
|
||||
ELSE
|
||||
-- Primul token necunoscut devine numar (daca numar e inca gol)
|
||||
IF p_numar IS NULL AND v_tok_idx = 1 THEN
|
||||
p_numar := v_token;
|
||||
ELSE
|
||||
-- Restul (cartier, sat, indicatii) se adauga la strada
|
||||
IF v_rest_parts IS NOT NULL THEN
|
||||
v_rest_parts := v_rest_parts || ', ' || v_token;
|
||||
ELSE
|
||||
v_rest_parts := v_token;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- Adauga restul la strada
|
||||
IF v_rest_parts IS NOT NULL THEN
|
||||
p_strada := SUBSTR(p_strada || ', ' || v_rest_parts, 1, 100);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Curatare finala
|
||||
p_judet := UPPER(TRIM(p_judet));
|
||||
p_localitate := UPPER(TRIM(p_localitate));
|
||||
p_strada := UPPER(TRIM(p_strada));
|
||||
p_numar := UPPER(TRIM(p_numar));
|
||||
p_sector := UPPER(TRIM(p_sector));
|
||||
|
||||
p_bloc := UPPER(TRIM(p_bloc));
|
||||
p_scara := UPPER(TRIM(p_scara));
|
||||
p_apart := UPPER(TRIM(p_apart));
|
||||
p_etaj := UPPER(TRIM(p_etaj));
|
||||
|
||||
-- Truncare de siguranta (limita coloanelor Oracle)
|
||||
p_numar := SUBSTR(p_numar, 1, 10);
|
||||
p_bloc := SUBSTR(p_bloc, 1, 30);
|
||||
p_scara := SUBSTR(p_scara, 1, 10);
|
||||
p_apart := SUBSTR(p_apart, 1, 10);
|
||||
p_etaj := SUBSTR(p_etaj, 1, 20);
|
||||
|
||||
-- Fallback pentru campuri goale
|
||||
IF p_judet IS NULL THEN
|
||||
p_judet := C_JUD_DEFAULT;
|
||||
END IF;
|
||||
|
||||
|
||||
IF p_localitate IS NULL THEN
|
||||
p_localitate := C_LOCALITATE_DEFAULT;
|
||||
END IF;
|
||||
|
||||
|
||||
IF p_sector IS NULL THEN
|
||||
p_sector := C_SECTOR_DEFAULT;
|
||||
END IF;
|
||||
|
||||
-- pINFO('Adresa parsata: JUD=' || p_judet || ', LOC=' || p_localitate ||
|
||||
-- ', STRADA=' || NVL(p_strada, 'NULL') || ', SECTOR=' || p_sector, 'IMPORT_PARTENERI');
|
||||
|
||||
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
g_last_error := 'ERROR in parseaza_adresa_semicolon: ' || SQLERRM;
|
||||
-- pINFO('ERROR in parseaza_adresa_semicolon: ' || SQLERRM, 'IMPORT_PARTENERI');
|
||||
|
||||
|
||||
-- Pastram valorile default in caz de eroare
|
||||
p_judet := C_JUD_DEFAULT;
|
||||
p_localitate := C_LOCALITATE_DEFAULT;
|
||||
@@ -676,6 +762,10 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
|
||||
v_strada VARCHAR2(1000);
|
||||
v_numar VARCHAR2(1000);
|
||||
v_sector VARCHAR2(100);
|
||||
v_bloc VARCHAR2(30);
|
||||
v_scara VARCHAR2(10);
|
||||
v_apart VARCHAR2(10);
|
||||
v_etaj VARCHAR2(20);
|
||||
v_id_tara NUMBER(10);
|
||||
v_principala NUMBER(1);
|
||||
begin
|
||||
@@ -695,13 +785,17 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
|
||||
where id_part = p_id_part
|
||||
and principala = 1);
|
||||
|
||||
-- Parseaza adresa
|
||||
-- Parseaza adresa (cu split inteligent numar/bloc/scara/apart/etaj)
|
||||
parseaza_adresa_semicolon(p_adresa,
|
||||
v_judet,
|
||||
v_localitate,
|
||||
v_strada,
|
||||
v_numar,
|
||||
v_sector);
|
||||
v_sector,
|
||||
v_bloc,
|
||||
v_scara,
|
||||
v_apart,
|
||||
v_etaj);
|
||||
|
||||
-- caut prima adresa dupa judet si localitate, ordonate dupa principala = 1
|
||||
begin
|
||||
@@ -782,10 +876,10 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
|
||||
tnDA_apare => 0,
|
||||
tcStrada => v_strada,
|
||||
tcNumar => v_numar,
|
||||
tcBloc => NULL,
|
||||
tcScara => NULL,
|
||||
tcApart => NULL,
|
||||
tnEtaj => NULL,
|
||||
tcBloc => v_bloc,
|
||||
tcScara => v_scara,
|
||||
tcApart => v_apart,
|
||||
tnEtaj => v_etaj,
|
||||
tnId_loc => v_id_localitate,
|
||||
tcLocalitate => v_localitate,
|
||||
tnId_judet => v_id_judet,
|
||||
|
||||
Reference in New Issue
Block a user