Two production bugs from VENDING (order 485224762, 2026-04-22):
1. Oracle: ORA-20000 when a GoMag order contains a kit SKU whose expansion
includes CODMAT X plus a second item with SKU=X. Two article-insert
call-sites in PACK_IMPORT_COMENZI bypassed merge_or_insert_articol —
line 622 (NOM_ARTICOLE fallback) and line 538 (kit discount line).
Both now use merge_or_insert_articol for consistent dedup semantics.
Regression test added in test_complete_import.py covering the exact
kit-plus-direct scenario.
2. SQLite: retry_service._download_and_reimport refreshed orders row but
never repopulated order_items. Combined with mark_order_deleted_in_roa
(which wipes items), any retry/resync left the UI showing "Niciun
articol" despite successful Oracle import. Retry now rebuilds items
from the fresh GoMag download on both success and error paths,
mirroring sync_service.
Includes scripts/backfill_order_items.py — one-shot recovery for orders
already in this bad state. Reads settings, re-fetches from GoMag,
rewrites order_items without touching Oracle or order status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SKUs mapped externally (via SSH script or direct SQL) never triggered
resolve_missing_sku(), leaving them stuck as unresolved=0 indefinitely.
New reconcile_unresolved_missing_skus() revalidates ALL unresolved SKUs
against Oracle at sync, rescan, and CSV import time. Fail-soft on Oracle
down. Clears the 7 prod false positives on next sync or manual rescan.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. SQLite order_items overwrite on re-import (VELA CAFE #484669620):
add_order_items, save_orders_batch, mark_order_deleted_in_roa now use
DELETE + INSERT so GoMag quantity changes propagate to dashboard.
2. PL/SQL strict CUI lookup tolerates whitespace (FG COFFE #485065210):
cauta_partener_dupa_cod_fiscal regex ^RO\d → ^RO\s*\d; IN-set uses
canonical v_ro_cui. Platitor/neplatitor business rule preserved.
Python defensive: re.sub whitespace collapse in determine_partner_data.
3. New PJ partners use ANAF official denumire (denumire_override) instead
of GoMag company_name. Existing partners (found by CUI) untouched.
Tests: 18 new (5 SQLite unit, 8 Python unit, 5 Oracle PL/SQL). All green
locally: 228 unit + 26 oracle + 33 e2e.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PJ: tooltip shows company on Facturat (display) vs shipping person.
PF ramburs: tooltip shows billing person vs shipping person when they
differ. Adds aria-label + title on indicator for keyboard/screen reader.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract shared setupAutocomplete() into shared.js so all three autocomplete
instances (mappings modal, inline add, quick-map modal) get keyboard nav
(ArrowDown/Up/Enter/Escape), scroll-safe blur handling, and capture-phase
keydown to prevent browser interception. Remove old onmousedown inline
handlers, use data-codmat/data-label attributes instead.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Kebab dropdown delete/resync used inlineConfirmAction which breaks inside
Bootstrap dropdowns (dropdown closes on click, hiding confirm state).
Replaced with confirm() dialog + direct async action with row feedback.
Detail modal resync/delete/retry now trigger onStatusChange callback to
refresh the orders table, so status dots update without page reload.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Bug 1: hover actions covered total column; replaced with kebab dropdown in dedicated 44px column
- Bug 2: resync/delete buttons kept stale state across modal opens; reset in modal init block
- Bug 3: delete success button was green (btn-success); changed to red (btn-danger)
- Dropdown styled per DESIGN.md: warm shadow, 8px radius, dark mode tokens
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resync soft-deletes from Oracle then re-imports from GoMag with fresh
article data. Delete soft-deletes and marks DELETED_IN_ROA. Both have
invoice safety gates (refuse if invoiced or Oracle unavailable).
UI: split modal footer (Delete left, Resync+Close right), inline
confirm pattern (no native confirm()), dashboard row hover action
icons, disabled+tooltip for invoiced orders. 8 unit tests for safety
gates and happy paths.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes false negatives where city spellings differ slightly (e.g.
"Sfântu Ilie" vs "SFINTU ILIE") or ROA stores "BUCURESTI SECTORUL 1"
while GoMag sends "Municipiul București". Both backend (_addr_match)
and frontend (addrMatch) now use identical SOUNDEX logic mirroring
Oracle's implementation.
Also fixes field order: etaj before apart in r_street concatenation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Oracle parser failed to extract sc/ap/et when GoMag addresses had no
commas. Added REGEXP_REPLACE to insert commas before address keywords
in v_strada before the comma-split, ensuring the token parser always
fires. Also added 5 Oracle integration tests calling
parseaza_adresa_semicolon directly, and improved diacritics handling
in addr_match (Python + JS).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GoMag vs ROA price comparison generated too many false positives
(kits, volume discounts, special prices). Removes comparison columns,
dots, badges, catalog sync endpoints, and ~950 lines of dead code.
Keeps WRITE path (sync_prices_from_order) for kit pricing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three root causes caused partner_mismatch=1 to loop indefinitely:
1. No-CUI company orders (is_pj=1, no cod_fiscal): old code flagged as
mismatch every cycle. Fixed by requiring new_cf to be non-null for
PF→PJ detection. Stale flags from old code cleared via new
clear_stale_partner_mismatches_no_cui() for out-of-window orders.
2. same_partner resync path did not update cod_fiscal_gomag in SQLite.
On next cycle GoMag returned a CUI but stored_cf was still NULL →
re-detected as mismatch. Fixed by also calling update_partner_resync_data
(not just update_partner_mismatch_batch) in the same_partner branch.
3. GoMag sends CUI with space: 'RO 17922480'. The _strip_ro() regex
^RO left the space → ' 17922480' != '17922480' → false mismatch.
Fixed: changed regex to ^RO\s* and added .strip().
Also adds diagnostic logger.info lines for mismatch detection/resync counts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- diff dot for partner_mismatch uses --warning (orange) instead of --error
to distinguish from price mismatch (also red)
- modal ROA column shows "necunoscut - se va actualiza la urmatorul sync"
when denumire_roa is null but partner_mismatch=1 (orders imported before
the column existed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
baseprice > price was wrongly treated as "quantity discount" — it's just
GoMag's promotional price. Now: price_gomag <= pret_roa is always OK,
only flag when GoMag charges MORE than ROA. Reset cached price_match
at startup for re-evaluation. Fix dashboard dot color for mismatches.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
GoMag sends baseprice (catalog price) alongside price (discounted).
When baseprice > price, the item is volume-discounted — skip ROA
price comparison and show amber "Disc." badge instead of false
mismatch. Strikethrough baseprice in price column for transparency.
Pipeline: parse baseprice → store in SQLite → skip in validation →
pass flag to frontend → render badge (desktop + mobile pill badge
with aria-label, opacity 0.6 for dark mode).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ANAF notFound items are plain integers, not dicts — caused 'int has no
attribute get'. 4xx errors (like 404) no longer retry uselessly. ANAF
errors now appear in the UI sync log via log_fn callback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Orders synced before address_mismatch was deployed had stale 0 values,
causing missing blue dots in the dashboard. Adds startup backfill from
stored address JSON + recomputes on each sync for ALREADY_IMPORTED orders.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CUI adjustment (RO prefix change) already implies TVA mismatch, so
suppress redundant TVA badge when CUI badge is shown. Remove
address_mismatch from DIFFS pill count since it's not an ANAF difference.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously the TVA badge/dot appeared whenever ANAF reported neplatitor.
Now it only appears when GoMag CUI prefix (RO=platitor) disagrees with
ANAF status — aligned across JS badges, red dots, SQL filter and count.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
Customers often swap firstname/lastname in GoMag forms, causing duplicate
partner creation in Oracle. Fix with two layers:
- Python: sort PF name words alphabetically before Oracle lookup
- PL/SQL: add Step 2b permutation search (2-3 word names, PF only)
- Normalize name order to lastname+firstname across all Python files
- Add diagnostic SQL for finding existing reversed-name duplicates
- Add Oracle integration test for reverse-name matching
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unreachable OCR-skip fallback (raw_bare can't be all-digits
if strip_ro_prefix changed it via OCR fix). Add real test for the
checksum result==10→0 branch using CUI 14186770.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Romanian CUI check digit algorithm (key 753217532) validates CUIs
before ANAF lookup. New sanitize_cui() fixes OCR typos (O→0, I→1)
and verifies checksum, logging warnings for invalid CUIs.
Applied at both ANAF batch verification and per-order import steps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GoMag order #4815967771 had CUI "49033O51" (letter O instead of zero).
validate_cui rejected it so ANAF badge showed "?". strip_ro_prefix now
translates common letter/digit confusions before validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Orders imported before the ANAF dedup feature had no anaf_platitor_tva.
Step 4c now auto-backfills on each sync: batch cache lookup, single
ANAF API call for uncached CUIs, bulk DB update in one transaction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address parser: extract APARTAMENT/SCARA/ETAJ embedded in street text
(previously only NR and BLOC were extracted). Two-tier matching:
separator-required for short prefixes (avoids "APATEULUI" false-positive)
and direct-digit fallback for "ap14", "sc1", "et2" patterns.
UI: show denumire_roa for PF orders too, not just PJ.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no sync run is selected, the logs page was completely blank
below the controls. Added a guidance message with icon explaining
what to do. Hides automatically when a run is selected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DESIGN.md motion rules: never use transition:all, list properties
explicitly. Changed preset-btn from transition:all to
background/color/border-color transitions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
H5 was using Bootstrap default (1.25rem = 20px), larger than the H4
page title (18px). This inverted the heading hierarchy. Set H5 to
16px/600 per DESIGN.md section title spec.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DESIGN.md type scale specifies 14px body / 13px data for table cells.
The table was using 1rem (16px), which is Bootstrap's default. This
reduces data density on the most visible component (the order table).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Kit articles (multi-component or cantitate_roa≠1) have expected price
differences between GoMag (commercial) and ROA (component sum).
Skip comparison entirely, mark with kit=True flag for UI badge.
Fix kit detection to use float()!=1 (catches cantitate_roa<1 like 0.5).
Update 3 existing tests + add multi-component kit test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PL/SQL cauta_partener_dupa_cod_fiscal gains p_strict_search param:
- strict (=1): search only exact CUI form (ANAF-determined)
- dual (NULL): search all forms (existing anti-dedup behavior)
Skip denomination fallback when strict to force new partner creation.
Python: country filter excludes foreign companies from ANAF batch,
anaf_strict flag threaded sync→import→PL/SQL, normalize RO-space
in cod_fiscal_adjusted comparison to eliminate false positives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Preset buttons: add min-height 32px desktop, 44px mobile
- btn-sm: add min-height 44px on mobile
- Checkbox: increase min-size on mobile
- Mobile overrides placed AFTER base rules for correct cascade
- Page title h4: override Bootstrap default (24px/500) to match
DESIGN.md spec (18px/600/Display)
- Cache-bust style.css v30
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The billing/shipping diff triangle used inline style="color:#6b7280"
which doesn't adapt to dark mode. Now uses a CSS class with
var(--text-muted) for proper theming.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Colored left-border on cards is AI Slop blacklist item #8.
Use warning-light background tint instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix ANAF API: extract CUI from date_generale (not top-level), fix
notFound casing (capital F)
- Fix missing facturare address when same ID as livrare (copy instead
of skip)
- Replace ANAF cache pre-population stub with real logic (3-month CUIs)
- Restructure order detail modal: inline 2-col GOMAG|ROA layout with
compact address lines replacing collapsed sections
- Fix addrMatch() to use field-level comparison with Romanian
abbreviation stripping (STR, NR, BL, SC, AP, ET, ETAJ, APART)
- Add dashboard "Diferente" filter pill for ANAF-adjusted orders
- Update e2e test for new modal structure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
updateSchedulerInterval() only saved when enabled=true, so changing
the dropdown with auto off was lost on refresh. Now always persists.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: GET /api/sync/schedule returned interval_minutes=null when
scheduler was stopped, causing dropdown to stay on first HTML option
(1 min). Setting .value programmatically could trigger onchange, sending
a second PUT with interval=1 right after the user's intended interval.
- GET schedule falls back to DB/default (10 min) when scheduler is off
- Add _schedulerLoading flag to block onchange during loadSchedulerStatus
- Default interval 10 min everywhere (was 5 in backend)
- Cache bust dashboard.js v=33
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>