feat(anaf-dedup): ANAF partner dedup + address fix + UI enrichment

Prevent partner duplicates via ANAF CUI verification and dual PL/SQL
search. Fix address matching with street-level comparison and diacritics
normalization. Show partner/address comparison in order detail modal.

- New anaf_service.py: batch ANAF API client with chunking, retry, cache
- PL/SQL: dual CUI search (bare/RO+bare/RO space+bare), 3-tier address
  search (street+city+id_loc → city+id_loc → create), strip_diacritics
  at storage for addresses and partner names
- SQLite: anaf_cache table, 12 new order columns for partner/address data
- import_service: cod_fiscal_override param, return partner/address from Oracle
- sync_service: ANAF batch integration, denomination mismatch detection,
  cache pre-population trigger
- Router: enriched order_detail with partner_info + addresses JSON
- UI: collapsible Detalii Partener + Adrese Comparativ sections in modal,
  auto-expand on mismatch, ANAF badges, mobile address cards
- Dashboard: address quality attention indicator
- New scan_duplicate_partners.py script for one-time duplicate audit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-01 14:36:52 +00:00
parent 3b9198d742
commit 2f593c30f6
12 changed files with 925 additions and 64 deletions

View File

@@ -1095,3 +1095,93 @@ tr.mapping-deleted td {
color: var(--info);
text-decoration: underline;
}
/* ── Partner/Address section headers (ANAF dedup) ── */
.detail-section-header {
font-family: var(--font-display);
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-secondary);
padding: 10px 0;
border-bottom: 1px solid var(--border);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.detail-section-header:hover { color: var(--text-primary); }
.detail-section-header .bi-chevron-right {
transition: transform 150ms ease-out;
font-size: 10px;
}
.detail-section-header[aria-expanded="true"] .bi-chevron-right {
transform: rotate(90deg);
}
.detail-section-header .alert-count {
font-family: var(--font-body);
font-size: 11px;
font-weight: 500;
background: var(--error-light);
color: var(--error-text);
padding: 2px 8px;
border-radius: 9999px;
margin-left: auto;
}
.detail-section-body { padding: 12px 0; }
.partner-row { display: flex; gap: 24px; flex-wrap: wrap; margin-bottom: 8px; }
.partner-field { min-width: 140px; }
.partner-label {
font-family: var(--font-display);
font-size: 12px;
font-weight: 500;
color: var(--text-muted);
text-transform: uppercase;
}
.partner-value {
font-family: var(--font-data);
font-size: 13px;
color: var(--text-primary);
}
.anaf-badge {
display: inline-block;
font-family: var(--font-body);
font-size: 12px;
font-weight: 500;
padding: 2px 8px;
border-radius: 9999px;
}
.anaf-badge-ok { background: var(--success-light); color: var(--success-text); }
.anaf-badge-warn { background: var(--warning-light); color: var(--warning-text); }
.anaf-badge-gray { background: var(--cancelled-light); color: var(--text-muted); }
.addr-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.addr-table th {
font-family: var(--font-display);
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
color: var(--text-muted);
padding: 6px 8px;
text-align: left;
}
.addr-table td { padding: 8px; vertical-align: top; font-family: var(--font-body); }
.addr-mismatch { background: var(--warning-light) !important; }
.addr-efactura-risk { background: var(--error-light) !important; }
.addr-label {
font-family: var(--font-display);
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
color: var(--text-secondary);
margin-bottom: 4px;
}
/* Mobile address cards */
.addr-card { border: 1px solid var(--border); border-radius: var(--card-radius); margin-bottom: 8px; overflow: hidden; }
.addr-card-header { padding: 6px 10px; font-family: var(--font-display); font-size: 11px; font-weight: 500; text-transform: uppercase; color: var(--text-secondary); background: var(--surface-raised); }
.addr-card-row { padding: 8px 10px; }
.addr-card-row + .addr-card-row { border-top: 1px dashed var(--border-subtle); }
.addr-card-source { font-size: 11px; font-weight: 500; color: var(--text-muted); margin-bottom: 2px; }
.addr-card-text { font-family: var(--font-body); font-size: 13px; }
.addr-card.mismatch { border-left: 3px solid var(--warning); }
.addr-card.match .addr-match-label { font-size: 11px; color: var(--success-text); }