fix(address+ui): remove TIER 2 reuse, typed diff badges, false positive reduction
- Remove TIER 2 address lookup (county+city without street) from PL/SQL — creates new address when street differs instead of reusing wrong one - Replace generic "N diferente" badge with typed micro-badges (CUI, Denumire, TVA, Adr. livr., Adr. fact., Preturi) with red/amber semantic colors - Extend addrMatch() regex to strip full Romanian address words (STRADA, NUMAR, BLOC, COMUNA, SAT, MUNICIPIUL, etc.) — fixes "Strada X" vs "X" false positives - Extend normalize_company_name() for II, PFA, INTREPRINDERE INDIVIDUALA legal forms - Persist address_mismatch to SQLite so "Dif." filter includes address-only diffs - Add red/amber indicator dots to desktop table and mobile list rows - 12 unit tests for normalization and server-side address matching Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -352,6 +352,7 @@ def init_sqlite():
|
|||||||
("adresa_facturare_roa", "TEXT"),
|
("adresa_facturare_roa", "TEXT"),
|
||||||
("anaf_denumire_mismatch", "INTEGER DEFAULT 0"),
|
("anaf_denumire_mismatch", "INTEGER DEFAULT 0"),
|
||||||
("denumire_anaf", "TEXT"),
|
("denumire_anaf", "TEXT"),
|
||||||
|
("address_mismatch", "INTEGER DEFAULT 0"),
|
||||||
]:
|
]:
|
||||||
if col not in order_cols:
|
if col not in order_cols:
|
||||||
conn.execute(f"ALTER TABLE orders ADD COLUMN {col} {typedef}")
|
conn.execute(f"ALTER TABLE orders ADD COLUMN {col} {typedef}")
|
||||||
|
|||||||
@@ -183,8 +183,10 @@ def normalize_company_name(name: str) -> str:
|
|||||||
result = name.strip().upper()
|
result = name.strip().upper()
|
||||||
# Strip diacritics
|
# Strip diacritics
|
||||||
result = result.translate(_DIACRITICS)
|
result = result.translate(_DIACRITICS)
|
||||||
# Remove common suffixes
|
# Remove common suffixes and legal forms
|
||||||
result = re.sub(r'\b(S\.?R\.?L\.?|S\.?A\.?|S\.?C\.?|S\.?N\.?C\.?|S\.?C\.?S\.?)\b', '', result)
|
result = re.sub(r'\b(S\.?R\.?L\.?|S\.?A\.?|S\.?C\.?|S\.?N\.?C\.?|S\.?C\.?S\.?|P\.?F\.?A\.?|INTREPRINDERE\s+INDIVIDUALA)\b', '', result)
|
||||||
|
# Strip II only at start of name (avoid matching Roman numeral II in "TEHNICA II SRL")
|
||||||
|
result = re.sub(r'^I\.?I\.?\s+', '', result)
|
||||||
# Remove punctuation and extra spaces
|
# Remove punctuation and extra spaces
|
||||||
result = re.sub(r'[^\w\s]', '', result)
|
result = re.sub(r'[^\w\s]', '', result)
|
||||||
result = re.sub(r'\s+', ' ', result).strip()
|
result = re.sub(r'\s+', ' ', result).strip()
|
||||||
|
|||||||
@@ -697,7 +697,7 @@ async def get_orders(page: int = 1, per_page: int = 50,
|
|||||||
if status_filter.upper() == "IMPORTED":
|
if status_filter.upper() == "IMPORTED":
|
||||||
data_clauses.append("UPPER(status) IN ('IMPORTED', 'ALREADY_IMPORTED')")
|
data_clauses.append("UPPER(status) IN ('IMPORTED', 'ALREADY_IMPORTED')")
|
||||||
elif status_filter.upper() == "DIFFS":
|
elif status_filter.upper() == "DIFFS":
|
||||||
data_clauses.append("(anaf_cod_fiscal_adjusted = 1 OR anaf_denumire_mismatch = 1)")
|
data_clauses.append("(anaf_cod_fiscal_adjusted = 1 OR anaf_denumire_mismatch = 1 OR address_mismatch = 1)")
|
||||||
else:
|
else:
|
||||||
data_clauses.append("UPPER(status) = ?")
|
data_clauses.append("UPPER(status) = ?")
|
||||||
data_params.append(status_filter.upper())
|
data_params.append(status_filter.upper())
|
||||||
@@ -751,9 +751,9 @@ async def get_orders(page: int = 1, per_page: int = 50,
|
|||||||
cursor = await db.execute(f"SELECT COUNT(*) FROM orders {uninv_old_where}", base_params)
|
cursor = await db.execute(f"SELECT COUNT(*) FROM orders {uninv_old_where}", base_params)
|
||||||
uninvoiced_old = (await cursor.fetchone())[0]
|
uninvoiced_old = (await cursor.fetchone())[0]
|
||||||
|
|
||||||
# Diffs count: orders with ANAF adjustments
|
# Diffs count: orders with ANAF adjustments or address mismatches
|
||||||
diffs_clauses = list(base_clauses) + [
|
diffs_clauses = list(base_clauses) + [
|
||||||
"(anaf_cod_fiscal_adjusted = 1 OR anaf_denumire_mismatch = 1)"
|
"(anaf_cod_fiscal_adjusted = 1 OR anaf_denumire_mismatch = 1 OR address_mismatch = 1)"
|
||||||
]
|
]
|
||||||
diffs_where = "WHERE " + " AND ".join(diffs_clauses)
|
diffs_where = "WHERE " + " AND ".join(diffs_clauses)
|
||||||
cursor = await db.execute(f"SELECT COUNT(*) FROM orders {diffs_where}", base_params)
|
cursor = await db.execute(f"SELECT COUNT(*) FROM orders {diffs_where}", base_params)
|
||||||
@@ -1138,6 +1138,7 @@ async def update_order_partner_data(order_number: str, partner_data: dict):
|
|||||||
adresa_facturare_roa = ?,
|
adresa_facturare_roa = ?,
|
||||||
anaf_denumire_mismatch = ?,
|
anaf_denumire_mismatch = ?,
|
||||||
denumire_anaf = ?,
|
denumire_anaf = ?,
|
||||||
|
address_mismatch = ?,
|
||||||
updated_at = datetime('now')
|
updated_at = datetime('now')
|
||||||
WHERE order_number = ?
|
WHERE order_number = ?
|
||||||
""", (
|
""", (
|
||||||
@@ -1153,6 +1154,7 @@ async def update_order_partner_data(order_number: str, partner_data: dict):
|
|||||||
partner_data.get("adresa_facturare_roa"),
|
partner_data.get("adresa_facturare_roa"),
|
||||||
partner_data.get("anaf_denumire_mismatch", 0),
|
partner_data.get("anaf_denumire_mismatch", 0),
|
||||||
partner_data.get("denumire_anaf"),
|
partner_data.get("denumire_anaf"),
|
||||||
|
partner_data.get("address_mismatch", 0),
|
||||||
order_number,
|
order_number,
|
||||||
))
|
))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
@@ -18,6 +20,34 @@ from .. import database
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _addr_match(gomag_json, roa_json):
|
||||||
|
"""Server-side address comparison matching JS addrMatch()."""
|
||||||
|
if not gomag_json or not roa_json:
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
g = json.loads(gomag_json) if isinstance(gomag_json, str) else gomag_json
|
||||||
|
r = json.loads(roa_json) if isinstance(roa_json, str) else roa_json
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
return True
|
||||||
|
_ADDR_WORDS = re.compile(
|
||||||
|
r'\b(STR|STRADA|NR|NUMAR|NUMARUL|BL|BLOC|SC|SCARA|AP|APART|APARTAMENT|'
|
||||||
|
r'ET|ETAJ|COM|COMUNA|SAT|MUN|MUNICIPIUL|JUD|JUDETUL|CARTIER|PARTER|SECTOR|ORAS)\b'
|
||||||
|
)
|
||||||
|
def norm(s):
|
||||||
|
s = unicodedata.normalize('NFD', s or '')
|
||||||
|
s = re.sub(r'[\u0300-\u036f]', '', s).upper()
|
||||||
|
s = _ADDR_WORDS.sub('', s)
|
||||||
|
return re.sub(r'[^A-Z0-9]', '', s)
|
||||||
|
g_street = norm(g.get('address') or g.get('strada') or '')
|
||||||
|
r_street = norm((r.get('strada') or '') + (r.get('numar') or ''))
|
||||||
|
g_city = norm(g.get('city') or g.get('localitate') or '')
|
||||||
|
r_city = norm(r.get('localitate') or '')
|
||||||
|
g_region = norm(g.get('region') or g.get('judet') or '')
|
||||||
|
r_region = norm(r.get('judet') or '')
|
||||||
|
return g_street == r_street and g_city == r_city and g_region == r_region
|
||||||
|
|
||||||
|
|
||||||
# Sync state
|
# Sync state
|
||||||
_sync_lock = asyncio.Lock()
|
_sync_lock = asyncio.Lock()
|
||||||
_current_sync = None # dict with run_id, status, progress info
|
_current_sync = None # dict with run_id, status, progress info
|
||||||
@@ -802,6 +832,11 @@ async def run_sync(id_pol: int = None, id_sectie: int = None, run_id: str = None
|
|||||||
partner_data["anaf_denumire_mismatch"] = 1
|
partner_data["anaf_denumire_mismatch"] = 1
|
||||||
partner_data["denumire_anaf"] = anaf_data_for_order["denumire_anaf"]
|
partner_data["denumire_anaf"] = anaf_data_for_order["denumire_anaf"]
|
||||||
|
|
||||||
|
# Address mismatch check (server-side, mirrors JS addrMatch)
|
||||||
|
livr_match = _addr_match(partner_data.get("adresa_livrare_gomag"), partner_data.get("adresa_livrare_roa"))
|
||||||
|
fact_match = _addr_match(partner_data.get("adresa_facturare_gomag"), partner_data.get("adresa_facturare_roa"))
|
||||||
|
partner_data["address_mismatch"] = 1 if (not livr_match or not fact_match) else 0
|
||||||
|
|
||||||
await sqlite_service.update_order_partner_data(order.number, partner_data)
|
await sqlite_service.update_order_partner_data(order.number, partner_data)
|
||||||
|
|
||||||
if not result["success"]:
|
if not result["success"]:
|
||||||
|
|||||||
@@ -1176,6 +1176,11 @@ tr.mapping-deleted td {
|
|||||||
.anaf-badge-warn { background: var(--warning-light); color: var(--warning-text); }
|
.anaf-badge-warn { background: var(--warning-light); color: var(--warning-text); }
|
||||||
.anaf-badge-gray { background: var(--cancelled-light); color: var(--text-muted); }
|
.anaf-badge-gray { background: var(--cancelled-light); color: var(--text-muted); }
|
||||||
|
|
||||||
|
/* Diff-type badges (reuses .anaf-badge sizing per DESIGN.md type scale minimum) */
|
||||||
|
.diff-badge { display:inline-block; font-family:var(--font-body); font-size:12px; font-weight:500; padding:2px 8px; border-radius:9999px; margin-left:4px; vertical-align:middle; }
|
||||||
|
.diff-badge-anaf { background:var(--error-light); color:var(--error-text); }
|
||||||
|
.diff-badge-info { background:var(--warning-light); color:var(--warning-text); }
|
||||||
|
|
||||||
/* ── Compact order detail layout ──────────────── */
|
/* ── Compact order detail layout ──────────────── */
|
||||||
.detail-col-label {
|
.detail-col-label {
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ async function loadDashOrders() {
|
|||||||
<td>${statusDot(o.status)}</td>
|
<td>${statusDot(o.status)}</td>
|
||||||
<td class="text-nowrap">${dateStr}</td>
|
<td class="text-nowrap">${dateStr}</td>
|
||||||
${renderClientCell(o)}
|
${renderClientCell(o)}
|
||||||
<td><code>${esc(o.order_number)}</code></td>
|
<td><code>${esc(o.order_number)}</code>${(o.anaf_cod_fiscal_adjusted===1||o.anaf_denumire_mismatch===1||(o.cod_fiscal_gomag&&o.anaf_platitor_tva===0))?'<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--error);margin-left:4px;vertical-align:middle" title="Diferente ANAF"></span>':''}${(o.address_mismatch===1||o.price_match===false)?'<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--warning);margin-left:2px;vertical-align:middle" title="Diferente adresa/pret"></span>':''}</td>
|
||||||
<td>${o.items_count || 0}</td>
|
<td>${o.items_count || 0}</td>
|
||||||
<td class="text-end text-muted">${fmtCost(o.delivery_cost)}</td>
|
<td class="text-end text-muted">${fmtCost(o.delivery_cost)}</td>
|
||||||
<td class="text-end text-muted">${fmtCost(o.discount_total)}</td>
|
<td class="text-end text-muted">${fmtCost(o.discount_total)}</td>
|
||||||
@@ -394,12 +394,14 @@ async function loadDashOrders() {
|
|||||||
}
|
}
|
||||||
const name = o.customer_name || o.shipping_name || o.billing_name || '\u2014';
|
const name = o.customer_name || o.shipping_name || o.billing_name || '\u2014';
|
||||||
const totalStr = o.order_total ? Number(o.order_total).toFixed(2) : '';
|
const totalStr = o.order_total ? Number(o.order_total).toFixed(2) : '';
|
||||||
|
const anafDiffDot = (o.anaf_cod_fiscal_adjusted===1||o.anaf_denumire_mismatch===1||(o.cod_fiscal_gomag&&o.anaf_platitor_tva===0)) ? '<span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--error);margin-right:2px;vertical-align:middle" title="ANAF"></span>' : '';
|
||||||
|
const addrDiffDot = o.address_mismatch===1 ? '<span style="display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--warning);margin-right:2px;vertical-align:middle" title="Adresa"></span>' : '';
|
||||||
const priceMismatch = o.price_match === false ? '<span class="dot dot-red" style="width:6px;height:6px" title="Pret!="></span> ' : '';
|
const priceMismatch = o.price_match === false ? '<span class="dot dot-red" style="width:6px;height:6px" title="Pret!="></span> ' : '';
|
||||||
return `<div class="flat-row" onclick="openDashOrderDetail('${esc(o.order_number)}')" style="font-size:0.875rem">
|
return `<div class="flat-row" onclick="openDashOrderDetail('${esc(o.order_number)}')" style="font-size:0.875rem">
|
||||||
${statusDot(o.status)}
|
${statusDot(o.status)}
|
||||||
<span style="color:var(--text-muted)" class="text-nowrap">${dateFmt}</span>
|
<span style="color:var(--text-muted)" class="text-nowrap">${dateFmt}</span>
|
||||||
<span class="grow truncate fw-bold">${esc(name)}</span>
|
<span class="grow truncate fw-bold">${esc(name)}</span>
|
||||||
<span class="text-nowrap">x${o.items_count || 0}${totalStr ? ' · ' + priceMismatch + '<strong>' + totalStr + '</strong>' : ''}</span>
|
<span class="text-nowrap">x${o.items_count || 0}${totalStr ? ' · ' + anafDiffDot + addrDiffDot + priceMismatch + '<strong>' + totalStr + '</strong>' : ''}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -856,7 +856,7 @@ function addrMatch(gomag, roa) {
|
|||||||
function norm(s) {
|
function norm(s) {
|
||||||
return (s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
return (s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.replace(/\b(STR|NR|BL|SC|AP|ET|ETAJ|APART)\b/g, '')
|
.replace(/\b(STR|STRADA|NR|NUMAR|NUMARUL|BL|BLOC|SC|SCARA|AP|APART|APARTAMENT|ET|ETAJ|COM|COMUNA|SAT|MUN|MUNICIPIUL|JUD|JUDETUL|CARTIER|PARTER|SECTOR|ORAS)\b/g, '')
|
||||||
.replace(/[^A-Z0-9]/g, '');
|
.replace(/[^A-Z0-9]/g, '');
|
||||||
}
|
}
|
||||||
const gStreet = norm(gomag.address || gomag.strada || '');
|
const gStreet = norm(gomag.address || gomag.strada || '');
|
||||||
@@ -1013,22 +1013,25 @@ function _renderHeaderInfo(order) {
|
|||||||
|
|
||||||
addressLines.innerHTML = html;
|
addressLines.innerHTML = html;
|
||||||
|
|
||||||
// Diff summary badge in modal header
|
// Typed diff badges in modal header
|
||||||
let diffCount = 0;
|
|
||||||
if (isPJ && pi.anaf_denumire_mismatch) diffCount++;
|
|
||||||
if (order.price_check && order.price_check.mismatches > 0) diffCount += order.price_check.mismatches;
|
|
||||||
if (addr) {
|
|
||||||
if (addr.livrare_roa && !addrMatch(addr.livrare_gomag, addr.livrare_roa)) diffCount++;
|
|
||||||
if (addr.facturare_roa && !addrMatch(addr.facturare_gomag, addr.facturare_roa)) diffCount++;
|
|
||||||
}
|
|
||||||
const orderNumEl = document.getElementById('detailOrderNumber');
|
const orderNumEl = document.getElementById('detailOrderNumber');
|
||||||
if (orderNumEl && diffCount > 0) {
|
if (orderNumEl) {
|
||||||
const existing = orderNumEl.querySelector('.diff-badge');
|
orderNumEl.parentNode.querySelectorAll('.diff-badge').forEach(b => b.remove());
|
||||||
if (existing) existing.remove();
|
const badges = [];
|
||||||
const badge = document.createElement('span');
|
if (isPJ && pi.anaf_cod_fiscal_adjusted) badges.push({label:'CUI', cls:'diff-badge-anaf', aria:'CUI ajustat conform ANAF'});
|
||||||
badge.className = 'diff-badge badge ms-2';
|
if (isPJ && pi.anaf_denumire_mismatch) badges.push({label:'Denumire', cls:'diff-badge-anaf', aria:'Denumire diferita fata de ANAF'});
|
||||||
badge.style.cssText = 'background:var(--warning-light);color:var(--warning-text);font-size:11px;vertical-align:middle';
|
if (isPJ && pi.anaf_platitor_tva === 0) badges.push({label:'TVA', cls:'diff-badge-anaf', aria:'Neplatitor TVA conform ANAF'});
|
||||||
badge.textContent = diffCount + ' diferente';
|
if (addr && addr.livrare_roa && !addrMatch(addr.livrare_gomag, addr.livrare_roa)) badges.push({label:'Adr. livr.', cls:'diff-badge-info', aria:'Adresa livrare diferita'});
|
||||||
orderNumEl.parentNode.insertBefore(badge, orderNumEl.nextSibling);
|
if (addr && addr.facturare_roa && !addrMatch(addr.facturare_gomag, addr.facturare_roa)) badges.push({label:'Adr. fact.', cls:'diff-badge-info', aria:'Adresa facturare diferita'});
|
||||||
|
if (order.price_check && order.price_check.mismatches > 0) badges.push({label:'Preturi (' + order.price_check.mismatches + ')', cls:'diff-badge-info', aria:'Preturi diferite: ' + order.price_check.mismatches});
|
||||||
|
let insertAfter = orderNumEl;
|
||||||
|
badges.forEach(b => {
|
||||||
|
const el = document.createElement('span');
|
||||||
|
el.className = 'diff-badge ' + b.cls;
|
||||||
|
el.setAttribute('aria-label', b.aria);
|
||||||
|
el.textContent = b.label;
|
||||||
|
insertAfter.parentNode.insertBefore(el, insertAfter.nextSibling);
|
||||||
|
insertAfter = el;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
{% set rp = request.scope.get('root_path', '') %}
|
{% set rp = request.scope.get('root_path', '') %}
|
||||||
<link href="{{ rp }}/static/css/style.css?v=34" rel="stylesheet">
|
<link href="{{ rp }}/static/css/style.css?v=35" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Top Navbar (hidden on mobile via CSS) -->
|
<!-- Top Navbar (hidden on mobile via CSS) -->
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
|
|
||||||
<script>window.ROOT_PATH = "{{ rp }}";</script>
|
<script>window.ROOT_PATH = "{{ rp }}";</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="{{ rp }}/static/js/shared.js?v=25"></script>
|
<script src="{{ rp }}/static/js/shared.js?v=26"></script>
|
||||||
<script>
|
<script>
|
||||||
// Dark mode toggle
|
// Dark mode toggle
|
||||||
function toggleDarkMode() {
|
function toggleDarkMode() {
|
||||||
|
|||||||
@@ -115,5 +115,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=36"></script>
|
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=37"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ CREATE OR REPLACE PACKAGE PACK_IMPORT_PARTENERI AS
|
|||||||
-- 02.04.2026 - cautare CUI strict (p_strict_search=1) sau dual anti-dedup (NULL)
|
-- 02.04.2026 - cautare CUI strict (p_strict_search=1) sau dual anti-dedup (NULL)
|
||||||
-- 02.04.2026 - parser adrese: extrage APARTAMENT/SCARA/ETAJ embedded in strada (fix "Nr17 apartament 8")
|
-- 02.04.2026 - parser adrese: extrage APARTAMENT/SCARA/ETAJ embedded in strada (fix "Nr17 apartament 8")
|
||||||
-- 02.04.2026 - fallback cautare PF cu permutari nume (evita duplicate la swap firstname/lastname)
|
-- 02.04.2026 - fallback cautare PF cu permutari nume (evita duplicate la swap firstname/lastname)
|
||||||
|
-- 06.04.2026 - eliminat TIER 2 cautare adresa (judet+loc fara strada) — creeaza adresa noua cand strada difera
|
||||||
|
|
||||||
-- ====================================================================
|
-- ====================================================================
|
||||||
-- CONSTANTS
|
-- CONSTANTS
|
||||||
@@ -948,23 +949,6 @@ CREATE OR REPLACE PACKAGE BODY PACK_IMPORT_PARTENERI AS
|
|||||||
when NO_DATA_FOUND then p_id_adresa := null;
|
when NO_DATA_FOUND then p_id_adresa := null;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
-- TIER 2: county + city (no street) but ONLY with valid id_loc
|
|
||||||
if p_id_adresa is null then
|
|
||||||
begin
|
|
||||||
select id_adresa into p_id_adresa from (
|
|
||||||
select id_adresa
|
|
||||||
from vadrese_parteneri
|
|
||||||
where id_part = p_id_part
|
|
||||||
and judet = v_judet
|
|
||||||
and localitate = v_localitate
|
|
||||||
and id_loc IS NOT NULL
|
|
||||||
order by principala desc, id_adresa desc
|
|
||||||
) where rownum = 1;
|
|
||||||
exception
|
|
||||||
when NO_DATA_FOUND then p_id_adresa := null;
|
|
||||||
end;
|
|
||||||
end if;
|
|
||||||
|
|
||||||
-- Adaug o adresa
|
-- Adaug o adresa
|
||||||
if p_id_adresa is null then
|
if p_id_adresa is null then
|
||||||
-- caut judetul
|
-- caut judetul
|
||||||
|
|||||||
@@ -634,3 +634,85 @@ class TestGetPricesForOrderCantitateRoa:
|
|||||||
assert result["items"][0]["match"] is None
|
assert result["items"][0]["match"] is None
|
||||||
assert result["items"][0]["kit"] is True
|
assert result["items"][0]["kit"] is True
|
||||||
assert result["summary"]["mismatches"] == 0
|
assert result["summary"]["mismatches"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ── normalize_company_name (II, PFA, INTREPRINDERE INDIVIDUALA) ──
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormalizeCompanyNameExtended:
|
||||||
|
"""Tests for extended legal form stripping in normalize_company_name."""
|
||||||
|
|
||||||
|
def test_strip_ii_at_start(self):
|
||||||
|
from app.services.anaf_service import normalize_company_name
|
||||||
|
assert normalize_company_name("II CHIRITA N ION") == "CHIRITA N ION"
|
||||||
|
|
||||||
|
def test_no_strip_ii_mid_name(self):
|
||||||
|
from app.services.anaf_service import normalize_company_name
|
||||||
|
result = normalize_company_name("TEHNICA II SRL")
|
||||||
|
assert "II" in result # II should remain (Roman numeral)
|
||||||
|
|
||||||
|
def test_strip_pfa(self):
|
||||||
|
from app.services.anaf_service import normalize_company_name
|
||||||
|
assert normalize_company_name("PFA POPESCU ION") == "POPESCU ION"
|
||||||
|
|
||||||
|
def test_strip_intreprindere_individuala(self):
|
||||||
|
from app.services.anaf_service import normalize_company_name
|
||||||
|
result = normalize_company_name("CHIRIȚĂ N. ION ÎNTREPRINDERE INDIVIDUALĂ")
|
||||||
|
assert "CHIRITA N ION" == result
|
||||||
|
|
||||||
|
def test_strip_ii_with_dots(self):
|
||||||
|
from app.services.anaf_service import normalize_company_name
|
||||||
|
assert normalize_company_name("I.I. CHIRITA N ION") == "CHIRITA N ION"
|
||||||
|
|
||||||
|
|
||||||
|
# ── _addr_match (server-side address comparison) ─<><E29480><EFBFBD>
|
||||||
|
|
||||||
|
|
||||||
|
class TestAddrMatch:
|
||||||
|
"""Tests for _addr_match server-side address comparison."""
|
||||||
|
|
||||||
|
def test_matching_addresses(self):
|
||||||
|
from app.services.sync_service import _addr_match
|
||||||
|
import json
|
||||||
|
g = json.dumps({"address": "Str. Elisabeta 10", "city": "Bucuresti", "region": "Bucuresti"})
|
||||||
|
r = json.dumps({"strada": "Elisabeta", "numar": "10", "localitate": "Bucuresti", "judet": "Bucuresti"})
|
||||||
|
assert _addr_match(g, r) is True
|
||||||
|
|
||||||
|
def test_street_mismatch(self):
|
||||||
|
from app.services.sync_service import _addr_match
|
||||||
|
import json
|
||||||
|
g = json.dumps({"address": "Str. Elisabeta 10", "city": "Bucuresti", "region": "Bucuresti"})
|
||||||
|
r = json.dumps({"strada": "Victoriei", "numar": "10", "localitate": "Bucuresti", "judet": "Bucuresti"})
|
||||||
|
assert _addr_match(g, r) is False
|
||||||
|
|
||||||
|
def test_city_mismatch(self):
|
||||||
|
from app.services.sync_service import _addr_match
|
||||||
|
import json
|
||||||
|
g = json.dumps({"address": "Elisabeta 10", "city": "Brasov", "region": "Brasov"})
|
||||||
|
r = json.dumps({"strada": "Elisabeta", "numar": "10", "localitate": "Cluj", "judet": "Brasov"})
|
||||||
|
assert _addr_match(g, r) is False
|
||||||
|
|
||||||
|
def test_county_mismatch(self):
|
||||||
|
from app.services.sync_service import _addr_match
|
||||||
|
import json
|
||||||
|
g = json.dumps({"address": "Elisabeta 10", "city": "Brasov", "region": "Brasov"})
|
||||||
|
r = json.dumps({"strada": "Elisabeta", "numar": "10", "localitate": "Brasov", "judet": "Cluj"})
|
||||||
|
assert _addr_match(g, r) is False
|
||||||
|
|
||||||
|
def test_none_input_returns_true(self):
|
||||||
|
from app.services.sync_service import _addr_match
|
||||||
|
assert _addr_match(None, None) is True
|
||||||
|
assert _addr_match(None, '{"strada":"x"}') is True
|
||||||
|
assert _addr_match('{"address":"x"}', None) is True
|
||||||
|
|
||||||
|
def test_strada_prefix_stripping(self):
|
||||||
|
"""'Strada Elisabeta' should match 'ELISABETA' after normalization."""
|
||||||
|
from app.services.sync_service import _addr_match
|
||||||
|
import json
|
||||||
|
g = json.dumps({"address": "Strada Elisabeta 10", "city": "Bucuresti", "region": "Bucuresti"})
|
||||||
|
r = json.dumps({"strada": "Elisabeta", "numar": "10", "localitate": "Bucuresti", "judet": "Bucuresti"})
|
||||||
|
assert _addr_match(g, r) is True
|
||||||
|
|
||||||
|
def test_malformed_json_returns_true(self):
|
||||||
|
from app.services.sync_service import _addr_match
|
||||||
|
assert _addr_match("{bad json", '{"strada":"x"}') is True
|
||||||
|
|||||||
Reference in New Issue
Block a user