# 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** să pot trage fișiere oriunde pe pagina de bonuri **Pentru că** vreau un flow natural fără să 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** să văd bonurile din același batch grupate vizual **Pentru că** vreau să 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** să văd statusul fiecărui bon din batch într-o coloană dedicată **Pentru că** vreau să ș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** să văd mesajul de eroare direct în listă (prescurtat) **Pentru că** vreau să înțeleg problema fără să 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 să mă 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)