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