feat(data-entry): Bulk Receipt Upload cu Mobile UX Android Nativ

## Funcționalități Principale

### Bulk Upload & Processing
- Drag & drop pentru upload bonuri multiple oriunde pe pagină
- Batch processing cu job queue și worker pool
- Real-time updates via SSE (Server-Sent Events) cu fallback polling
- Duplicate detection via SHA-256 file hash
- Auto-retry pentru job-uri failed
- Cancel individual jobs sau batch complet

### Mobile UX - Android Native Style
- Top bar fixă cu hamburger, titlu centrat, acțiuni (search/filter)
- Bottom navigation cu 4 tab-uri (Bonuri, Upload, Rapoarte, Setări)
- FAB (Floating Action Button) cu hide/show on scroll
- Filter chips orizontal scrollabile
- Selecție multiplă prin long-press (500ms)
- Select All + Bulk Delete cu confirmare
- Layout Android pentru Create/Edit/View bon (Gmail compose style)

### Bug Fixes
- Refresh individual via SSE în loc de refresh total pagină
- Bonurile cu eroare OCR rămân vizibile pentru editare manuală
- Afișare nume fișier original pentru toate bonurile
- Upload stabil pe mobil (fix race condition File API)
- Păstrare ordine bonuri la refresh (nu se reordonează)

### Backend
- SSE endpoint pentru status updates real-time
- Bulk delete endpoint cu partial success
- Auto-cleanup bonuri failed după 7 zile
- Batch model cu tracking complet

### Testing
- E2E tests cu Playwright
- Unit tests pentru bulk upload, auto-create, cleanup

## Commits Squashed: 43 user stories (US-001 → US-043)
## Branch: ralph/bulk-receipt-upload
## Timp dezvoltare: ~3 zile (Ralph autonomous)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-12 08:33:17 +00:00
parent b4a226409c
commit 7b3541403f
53 changed files with 15810 additions and 196 deletions

View File

@@ -0,0 +1,300 @@
# PRD: Bulk Actions & SSE Real-time Status Updates
## Branch de lucru
> **IMPORTANT**: Toate implementările din acest PRD trebuie făcute în branch-ul existent:
> `ralph/bulk-receipt-upload`
---
## 1. Introducere
Pagina de listă bonuri fiscale (`ReceiptsListView.vue`) are deja funcționalitate de selecție multiplă (select all și individual), dar lipsesc acțiunile bulk (în special ștergerea). De asemenea, actualizarea statusului bonurilor la bulk import se face prin polling periodic care reîncarcă toată lista, ceea ce creează o experiență vizuală neplăcută (flicker, pierdere scroll, etc.).
Acest PRD adresează:
1. **Bulk Delete** - posibilitatea de a șterge mai multe bonuri odată
2. **SSE Real-time Updates** - actualizări incrementale ale statusului în loc de refresh total
---
## 2. Obiective
### Obiectiv Principal
- Permiterea ștergerii în masă a bonurilor selectate cu UX fluid
### Obiective Secundare
- Eliminarea refresh-ului total al paginii la actualizarea statusului bonurilor
- Actualizări real-time via SSE pentru starea bonurilor în procesare
- Experiență vizuală smooth fără flicker
### Metrici de Succes
- Ștergerea a 10+ bonuri să dureze <2s
- Nicio reîncărcare completă a listei la update status
- SSE reducă request-urile de polling cu 80%+
---
## 3. User Stories
### US-001: Buton Șterge în Bulk Actions Bar
**Ca** utilizator
**Vreau** văd un buton "Șterge" când am selecții
**Pentru că** vreau pot șterge mai multe bonuri odată
**Acceptance Criteria:**
- [ ] Butonul "Șterge" apare în bulk actions bar când `selectedReceipts.length > 0`
- [ ] Butonul e vizibil lângă "Validează selectate" și "Deselectează" în header inline
- [ ] Butonul are icon `pi-trash` și severity `danger`
- [ ] Bonurile în procesare (pending/processing) NU sunt selectabile (deja implementat - verifică funcționează)
- [ ] npm run typecheck passes
- [ ] Verify in browser: butonul apare doar când există selecții
---
### US-002: Dialog Confirmare Ștergere Bulk
**Ca** utilizator
**Vreau** o confirmare simplă înainte de ștergere
**Pentru că** vreau evit ștergeri accidentale
**Acceptance Criteria:**
- [ ] La click pe "Șterge", apare dialog cu mesaj: "Ești sigur vrei ștergi {N} bonuri?"
- [ ] Dialog-ul are 2 butoane: "Anulează" (secondary) și "Șterge" (danger)
- [ ] Dialog-ul folosește componenta PrimeVue `ConfirmDialog` sau `Dialog`
- [ ] npm run typecheck passes
- [ ] Verify in browser: dialogul apare cu numărul corect de bonuri
---
### US-003: Backend Endpoint Bulk Delete
**Ca** frontend
**Vreau** pot trimite o listă de ID-uri pentru ștergere
**Pentru că** e mai eficient decât request-uri individuale
**Acceptance Criteria:**
- [ ] Endpoint `DELETE /api/data-entry/receipts/bulk` acceptă body: `{ "ids": [1, 2, 3] }`
- [ ] Validează fiecare bon e în status DRAFT și creat de user-ul curent
- [ ] Returnează partial success: `{ "deleted": [1, 2], "failed": [{"id": 3, "error": "..."}] }`
- [ ] Șterge atașamentele și înregistrările contabile asociate
- [ ] Bonurile în procesare nu pot fi șterse - returnează eroare specifică
- [ ] npm run typecheck passes (pentru Python: ruff check)
---
### US-004: Frontend Bulk Delete cu Partial Success
**Ca** utilizator
**Vreau** văd rezultatul ștergerii
**Pentru că** vreau știu dacă toate bonurile au fost șterse
**Acceptance Criteria:**
- [ ] După ștergere, toast arată rezultatul: "X bonuri șterse" sau "X din Y șterse, Z au eșuat"
- [ ] Bonurile șterse dispar instant din listă (fără animație per specificații)
- [ ] Lista se actualizează local (nu re-fetch complet dacă nu e necesar)
- [ ] Stats se actualizează după ștergere
- [ ] Selecția se golește după ștergere
- [ ] npm run typecheck passes
- [ ] Verify in browser: bonurile dispar instant, toast corect
---
### US-005: Navigare la Pagina Anterioară când Lista Devine Goală
**Ca** utilizator
**Vreau** fiu redirecționat automat la pagina anterioară
**Pentru că** nu vreau văd o pagină goală
**Acceptance Criteria:**
- [ ] După bulk delete, dacă pagina curentă devine goală și există pagini anterioare, navighează la pagina anterioară
- [ ] Dacă eram pe pagina 1 și devine goală, afișează empty state
- [ ] npm run typecheck passes
- [ ] Verify in browser: navigare automată funcționează
---
### US-006: Backend SSE Endpoint pentru Status Updates
**Ca** frontend
**Vreau** primesc notificări real-time despre schimbări de status
**Pentru că** vreau actualizez UI-ul fără polling
**Acceptance Criteria:**
- [ ] Endpoint `GET /api/data-entry/receipts/sse/status` returnează Server-Sent Events
- [ ] Conexiunea e persistent și trimite evenimente la schimbări de status
- [ ] Format eveniment: `data: {"receipt_id": 123, "status": "completed", "processing_status": "completed"}`
- [ ] Suport pentru filtrare pe batch_id (query param)
- [ ] Include timeout handling și reconnect hints
- [ ] ruff check passes
---
### US-007: Frontend SSE Client pentru Status Updates
**Ca** frontend
**Vreau** conectez la SSE și actualizez rândurile individual
**Pentru că** vreau actualizări smooth fără reload
**Acceptance Criteria:**
- [ ] Creează serviciu SSE în `src/modules/data-entry/services/sseService.js`
- [ ] Conectare automată când există bonuri în procesare în listă
- [ ] La primire eveniment, actualizează doar rândul afectat (nu toată lista)
- [ ] Deconectare automată când nu mai sunt bonuri în procesare
- [ ] Retry logic cu exponential backoff la disconnect
- [ ] npm run typecheck passes
- [ ] Verify in browser: statusul se actualizează în timp real fără flicker
---
### US-008: Înlocuire Polling cu SSE
**Ca** frontend
**Vreau** folosesc SSE în loc de polling interval
**Pentru că** e mai eficient și mai smooth
**Acceptance Criteria:**
- [ ] Elimină `setInterval` pentru auto-refresh când SSE e activ
- [ ] Fallback la polling dacă SSE nu e disponibil (WebSocket blocked, etc.)
- [ ] Când toate bonurile din batch sunt procesate, închide conexiunea SSE
- [ ] Logging pentru debug (console.log la conectare/deconectare/evenimente)
- [ ] npm run typecheck passes
- [ ] Verify in browser: consola arată evenimente SSE, nu polling requests
---
### US-009: Update Row Individual fără Re-render Lista
**Ca** frontend
**Vreau** actualizez un singur rând fără re-renderez toată lista
**Pentru că** vreau performanță și stabilitate vizuală
**Acceptance Criteria:**
- [ ] Creează metodă `updateReceiptInPlace(receiptId, updates)` în store
- [ ] Metoda modifică doar obiectul specific din array, nu înlocuiește array-ul
- [ ] Vue reactivity detectează schimbarea și actualizează doar rândul afectat
- [ ] Stats se actualizează separat (poate necesita re-fetch stats)
- [ ] npm run typecheck passes
- [ ] Verify in browser: DevTools Performance - doar un rând se re-renderează
---
### US-010: Graceful Degradation la SSE Failure
**Ca** utilizator
**Vreau** ca aplicația funcționeze și fără SSE
**Pentru că** pot avea probleme de rețea sau proxy
**Acceptance Criteria:**
- [ ] Dacă SSE fail la connect, fallback la polling clasic (interval 5s)
- [ ] Mesaj discret în consolă (nu toast pentru user)
- [ ] Retry SSE periodic (la 30s) pentru a vedea dacă funcționează din nou
- [ ] npm run typecheck passes
- [ ] Verify in browser: deconectează SSE, verifică polling-ul preia
---
## 4. Cerințe Funcționale
1. [REQ-001] Butonul "Șterge" trebuie fie roșu (severity danger) cu icon trash
2. [REQ-002] Bonurile cu `processing_status` în `pending` sau `processing` nu pot fi șterse
3. [REQ-003] Ștergerea bulk trebuie șteargă și atașamentele și înregistrările contabile asociate
4. [REQ-004] SSE trebuie trimită evenimente pentru: status change, processing_status change
5. [REQ-005] Frontend trebuie mențină conexiunea SSE doar când există bonuri în procesare
6. [REQ-006] La ștergerea ultimului bon de pe pagină, navighează automat la pagina anterioară
---
## 5. Non-Goals (Ce NU facem)
- Bulk edit (modificare date pe mai multe bonuri) - poate fi un alt PRD
- Animații fade/slide la ștergere - utilizatorul a cerut instant remove
- Undo pentru ștergere - nu e în scope
- WebSocket în loc de SSE - SSE e mai simplu și suficient
- Notificări push/toast pentru statusuri - doar actualizare în tabel
---
## 6. Considerații Tehnice
### Stack/Tehnologii
- **Backend**: FastAPI cu SSE support (StreamingResponse)
- **Frontend**: Vue 3 Composition API, Pinia store
- **SSE**: Native EventSource API (bun suport browser)
### Patterns de Urmat
- Service pattern în backend (nu logică în router)
- Store actions în frontend pentru mutații
- `@cached` decorator DOAR pentru reads, nu pentru deletes
### Dependențe
- PrimeVue ConfirmDialog sau Dialog pentru confirmare
- FastAPI `StreamingResponse` pentru SSE
- Deja există: bulk actions bar, selection logic, checkbox disable pentru processing
### Riscuri Tehnice
- SSE poate fi blocat de proxy/firewall - mitigat cu fallback la polling
- Concurrency la delete bulk - poate conflicta cu procesare - mitigat cu check status
### Structură Fișiere Noi
```
backend/modules/data_entry/
├── routers/
│ └── receipts.py # Adaugă DELETE /bulk și GET /sse/status
├── services/
│ └── sse_service.py # NOU: SSE broadcaster
└── schemas/
└── receipt.py # Adaugă BulkDeleteRequest, BulkDeleteResponse
src/modules/data-entry/
├── services/
│ └── sseService.js # NOU: SSE client
├── stores/
│ └── receiptsStore.js # Adaugă updateReceiptInPlace
└── views/receipts/
└── ReceiptsListView.vue # Adaugă buton Șterge, integrare SSE
```
---
## 7. Considerații UI/UX
### Layout
- Butonul Șterge: în bulk actions bar, header inline, lângă celelalte butoane
- Dialog confirmare: centered modal, compact
### Stări
- **Loading delete**: butonul Șterge arată spinner
- **Success**: toast verde "X bonuri șterse"
- **Partial fail**: toast warning "X din Y șterse, Z au eșuat"
- **SSE connected**: indicator vizual opțional (poate în devtools)
### Accesibilitate
- Dialog accesibil (focus trap, escape to close)
- Buton cu label clear
---
## 8. Success Metrics
- Bulk delete 10 bonuri: < 2 secunde end-to-end
- Zero full page refreshes la status updates
- SSE latency: < 500ms de la schimbare status la UI update
- Fallback polling activat în < 5s dacă SSE eșuează
---
## 9. Open Questions
- [x] Confirmare: simplă cu count
- [x] Bonuri în procesare: exclude automat
- [x] Polling: SSE
- [x] Error handling: partial success
- [x] Bara acțiuni: header inline
- [x] Animații: instant remove
- [x] Pagină goală: du la pagina anterioară
---
## 10. Ordine Implementare Recomandată
1. **US-003** - Backend bulk delete (fundație)
2. **US-001** - Buton Șterge în UI
3. **US-002** - Dialog confirmare
4. **US-004** - Frontend bulk delete cu toast
5. **US-005** - Navigare pagină anterioară
6. **US-009** - Update row in place (pregătire pentru SSE)
7. **US-006** - Backend SSE endpoint
8. **US-007** - Frontend SSE client
9. **US-008** - Înlocuire polling cu SSE
10. **US-010** - Graceful degradation

