Linking the VANZARI header (ID_COMANDA) makes the app dashboard show an order facturat, but ROA decides facturat at the LINE level (PACK_FACTURARE.cursor_comanda matches invoiced qty on ID_ARTICOL + exact PRET). When a manual invoice represented lines differently than the order (e.g. per-VAT-rate discounts consolidated into one 0%-TVA line), the order stays nefacturat in ROA despite the header link. Add order_line_residual(): predicts the residual before --apply (via extra_idv) and re-verifies after linking. Warns in the plan, the summary counter, and post-apply when an order will still show nefacturat. The script never touches COMENZI_ELEMENTE — those need a manual line fix. Surfaced by orders 5419/5423 (web 492710430/492710513) on 2026-06-26. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
134 lines
6.4 KiB
Markdown
134 lines
6.4 KiB
Markdown
# Reconciliere facturi manuale ROA ↔ comenzi GoMag
|
||
|
||
**Script:** [`scripts/relink_manual_invoices.py`](../scripts/relink_manual_invoices.py)
|
||
**Launcher Windows:** [`scripts/relink_manual_invoices.bat`](../scripts/relink_manual_invoices.bat)
|
||
|
||
## Cand se foloseste
|
||
|
||
Cand Oracle pool / sync-ul **a fost cazut** (ex. dupa o pana de curent la VENDING)
|
||
si operatorul a emis **facturi manual direct in ROA** in acel interval. Dupa ce
|
||
aplicatia revine, importeaza aceleasi comenzi web in `COMENZI`, **dar** factura
|
||
manuala nu se leaga de comanda → comanda ramane **nefacturata** in ROA si in
|
||
dashboard, desi factura exista.
|
||
|
||
Semnal: in dashboard apar comenzi „nefacturate" pentru clienti care au fost de
|
||
fapt facturati, iar in `VANZARI` exista facturi cu `ID_COMANDA IS NULL`.
|
||
|
||
## Cauza tehnica
|
||
|
||
| Flux | `VANZARI.ID_COMANDA` | `COMENZI.COMANDA_EXTERNA` |
|
||
|------|----------------------|----------------------------|
|
||
| Normal (app) | setat (leaga factura de comanda) | nr comanda GoMag |
|
||
| Manual (operator, in downtime) | **NULL** | — |
|
||
|
||
Dashboard-ul / cache-ul SQLite marcheaza o comanda „Facturat" doar daca exista
|
||
`VANZARI ... WHERE id_comanda = <comanda> AND sters = 0`
|
||
(vezi `invoice_service.check_invoices_for_orders`). Factura manuala cu
|
||
`ID_COMANDA NULL` nu e niciodata gasita → comanda apare nefacturata.
|
||
|
||
`ID_FACT` (documentul fiscal) si `COMENZI.COMANDA_EXTERNA` sunt deja completate;
|
||
**singura piesa lipsa e legatura `VANZARI → COMANDA`.**
|
||
|
||
## ⚠️ Facturi de depozit (walk-in) — NU se ating
|
||
|
||
Operatorul emite zilnic si **facturi manuale legitime din depozit** (~20+/zi),
|
||
fara nicio comanda online in spate. Acestea au tot `ID_COMANDA NULL`, dar **nu**
|
||
trebuie legate de nimic. De aceea matching-ul e **conservator**: orice nu e o
|
||
potrivire 1:1 sigura e raportat, niciodata legat automat.
|
||
|
||
## Cum potriveste
|
||
|
||
Pentru fiecare factura orfana (`VANZARI.ID_COMANDA NULL`, `sters=0`, in fereastra
|
||
`--days`) cauta o comanda GoMag nefacturata (in SQLite, cu `id_comanda` setat) cu:
|
||
|
||
1. **Total identic** (`TOTAL_CU_TVA` ≈ `order_total`, toleranta 0.01 lei), apoi
|
||
2. **acelasi partener** (`ID_PART` = `id_partener`) **SAU**
|
||
3. **nume potrivit** (token-overlap, tolereaza SRL/SC si ordinea cuvintelor) —
|
||
acopera cazul in care operatorul a creat un **partener duplicat** la facturare.
|
||
|
||
Verifica si ca acea comanda e activa in ROA (`sters=0`) si **nu** are deja factura
|
||
(anti-dubla-facturare).
|
||
|
||
### Clasificare (in raport)
|
||
|
||
| Eticheta | Ce inseamna | Actiune |
|
||
|----------|-------------|---------|
|
||
| `LINK` | potrivire 1:1 sigura | leaga (la `--apply`) |
|
||
| `SKIP_NOMATCH` | nicio comanda online cu acel total | **factura de depozit — lasata neatinsa** |
|
||
| `SKIP_AMBIGUOUS` | mai multe comenzi plauzibile, sau total potrivit dar partener+nume diferit | raportat pentru verificare manuala |
|
||
| `SKIP_ALREADY` | comanda nu mai e activa / are deja factura | sarit |
|
||
|
||
La aplicare: `UPDATE VANZARI SET ID_COMANDA = <comanda>` + populeaza
|
||
`orders.factura_*` in SQLite, exact ca aplicatia (`update_order_invoice`).
|
||
|
||
### ⚠ Verificare reziduu de linie (legatura header nu e mereu suficienta)
|
||
|
||
Aplicatia / dashboard-ul marcheaza o comanda „Facturat" doar dupa **legatura header**
|
||
(`VANZARI.ID_COMANDA`). **ROA insa verifica la nivel de linie**: in
|
||
`PACK_FACTURARE.cursor_comanda`, cantitatea facturata se potriveste cu comanda pe
|
||
**`ID_ARTICOL` + `PRET` exact**, iar o linie e „de facturat" cand
|
||
`SIGN(CANTITATE) * (CANTITATE − NVL(facturat,0)) > 0`.
|
||
|
||
Daca factura manuala a reprezentat liniile **altfel** decat comanda — tipic discounturi
|
||
comasate (ex. discounturile pe cote de TVA 11%/21% puse intr-o singura linie la 0% TVA) —
|
||
preturile nu se mai potrivesc, deci ROA arata comanda **tot nefacturata** desi headerul
|
||
e legat si dashboard-ul o vede facturata.
|
||
|
||
Scriptul **prezice** acest reziduu inainte de `--apply` (functia `order_line_residual`,
|
||
simuland factura ce urmeaza a fi legata) si il **re-verifica** dupa legare. Cand exista,
|
||
afiseaza `!! ATENTIE ...` cu liniile reziduale (ART / cantitate comanda / pret / facturat)
|
||
si un contor in rezumat. **Scriptul NU atinge `COMENZI_ELEMENTE`** — aceste cazuri se
|
||
corecteaza **manual in ROA** (aliniezi liniile comenzii la factura, ex. comasezi liniile
|
||
de discount ca in factura, pastrand valoarea totala).
|
||
|
||
## Utilizare
|
||
|
||
Ruleaza **pe serverul de productie VENDING** (are nevoie de Oracle prod +
|
||
`api/data/import.db` prod). Foloseste `app.config.settings` (deci `.env`-ul prod).
|
||
|
||
```bat
|
||
REM din C:\gomag-vending\scripts
|
||
relink_manual_invoices.bat REM dry-run, ultimele 3 zile (NU modifica)
|
||
relink_manual_invoices.bat --apply REM aplica, cu confirmare
|
||
relink_manual_invoices.bat --apply --yes REM aplica fara confirmare
|
||
relink_manual_invoices.bat --days 7 REM alta fereastra
|
||
```
|
||
|
||
Dublu-click pe `.bat` = dry-run. `.bat`-ul seteaza mediul Oracle thick-mode
|
||
(`TNS_ADMIN` + PATH instant client) ca `start.bat`.
|
||
|
||
Direct cu Python (echivalent):
|
||
|
||
```bat
|
||
set TNS_ADMIN=C:\roa\instantclient_11_2_0_2
|
||
set PATH=C:\app\Server\product\18.0.0\dbhomeXE\bin;%PATH%
|
||
C:\gomag-vending\venv\Scripts\python.exe scripts\relink_manual_invoices.py --apply
|
||
```
|
||
|
||
Din containerul de dev, peste SSH:
|
||
|
||
```bash
|
||
scp -P 22122 scripts/relink_manual_invoices.* gomag@79.119.86.134:C:/gomag-vending/scripts/
|
||
ssh -p 22122 gomag@79.119.86.134 'cmd /c "C:\gomag-vending\scripts\relink_manual_invoices.bat --days 3 < nul"'
|
||
```
|
||
|
||
**Workflow recomandat:** intai dry-run → verifica lista `LINK` si `SKIP_AMBIGUOUS`
|
||
→ apoi `--apply`. Cazurile `SKIP_AMBIGUOUS` se rezolva manual in ROA.
|
||
|
||
## Istoric
|
||
|
||
Codifica reconcilierea din **2026-06-26** (pana de curent la VENDING): pool cazut
|
||
~09:07–10:25; 12 facturi manuale `TIP=1` (IDV 138191–138203 → comenzi 5419–5430)
|
||
legate; 3 facturi de depozit corect excluse (CRISS VENDING, COFEE SEVEN TO GO,
|
||
PANDELE MIOARA); 2 parteneri duplicati semnalati (CERBU, MILITARU).
|
||
|
||
**Follow-up 2026-06-26 (reziduu de linie):** 2 din cele 12 comenzi (5419/web 492710430,
|
||
5423/web 492710513) au ramas nefacturate **in ROA** desi headerul era legat — factura
|
||
manuala comasase cele 2 linii de discount (ART 2077, split pe TVA 11%/21%) intr-una la
|
||
0% TVA, deci nu se potriveau pe `ID_ARTICOL+PRET`. Reparate manual prin alinierea
|
||
liniilor comenzii la factura (comasare in `COMENZI_ELEMENTE`, valoare discount pastrata).
|
||
De aici provine verificarea de reziduu de linie adaugata in script.
|
||
|
||
Vezi si: [oracle-schema-notes.md](oracle-schema-notes.md) (tabele `COMENZI`/`VANZARI`),
|
||
sectiunea „Facturi & Cache" din [README](../README.md).
|