## 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>
11 KiB
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ă:
- Bulk Delete - posibilitatea de a șterge mai multe bonuri odată
- 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 să reducă request-urile de polling cu 80%+
3. User Stories
US-001: Buton Șterge în Bulk Actions Bar
Ca utilizator Vreau să văd un buton "Șterge" când am selecții Pentru că vreau să 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 severitydanger - Bonurile în procesare (pending/processing) NU sunt selectabile (deja implementat - verifică că 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 să evit ștergeri accidentale
Acceptance Criteria:
- La click pe "Șterge", apare dialog cu mesaj: "Ești sigur că vrei să ștergi {N} bonuri?"
- Dialog-ul are 2 butoane: "Anulează" (secondary) și "Șterge" (danger)
- Dialog-ul folosește componenta PrimeVue
ConfirmDialogsauDialog - npm run typecheck passes
- Verify in browser: dialogul apare cu numărul corect de bonuri
US-003: Backend Endpoint Bulk Delete
Ca frontend Vreau să 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/bulkacceptă body:{ "ids": [1, 2, 3] } - Validează că 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 să văd rezultatul ștergerii Pentru că vreau să ș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 să fiu redirecționat automat la pagina anterioară Pentru că nu vreau să 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 să primesc notificări real-time despre schimbări de status Pentru că vreau să actualizez UI-ul fără polling
Acceptance Criteria:
- Endpoint
GET /api/data-entry/receipts/sse/statusreturnează 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 să mă conectez la SSE și să 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 să folosesc SSE în loc de polling interval Pentru că e mai eficient și mai smooth
Acceptance Criteria:
- Elimină
setIntervalpentru 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 să actualizez un singur rând fără să 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 să 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ă că polling-ul preia
4. Cerințe Funcționale
- [REQ-001] Butonul "Șterge" trebuie să fie roșu (severity danger) cu icon trash
- [REQ-002] Bonurile cu
processing_statusînpendingsauprocessingnu pot fi șterse - [REQ-003] Ștergerea bulk trebuie să șteargă și atașamentele și înregistrările contabile asociate
- [REQ-004] SSE trebuie să trimită evenimente pentru: status change, processing_status change
- [REQ-005] Frontend trebuie să mențină conexiunea SSE doar când există bonuri în procesare
- [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
@cacheddecorator DOAR pentru reads, nu pentru deletes
Dependențe
- PrimeVue ConfirmDialog sau Dialog pentru confirmare
- FastAPI
StreamingResponsepentru 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
- Confirmare: simplă cu count ✓
- Bonuri în procesare: exclude automat ✓
- Polling: SSE ✓
- Error handling: partial success ✓
- Bara acțiuni: header inline ✓
- Animații: instant remove ✓
- Pagină goală: du la pagina anterioară ✓
10. Ordine Implementare Recomandată
- US-003 - Backend bulk delete (fundație)
- US-001 - Buton Șterge în UI
- US-002 - Dialog confirmare
- US-004 - Frontend bulk delete cu toast
- US-005 - Navigare pagină anterioară
- US-009 - Update row in place (pregătire pentru SSE)
- US-006 - Backend SSE endpoint
- US-007 - Frontend SSE client
- US-008 - Înlocuire polling cu SSE
- US-010 - Graceful degradation