View File

@@ -0,0 +1,406 @@
# PRD: Bulk Receipt Upload & Auto-Processing
## 1. Introducere
Sistemul actual permite upload-ul și procesarea unui singur bon la un moment dat, cu intervenție manuală la editare/salvare. Această funcționalitate adaugă o pagină separată pentru upload multiple bonuri (PDF/PNG/JPG) care sunt procesate automat prin OCR și salvate direct în baza de date, fără intervenție manuală.
**Context tehnic:** Există deja infrastructura de job queue (SQLite) și worker pool pentru procesare paralelă OCR. Această funcționalitate va extinde sistemul existent.
## 2. Obiective
### Obiectiv Principal
- Permiterea upload-ului bulk de bonuri (10-50 fișiere) cu procesare automată end-to-end
### Obiective Secundare
- Reducerea timpului de introducere date cu 90%+ pentru batch-uri mari
- Vizibilitate în timp real asupra progresului procesării
- Separare clară între flow-ul manual (editare) și automat (bulk)
### Metrici de Succes
- Timp mediu per bon < 10 secunde (vs. 2-3 minute manual)
- Rata de succes OCR > 80% (bonuri procesate fără erori)
- Upload batch 50 bonuri în < 10 minute
## 3. User Stories
### US-001: Upload Multiple Fișiere
**Ca** utilizator data-entry
**Vreau** selectez/trag multiple fișiere (PDF/PNG/JPG) într-o zonă de upload
**Pentru că** vreau procesez un lot întreg de bonuri dintr-o dată
**Acceptance Criteria:**
- [ ] Drag & drop zone acceptă multiple fișiere simultan
- [ ] Click pe zonă deschide file picker cu multi-select activat
- [ ] Fișierele acceptate: PDF, PNG, JPG (max 10MB/fișier)
- [ ] Fișierele invalide sunt ignorate cu mesaj de avertizare
- [ ] Lista fișierelor selectate apare sub zona de upload
- [ ] Buton "Șterge" per fișier pentru eliminare din batch
- [ ] npm run typecheck passes
- [ ] Verify in browser that files appear in list after selection
- [ ] **CSS:** Drop zone folosește `var(--surface-card)` background, `var(--surface-border)` border
- [ ] **CSS:** Drop zone hover/active folosește `var(--blue-50)` background
- [ ] **CSS:** Spacing între elemente folosește tokens (`--space-md`, `--space-lg`)
- [ ] **CSS:** Testează în dark mode - drop zone vizibilă și contrastantă
### US-002: Vizualizare Batch Înainte de Submit
**Ca** utilizator
**Vreau** văd lista fișierelor selectate cu preview
**Pentru că** vreau verific am selectat fișierele corecte înainte de procesare
**Acceptance Criteria:**
- [ ] Lista arată: thumbnail (pentru imagini), nume fișier, mărime
- [ ] Pentru PDF-uri se arată icon generic PDF
- [ ] Counter total: "X fișiere selectate (Y MB)"
- [ ] Buton "Golește lista" pentru resetare completă
- [ ] Buton "Adaugă fișiere" pentru a adăuga la selecție existentă
- [ ] npm run typecheck passes
- [ ] Verify in browser that thumbnails render correctly
- [ ] **CSS:** Lista folosește pattern `.card` din `cards.css`
- [ ] **CSS:** Thumbnail cu `border-radius: var(--radius-md)`
- [ ] **CSS:** File size text cu `color: var(--color-text-secondary)`, `font-size: var(--text-sm)`
- [ ] **CSS:** Butoane folosesc clasele `.btn .btn-secondary` și `.btn .btn-primary`
- [ ] **CSS:** Testează în dark mode - thumbnails și text lizibile
### US-003: Submit Batch pentru Procesare
**Ca** utilizator
**Vreau** trimit toate fișierele pentru procesare cu un singur click
**Pentru că** vreau declanșez procesarea automată a întregului lot
**Acceptance Criteria:**
- [ ] Buton "Procesează X bonuri" submit-ează batch-ul
- [ ] La submit, toate fișierele se uploadează și se creează câte un OCR job per fișier
- [ ] După submit, UI-ul trece în modul "progres" (nu mai permite adăugare fișiere)
- [ ] Dacă un upload individual eșuează (network error), se reîncearcă automat (max 3 retry)
- [ ] npm run typecheck passes
- [ ] Verify in browser that submit disables file addition
### US-004: Progres Real-Time per Fișier
**Ca** utilizator
**Vreau** văd progresul fiecărui fișier în timp real
**Pentru că** vreau știu câte bonuri s-au procesat și câte mai sunt în așteptare
**Acceptance Criteria:**
- [ ] Fiecare fișier din listă arată status: "În așteptare" / "Se procesează..." / "Completat" / "Eroare"
- [ ] Status vizual diferențiat: badge/icon color-coded (gri/albastru/verde/roșu)
- [ ] La completare se arată confidence score overall (ex: "87% confidence")
- [ ] Progress bar global: "15/50 procesate"
- [ ] Timpul estimat rămas bazat pe average processing time
- [ ] npm run typecheck passes
- [ ] Verify in browser that status updates in real-time without page refresh
- [ ] **CSS:** Status badges folosesc culorile din tabel (vezi secțiunea 6):
- Pending: `background: var(--surface-hover)`, `color: var(--color-text-secondary)`
- Processing: `background: var(--blue-50)`, `color: var(--blue-600)` + spinner
- Success: `background: var(--green-50)`, `color: var(--green-600)`
- Error: `background: var(--red-50)`, `color: var(--red-600)`
- [ ] **CSS:** Progress bar folosește PrimeVue ProgressBar (stilizat global)
- [ ] **CSS:** Confidence score cu `font-family: var(--font-mono)` pentru numere
- [ ] **CSS:** Testează în dark mode - badges vizibile și contrastate
### US-005: Salvare Automată a Bonurilor Procesate
**Ca** sistem
**Vreau** salvez automat bonurile procesate cu succes în baza de date
**Pentru că** utilizatorul dorește procesare 100% automată fără intervenție
**Acceptance Criteria:**
- [ ] După OCR completat cu succes, se creează automat un receipt în DB
- [ ] Receipt-ul primește status "DRAFT" (poate fi editat ulterior dacă e nevoie)
- [ ] Se atașează automat fișierul original la receipt
- [ ] Se salvează toate câmpurile extrase: vendor, CUI, dată, sumă, TVA
- [ ] Se generează automat accounting entries (ca la flow-ul manual)
- [ ] npm run typecheck passes
- [ ] Verify that receipts appear in receipt list after bulk processing
### US-006: Gestionare Erori OCR
**Ca** utilizator
**Vreau** ca bonurile cu erori OCR fie marcate pentru review manual
**Pentru că** vreau procesez restul batch-ului fără blocaj, dar nu pierd bonurile problematice
**Acceptance Criteria:**
- [ ] La eroare OCR, fișierul primește status "Eroare" cu mesaj explicativ
- [ ] Bonurile cu erori rămân în lista vizibilă, nu sunt șterse
- [ ] Buton "Deschide în editor" pentru fiecare bon cu eroare (redirect la pagina de editare manuală)
- [ ] Procesarea celorlalte bonuri continuă independent
- [ ] La final se arată sumar: "45 procesate cu succes, 5 cu erori"
- [ ] npm run typecheck passes
- [ ] Verify in browser that error files show retry/edit options
- [ ] **CSS:** Error row cu `background: var(--red-50)`, `border-left: 3px solid var(--red-500)`
- [ ] **CSS:** Error message cu `color: var(--red-600)`, `font-size: var(--text-sm)`
- [ ] **CSS:** Action buttons în error row folosesc `.btn .btn-sm` pattern
- [ ] **CSS:** Testează în dark mode - erori vizibile dar nu agresive
### US-007: Rezumat Final Batch
**Ca** utilizator
**Vreau** văd un rezumat la finalul procesării batch-ului
**Pentru că** vreau știu câte bonuri s-au salvat și ce acțiuni mai am de făcut
**Acceptance Criteria:**
- [ ] Modal/panel de rezumat cu statistici: procesate OK, erori, total sumă
- [ ] Link direct la lista de receipts filtrat pe batch-ul curent (by date/user)
- [ ] Opțiune "Încarcă alt batch" pentru a începe de la zero
- [ ] Opțiune "Vezi bonurile cu erori" pentru review rapid
- [ ] npm run typecheck passes
- [ ] Verify in browser that summary modal shows correct counts
- [ ] **CSS:** Modal folosește PrimeVue Dialog (stilizat global) sau pattern `.card`
- [ ] **CSS:** Statistici success cu `color: var(--green-600)`, errors cu `color: var(--red-600)`
- [ ] **CSS:** Total sumă cu `font-size: var(--text-2xl)`, `font-weight: var(--font-bold)`, `font-family: var(--font-mono)`
- [ ] **CSS:** Spacing consistent: `padding: var(--space-lg)`, `gap: var(--space-md)`
- [ ] **CSS:** Testează în dark mode - modal și conținut lizibile
### US-008: Backend - Endpoint Batch Upload
**Ca** developer
**Vreau** un endpoint optimizat pentru upload multiple fișiere
**Pentru că** upload-ul secvențial ar fi prea lent pentru 50+ fișiere
**Acceptance Criteria:**
- [ ] POST `/api/data-entry/bulk/upload` acceptă multipart cu multiple fișiere
- [ ] Returnează lista de job_id-uri pentru tracking
- [ ] Validare: max 100 fișiere per batch, max 10MB per fișier
- [ ] Jobs se creează atomic (toate sau niciunul)
- [ ] Returnează și un batch_id pentru grouping
- [ ] pytest tests pass
- [ ] API returns correct response schema
### US-009: Backend - Auto-Save Receipt din OCR Result
**Ca** developer
**Vreau** un service care creează automat receipt-uri din rezultatele OCR
**Pentru că** flow-ul bulk trebuie fie end-to-end fără intervenție UI
**Acceptance Criteria:**
- [ ] `ReceiptAutoCreateService.create_from_ocr_result(job_id, ocr_result, user)`
- [ ] Mapare completă OCR fields Receipt fields
- [ ] Creare attachment cu fișierul original
- [ ] Generare accounting entries via existing logic
- [ ] Validare minimă: suma > 0, dată validă
- [ ] Return receipt_id sau error message
- [ ] pytest tests pass
### US-010: Backend - Batch Status Endpoint
**Ca** developer
**Vreau** un endpoint pentru status-ul întregului batch
**Pentru că** frontend-ul trebuie să poll-eze eficient pentru toate fișierele
**Acceptance Criteria:**
- [ ] GET `/api/data-entry/bulk/batches/{batch_id}/status`
- [ ] Returnează status agregat: pending_count, processing_count, completed_count, failed_count
- [ ] Include lista de job_id + status pentru fiecare fișier
- [ ] Include receipt_id pentru jobs completate cu succes
- [ ] Suportă long-polling (wait param) pentru eficiență
- [ ] pytest tests pass
## 4. Cerințe Funcționale
1. [REQ-001] Sistemul trebuie să accepte upload simultan de până la 100 fișiere
2. [REQ-002] Fiecare fișier trebuie să fie max 10MB
3. [REQ-003] Formatele acceptate: PDF, PNG, JPG, JPEG
4. [REQ-004] Procesarea trebuie să fie paralelă (max N workers din config)
5. [REQ-005] Bonurile procesate cu succes se salvează automat cu status DRAFT
6. [REQ-006] Bonurile cu erori rămân disponibile pentru retry/editare manuală
7. [REQ-007] Fișierele originale se atașează automat la receipt-uri
8. [REQ-008] Se generează automat accounting entries pentru fiecare receipt
9. [REQ-009] Batch-urile trebuie să fie tracked per user (nu se văd batch-urile altora)
10. [REQ-010] Job files se curăță automat după 24h (cleanup existing)
## 5. Non-Goals (Ce NU facem)
- **NU** facem aprobare automată (bonurile rămân DRAFT, nu APPROVED)
- **NU** facem machine learning pentru îmbunătățirea OCR-ului
- **NU** facem procesare pe server extern (totul rămâne local)
- **NU** facem notificări (email/push) la finalizare batch
- **NU** facem preview/editare în bulk page - pentru asta există pagina individuală
- **NU** facem undo/rollback batch (bonurile create pot fi șterse individual)
- **NU** facem scheduling (procesare imediată, nu amânată)
- **NU** facem duplicate detection (poate fi adăugat ulterior)
## 6. Considerații Tehnice
### Stack/Tehnologii
- **Frontend:** Vue 3 Composition API, PrimeVue (FileUpload, ProgressBar, DataTable)
- **Backend:** FastAPI, SQLite (job queue existent), SQLModel (receipts)
- **State:** Pinia store pentru batch progress tracking
### Patterns de Urmat
- Folosește `OCRJobQueue` existent pentru job management
- Extinde `job_worker.py` pentru auto-save la completare
- Folosește pattern-ul de polling din `OCRUploadZone.vue` existent
### ⚠️ REGULI CSS OBLIGATORII
**CITEȘTE ÎNTÂI:** `docs/ONBOARDING_CSS.md` și `docs/DESIGN_TOKENS.md`
#### Golden Rules
```
✅ Folosește DOAR design tokens - NICIODATĂ valori hardcodate
✅ Verifică CSS_PATTERNS.md înainte de a scrie CSS nou
✅ Testează în AMBELE teme (light + dark mode)
❌ NICIODATĂ :deep() în componente (PrimeVue → vendor/)
❌ NICIODATĂ duplicate CSS (write once, use everywhere)
```
#### Design Tokens Obligatorii
| Categorie | ❌ GREȘIT | ✅ CORECT |
|-----------|-----------|-----------|
| Spacing | `padding: 8px` | `padding: var(--space-sm)` |
| Spacing | `margin: 16px` | `margin: var(--space-md)` |
| Spacing | `gap: 24px` | `gap: var(--space-lg)` |
| Font size | `font-size: 14px` | `font-size: var(--text-sm)` |
| Font weight | `font-weight: 500` | `font-weight: var(--font-medium)` |
| Font weight | `font-weight: 600` | `font-weight: var(--font-semibold)` |
| Colors | `color: #111827` | `color: var(--color-text)` |
| Colors | `color: #6b7280` | `color: var(--color-text-secondary)` |
| Colors | `background: #ffffff` | `background: var(--surface-card)` |
| Colors | `background: #f0fdf4` | `background: var(--green-50)` |
| Colors | `border: #e5e7eb` | `border-color: var(--surface-border)` |
| Radius | `border-radius: 8px` | `border-radius: var(--radius-md)` |
| Shadow | `box-shadow: 0 4px 6px...` | `box-shadow: var(--shadow-md)` |
| Transition | `transition: 0.2s` | `transition: var(--transition-fast)` |
#### Spacing Scale Reference
| Token | Value | Use Case |
|-------|-------|----------|
| `--space-xs` | 4px | Icon gaps, badges |
| `--space-sm` | 8px | Between related items |
| `--space-md` | 16px | Component padding |
| `--space-lg` | 24px | Section padding, cards |
| `--space-xl` | 32px | Page margins |
#### Status Colors (pentru progres/erori)
| Status | Background | Text/Icon |
|--------|------------|-----------|
| Pending | `var(--surface-hover)` | `var(--color-text-secondary)` |
| Processing | `var(--blue-50)` | `var(--blue-600)` |
| Success | `var(--green-50)` | `var(--green-600)` |
| Error | `var(--red-50)` | `var(--red-600)` |
| Warning | `var(--yellow-50)` | `var(--yellow-600)` |
#### Dark Mode - OBLIGATORIU
- Folosește `--surface-*` tokens pentru backgrounds (auto-switch în dark mode)
- Testează cu theme toggle din header (auto → light → dark)
- NU folosi culori hardcodate care nu se schimbă în dark mode
#### Patterns Existente de Folosit
| Pattern | File | Use Case |
|---------|------|----------|
| `.card` | `cards.css` | Container principal |
| `.btn`, `.btn-primary` | `buttons.css` | Butoane |
| `.form-group`, `.form-label` | `forms.css` | Formulare |
| `.spinner` | `spinners.css` | Loading states |
| `.trend`, `.trend-up` | `trends.css` | Indicators |
| Utility classes | `utilities/` | `gap-md`, `text-center`, etc. |
#### PrimeVue Components
- FileUpload, ProgressBar, DataTable, Tag, Badge - toate sunt stilizate global
- NU adăuga `:deep()` în componente
- Modificări PrimeVue → `src/assets/css/vendor/primevue-overrides.css`
### Dependențe
- Job Queue existent: `backend/modules/data_entry/services/ocr/job_queue.py`
- Worker Pool existent: `backend/modules/data_entry/services/ocr/ocr_worker_pool.py`
- Receipt CRUD: `backend/modules/data_entry/db/crud/receipt.py`
- Attachment CRUD: `backend/modules/data_entry/db/crud/attachment.py`
### Riscuri Tehnice
- **Memory pressure:** Upload simultan de 100 fișiere × 10MB = 1GB potențial
- Mitigare: Upload secvențial intern, buffer 5 fișiere max în memorie
- **Queue overflow:** 100 jobs noi pot încetini procesarea existentă
- Mitigare: Worker pool deja limitează concurența
- **Browser crash:** Tab închis pierde tracking progress
- Mitigare: Jobs persistă în DB, refresh poate recupera status
## 7. Considerații UI/UX
### Layout
1. **Header:** Titlu "Upload Bulk Bonuri" + link înapoi la lista principală
2. **Drop Zone:** Mare, centrată, cu icon și text instructiv
3. **File List:** Tabel/listă sub drop zone cu progres per fișier
4. **Actions Bar:** Butoane "Procesează", "Golește lista" - sticky la bottom
### Layout CSS Structure
```
.bulk-upload-page
├── .page-header (pattern existent)
│ └── h1 + breadcrumb
├── .card (drop zone container)
│ └── .upload-zone (dashed border, centered)
├── .card (file list container)
│ └── DataTable sau custom list
└── .form-actions (sticky footer cu butoane)
```
### Stări UI cu CSS
| Stare | Background | Border | Elements |
|-------|------------|--------|----------|
| **Empty** | `var(--surface-card)` | `2px dashed var(--surface-border)` | Icon mare + text instructiv |
| **Drag Over** | `var(--blue-50)` | `2px dashed var(--blue-500)` | Border evidențiat |
| **Files Selected** | `var(--surface-card)` | `1px solid var(--surface-border)` | Lista + action buttons |
| **Processing** | `var(--surface-card)` | - | Spinner global + per-file status |
| **Complete** | `var(--green-50)` subtle | - | Summary card |
| **Has Errors** | - | - | Error items highlighted |
### Status Badge Styles
```css
/* Folosește PrimeVue Tag sau custom badges */
.status-pending { background: var(--surface-hover); color: var(--color-text-secondary); }
.status-processing { background: var(--blue-50); color: var(--blue-600); }
.status-success { background: var(--green-50); color: var(--green-600); }
.status-error { background: var(--red-50); color: var(--red-600); }
```
### Accesibilitate
- Keyboard navigation pentru file list
- Screen reader announcements la status changes
- Focus management la modal rezumat
- WCAG contrast ratios respectate (toate token-urile sunt compliant)
## 8. Success Metrics
- **Upload Success Rate:** > 99% (fișierele ajung în queue)
- **OCR Success Rate:** > 80% (bonuri procesate fără erori)
- **Average Processing Time:** < 8 secunde/bon
- **User Satisfaction:** Reducere timp introducere date cu 90%
## 9. Open Questions
- [ ] Limita de 100 fișiere este suficientă sau trebuie mărită?
- [ ] Dorim afișăm preview al datelor extrase înainte de save (ar contrazice "100% automat")?
- [ ] Ce facem cu bonurile duplicate detectate ulterior (același număr bon + dată)?
- [ ] Trebuie un "dry run" mode care procesează dar nu salvează?
---
## 10. Dependențe între User Stories
```
US-001 (Upload Files)
US-002 (Preview List) ← independent
US-003 (Submit Batch) → US-008 (Backend Upload Endpoint)
US-004 (Progress) ← US-010 (Backend Status Endpoint)
US-005 (Auto-Save) ← US-009 (Backend Auto-Create Service)
US-006 (Error Handling)
US-007 (Summary)
```
**Ordine recomandată de implementare:**
1. US-008: Backend - Batch Upload Endpoint
2. US-010: Backend - Batch Status Endpoint
3. US-009: Backend - Auto-Save Service
4. US-001: Frontend - Upload Zone
5. US-002: Frontend - File Preview
6. US-003: Frontend - Submit Batch
7. US-004: Frontend - Progress Tracking
8. US-005: Integration - Auto-Save Flow
9. US-006: Frontend - Error Handling
10. US-007: Frontend - Summary Modal
---
**Last Updated:** 2026-01-09
**Author:** Claude Code
**Status:** Draft - Pending Review

