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,406 @@
# PRD: Bulk Receipt Upload & Auto-Processing
## 1. Introducere
Sistemul actual permite upload-ul și procesarea unui singur bon la un moment dat, cu intervenție manuală la editare/salvare. Această funcționalitate adaugă o pagină separată pentru upload multiple bonuri (PDF/PNG/JPG) care sunt procesate automat prin OCR și salvate direct în baza de date, fără intervenție manuală.
**Context tehnic:** Există deja infrastructura de job queue (SQLite) și worker pool pentru procesare paralelă OCR. Această funcționalitate va extinde sistemul existent.
## 2. Obiective
### Obiectiv Principal
- Permiterea upload-ului bulk de bonuri (10-50 fișiere) cu procesare automată end-to-end
### Obiective Secundare
- Reducerea timpului de introducere date cu 90%+ pentru batch-uri mari
- Vizibilitate în timp real asupra progresului procesării
- Separare clară între flow-ul manual (editare) și automat (bulk)
### Metrici de Succes
- Timp mediu per bon < 10 secunde (vs. 2-3 minute manual)
- Rata de succes OCR > 80% (bonuri procesate fără erori)
- Upload batch 50 bonuri în < 10 minute
## 3. User Stories
### US-001: Upload Multiple Fișiere
**Ca** utilizator data-entry
**Vreau** selectez/trag multiple fișiere (PDF/PNG/JPG) într-o zonă de upload
**Pentru că** vreau procesez un lot întreg de bonuri dintr-o dată
**Acceptance Criteria:**
- [ ] Drag & drop zone acceptă multiple fișiere simultan
- [ ] Click pe zonă deschide file picker cu multi-select activat
- [ ] Fișierele acceptate: PDF, PNG, JPG (max 10MB/fișier)
- [ ] Fișierele invalide sunt ignorate cu mesaj de avertizare
- [ ] Lista fișierelor selectate apare sub zona de upload
- [ ] Buton "Șterge" per fișier pentru eliminare din batch
- [ ] npm run typecheck passes
- [ ] Verify in browser that files appear in list after selection
- [ ] **CSS:** Drop zone folosește `var(--surface-card)` background, `var(--surface-border)` border
- [ ] **CSS:** Drop zone hover/active folosește `var(--blue-50)` background
- [ ] **CSS:** Spacing între elemente folosește tokens (`--space-md`, `--space-lg`)
- [ ] **CSS:** Testează în dark mode - drop zone vizibilă și contrastantă
### US-002: Vizualizare Batch Înainte de Submit
**Ca** utilizator
**Vreau** văd lista fișierelor selectate cu preview
**Pentru că** vreau verific am selectat fișierele corecte înainte de procesare
**Acceptance Criteria:**
- [ ] Lista arată: thumbnail (pentru imagini), nume fișier, mărime
- [ ] Pentru PDF-uri se arată icon generic PDF
- [ ] Counter total: "X fișiere selectate (Y MB)"
- [ ] Buton "Golește lista" pentru resetare completă
- [ ] Buton "Adaugă fișiere" pentru a adăuga la selecție existentă
- [ ] npm run typecheck passes
- [ ] Verify in browser that thumbnails render correctly
- [ ] **CSS:** Lista folosește pattern `.card` din `cards.css`
- [ ] **CSS:** Thumbnail cu `border-radius: var(--radius-md)`
- [ ] **CSS:** File size text cu `color: var(--color-text-secondary)`, `font-size: var(--text-sm)`
- [ ] **CSS:** Butoane folosesc clasele `.btn .btn-secondary` și `.btn .btn-primary`
- [ ] **CSS:** Testează în dark mode - thumbnails și text lizibile
### US-003: Submit Batch pentru Procesare
**Ca** utilizator
**Vreau** trimit toate fișierele pentru procesare cu un singur click
**Pentru că** vreau declanșez procesarea automată a întregului lot
**Acceptance Criteria:**
- [ ] Buton "Procesează X bonuri" submit-ează batch-ul
- [ ] La submit, toate fișierele se uploadează și se creează câte un OCR job per fișier
- [ ] După submit, UI-ul trece în modul "progres" (nu mai permite adăugare fișiere)
- [ ] Dacă un upload individual eșuează (network error), se reîncearcă automat (max 3 retry)
- [ ] npm run typecheck passes
- [ ] Verify in browser that submit disables file addition
### US-004: Progres Real-Time per Fișier
**Ca** utilizator
**Vreau** văd progresul fiecărui fișier în timp real
**Pentru că** vreau știu câte bonuri s-au procesat și câte mai sunt în așteptare
**Acceptance Criteria:**
- [ ] Fiecare fișier din listă arată status: "În așteptare" / "Se procesează..." / "Completat" / "Eroare"
- [ ] Status vizual diferențiat: badge/icon color-coded (gri/albastru/verde/roșu)
- [ ] La completare se arată confidence score overall (ex: "87% confidence")
- [ ] Progress bar global: "15/50 procesate"
- [ ] Timpul estimat rămas bazat pe average processing time
- [ ] npm run typecheck passes
- [ ] Verify in browser that status updates in real-time without page refresh
- [ ] **CSS:** Status badges folosesc culorile din tabel (vezi secțiunea 6):
- Pending: `background: var(--surface-hover)`, `color: var(--color-text-secondary)`
- Processing: `background: var(--blue-50)`, `color: var(--blue-600)` + spinner
- Success: `background: var(--green-50)`, `color: var(--green-600)`
- Error: `background: var(--red-50)`, `color: var(--red-600)`
- [ ] **CSS:** Progress bar folosește PrimeVue ProgressBar (stilizat global)
- [ ] **CSS:** Confidence score cu `font-family: var(--font-mono)` pentru numere
- [ ] **CSS:** Testează în dark mode - badges vizibile și contrastate
### US-005: Salvare Automată a Bonurilor Procesate
**Ca** sistem
**Vreau** salvez automat bonurile procesate cu succes în baza de date
**Pentru că** utilizatorul dorește procesare 100% automată fără intervenție
**Acceptance Criteria:**
- [ ] După OCR completat cu succes, se creează automat un receipt în DB
- [ ] Receipt-ul primește status "DRAFT" (poate fi editat ulterior dacă e nevoie)
- [ ] Se atașează automat fișierul original la receipt
- [ ] Se salvează toate câmpurile extrase: vendor, CUI, dată, sumă, TVA
- [ ] Se generează automat accounting entries (ca la flow-ul manual)
- [ ] npm run typecheck passes
- [ ] Verify that receipts appear in receipt list after bulk processing
### US-006: Gestionare Erori OCR
**Ca** utilizator
**Vreau** ca bonurile cu erori OCR fie marcate pentru review manual
**Pentru că** vreau procesez restul batch-ului fără blocaj, dar nu pierd bonurile problematice
**Acceptance Criteria:**
- [ ] La eroare OCR, fișierul primește status "Eroare" cu mesaj explicativ
- [ ] Bonurile cu erori rămân în lista vizibilă, nu sunt șterse
- [ ] Buton "Deschide în editor" pentru fiecare bon cu eroare (redirect la pagina de editare manuală)
- [ ] Procesarea celorlalte bonuri continuă independent
- [ ] La final se arată sumar: "45 procesate cu succes, 5 cu erori"
- [ ] npm run typecheck passes
- [ ] Verify in browser that error files show retry/edit options
- [ ] **CSS:** Error row cu `background: var(--red-50)`, `border-left: 3px solid var(--red-500)`
- [ ] **CSS:** Error message cu `color: var(--red-600)`, `font-size: var(--text-sm)`
- [ ] **CSS:** Action buttons în error row folosesc `.btn .btn-sm` pattern
- [ ] **CSS:** Testează în dark mode - erori vizibile dar nu agresive
### US-007: Rezumat Final Batch
**Ca** utilizator
**Vreau** văd un rezumat la finalul procesării batch-ului
**Pentru că** vreau știu câte bonuri s-au salvat și ce acțiuni mai am de făcut
**Acceptance Criteria:**
- [ ] Modal/panel de rezumat cu statistici: procesate OK, erori, total sumă
- [ ] Link direct la lista de receipts filtrat pe batch-ul curent (by date/user)
- [ ] Opțiune "Încarcă alt batch" pentru a începe de la zero
- [ ] Opțiune "Vezi bonurile cu erori" pentru review rapid
- [ ] npm run typecheck passes
- [ ] Verify in browser that summary modal shows correct counts
- [ ] **CSS:** Modal folosește PrimeVue Dialog (stilizat global) sau pattern `.card`
- [ ] **CSS:** Statistici success cu `color: var(--green-600)`, errors cu `color: var(--red-600)`
- [ ] **CSS:** Total sumă cu `font-size: var(--text-2xl)`, `font-weight: var(--font-bold)`, `font-family: var(--font-mono)`
- [ ] **CSS:** Spacing consistent: `padding: var(--space-lg)`, `gap: var(--space-md)`
- [ ] **CSS:** Testează în dark mode - modal și conținut lizibile
### US-008: Backend - Endpoint Batch Upload
**Ca** developer
**Vreau** un endpoint optimizat pentru upload multiple fișiere
**Pentru că** upload-ul secvențial ar fi prea lent pentru 50+ fișiere
**Acceptance Criteria:**
- [ ] POST `/api/data-entry/bulk/upload` acceptă multipart cu multiple fișiere
- [ ] Returnează lista de job_id-uri pentru tracking
- [ ] Validare: max 100 fișiere per batch, max 10MB per fișier
- [ ] Jobs se creează atomic (toate sau niciunul)
- [ ] Returnează și un batch_id pentru grouping
- [ ] pytest tests pass
- [ ] API returns correct response schema
### US-009: Backend - Auto-Save Receipt din OCR Result
**Ca** developer
**Vreau** un service care creează automat receipt-uri din rezultatele OCR
**Pentru că** flow-ul bulk trebuie fie end-to-end fără intervenție UI
**Acceptance Criteria:**
- [ ] `ReceiptAutoCreateService.create_from_ocr_result(job_id, ocr_result, user)`
- [ ] Mapare completă OCR fields Receipt fields
- [ ] Creare attachment cu fișierul original
- [ ] Generare accounting entries via existing logic
- [ ] Validare minimă: suma > 0, dată validă
- [ ] Return receipt_id sau error message
- [ ] pytest tests pass
### US-010: Backend - Batch Status Endpoint
**Ca** developer
**Vreau** un endpoint pentru status-ul întregului batch
**Pentru că** frontend-ul trebuie să poll-eze eficient pentru toate fișierele
**Acceptance Criteria:**
- [ ] GET `/api/data-entry/bulk/batches/{batch_id}/status`
- [ ] Returnează status agregat: pending_count, processing_count, completed_count, failed_count
- [ ] Include lista de job_id + status pentru fiecare fișier
- [ ] Include receipt_id pentru jobs completate cu succes
- [ ] Suportă long-polling (wait param) pentru eficiență
- [ ] pytest tests pass
## 4. Cerințe Funcționale
1. [REQ-001] Sistemul trebuie să accepte upload simultan de până la 100 fișiere
2. [REQ-002] Fiecare fișier trebuie să fie max 10MB
3. [REQ-003] Formatele acceptate: PDF, PNG, JPG, JPEG
4. [REQ-004] Procesarea trebuie să fie paralelă (max N workers din config)
5. [REQ-005] Bonurile procesate cu succes se salvează automat cu status DRAFT
6. [REQ-006] Bonurile cu erori rămân disponibile pentru retry/editare manuală
7. [REQ-007] Fișierele originale se atașează automat la receipt-uri
8. [REQ-008] Se generează automat accounting entries pentru fiecare receipt
9. [REQ-009] Batch-urile trebuie să fie tracked per user (nu se văd batch-urile altora)
10. [REQ-010] Job files se curăță automat după 24h (cleanup existing)
## 5. Non-Goals (Ce NU facem)
- **NU** facem aprobare automată (bonurile rămân DRAFT, nu APPROVED)
- **NU** facem machine learning pentru îmbunătățirea OCR-ului
- **NU** facem procesare pe server extern (totul rămâne local)
- **NU** facem notificări (email/push) la finalizare batch
- **NU** facem preview/editare în bulk page - pentru asta există pagina individuală
- **NU** facem undo/rollback batch (bonurile create pot fi șterse individual)
- **NU** facem scheduling (procesare imediată, nu amânată)
- **NU** facem duplicate detection (poate fi adăugat ulterior)
## 6. Considerații Tehnice
### Stack/Tehnologii
- **Frontend:** Vue 3 Composition API, PrimeVue (FileUpload, ProgressBar, DataTable)
- **Backend:** FastAPI, SQLite (job queue existent), SQLModel (receipts)
- **State:** Pinia store pentru batch progress tracking
### Patterns de Urmat
- Folosește `OCRJobQueue` existent pentru job management
- Extinde `job_worker.py` pentru auto-save la completare
- Folosește pattern-ul de polling din `OCRUploadZone.vue` existent
### ⚠️ REGULI CSS OBLIGATORII
**CITEȘTE ÎNTÂI:** `docs/ONBOARDING_CSS.md` și `docs/DESIGN_TOKENS.md`
#### Golden Rules
```
✅ Folosește DOAR design tokens - NICIODATĂ valori hardcodate
✅ Verifică CSS_PATTERNS.md înainte de a scrie CSS nou
✅ Testează în AMBELE teme (light + dark mode)
❌ NICIODATĂ :deep() în componente (PrimeVue → vendor/)
❌ NICIODATĂ duplicate CSS (write once, use everywhere)
```
#### Design Tokens Obligatorii
| Categorie | ❌ GREȘIT | ✅ CORECT |
|-----------|-----------|-----------|
| Spacing | `padding: 8px` | `padding: var(--space-sm)` |
| Spacing | `margin: 16px` | `margin: var(--space-md)` |
| Spacing | `gap: 24px` | `gap: var(--space-lg)` |
| Font size | `font-size: 14px` | `font-size: var(--text-sm)` |
| Font weight | `font-weight: 500` | `font-weight: var(--font-medium)` |
| Font weight | `font-weight: 600` | `font-weight: var(--font-semibold)` |
| Colors | `color: #111827` | `color: var(--color-text)` |
| Colors | `color: #6b7280` | `color: var(--color-text-secondary)` |
| Colors | `background: #ffffff` | `background: var(--surface-card)` |
| Colors | `background: #f0fdf4` | `background: var(--green-50)` |
| Colors | `border: #e5e7eb` | `border-color: var(--surface-border)` |
| Radius | `border-radius: 8px` | `border-radius: var(--radius-md)` |
| Shadow | `box-shadow: 0 4px 6px...` | `box-shadow: var(--shadow-md)` |
| Transition | `transition: 0.2s` | `transition: var(--transition-fast)` |
#### Spacing Scale Reference
| Token | Value | Use Case |
|-------|-------|----------|
| `--space-xs` | 4px | Icon gaps, badges |
| `--space-sm` | 8px | Between related items |
| `--space-md` | 16px | Component padding |
| `--space-lg` | 24px | Section padding, cards |
| `--space-xl` | 32px | Page margins |
#### Status Colors (pentru progres/erori)
| Status | Background | Text/Icon |
|--------|------------|-----------|
| Pending | `var(--surface-hover)` | `var(--color-text-secondary)` |
| Processing | `var(--blue-50)` | `var(--blue-600)` |
| Success | `var(--green-50)` | `var(--green-600)` |
| Error | `var(--red-50)` | `var(--red-600)` |
| Warning | `var(--yellow-50)` | `var(--yellow-600)` |
#### Dark Mode - OBLIGATORIU
- Folosește `--surface-*` tokens pentru backgrounds (auto-switch în dark mode)
- Testează cu theme toggle din header (auto → light → dark)
- NU folosi culori hardcodate care nu se schimbă în dark mode
#### Patterns Existente de Folosit
| Pattern | File | Use Case |
|---------|------|----------|
| `.card` | `cards.css` | Container principal |
| `.btn`, `.btn-primary` | `buttons.css` | Butoane |
| `.form-group`, `.form-label` | `forms.css` | Formulare |
| `.spinner` | `spinners.css` | Loading states |
| `.trend`, `.trend-up` | `trends.css` | Indicators |
| Utility classes | `utilities/` | `gap-md`, `text-center`, etc. |
#### PrimeVue Components
- FileUpload, ProgressBar, DataTable, Tag, Badge - toate sunt stilizate global
- NU adăuga `:deep()` în componente
- Modificări PrimeVue → `src/assets/css/vendor/primevue-overrides.css`
### Dependențe
- Job Queue existent: `backend/modules/data_entry/services/ocr/job_queue.py`
- Worker Pool existent: `backend/modules/data_entry/services/ocr/ocr_worker_pool.py`
- Receipt CRUD: `backend/modules/data_entry/db/crud/receipt.py`
- Attachment CRUD: `backend/modules/data_entry/db/crud/attachment.py`
### Riscuri Tehnice
- **Memory pressure:** Upload simultan de 100 fișiere × 10MB = 1GB potențial
- Mitigare: Upload secvențial intern, buffer 5 fișiere max în memorie
- **Queue overflow:** 100 jobs noi pot încetini procesarea existentă
- Mitigare: Worker pool deja limitează concurența
- **Browser crash:** Tab închis pierde tracking progress
- Mitigare: Jobs persistă în DB, refresh poate recupera status
## 7. Considerații UI/UX
### Layout
1. **Header:** Titlu "Upload Bulk Bonuri" + link înapoi la lista principală
2. **Drop Zone:** Mare, centrată, cu icon și text instructiv
3. **File List:** Tabel/listă sub drop zone cu progres per fișier
4. **Actions Bar:** Butoane "Procesează", "Golește lista" - sticky la bottom
### Layout CSS Structure
```
.bulk-upload-page
├── .page-header (pattern existent)
│ └── h1 + breadcrumb
├── .card (drop zone container)
│ └── .upload-zone (dashed border, centered)
├── .card (file list container)
│ └── DataTable sau custom list
└── .form-actions (sticky footer cu butoane)
```
### Stări UI cu CSS
| Stare | Background | Border | Elements |
|-------|------------|--------|----------|
| **Empty** | `var(--surface-card)` | `2px dashed var(--surface-border)` | Icon mare + text instructiv |
| **Drag Over** | `var(--blue-50)` | `2px dashed var(--blue-500)` | Border evidențiat |
| **Files Selected** | `var(--surface-card)` | `1px solid var(--surface-border)` | Lista + action buttons |
| **Processing** | `var(--surface-card)` | - | Spinner global + per-file status |
| **Complete** | `var(--green-50)` subtle | - | Summary card |
| **Has Errors** | - | - | Error items highlighted |
### Status Badge Styles
```css
/* Folosește PrimeVue Tag sau custom badges */
.status-pending { background: var(--surface-hover); color: var(--color-text-secondary); }
.status-processing { background: var(--blue-50); color: var(--blue-600); }
.status-success { background: var(--green-50); color: var(--green-600); }
.status-error { background: var(--red-50); color: var(--red-600); }
```
### Accesibilitate
- Keyboard navigation pentru file list
- Screen reader announcements la status changes
- Focus management la modal rezumat
- WCAG contrast ratios respectate (toate token-urile sunt compliant)
## 8. Success Metrics
- **Upload Success Rate:** > 99% (fișierele ajung în queue)
- **OCR Success Rate:** > 80% (bonuri procesate fără erori)
- **Average Processing Time:** < 8 secunde/bon
- **User Satisfaction:** Reducere timp introducere date cu 90%
## 9. Open Questions
- [ ] Limita de 100 fișiere este suficientă sau trebuie mărită?
- [ ] Dorim afișăm preview al datelor extrase înainte de save (ar contrazice "100% automat")?
- [ ] Ce facem cu bonurile duplicate detectate ulterior (același număr bon + dată)?
- [ ] Trebuie un "dry run" mode care procesează dar nu salvează?
---
## 10. Dependențe între User Stories
```
US-001 (Upload Files)
US-002 (Preview List) ← independent
US-003 (Submit Batch) → US-008 (Backend Upload Endpoint)
US-004 (Progress) ← US-010 (Backend Status Endpoint)
US-005 (Auto-Save) ← US-009 (Backend Auto-Create Service)
US-006 (Error Handling)
US-007 (Summary)
```
**Ordine recomandată de implementare:**
1. US-008: Backend - Batch Upload Endpoint
2. US-010: Backend - Batch Status Endpoint
3. US-009: Backend - Auto-Save Service
4. US-001: Frontend - Upload Zone
5. US-002: Frontend - File Preview
6. US-003: Frontend - Submit Batch
7. US-004: Frontend - Progress Tracking
8. US-005: Integration - Auto-Save Flow
9. US-006: Frontend - Error Handling
10. US-007: Frontend - Summary Modal
---
**Last Updated:** 2026-01-09
**Author:** Claude Code
**Status:** Draft - Pending Review