Files
roa2web-service-auto/tasks/prd-bulk-upload-list-integration.md
Claude Agent 7b3541403f 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>
2026-01-12 08:33:17 +00:00

500 lines
21 KiB
Markdown

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