View File

@@ -0,0 +1,499 @@
# PRD: Bulk Upload Integration in Receipts List
## 1. Introducere
Funcționalitatea actuală de bulk upload există într-o pagină separată (`BulkUploadView.vue`). Când un fișier dă eroare la procesare, utilizatorul nu îl mai regăsește - acesta "dispare" din UI. Această funcționalitate integrează bulk upload direct în lista principală de bonuri, permițând vizibilitatea completă a tuturor bonurilor: în curs de upload, în procesare, procesate cu succes și cu erori.
**Problema de rezolvat:** Bonurile cu erori din bulk upload se pierd și nu mai pot fi regăsite. Utilizatorul nu are vizibilitate asupra batch-ului după ce părăsește pagina de bulk upload.
**Soluția:** Integrare completă în lista de bonuri cu:
- Row grouping vizual per batch
- Coloană pentru batch ID și status procesare
- Mesaje de eroare vizibile în listă
- Drag & drop overlay pe toată pagina
- Quick filter chips pentru statusuri de procesare
## 2. Obiective
### Obiectiv Principal
- Vizibilitate completă a tuturor bonurilor din bulk upload direct în lista principală
### Obiective Secundare
- Eliminarea paginii separate de bulk upload (consolidare UX)
- Recuperarea bonurilor cu erori fără a le pierde
- Persistență: bonurile rămân vizibile chiar și după refresh/revenire
### Metrici de Succes
- 0 bonuri "pierdute" după erori OCR
- 100% tracking vizibil pentru toate batch-urile
- Timp de onboarding pentru bulk upload < 30 secunde
## 3. User Stories
### US-001: Drag Anywhere pentru Upload
**Ca** utilizator data-entry
**Vreau** pot trage fișiere oriunde pe pagina de bonuri
**Pentru că** vreau un flow natural fără caut zona specifică de upload
**Acceptance Criteria:**
- [ ] Tragerea unui fișier oriunde pe pagina de bonuri activează un overlay fullscreen
- [ ] Overlay-ul arată: icon upload + text "Eliberează pentru a încărca X fișiere"
- [ ] La eliberare, fișierele se validează și se uploadează
- [ ] Fișierele invalide se afișează în toast cu motivul
- [ ] Overlay dispare dacă utilizatorul trage fișierele în afara paginii
- [ ] npm run typecheck passes
- [ ] Verify in browser that overlay appears on drag
**Detalii tehnice:**
- Global event listeners pe `window`: `dragenter`, `dragover`, `dragleave`, `drop`
- Cleanup listeners în `onUnmounted` pentru a evita memory leaks
- `e.preventDefault()` pe toate evenimentele pentru a preveni deschiderea fișierelor în browser
### US-002: Row Grouping per Batch în DataTable
**Ca** utilizator
**Vreau** văd bonurile din același batch grupate vizual
**Pentru că** vreau identific rapid care bonuri aparțin aceluiași upload
**Acceptance Criteria:**
- [ ] Bonurile cu același `batch_id` apar grupate în tabel
- [ ] Header de grup expandabil: "Batch B-001 12 Jan 2026 15 fișiere"
- [ ] Click pe header expandează/colapsează grupul
- [ ] Bonurile fără batch (create manual) apar în grupul "Alte bonuri"
- [ ] Grupurile sunt sortate după timestamp (cel mai recent sus)
- [ ] Grupul în procesare este automat expandat
- [ ] npm run typecheck passes
- [ ] Verify in browser that groups expand/collapse correctly
**Detalii tehnice:**
- PrimeVue DataTable cu `rowGroupMode="subheader"` și `groupRowsBy="batch_id"`
- Custom header slot pentru styling
- State local pentru expanded groups
### US-003: Coloană Status Batch în Tabel
**Ca** utilizator
**Vreau** văd statusul fiecărui bon din batch într-o coloană dedicată
**Pentru că** vreau știu rapid care bonuri au reușit și care au erori
**Acceptance Criteria:**
- [ ] Coloană nouă "Procesare" după coloana "Status" existentă
- [ ] Valori posibile:
- `pending` - "În așteptare" (gri)
- `processing` - "Se procesează..." + spinner (albastru)
- `completed` - "✓ Procesat" (verde)
- `failed` - "✗ Eroare" (roșu) cu expand pentru mesaj
- [ ] Bonurile manuale (fără batch) arată "-" în această coloană
- [ ] Click pe "✗ Eroare" deschide tooltip/popover cu mesajul complet
- [ ] npm run typecheck passes
- [ ] Verify in browser that status updates in real-time
**CSS Design Tokens:**
```css
.processing-pending { background: var(--surface-hover); color: var(--text-color-secondary); }
.processing-active { background: var(--blue-50); color: var(--blue-600); }
.processing-success { background: var(--green-50); color: var(--green-600); }
.processing-failed { background: var(--red-50); color: var(--red-600); }
```
### US-004: Mesaj Eroare Vizibil în Listă
**Ca** utilizator
**Vreau** văd mesajul de eroare direct în listă (prescurtat)
**Pentru că** vreau înțeleg problema fără deschid detalii
**Acceptance Criteria:**
- [ ] Pentru bonuri cu `status=failed`, se afișează primele 50 caractere din mesaj
- [ ] Mesajul e trunchiat cu "..." dacă depășește 50 caractere
- [ ] Hover/click arată mesajul complet într-un tooltip
- [ ] Mesajul e afișat sub rândul principal (inline expand) sau în popover
- [ ] npm run typecheck passes
- [ ] Verify in browser that full error message is accessible
**Exemple de mesaje:**
- "OCR failed: format nerecunoscut pentru bon"
- "Duplicate: bon similar existent (ID: 123)"
- "Validare: suma totală nu poate fi 0"
### US-005: Quick Filter Chips pentru Statusuri Procesare
**Ca** utilizator
**Vreau** filtre rapide pentru a vedea doar bonurile cu erori sau în procesare
**Pentru că** vreau concentrez pe ce necesită atenție
**Acceptance Criteria:**
- [ ] Chips noi în rândul de statusuri existente:
- "În procesare (3)" - bonuri cu processing_status='processing' sau 'pending'
- "Cu erori (2)" - bonuri cu processing_status='failed'
- [ ] Chips apar doar când există batch-uri active (count > 0)
- [ ] Click pe chip filtrează lista
- [ ] Chips sunt colorat conform statusului (albastru/roșu)
- [ ] npm run typecheck passes
- [ ] Verify in browser that filtering works correctly
### US-006: Retry Individual și Retry All Failed
**Ca** utilizator
**Vreau** să pot re-procesa bonurile cu erori
**Pentru că** unele erori pot fi temporare sau vreau să încerc din nou
**Acceptance Criteria:**
- [ ] Buton "Reîncercă" pe fiecare rând cu eroare
- [ ] Buton "Reîncercă toate erorile" în header-ul grupului de batch
- [ ] La retry, statusul revine la "pending" și se re-uploadează fișierul
- [ ] Retry păstrează batch_id original
- [ ] Dacă fișierul original nu mai există, se afișează eroare
- [ ] npm run typecheck passes
- [ ] Verify in browser that retry updates status correctly
**Detalii tehnice:**
- Fișierele originale trebuie păstrate în storage până la cleanup (7 zile)
- Retry apelează același endpoint de upload cu job_id existent
### US-007: Reject Automat pentru Duplicate (File Hash)
**Ca** sistem
**Vreau** să detectez și să reject fișierele duplicate la upload
**Pentru că** utilizatorul nu trebuie să proceseze același bon de două ori
**Acceptance Criteria:**
- [ ] La upload, se calculează SHA-256 hash al fișierului
- [ ] Dacă hash-ul există deja în DB, upload-ul e respins
- [ ] Mesaj: "Fișier duplicat - există deja ca bon #123"
- [ ] Link direct către bonul existent
- [ ] Verificarea se face înainte de a crea job-ul OCR
- [ ] pytest tests pass
- [ ] API returns correct error schema for duplicates
### US-008: Auto-Cleanup Erori După 7 Zile
**Ca** sistem
**Vreau** să șterg automat bonurile cu erori după 7 zile
**Pentru că** vreau să păstrez lista curată fără intervenție manuală
**Acceptance Criteria:**
- [ ] Background job zilnic verifică bonurile cu `processing_status='failed'`
- [ ] Bonurile mai vechi de 7 zile se șterg automat
- [ ] Fișierele atașate se șterg și ele
- [ ] Notificare toast la login: "3 bonuri cu erori au fost șterse (expirate)"
- [ ] Utilizatorul poate vedea/extinde TTL în setări (opțional, nice-to-have)
- [ ] pytest tests pass
### US-009: Auto-Resume Polling la Refresh/Revenire
**Ca** utilizator
**Vreau** ca procesarea să continue și să văd statusul actualizat automat când revin
**Pentru că** nu vreau să pierd progresul sau să fac acțiuni manuale
**Acceptance Criteria:**
- [ ] Procesarea OCR pe backend CONTINUĂ indiferent de starea browser-ului
- [ ] La refresh/revenire, frontend detectează batch-uri incomplete și reia polling automat
- [ ] Starea se stochează în localStorage: `active_batch_ids`
- [ ] La completare, se curăță din localStorage
- [ ] Dacă utilizatorul revine după ce procesarea s-a terminat, vede statusul final corect
- [ ] npm run typecheck passes
- [ ] Verify in browser that progress resumes after refresh
**Detalii tehnice:**
- Backend worker pool procesează independent de frontend
- La `onMounted`, verifică localStorage pentru batch-uri active
- Query backend pentru status curent al fiecărui batch
- Reia polling doar pentru batch-uri care încă nu sunt complete
- Afișează toast: "Procesare în curs detectată, se actualizează..."
### US-010: Lock Row în Procesare (Read-Only)
**Ca** utilizator
**Vreau** ca bonurile în procesare să fie read-only
**Pentru că** nu are sens să editez un bon care încă nu e complet
**Acceptance Criteria:**
- [ ] Bonuri cu `processing_status='pending'` sau `'processing'` au butoanele dezactivate
- [ ] Visual: row are opacity 0.7 sau border-left albastru
- [ ] Tooltip pe butoane: "Bonul se procesează, vă rugăm așteptați"
- [ ] După completare, row-ul devine interactiv normal
- [ ] npm run typecheck passes
- [ ] Verify in browser that buttons are disabled during processing
### US-011: Backend - Stocare Batch și Processing Status
**Ca** developer
**Vreau** să extind schema Receipt pentru a stoca informații de batch
**Pentru că** am nevoie de persistență pentru tracking
**Acceptance Criteria:**
- [ ] Câmpuri noi în tabelul `receipts`:
- `batch_id` (string, nullable) - UUID batch
- `processing_status` (enum: pending/processing/completed/failed, nullable)
- `processing_error` (text, nullable) - mesaj eroare complet
- `file_hash` (string, nullable) - SHA-256 pentru duplicate detection
- `processing_started_at` (datetime, nullable)
- `processing_completed_at` (datetime, nullable)
- [ ] Index pe `batch_id` pentru query-uri eficiente
- [ ] Index pe `file_hash` pentru duplicate detection
- [ ] Migration reversibilă
- [ ] pytest tests pass
- [ ] Alembic migration works
### US-012: Backend - Endpoint List cu Batch Info
**Ca** developer
**Vreau** să extind endpoint-ul GET /receipts pentru a include info de batch
**Pentru că** frontend-ul are nevoie de toate datele într-un singur request
**Acceptance Criteria:**
- [ ] Response include câmpurile noi pentru fiecare receipt
- [ ] Filtrare pe `processing_status` funcționează
- [ ] Filtrare pe `batch_id` funcționează
- [ ] Sorting pe `processing_started_at` funcționează
- [ ] Include count-uri pentru fiecare processing_status în response (pentru chips)
- [ ] pytest tests pass
### US-013: Eliminare Pagină Separată Bulk Upload
**Ca** developer
**Vreau** să elimin pagina separată de bulk upload
**Pentru că** funcționalitatea e acum integrată în lista principală
**Acceptance Criteria:**
- [ ] Șterge `/data-entry/bulk-upload` route
- [ ] Șterge `BulkUploadView.vue`
- [ ] Redirect de la vechea rută la `/data-entry` (pentru bookmarks)
- [ ] Actualizare meniu/navigație să nu mai arate link-ul separat
- [ ] npm run typecheck passes
- [ ] Verify in browser that old route redirects correctly
## 4. Cerințe Funcționale
1. [REQ-001] Drag & drop pe toată pagina de bonuri activează upload overlay
2. [REQ-002] Bonurile din batch-uri sunt grupate vizual în tabel
3. [REQ-003] Coloană dedicată pentru status procesare (pending/processing/success/failed)
4. [REQ-004] Mesajele de eroare sunt vizibile direct în listă
5. [REQ-005] Fișierele duplicate (același hash) sunt respinse automat
6. [REQ-006] Retry disponibil per bon și per batch pentru erori
7. [REQ-007] Bonurile cu erori se șterg automat după 7 zile
8. [REQ-008] Starea procesării persistă la refresh
9. [REQ-009] Bonurile în procesare sunt read-only (locked)
10. [REQ-010] Procesarea folosește configurația existentă din .env pentru workers paraleli
11. [REQ-011] Procesarea backend continuă independent de browser; polling se reia automat la revenire
## 5. Non-Goals (Ce NU facem)
- **NU** implementăm WebSocket pentru status updates (rămânem pe long-polling existent)
- **NU** adăugăm suport pentru editare inline în listă
- **NU** implementăm preview imagine în row (poate fi adăugat ulterior)
- **NU** facem grouping recursiv (batch în batch)
- **NU** implementăm undo pentru retry
- **NU** adăugăm notificări push/email la completare batch
- **NU** facem drag & drop pentru reordonare (doar upload)
## 6. Considerații Tehnice
### Schema DB Extinsă
```sql
ALTER TABLE receipts ADD COLUMN batch_id VARCHAR(36);
ALTER TABLE receipts ADD COLUMN processing_status VARCHAR(20)
CHECK (processing_status IN ('pending', 'processing', 'completed', 'failed'));
ALTER TABLE receipts ADD COLUMN processing_error TEXT;
ALTER TABLE receipts ADD COLUMN file_hash VARCHAR(64);
ALTER TABLE receipts ADD COLUMN processing_started_at TIMESTAMP;
ALTER TABLE receipts ADD COLUMN processing_completed_at TIMESTAMP;
CREATE INDEX idx_receipts_batch_id ON receipts(batch_id);
CREATE INDEX idx_receipts_file_hash ON receipts(file_hash);
CREATE INDEX idx_receipts_processing_status ON receipts(processing_status);
```
### Component Architecture
```
ReceiptsListView.vue (enhanced)
├── DragDropOverlay.vue (new - fullscreen overlay)
├── BatchGroupHeader.vue (new - expandable group header)
├── ProcessingStatusCell.vue (new - status + error display)
├── QuickFilterChips.vue (enhanced - add processing filters)
└── DataTable (PrimeVue with row grouping)
```
### Store Changes
```javascript
// receiptsStore.js - extended
state: {
// ... existing
processingStats: {
pending_count: 0,
processing_count: 0,
failed_count: 0
},
activeBatchIds: [] // for localStorage persistence
}
// batchProgressStore.js - reuse existing
// Just connect to receiptsStore for updates
```
### ⚠️ REGULI CSS OBLIGATORII
**CITEȘTE ÎNTÂI:** `docs/ONBOARDING_CSS.md` și `docs/DESIGN_TOKENS.md`
#### Processing Status Colors
| Status | Background | Text/Icon | Border |
|--------|------------|-----------|--------|
| Pending | `var(--surface-hover)` | `var(--text-color-secondary)` | - |
| Processing | `var(--blue-50)` | `var(--blue-600)` | `var(--blue-500)` |
| Success | `var(--green-50)` | `var(--green-600)` | - |
| Failed | `var(--red-50)` | `var(--red-600)` | `var(--red-500)` |
#### Drag Overlay
```css
.drag-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.drag-overlay-content {
background: var(--surface-card);
border-radius: var(--radius-lg);
padding: var(--space-xl);
text-align: center;
border: 3px dashed var(--primary-500);
}
```
#### Row Grouping Header
```css
.batch-group-header {
background: var(--surface-ground);
padding: var(--space-sm) var(--space-md);
font-weight: var(--font-semibold);
display: flex;
align-items: center;
gap: var(--space-sm);
cursor: pointer;
}
.batch-group-header:hover {
background: var(--surface-hover);
}
```
### Dependențe
- Refolosește `batchProgressStore.js` existent
- Refolosește componente din `components/bulk/` unde posibil
- Extinde `receiptsStore.js` cu câmpuri noi
### Riscuri Tehnice
- **Row grouping performance:** Pentru liste mari (>500 bonuri), grouping poate fi lent
- Mitigare: Virtualizare sau paginare strictă
- **LocalStorage limits:** Dacă sunt multe batch-uri active
- Mitigare: Stochează doar ID-uri, nu date complete
- **Race condition la retry:** Dacă utilizatorul face retry în timp ce procesarea originală se termină
- Mitigare: Check status înainte de retry, abort dacă deja completat
## 7. Considerații UI/UX
### Layout Update
```
ReceiptsListView.vue
├── PageHeader
├── QuickFilterChips (enhanced: + Processing | Erori)
├── FiltersRow (existing)
├── DragDropOverlay (fullscreen, hidden until drag)
└── DataTable
├── BatchGroupHeader (expandable)
│ └── Receipt rows (with new Processing column)
├── BatchGroupHeader
│ └── Receipt rows
└── "Alte bonuri" group (receipts fără batch)
```
### Stări UI
| Stare | Visual |
|-------|--------|
| **Idle** | Pagină normală, fără overlay |
| **Dragging** | Overlay fullscreen cu drop zone |
| **Uploading** | Toast progress + batch nou apare în listă |
| **Processing** | Rows cu spinner, read-only |
| **Complete** | Rows normale, toast success |
| **Has Errors** | Rows roșii cu mesaj, buton retry |
### Mobile Considerations
- Drag & drop nu funcționează pe mobile - oferă buton "Upload" explicit
- Row grouping se transformă în cards grupate
- Error messages în accordion expandabil
## 8. Success Metrics
- **Zero bonuri pierdute:** 100% vizibilitate pentru toate fișierele uploadate
- **Retry success rate:** > 50% dintre erorile retry-uite să reușească
- **Auto-cleanup:** < 100 bonuri cu erori la orice moment (TTL 7 zile)
- **User satisfaction:** Reducere support tickets pentru "nu găsesc bonul"
## 9. Open Questions
- [x] Comportament la refresh **Backend continuă procesarea, frontend reia polling automat la revenire**
- [x] Auto-cleanup după 7 zile **Confirmat**
- [x] Retry individual + batch **Confirmat ambele**
- [x] Row complet blocat în procesare **Confirmat**
- [x] Grupare vizuală per batch **Confirmat, row grouping**
- [x] Mesaj eroare prescurtat **Confirmat, 50 caractere**
- [x] Drag anywhere **Confirmat, overlay fullscreen**
- [x] Quick filter chips **Confirmat**
- [x] Reject duplicate (hash) **Confirmat**
- [x] Batch info permanent **Confirmat, rămâne în DB**
- [x] Workers paraleli **Configurabil din .env, existent**
---
## 10. Dependențe între User Stories
```
US-011 (DB Schema) ─────────────────────────────────────────┐
↓ │
US-012 (API List + Batch Info) ─────────────────────────────┤
↓ │
US-007 (Duplicate Detection) ←───────────────────────────────┤
↓ │
US-001 (Drag Anywhere) ─────────────────────────────────────┤
↓ │
US-002 (Row Grouping) ←──────────────────────────────────────┤
↓ │
US-003 (Processing Status Column) ←──────────────────────────┤
↓ │
US-004 (Error Message Display) ←─────────────────────────────┤
↓ │
US-005 (Quick Filter Chips) │
↓ │
US-010 (Lock Row Processing) ←───────────────────────────────┤
↓ │
US-006 (Retry Individual + All) │
↓ │
US-009 (Persistence at Refresh) │
↓ │
US-008 (Auto-Cleanup 7 Days) │
↓ │
US-013 (Remove Old Bulk Page) │
```
**Ordine recomandată de implementare:**
1. **US-011:** Backend - Extindere schema DB
2. **US-012:** Backend - Endpoint list cu batch info
3. **US-007:** Backend - Duplicate detection (file hash)
4. **US-001:** Frontend - Drag Anywhere Overlay
5. **US-002:** Frontend - Row Grouping per Batch
6. **US-003:** Frontend - Processing Status Column
7. **US-004:** Frontend - Error Message Display
8. **US-005:** Frontend - Quick Filter Chips
9. **US-010:** Frontend - Lock Row în Procesare
10. **US-006:** Frontend - Retry Individual + All
11. **US-009:** Frontend - Persistence at Refresh
12. **US-008:** Backend - Auto-Cleanup Job
13. **US-013:** Cleanup - Remove Old Bulk Page
---
**Last Updated:** 2026-01-11
**Author:** Claude Code
**Status:** Draft - Pending Review
**Predecessor:** `prd-bulk-receipt-upload.md` (implementat)

