## 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>
280 lines
14 KiB
Markdown
280 lines
14 KiB
Markdown
# 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** să văd toate fișierele selectate în tabel imediat după upload
|
||
**Pentru că** vreau să știu că 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ă că apar instant în tabel
|
||
|
||
### US-002: Actualizare Status în Timp Real
|
||
**Ca** utilizator
|
||
**Vreau** să văd statusul fiecărui fișier actualizându-se automat
|
||
**Pentru că** vreau să urmăresc progresul fără să 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 să 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** să 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ă că dispare
|
||
|
||
### US-005: Cancel Tot Batch-ul
|
||
**Ca** utilizator
|
||
**Vreau** să 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ă că 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ă că doar pending/processing dispar
|
||
|
||
### US-006: Checkbox Disabled pentru Fișiere în Procesare
|
||
**Ca** utilizator
|
||
**Vreau** să nu pot selecta fișierele în procesare pentru acțiuni bulk
|
||
**Pentru că** nu are sens să 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ă să 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 să 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 să 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ă că store se actualizează
|
||
|
||
### US-009: Afișare Fișiere în Tabel - Componenta Row
|
||
**Ca** dezvoltator
|
||
**Vreau** să pot randa un rând pentru un job neprocesar încă
|
||
**Pentru că** trebuie să 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 că 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** să asociez job-urile cu receipt-urile create
|
||
**Pentru că** când OCR termină, rândul trebuie să 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 să creeze rânduri vizuale în tabel imediat după upload, înainte de procesare OCR
|
||
2. **[REQ-002]** Fiecare fișier încărcat trebuie să aibă vizibil: nume fișier, status procesare, acțiune cancel
|
||
3. **[REQ-003]** Status-ul trebuie să 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 job→receipt (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)
|