Adaugat in sectiunea 'Depanare SSH' din README:
- Comanda corecta ssh cu -i ~/.ssh/id_ed25519
- Deploy pachet Oracle via sqlplus @fisier.pck
- Restart FastAPI cand nssm e indisponibil (kill python + start.ps1)
- Tabel explicit cu ce NU merge (nssm/sc/WMI/pipe/curl -m/here-doc)
si alternativele corecte
- Metoda scriere SQL ad-hoc: fisier local -> scp -> @fisier.sql
Co-Authored-By: Claude Sonnet 4.6 <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>
Safety net dupa blocul de overflow split: garanteaza ca p_numar nu
depaseste 10 caractere chiar daca prefixul inaintea primului spatiu
este el insusi >10 char.
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>
Script failed with "SQLite not initialized" because module-level connection
state wasn't set up when invoked standalone.
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>
In parseaza_adresa_semicolon, text după NR ("5 la non stop", "21 sat
Grozavesti corbii mari") era împins în p_numar și trunchiat brutal la
10 chars ("5 LA NON S", "21 SAT GRO").
Fix: când p_numar > 10 chars, prima componentă rămâne numar; restul se
clasifică:
- "SAT X ..." → p_localitate := "X ..." (satul = localitate, TIER
L1/L2/L3 existent rezolvă id_loc)
- "COM/ORAS/MUN X" → aruncat (deja în p_localitate din GoMag city)
- altceva (landmark ex "LA NON STOP") → concatenat în p_strada
Semnătura parseaza_adresa_semicolon neschimbată. Zero callers afectați.
Teste: landmark → strada, SAT → localitate, numar normal neschimbat.
Co-Authored-By: Claude Sonnet 4.6 <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>
cauta_partener_dupa_denumire nu filtra sters=0, deci importul PF
putea lega comanda la un partener sters=1 (bug GoMag #484668145 —
RADULESCU ANA MARIA, id_part=21946). Adaugat NVL(sters,0)=0 si
ORDER BY inactiv ASC pentru a prefera parteneri activi, in linie cu
cauta_partener_dupa_cod_fiscal.
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>
README.md: replace old different_person logic with PJ/PF rule description
docs/adrese_facturare_variante.md: new file — decision rationale, implementation
summary, verification commands, history of the change
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>
DPY-4000 (no tnsnames in dev) and ANAF 404/500 (mock server) are expected
in CI — add them to _KNOWN_ISSUES so the log monitor test passes at 100/100.
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>
TIER L2: SOUNDEX match pe judet (ex: CRAMPOIA→CRIMPOIA, varianta â/î).
TIER L3: pastreaza judetul corect rezolvat, nu mai reseteaza la default global.
Co-Authored-By: Claude Opus 4.6 <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>
TRANSLATE with UTF-8 literals was silently corrupted when compiled via
Windows sqlplus (ĂăÂâÎî→����, ȘșȚț→????). Replaced with REPLACE/UNISTR
for comma-below→cedilla normalization + CONVERT US7ASCII. Also applied
strip_diacritics to localitate/judet in TIER 1 lookup and locality search
(was only on strada), fixing 'FĂLTICENI' vs 'FALTICENI' BINARY mismatch.
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>