View File

@@ -0,0 +1,279 @@
# PRD: Bulk Upload - Progres Real-Time în Listă
## 1. Introducere
Sistemul actual de bulk upload afișează fișierele în lista de bonuri **doar după** ce procesarea OCR se finalizează. Utilizatorul nu poate vedea ce fișiere a încărcat și în ce stadiu se află fiecare. Această funcționalitate va afișa toate fișierele imediat după upload, direct în tabelul de bonuri, cu actualizare în timp real a statusului pe măsură ce sunt procesate.
### Context Dezvoltare
| Aspect | Valoare |
|--------|---------|
| **Branch** | `ralph/bulk-receipt-upload` (continuare branch existent) |
| **Bază** | Sistemul bulk upload implementat anterior (US-001 → US-013) |
| **NU crea branch nou** | Toate modificările se fac în branch-ul existent |
## 2. Obiective
### Obiectiv Principal
- Vizibilitate completă a tuturor fișierelor încărcate, de la momentul upload-ului până la finalizare
### Obiective Secundare
- Feedback vizual clar pentru progresul procesării
- Posibilitatea de a anula fișiere individuale sau întreg batch-ul
- Identificarea rapidă a fișierelor cu erori
### Metrici de Succes
- Utilizatorul vede fișierele în tabel în <2 secunde de la upload
- Status-ul se actualizează în timp real fără refresh manual
- Rata de abandon a bulk upload scade (utilizatorii înțeleg ce se întâmplă)
## 3. User Stories
### US-001: Afișare Fișiere Imediat După Upload
**Ca** utilizator care încarcă bonuri în bulk
**Vreau** văd toate fișierele selectate în tabel imediat după upload
**Pentru că** vreau știu fișierele mele au fost primite și sunt în procesare
**Acceptance Criteria:**
- [ ] După drag-drop sau selectare fișiere, rândurile apar în tabel în <2 secunde
- [ ] Fiecare fișier are un rând propriu în tabelul de bonuri
- [ ] Rândurile sunt grupate în batch-ul corespunzător (BatchGroupHeader)
- [ ] Coloanele de date (Data, Sumă, Furnizor, Tip) afișează '-' pentru fișiere neprocesate
- [ ] Coloana "Fișier" afișează numele fișierului original
- [ ] Coloana "Status Procesare" afișează "În așteptare" (pending)
- [ ] npm run typecheck passes
- [ ] Verify in browser: upload 3 fișiere și confirmă apar instant în tabel
### US-002: Actualizare Status în Timp Real
**Ca** utilizator
**Vreau** văd statusul fiecărui fișier actualizându-se automat
**Pentru că** vreau urmăresc progresul fără dau refresh
**Acceptance Criteria:**
- [ ] Statusul se schimbă de la "În așteptare" "Se procesează..." "Procesat" / "Eroare"
- [ ] Tranziția status include animație fade subtilă (CSS transition 300ms)
- [ ] Badge-ul "Se procesează..." include spinner animat
- [ ] Când procesarea se termină cu succes, coloanele se populează cu date extrase (Data, Sumă, etc.)
- [ ] Când procesarea eșuează, coloana Status afișează "Eroare" cu tooltip pentru mesajul de eroare
- [ ] Rândul rămâne în aceeași poziție în grup (nu se mută)
- [ ] npm run typecheck passes
- [ ] Verify in browser: urmărește un fișier din pending processing completed
### US-003: Animație la Schimbarea Statusului
**Ca** utilizator
**Vreau** o indicație vizuală când un fișier își schimbă statusul
**Pentru că** vreau observ ușor progresul când am multe fișiere
**Acceptance Criteria:**
- [ ] Badge-ul de status se schimbă cu CSS transition (opacity fade)
- [ ] Când trece la "Procesat", rândul primește un highlight verde subtil pentru 2s
- [ ] Când trece la "Eroare", rândul primește un highlight roșu subtil pentru 2s
- [ ] Animațiile folosesc design tokens (`--green-50`, `--red-50`)
- [ ] Animațiile nu sunt distragătoare (subtile, nu flashy)
- [ ] npm run typecheck passes
- [ ] Verify in browser: testează tranziția vizuală pentru success și error
### US-004: Cancel Fișier Individual
**Ca** utilizator
**Vreau** pot anula procesarea unui singur fișier
**Pentru că** poate am încărcat din greșeală un fișier
**Acceptance Criteria:**
- [ ] Fișierele cu status "În așteptare" sau "Se procesează" au buton/icon Cancel (×)
- [ ] Click pe Cancel afișează confirmare: "Anulezi procesarea pentru {filename}?"
- [ ] După confirmare, fișierul este eliminat din coadă și dispare din tabel
- [ ] Fișierele deja procesate NU au buton Cancel (sunt bonuri valide)
- [ ] Fișierele cu eroare au buton Retry, nu Cancel
- [ ] Cancel individual nu afectează alte fișiere din batch
- [ ] npm run typecheck passes
- [ ] Verify in browser: anulează un fișier pending și confirmă dispare
### US-005: Cancel Tot Batch-ul
**Ca** utilizator
**Vreau** pot anula toate fișierele dintr-un batch
**Pentru că** poate am încărcat batch-ul greșit
**Acceptance Criteria:**
- [ ] BatchGroupHeader pentru batch-uri în procesare are buton "Anulează tot"
- [ ] Click pe "Anulează tot" afișează confirmare cu numărul de fișiere afectate
- [ ] Confirmarea menționează fișierele deja procesate rămân ca bonuri
- [ ] După confirmare, toate fișierele pending/processing sunt eliminate
- [ ] Fișierele deja procesate (completed) rămân în sistem ca bonuri valide
- [ ] Dacă toate fișierele sunt completed/failed, butonul nu apare
- [ ] npm run typecheck passes
- [ ] Verify in browser: anulează un batch și confirmă doar pending/processing dispar
### US-006: Checkbox Disabled pentru Fișiere în Procesare
**Ca** utilizator
**Vreau** nu pot selecta fișierele în procesare pentru acțiuni bulk
**Pentru că** nu are sens validez/șterg un fișier care nu e încă procesat
**Acceptance Criteria:**
- [ ] Checkbox-ul este disabled pentru rânduri cu status pending/processing
- [ ] Hover pe checkbox disabled afișează tooltip: "Fișierul se procesează"
- [ ] "Select All" nu include fișierele în procesare
- [ ] Acțiunile bulk (Validează, Șterge) funcționează doar pe fișiere completed/failed
- [ ] npm run typecheck passes
- [ ] Verify in browser: încearcă selectezi un fișier pending - checkbox disabled
### US-007: Backend - Endpoint pentru Cancel Job
**Ca** sistem
**Vreau** un endpoint API pentru anularea job-urilor de procesare
**Pentru că** frontend-ul trebuie poată cancela fișiere
**Acceptance Criteria:**
- [ ] POST /api/data-entry/bulk/cancel/{job_id} - anulează un job specific
- [ ] POST /api/data-entry/bulk/cancel-batch/{batch_id} - anulează tot batch-ul (pending/processing)
- [ ] Endpoint-urile returnează lista de job-uri anulate
- [ ] Job-urile cu status completed/failed nu pot fi anulate (return 400)
- [ ] Dacă receipt-ul a fost deja creat, nu se șterge (rămâne în sistem)
- [ ] Endpoint-urile actualizează status în job_queue la 'cancelled'
- [ ] npm run typecheck passes (Python)
- [ ] Testează cu curl: cancel job pending returnează 200
### US-008: Frontend - Integrare Store pentru Cancel
**Ca** dezvoltator
**Vreau** acțiuni în store pentru cancel
**Pentru că** componentele trebuie poată apela cancel
**Acceptance Criteria:**
- [ ] batchProgressStore.cancelJob(jobId) - apelează endpoint cancel individual
- [ ] batchProgressStore.cancelBatch(batchId) - apelează endpoint cancel batch
- [ ] După cancel, job-ul dispare din Map-ul de jobs
- [ ] După cancel batch, toate job-urile pending/processing dispar
- [ ] Polling-ul continuă pentru job-urile rămase (completed)
- [ ] Erori la cancel sunt afișate utilizatorului (toast)
- [ ] npm run typecheck passes
- [ ] Verify in browser: cancel + verifică store se actualizează
### US-009: Afișare Fișiere în Tabel - Componenta Row
**Ca** dezvoltator
**Vreau** pot randa un rând pentru un job neprocesar încă
**Pentru că** trebuie afișez fișierele înainte de a deveni Receipt-uri
**Acceptance Criteria:**
- [ ] Tabelul poate randa atât Receipt-uri cât și BatchJob-uri (pending files)
- [ ] Pentru BatchJob: afișează filename în coloana Fișier
- [ ] Pentru BatchJob: coloanele Data, Sumă, Furnizor, Tip afișează '-'
- [ ] Pentru BatchJob: ProcessingStatusCell afișează status corect
- [ ] Pentru BatchJob: acțiunile din meniu sunt disabled (doar Cancel disponibil)
- [ ] Stilul rândului indică vizual e în procesare (ușor muted/opacity)
- [ ] npm run typecheck passes
- [ ] Verify in browser: rândul pentru job pending arată corect
### US-010: Sincronizare Job Status cu Receipt
**Ca** sistem
**Vreau** asociez job-urile cu receipt-urile create
**Pentru că** când OCR termină, rândul trebuie se transforme în receipt
**Acceptance Criteria:**
- [ ] Când OCR termină cu succes, job-ul primește receipt_id
- [ ] Frontend-ul detectează receipt_id și înlocuiește rândul de job cu receipt-ul real
- [ ] Coloanele se populează cu datele din receipt (Data, Sumă, Furnizor)
- [ ] Tranziția e smooth - nu dispare/reapare rândul
- [ ] Dacă OCR eșuează, job-ul rămâne cu status 'failed' și error_message
- [ ] npm run typecheck passes
- [ ] Verify in browser: urmărește un fișier cum devine receipt cu date populate
## 4. Cerințe Funcționale
1. **[REQ-001]** Sistemul trebuie creeze rânduri vizuale în tabel imediat după upload, înainte de procesare OCR
2. **[REQ-002]** Fiecare fișier încărcat trebuie aibă vizibil: nume fișier, status procesare, acțiune cancel
3. **[REQ-003]** Status-ul trebuie se actualizeze în timp real via long-polling existent
4. **[REQ-004]** Utilizatorul poate anula fișiere individuale sau tot batch-ul
5. **[REQ-005]** Anularea afectează doar fișierele pending/processing, nu cele deja procesate
6. **[REQ-006]** Rândurile pentru fișiere neprocesate au coloane de date goale ('-')
7. **[REQ-007]** Checkbox-urile sunt disabled pentru fișiere în procesare
8. **[REQ-008]** Animațiile de tranziție folosesc design tokens CSS
## 5. Non-Goals (Ce NU facem)
- **NU** implementăm preview/thumbnail pentru fișiere - doar nume + status
- **NU** implementăm progress bar procentual per fișier - doar status discret
- **NU** implementăm reordonare/prioritizare a cozii de procesare
- **NU** implementăm pause/resume pentru procesare
- **NU** ștergem receipt-urile deja create când se anulează un batch
- **NU** permitem editarea fișierelor în timpul procesării
## 6. Considerații Tehnice
### Git Branch
- **Branch:** `ralph/bulk-receipt-upload` - **CONTINUĂ în acest branch**
- **NU crea branch nou** - această funcționalitate extinde bulk upload-ul existent
- Commit messages: `feat(bulk-upload-realtime): US-XXX - Descriere`
### Stack/Tehnologii
- **Frontend:** Vue 3, Pinia stores, PrimeVue components
- **Backend:** FastAPI, SQLite (data-entry module), job_queue table
- **Polling:** Long-polling existent în batchProgressStore
### Patterns de Urmat
- ProcessingStatusCell pentru afișare status (extinde pentru cancel)
- BatchGroupHeader pentru grupare (adaugă buton cancel all)
- batchProgressStore pentru state management (adaugă cancel actions)
- Design tokens din `docs/DESIGN_TOKENS.md` pentru culori/spacing
### Dependențe
- Sistemul de bulk upload existent (US-001 până la US-013 anterioare)
- Long-polling pentru status updates
- BatchJob model din backend
### Riscuri Tehnice
- **Race condition:** Job poate termina între request cancel și procesare - handle gracefully
- **Performance:** Multe rânduri în tabel cu polling - optimizează re-renders cu Vue keys
- **UX:** Rânduri care "dispar" pot fi confuze - animație clear pentru cancel
## 7. Considerații UI/UX
### Layout și Flow
```
┌─────────────────────────────────────────────────────────────────────┐
│ BatchGroupHeader: "Batch B-abc123" - 5 fișiere [Anulează tot] │
├─────────────────────────────────────────────────────────────────────┤
│ □ │ bon1.pdf │ - │ - │ - │ În așteptare │ [×] │
│ □ │ bon2.pdf │ - │ - │ - │ Se procesează...│ [×] │
│ ☑ │ bon3.pdf │ 15 Ian │ 125.50 │ LIDL │ ✓ Procesat │ │
│ □ │ bon4.pdf │ - │ - │ - │ ✗ Eroare │ [↻] │
│ ☑ │ bon5.pdf │ 15 Ian │ 89.00 │ Mega │ ✓ Procesat │ │
└─────────────────────────────────────────────────────────────────────┘
```
### Stări (loading, error, empty, success)
- **Pending:** Badge gri "În așteptare", checkbox disabled, buton Cancel
- **Processing:** Badge albastru cu spinner "Se procesează...", checkbox disabled, buton Cancel
- **Completed:** Badge verde "✓ Procesat", checkbox enabled, fără Cancel
- **Failed:** Badge roșu "✗ Eroare" cu tooltip, checkbox disabled, buton Retry
- **Cancelled:** Rândul dispare cu animație fade-out
### Accesibilitate
- Buton Cancel are aria-label descriptiv
- Tooltip-urile de eroare sunt accesibile cu keyboard
- Animațiile respectă `prefers-reduced-motion`
## 8. Success Metrics
- **Time to visibility:** Fișierele apar în tabel în <2s de la upload
- **Real-time accuracy:** Status-ul reflectă starea reală în <1s de la schimbare
- **Cancel success rate:** 100% din cancel-uri pentru pending jobs funcționează
- **User satisfaction:** Utilizatorii înțeleg ce se întâmplă cu fișierele lor
## 9. Open Questions
- [ ] Dacă utilizatorul navighează away și revine, cum se restaurează rândurile pentru jobs pending? (Probabil din localStorage + API call)
- [ ] Limită maximă de fișiere afișate simultan în tabel? (Performance consideration)
- [ ] Notificare sonoră/vizuală când tot batch-ul e procesat?
---
## Appendix: Ordine Implementare Recomandată
1. **US-007** - Backend endpoints pentru cancel (independent)
2. **US-008** - Store actions pentru cancel (depinde de US-007)
3. **US-009** - Componenta row pentru jobs (independent)
4. **US-001** - Afișare fișiere în tabel (depinde de US-009)
5. **US-002** - Actualizare status real-time (depinde de US-001)
6. **US-010** - Sincronizare jobreceipt (depinde de US-002)
7. **US-003** - Animații tranziție (depinde de US-002)
8. **US-004** - Cancel individual (depinde de US-008)
9. **US-005** - Cancel batch (depinde de US-008)
10. **US-006** - Checkbox disabled (depinde de US-009)

