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,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)