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:
@@ -58,6 +58,7 @@ logger = logging.getLogger(__name__)
|
||||
# Global variables for background tasks
|
||||
telegram_bot_task = None
|
||||
ocr_job_worker_running = False
|
||||
cleanup_task_running = False
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -160,6 +161,33 @@ async def init_ocr_job_worker():
|
||||
ocr_job_worker_running = False
|
||||
|
||||
|
||||
async def init_cleanup_task():
|
||||
"""Initialize the cleanup background task for expired failed receipts (US-008).
|
||||
|
||||
Runs cleanup at startup and then every 24 hours:
|
||||
- Finds receipts with processing_status='failed' older than 7 days
|
||||
- Deletes the receipts and their attachment files from storage
|
||||
"""
|
||||
global cleanup_task_running
|
||||
|
||||
logger.info("[CLEANUP] Initializing cleanup background task...")
|
||||
try:
|
||||
from backend.modules.data_entry.services.cleanup_service import start_cleanup_task
|
||||
from backend.modules.data_entry.db.database import get_session
|
||||
|
||||
success = await start_cleanup_task(get_session)
|
||||
cleanup_task_running = success
|
||||
|
||||
if success:
|
||||
logger.info("[CLEANUP] ✅ Cleanup task started (runs daily)")
|
||||
else:
|
||||
logger.warning("[CLEANUP] ⚠️ Cleanup task failed to start")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[CLEANUP] ⚠️ Cleanup task init failed: {e}")
|
||||
cleanup_task_running = False
|
||||
|
||||
|
||||
async def run_telegram_bot():
|
||||
"""Run Telegram bot as background task."""
|
||||
logger.info("[TELEGRAM] Starting bot...")
|
||||
@@ -270,7 +298,10 @@ async def startup_event():
|
||||
# Step 3: Initialize OCR job worker (with persistent PaddleOCR)
|
||||
await init_ocr_job_worker()
|
||||
|
||||
# Step 4: Start Telegram bot as background task
|
||||
# Step 4: Initialize cleanup task for expired failed receipts (US-008)
|
||||
await init_cleanup_task()
|
||||
|
||||
# Step 5: Start Telegram bot as background task
|
||||
if settings.telegram_bot_token:
|
||||
telegram_bot_task = asyncio.create_task(run_telegram_bot())
|
||||
logger.info("[STARTUP] ✅ Telegram bot task created")
|
||||
@@ -290,13 +321,24 @@ async def startup_event():
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""Application shutdown - Cleanup resources."""
|
||||
global telegram_bot_task, ocr_job_worker_running
|
||||
global telegram_bot_task, ocr_job_worker_running, cleanup_task_running
|
||||
|
||||
logger.info("=" * 80)
|
||||
logger.info("[SHUTDOWN] Stopping ROA2WEB Unified Backend...")
|
||||
logger.info("=" * 80)
|
||||
|
||||
try:
|
||||
# Stop cleanup task (US-008)
|
||||
if cleanup_task_running:
|
||||
logger.info("[SHUTDOWN] Stopping cleanup task...")
|
||||
try:
|
||||
from backend.modules.data_entry.services.cleanup_service import stop_cleanup_task
|
||||
await stop_cleanup_task()
|
||||
cleanup_task_running = False
|
||||
logger.info("[SHUTDOWN] Cleanup task stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"[SHUTDOWN] Cleanup task error: {e}")
|
||||
|
||||
# Stop OCR job worker
|
||||
if ocr_job_worker_running:
|
||||
logger.info("[SHUTDOWN] Stopping OCR job worker...")
|
||||
|
||||
Reference in New Issue
Block a user