View File

@@ -0,0 +1,424 @@
# PRD: Mobile UX Improvements & Bulk Upload Bug Fixes
## 1. Introducere
Acest PRD adresează multiple probleme identificate în modulul de bulk upload bonuri și îmbunătățiri UX pentru interfața mobilă. Problemele includ refresh incorect al listei, pierderea bonurilor cu eroare, lipsa selecției multiple pe mobil, și nevoia unei interfețe mai native pentru Android.
**Branch de lucru:** `ralph/bulk-receipt-upload` (continuăm în același branch)
**Context:** PRD-ul anterior (prd-bulk-receipt-upload.md) a implementat funcționalitatea de bază, dar testarea în producție a evidențiat probleme de UX și bugs ce necesită rezolvare.
## 2. Obiective
### Obiectiv Principal
- Corectarea comportamentului de refresh pentru a păstra bonurile cu eroare în listă și a menține ordinea
### Obiective Secundare
- Implementarea selecției multiple pe mobil cu interfață în stil Android nativ
- Afișarea numelui fișierului pentru toate bonurile (nu doar cele în procesare)
- Rezolvarea bug-ului de refresh automat la selectarea fișierelor
### Metrici de Succes
- Zero bonuri "pierdute" după procesare bulk
- Timp de selecție multiple pe mobil < 3 secunde pentru 10 bonuri
- User satisfaction score > 4/5 pentru interfața mobilă
## 3. User Stories
### US-034: Fix - Refresh Individual vs Refresh Total
**Ca** utilizator
**Vreau** ca bonurile să se actualizeze individual fără să se reîncarce toată lista
**Pentru că** vreau să văd progresul în timp real fără să pierd poziția în listă
**Acceptance Criteria:**
- [ ] SSE handler NU apelează `store.fetchReceipts()` când un receipt nu este în pagina curentă
- [ ] În schimb, verifică dacă receipt-ul aparține unui batch activ și îl adaugă local
- [ ] Ordinea bonurilor din batch rămâne stabilă (nu se reordonează)
- [ ] Bonurile noi din procesare se inserează în poziția corectă
- [ ] npm run typecheck passes
- [ ] Verify in browser: uploadează 5 bonuri, nu se reîncarcă pagina între procesări
**Technical Notes:**
- Modificare în `handleSSEStatusChange` din `ReceiptsListView.vue:2397`
- Când receipt nu e găsit: verifică dacă batch_id e în batchProgressStore, apoi fetch individual receipt și inserează local
### US-035: Fix - Bonuri cu Eroare Rămân în Listă
**Ca** utilizator
**Vreau** ca bonurile cu eroare de procesare să rămână vizibile în listă
**Pentru că** vreau să le pot edita manual sau să le șterg
**Acceptance Criteria:**
- [ ] Bonurile cu `processing_status='failed'` NU sunt eliminate din view după refresh
- [ ] Eroarea de extragere dată/sumă afișează bonul cu status "Eroare" și buton "Editează"
- [ ] Link rapid "Editează manual" duce la formularul de editare cu datele disponibile pre-populate
- [ ] Bonurile failed au highlight vizual (roșu subtil) pentru identificare ușoară
- [ ] npm run typecheck passes
- [ ] Verify in browser: bon cu eroare OCR rămâne în listă și poate fi editat
**Technical Notes:**
- Bonurile cu eroare ar trebui să aibă `status='draft'` și `processing_status='failed'`
- Nu se șterg la refresh, doar se actualizează in-place
### US-036: Afișare Nume Fișier pentru Toate Bonurile
**Ca** utilizator
**Vreau** să văd numele fișierului original pentru bonurile procesate
**Pentru că** vreau să identific care bon corespunde cărui fișier uploadat
**Acceptance Criteria:**
- [ ] Coloana "Fișier" afișează `original_filename` și pentru receipt-uri completate, nu doar pentru job-uri
- [ ] Pe mobil, numele fișierului apare sub partenăr (font mic, gri)
- [ ] Tooltip pe desktop arată numele complet dacă e trunchiat
- [ ] Dacă receipt nu are `original_filename`, afișează "-"
- [ ] npm run typecheck passes
- [ ] Verify in browser: bon procesat afișează numele fișierului original
**Technical Notes:**
- Backend trebuie să populeze `original_filename` pe receipt din job-ul OCR
- Verifică că auto-save service copiază filename din job la receipt
### US-037: Fix - Upload Nu Mai Face Refresh Automat
**Ca** utilizator
**Vreau** ca selectarea fișierelor să nu reîncarce pagina
**Pentru că** pierd fișierele selectate înainte să apăs "Procesează"
**Acceptance Criteria:**
- [ ] Selectarea fișierelor via input NU declanșează refresh/reload pagină
- [ ] Fișierele selectate rămân în listă până la submit explicit
- [ ] Comportament identic pe desktop și mobil (Chrome, Safari)
- [ ] Nu există race condition între clonarea fișierelor și resetarea input-ului
- [ ] npm run typecheck passes
- [ ] Verify on mobile: selectează 5 fișiere, toate rămân în listă
**Technical Notes:**
- Bug probabil în `onBulkFileInputChange` sau în interacțiunea cu store
- Verifică că nu există watchers care declanșează refresh la schimbări de state
- Test specific pe Chrome Android - vezi gotcha din CLAUDE.md despre File API
### US-038: Mobile - Selecție Multiplă prin Long-Press
**Ca** utilizator mobil
**Vreau** să selectez bonuri prin apăsare lungă
**Pentru că** vreau să pot șterge sau acționa asupra mai multor bonuri simultan
**Acceptance Criteria:**
- [ ] Long-press (500ms) pe un card activează modul de selecție
- [ ] Card-ul selectat primește checkmark și background diferit
- [ ] După activare, tap simplu pe alte carduri le adaugă/elimină din selecție
- [ ] Tap în afara cardurilor (pe background) dezactivează modul selecție
- [ ] Header contextual arată numărul de elemente selectate
- [ ] npm run typecheck passes
- [ ] Verify on mobile: long-press selectează, tap adaugă la selecție
**CSS Requirements:**
- `.receipt-card.selected { background: var(--blue-50); border-color: var(--blue-500); }`
- `.receipt-card .selection-check { position: absolute; top: 8px; right: 8px; }`
- Testează dark mode: background selectat vizibil
### US-039: Mobile - Select All și Buton Ștergere
**Ca** utilizator mobil
**Vreau** să am butoane "Selectează tot" și "Șterge" în modul selecție
**Pentru că** vreau să pot șterge rapid mai multe bonuri
**Acceptance Criteria:**
- [ ] În modul selecție, apare top bar contextual cu:
- Numărul de selectate: "X selectate"
- Buton "Selectează tot"
- Buton X pentru a ieși din modul selecție
- [ ] Apare bottom bar cu buton "Șterge" (roșu, icon coș)
- [ ] "Selectează tot" selectează toate bonurile din pagina curentă
- [ ] Buton "Șterge" afișează confirmare înainte de ștergere
- [ ] După ștergere, modul selecție se dezactivează
- [ ] npm run typecheck passes
- [ ] Verify on mobile: select all + delete funcționează
**CSS Requirements:**
- Top bar: `position: sticky; top: 0; z-index: 100;`
- Bottom bar: `position: fixed; bottom: 0; left: 0; right: 0;`
- Animație slide-in pentru bars
### US-040: Mobile - Layout Android Nativ pentru Lista Bonuri
**Ca** utilizator mobil
**Vreau** o interfață similară cu aplicațiile Android native
**Pentru că** vreau experiență familiară și intuitivă
**Acceptance Criteria:**
- [ ] **Top Bar** fixă cu:
- Stânga: Hamburger menu / Back arrow
- Centru: Titlu "Bonuri Fiscale"
- Dreapta: Search icon, Filter icon, More options (3 dots)
- [ ] **Filter Chips** sub top bar (orizontal scrollabil):
- Toate, Ciorne, În așteptare, Validate, Respinse
- Chip activ are background primary color
- [ ] **Bottom Navigation** fixă cu 4 tab-uri:
- Bonuri (icon receipt, activ)
- Upload (icon cloud-upload)
- Rapoarte (icon chart)
- Setări (icon cog)
- [ ] **FAB** (Floating Action Button) în colț dreapta jos:
- Icon "+" pentru adăugare bon nou
- Se ascunde când se face scroll în jos, apare când scroll în sus
- [ ] Lista ocupă spațiul dintre top bar și bottom nav
- [ ] npm run typecheck passes
- [ ] Verify on mobile: layout similar cu Gmail/WhatsApp
**CSS Requirements:**
```css
.mobile-top-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 56px;
background: var(--surface-card);
border-bottom: 1px solid var(--surface-border);
z-index: 1000;
}
.mobile-bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 56px;
background: var(--surface-card);
border-top: 1px solid var(--surface-border);
z-index: 1000;
}
.mobile-fab {
position: fixed;
bottom: 72px; /* above bottom nav */
right: 16px;
width: 56px;
height: 56px;
border-radius: 16px;
background: var(--primary-500);
box-shadow: var(--shadow-lg);
}
```
### US-041: Mobile - Layout Android pentru Editare/Creare Bon
**Ca** utilizator mobil
**Vreau** interfață de editare bon similară cu compose email în Gmail
**Pentru că** vreau butoane de acțiune accesibile și layout familiar
**Acceptance Criteria:**
- [ ] **Top Bar** cu:
- Stânga: X (close) sau Back arrow
- Centru: "Bon Nou" / "Editare Bon #123"
- Dreapta: Attach icon, Save/Submit icon
- [ ] **Content Area** scrollabilă cu form fields
- [ ] **Bottom Action Bar** fixă cu butoane:
- "Salvează Ciornă" (secondary)
- "Trimite pentru Validare" (primary)
- [ ] Form fields ocupă 100% width pe mobil
- [ ] Keyboard-aware: bottom bar se mută deasupra tastaturii
- [ ] npm run typecheck passes
- [ ] Verify on mobile: layout similar cu Gmail compose
**CSS Requirements:**
```css
.mobile-receipt-form {
padding-bottom: 80px; /* space for bottom bar */
}
.mobile-form-bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-sm) var(--space-md);
background: var(--surface-card);
border-top: 1px solid var(--surface-border);
display: flex;
gap: var(--space-sm);
}
.mobile-form-bottom-bar .p-button {
flex: 1;
}
```
### US-042: Mobile - Layout Android pentru Vizualizare Bon
**Ca** utilizator mobil
**Vreau** interfață de vizualizare bon similară cu view email în Gmail
**Pentru că** vreau acțiuni rapide și navigare ușoară
**Acceptance Criteria:**
- [ ] **Top Bar** cu:
- Stânga: Back arrow
- Dreapta: Edit icon, Delete icon, More options
- [ ] **Content Area** cu detalii bon (read-only)
- [ ] **Bottom Action Bar** cu butoane contextuale:
- Pentru DRAFT: "Editează", "Trimite"
- Pentru PENDING: "Validează", "Respinge" (dacă are permisiuni)
- Pentru APPROVED: "Anulează validare" (dacă are permisiuni)
- [ ] Swipe left/right pentru navigare între bonuri (optional, nice-to-have)
- [ ] npm run typecheck passes
- [ ] Verify on mobile: acțiuni accesibile din bottom bar
### US-043: Păstrare Ordine Bonuri la Refresh
**Ca** utilizator
**Vreau** ca bonurile să rămână în ordinea în care au fost uploadate
**Pentru că** vreau să urmăresc progresul fiecărui bon uploadat
**Acceptance Criteria:**
- [ ] Bonurile din același batch păstrează ordinea de upload (nu se reordonează alfabetic sau by date)
- [ ] Un bon finalizat rămâne în aceeași poziție vizuală, nu sare la sfârșit
- [ ] SSE updates modifică status-ul in-place, fără a muta rândul
- [ ] Refresh manual (pull-to-refresh) poate reordona, dar SSE updates nu
- [ ] npm run typecheck passes
- [ ] Verify in browser: bon procesat nu își schimbă poziția în listă
**Technical Notes:**
- Lista trebuie sortată by `created_at DESC` sau by batch order, nu by last_modified
- `updateReceiptInPlace` din store NU trebuie să reordoneze array-ul
## 4. Cerințe Funcționale
1. [REQ-011] Bonurile cu eroare OCR rămân vizibile în listă cu status "Eroare"
2. [REQ-012] Refresh via SSE NU reîncarcă toată lista, doar actualizează individual
3. [REQ-013] Numele fișierului original e afișat pentru toate bonurile
4. [REQ-014] Selecția multiplă pe mobil funcționează via long-press
5. [REQ-015] Interfața mobilă urmează design patterns Android Material
6. [REQ-016] Bottom navigation are 4 tab-uri: Bonuri, Upload, Rapoarte, Setări
7. [REQ-017] FAB pentru "Bon Nou" în colț dreapta jos pe mobil
8. [REQ-018] Ordinea bonurilor rămâne stabilă în cadrul unui batch
## 5. Non-Goals (Ce NU facem)
- **NU** facem swipe gestures pentru acțiuni (delete/archive) - prea complex
- **NU** facem animații complexe de tranziție între pagini
- **NU** schimbăm layout-ul desktop - doar mobil
- **NU** facem tab-uri funcționale pentru Rapoarte/Setări (placeholder pentru moment)
- **NU** facem pull-to-refresh - refresh via SSE e suficient
- **NU** facem offline mode sau caching local
## 6. Considerații Tehnice
### Stack/Tehnologii
- **Frontend:** Vue 3 Composition API, PrimeVue
- **Mobile Detection:** `window.innerWidth < 768` sau `navigator.userAgent`
- **Touch Events:** Native `touchstart`/`touchend` pentru long-press
### Patterns de Urmat
- Design tokens din `docs/DESIGN_TOKENS.md`
- CSS patterns din `docs/CSS_PATTERNS.md`
- Mobile-first responsive approach
### ⚠️ Mobile Long-Press Implementation
```javascript
// Long-press detection (500ms)
let pressTimer = null
const onTouchStart = (item) => {
pressTimer = setTimeout(() => {
enterSelectionMode(item)
}, 500)
}
const onTouchEnd = () => {
clearTimeout(pressTimer)
}
const onTouchMove = () => {
clearTimeout(pressTimer) // Cancel if user drags
}
```
### Dependențe
- Store existent: `receiptsStore.js`
- SSE Service: `sseService.js`
- Batch Progress Store: `batchProgressStore.js`
### Riscuri Tehnice
- **Touch event conflicts:** Long-press poate conflicta cu scroll pe mobile
- Mitigare: Setează threshold mic pentru detectare scroll vs. hold
- **Performance:** Lista mare poate încetini selecția
- Mitigare: Virtual scrolling dacă > 100 items
- **Cross-browser:** Safari iOS poate avea comportament diferit
- Mitigare: Test explicit pe Safari iOS
## 7. Considerații UI/UX
### Mobile Layout Reference (din imagini)
**img1.jpg & img2.jpg (WhatsApp style):**
- Top bar contextual cu count și acțiuni
- Filter chips orizontal scrollabile
- Bottom navigation cu 4 tabs
- FAB în colț dreapta jos
**img3.jpg & img4.jpg (Gmail style):**
- Top bar minimalist cu back + acțiuni
- Bottom action bar cu butoane contextuale
- Full-width content area
### Stări Mobile
| Stare | Top Bar | Content | Bottom |
|-------|---------|---------|--------|
| **Normal** | Title + Search + Filter | Lista carduri | Bottom Nav + FAB |
| **Selection Mode** | "X selectate" + Select All + Close | Carduri cu checkmarks | Delete button |
| **View Receipt** | Back + Edit + Delete | Detalii bon | Acțiuni contextuale |
| **Edit Receipt** | Close + Attach + Save | Form fields | Save Draft + Submit |
### Accesibilitate
- Touch targets minim 48x48px
- Contrast suficient pentru badges și text secundar
- Screen reader support pentru selection mode
## 8. Success Metrics
- **Bug Fixes:** 0 bonuri pierdute la procesare bulk
- **Upload Success:** 0 refresh-uri neintenționate la selectare fișiere
- **Selection Time:** < 2 secunde pentru selectare 5 bonuri
- **User Feedback:** Score > 4/5 pentru "Interfața mobilă e intuitivă"
## 9. Open Questions
- [ ] FAB-ul trebuie să aibă sub-menu (speed dial) pentru "Bon Nou" + "Upload Bulk"?
- [ ] Bottom navigation tab "Upload" deschide file picker direct sau navighează la pagină separată?
- [ ] Swipe gestures pentru delete/archive - implementăm în versiunea inițială sau amânăm?
- [ ] Animație de highlight când un bon termină procesarea - fade green sau pulse?
---
## 10. Dependențe între User Stories
```
US-034 (Fix Refresh) ─┬─→ US-035 (Erori rămân) → US-036 (Filename)
US-037 (Fix Upload) ──┘
US-038 (Long-press) → US-039 (Select All + Delete)
US-040 (Mobile List Layout) ←─┘
US-041 (Mobile Edit Layout) → US-042 (Mobile View Layout)
US-043 (Ordine) - independent, poate fi făcut oricând
```
**Ordine recomandată de implementare:**
### Faza 1: Bug Fixes (Critice)
1. **US-037**: Fix Upload Nu Mai Face Refresh Automat
2. **US-034**: Fix Refresh Individual vs Total
3. **US-035**: Bonuri cu Eroare Rămân în Listă
4. **US-043**: Păstrare Ordine Bonuri
### Faza 2: Mobile Enhancements
5. **US-036**: Afișare Nume Fișier pentru Toate Bonurile
6. **US-038**: Mobile - Selecție Multiplă prin Long-Press
7. **US-039**: Mobile - Select All și Buton Ștergere
### Faza 3: Mobile Native Layout
8. **US-040**: Mobile - Layout Android pentru Lista Bonuri
9. **US-041**: Mobile - Layout Android pentru Editare/Creare
10. **US-042**: Mobile - Layout Android pentru Vizualizare
---
**Last Updated:** 2026-01-12
**Author:** Claude Code
**Status:** Draft - Pending Review