Production VENDING orders #485841978 and #485841895 (2026-04-28) crashed
on Retry with PL/SQL COM-001 because the retry path skipped the
CRM_POLITICI_PRET_ART pre-population step that bulk sync runs.
The price-list auto-insert (PRET=0) for missing CODMATs was only invoked
in sync_service.run_sync (lines 592-718). retry_service called
import_single_order directly, hitting pack_comenzi.adauga_articol_comanda
NO_DATA_FOUND on every CODMAT without a price entry.
Extracted the validation block into validation_service.pre_validate_order_prices
and call it from both bulk sync and retry. Single source of truth for
SKU validation, dual-policy routing (cont 341/345 → productie),
ARTICOLE_TERTI mapping resolution, and kit component price gating.
Tests: 3 unit + 3 oracle integration covering the regression scenario,
empty input, dual-policy routing, idempotency, and pre-validation
exception propagation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Doua root cause-uri pentru ORA-12899 la importul comenzii #485841056:
1. Oracle ORA-06502: v_apart/v_scara/v_bloc/v_etaj in cauta_sau_creeaza_adresa
declarate VARCHAR2(10/20/30) → Oracle mostenea constrangerea pe OUT parametrii
din parseaza_adresa_semicolon → crash INAINTE de fix-ul overflow de la linia 521.
Fix: marite la VARCHAR2(100).
2. Python format_address_for_oracle stripuia doar city exact, nu si 'city region'
sau 'region city'. GoMag trimite adresa cu suffix 'Municipiul Bucuresti Bucuresti'
(city+region) → token urias pentru apartament → declansa ORA-06502 de mai sus.
Fix: incearca toate combinatiile city+region, region+city, city, region.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Incident 22.04.2026 (#485225171 NONA ROYAL SRL): clientul a inversat
cod_fiscal cu registru in GoMag → sistem a creat partener cu CUI=J1994000194225.
Adauga evaluate_cui_gate() care blocheaza comanda (ERROR) daca:
- CUI format invalid (ex: J.. in loc de cifre)
- CUI nu trece cifra de control
- ANAF returneaza explicit notFound (scpTVA=None + denumire_anaf="")
ANAF down (anaf_data=None) → fallback pass, comportament existent pastrat.
_record_order_error() DRY helper evita duplicarea upsert/add_items.
Contract ANAF down/notFound/found documentat in anaf_service._call_anaf_api.
9 teste unit (inclusiv T5 CRITIC: ANAF down nu blocheaza) + T7 COALESCE.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MALFORMED is now a valid retry source alongside ERROR / SKIPPED /
DELETED_IN_ROA. The next sync will re-run validate_structural and
either reclassify or keep the MALFORMED tag — either way, operators
get the same "Retry" button they have for other failure paths
without needing a separate UI affordance.
278 unit + 33 e2e green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend:
- GET /api/sync/health returns {last_sync_at, last_sync_status,
last_halt_reason, recent_phase_failures, escalation_phase, is_healthy}.
healthy when last run was completed (or none yet), no phase has
tripped the 3-in-a-row escalation, and recent failures <= 1.
- Dashboard + run-level endpoints include `malformed` count so the
Defecte pill can render.
Frontend:
- Health pill in .sync-card-controls with three states — healthy
(success green, check icon), warning (amber, triangle), escalated
(error red, x-octagon + glow). Tooltip exposes the halt reason and
the top phases with recent failures.
- Status-dot + badge add MALFORMED treatment via --compare orange,
distinct from ERROR red. DESIGN.md notes the diagnostic rationale
(ERROR = runtime, MALFORMED = payload source issue).
- Defecte filter pill on dashboard + logs pages. Mobile segmented
control includes Defecte count. Counts wired to the malformed key.
- startSync() shows a native confirm modal when state is
halted_escalation — operator override still possible, not silenced.
- ORDER_STATUS.MALFORMED mirror added to shared.js.
- Cache-bust: style.css v46, shared.js v47, dashboard.js v52,
logs.js v16.
5 endpoint tests cover empty state, completed, failed, escalated,
single-failure warning. Full CI: 257 unit + 33 e2e green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sync_service gains DATA_ERRORS tuple + two new primitives:
_record_phase_err(run_id, phase, err)
Logs, appends to run text log, persists to sync_phase_failures.
_check_escalation()
Reads the last 3 runs and returns the first phase that has failed
all 3 in a row, or (None, counts) otherwise.
run_sync now runs a pre-flight escalation check — if a phase has failed
3 consecutive runs, the incoming sync is halted with
status='halted_escalation' and a descriptive error_message. The
dashboard Start Sync button can still override (UI comes in the next
PR2 phase).
Wrapped phases (DATA_ERRORS caught, sync continues):
cancelled_batch, already_batch, addresses_batch, skipped_batch,
price_sync, invoice_check, anaf_backfill.
Partner mismatch retains its existing per-order guards. OperationalError
and OS-level errors still propagate to the top-level handler (halt).
6 unit tests cover record + counts + threshold + mixed-phase +
short-circuit + DATA_ERRORS contract. Full CI green: 251 unit + 33 e2e.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New table sync_phase_failures(run_id, phase, error_summary, created_at)
with index on (phase, created_at). Minimal schema — no raw payload, no
PII — stores just enough to answer "did phase X fail in the last N
runs?" for the escalation check and the /api/sync/health pill.
Helpers in sqlite_service:
record_phase_failure(run_id, phase, error_summary)
INSERT OR REPLACE semantics (one row per run+phase), then prunes
to the most recent 100 sync_runs. error_summary clipped at 500
chars defensively.
get_recent_phase_failures(limit=3) → {phase: count} across the last N
runs, ordered by started_at desc.
6 unit tests cover creation, counting, pruning, empty state,
idempotency, and limit semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_safe_upsert_order_items(db, order_number, items) wraps the
DELETE + INSERT OR REPLACE pair in SAVEPOINT items. On
IntegrityError / ValueError / TypeError it rolls the savepoint
back, tags the parent order MALFORMED, logs to the error history
file, and returns False to the caller. add_order_items now delegates
to this helper so a single bad payload cannot leave order_items in
a split state.
2 integration tests: happy path + simulated INSERT crash via
aiosqlite monkeypatch. Existing order_items overwrite regression
tests still pass (5/5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
save_orders_batch now runs in three tiers:
1. validate_structural pre-flight splits each payload into valid or
MALFORMED. MALFORMED rows persist with status + error_message + no
items, and an append-only entry lands in sync_errors_history.log.
2. Optimistic executemany over the valid list inside a SAVEPOINT batch.
3. On IntegrityError / ValueError / TypeError, rollback the savepoint
and fall back to per-order SAVEPOINT inserts so a single bad row
cannot poison the rest of the batch.
Mid-loop SAVEPOINT rollback failure now triggers _safe_reconnect:
commit whatever survived, close the broken connection, open a fresh
one and keep processing. Preserves MALFORMED rows recorded earlier —
addresses the outside-voice gap where a crashed connection would lose
uncommitted malformed evidence.
Adds OrderStatus.MALFORMED and helper functions:
_insert_orders_only — orders + sync_run_orders, no items
_insert_valid_batch — happy-path bulk executemany
_insert_single_order — per-order execute within savepoint
_mark_malformed — non-mutating copy with wiped items
_safe_reconnect — commit-close-reconnect guard
8 integration tests covering regression 485224762, structural
pre-flight, per-order isolation on runtime fail, caller-dict
immutability, and reconnect durability. 239 unit + 33 e2e green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_log_order_error_history(order_number, msg) writes to
logs/sync_errors_history.log via a dedicated RotatingFileHandler
(100MB × 12 backups). Logger is lazy-initialised and non-propagating
so it doesn't pollute the root logger.
Purpose: orders.error_message is overwritten when a retry succeeds,
so the history log preserves permanent audit of every malformed-order
event regardless of later outcome. Helper never raises — callers are
already in a degraded path.
3 unit tests: append semantics, multi-order, exception isolation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
validate_structural(order) runs before save_orders_batch insert.
Catches malformed payloads (MISSING_FIELD, INVALID_DATE, EMPTY_ITEMS,
INVALID_QUANTITY, INVALID_PRICE) that would otherwise crash the batch
insert or downstream pipeline. 17 unit tests cover each rule.
Does NOT validate SKU existence — redundant with _dedup_items_by_sku
pass-through and validate_skus Oracle lookup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Production sync was failing every minute with:
UNIQUE constraint failed: order_items.order_number, order_items.sku
GoMag occasionally returns the same SKU on multiple lines within one order
(configurable products, promo splits). The order_items PK is
(order_number, sku), so the raw batch insert violates UNIQUE and aborts
the entire sync — blocking partner-mismatch updates, address refresh,
and items repopulation for already-imported orders.
Added _dedup_items_by_sku() helper. Applied in save_orders_batch
(cancelled/already/skipped paths) and add_order_items (retry/sync import
paths). Keeps first price/vat/name, sums quantities on collision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
- 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>
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>
Kit detection only checked cantitate_roa > 1, missing fractional values
like 0.5 (GoMag 50buc/set → ROA 100buc/set). This caused false price
difference alerts (e.g. GoMag 7.00 vs ROA 14.00 for order #481595156).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
- Add price_match column to SQLite, cached on order detail view
- Background backfill on startup checks all unchecked imported orders
- Extract _enrich_items_with_codmat() helper to deduplicate SKU enrichment
- Attention card now shows same nefacturate count as filter pill
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After saving a SKU mapping, check for SKIPPED orders containing that
SKU and show a floating banner with count + "Importa" button. Batch
retries up to 20 orders and shows result feedback.
Backend:
- get_skipped_orders_with_sku() in sqlite_service.py
- GET /api/orders/by-sku/{sku}/pending endpoint
- POST /api/orders/batch-retry endpoint (max 20, sequential)
Frontend:
- Auto-retry banner after quickMap save with batch import button
- Success/error feedback, auto-dismiss after 15s
Cache-bust: shared.js?v=19
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ability to re-import individual ERROR/SKIPPED orders directly from
the order detail modal. Downloads narrow date range from GoMag API,
finds the specific order, and re-runs import_single_order().
Backend:
- New retry_service.py with retry_single_order() — downloads order_date
±1 day from GoMag, finds order by number, imports via import_service
- Guard: blocks retry during active sync (_sync_lock check)
- POST /api/orders/{order_number}/retry endpoint
Frontend:
- "Reimporta" button in modal footer (visible only for ERROR/SKIPPED)
- Spinner during retry, success/error feedback with auto-refresh
Cache-bust: shared.js?v=18
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a "Needs Attention" card above the orders table that surfaces:
- Import errors count (click → ERROR filter)
- Unmapped SKUs count (click → Missing SKUs page)
- Uninvoiced orders >3 days (click → UNINVOICED filter)
Shows green "Totul in ordine" when all metrics are zero.
Backend: add uninvoiced_old count to get_orders() and unresolved_skus
from get_dashboard_stats() to dashboard/orders API response.
Cache-bust: style.css?v=21, dashboard.js?v=29
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ROA price comparison to order detail modal — operators can now see
if GoMag prices match Oracle before invoicing. Eliminates the #1 risk
of invoicing with wrong prices.
Backend:
- New get_prices_for_order() in validation_service.py — batch Oracle
query with dual-policy routing (sales/production by cont 341/345),
PRETURI_CU_TVA handling, kit total calculation
- Extend GET /api/sync/order/{orderNumber} with per-item pret_roa and
order-level price_check summary
- GET /api/dashboard/orders returns price_match=null (lightweight)
Frontend:
- Modal: price check badge (green/red/grey), "Pret GoMag" + "Pret ROA"
columns, match dot per row, mismatch rows highlighted
- Dashboard: price dot column (₽) in orders table
- Mobile: inline mismatch indicator
Cache-bust: shared.js?v=16, dashboard.js?v=28
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>