diff --git a/scripts/ralph/prd.json b/scripts/ralph/prd.json index fa5f38e..7650388 100644 --- a/scripts/ralph/prd.json +++ b/scripts/ralph/prd.json @@ -104,8 +104,8 @@ "unifiedItems computed include joburile failed pentru afișare", "npm run build passes" ], - "passes": false, - "notes": "" + "passes": true, + "notes": "Completed in iteration 4" }, { "id": "US-406", diff --git a/scripts/ralph/progress.txt b/scripts/ralph/progress.txt index 6bb5f98..18ad321 100644 --- a/scripts/ralph/progress.txt +++ b/scripts/ralph/progress.txt @@ -21,3 +21,9 @@ Stories: 8 (US-401 to US-408) [2026-01-12 19:23:21] Working on story: US-403 [2026-01-12 19:23:21] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_3_US-403.log) [2026-01-12 19:27:05] SUCCESS: Story US-403 passed! +[2026-01-12 19:27:05] Changes committed +[2026-01-12 19:27:05] Progress: 4/8 stories completed +[2026-01-12 19:27:07] === Iteration 4/50 === +[2026-01-12 19:27:07] Working on story: US-405 +[2026-01-12 19:27:07] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_4_US-405.log) +[2026-01-12 19:31:08] SUCCESS: Story US-405 passed! diff --git a/src/modules/data-entry/stores/batchProgressStore.js b/src/modules/data-entry/stores/batchProgressStore.js index 0c4ed45..d7b8570 100644 --- a/src/modules/data-entry/stores/batchProgressStore.js +++ b/src/modules/data-entry/stores/batchProgressStore.js @@ -287,9 +287,13 @@ export const useBatchProgressStore = defineStore('batchProgress', () => { console.log('[BatchProgress] All jobs finished, stopping polling') isPolling.value = false - // US-009: Remove completed batch from localStorage - if (batchId.value) { + // US-405: Only remove batch from localStorage if there are NO failed jobs + // Failed jobs need to persist so they're visible after refresh + if (batchId.value && data.failed_count === 0) { + console.log('[BatchProgress] No failed jobs, removing batch from storage') removeActiveBatch(batchId.value) + } else if (data.failed_count > 0) { + console.log(`[BatchProgress] Batch has ${data.failed_count} failed jobs, keeping in storage for retry`) } break @@ -339,12 +343,46 @@ export const useBatchProgressStore = defineStore('batchProgress', () => { * Clear a specific batch ID from localStorage. * Called when a batch is determined to be complete (all items processed). * + * US-405: Can also be called when all failed jobs have been resolved + * (e.g., after successful retry or deletion of failed receipts). + * * @param {string} batchIdToRemove - Batch ID to remove from storage */ function clearStoredBatch(batchIdToRemove) { removeActiveBatch(batchIdToRemove) } + /** + * US-405: Check if a batch should be cleared from storage. + * Returns true if the batch has no remaining failed jobs that need attention. + * + * Call this after retry or delete operations to clean up batches + * that no longer have failed jobs. + */ + function shouldClearBatch() { + // If no jobs remain, batch can be cleared + if (jobs.value.size === 0) return true + + // If any jobs are still failed, keep the batch + for (const job of jobs.value.values()) { + if (job.status === 'failed') return false + } + + // No failed jobs remain + return true + } + + /** + * US-405: Clear batch from storage if no failed jobs remain. + * Call this after retry/delete operations to clean up localStorage. + */ + function clearBatchIfNoFailedJobs() { + if (shouldClearBatch() && batchId.value) { + console.log('[BatchProgress] No failed jobs remain, clearing batch from storage') + removeActiveBatch(batchId.value) + } + } + /** * Clear all stored batch IDs from localStorage. * Used during cleanup or when all batches are confirmed complete. @@ -365,12 +403,13 @@ export const useBatchProgressStore = defineStore('batchProgress', () => { /** * Restore jobs from a stored batch by fetching current status from API. - * Only pending/processing jobs are added to the store (completed/failed are already receipts). + * Restores pending, processing, AND failed jobs to the store. * * US-023: Called on page refresh/return to restore visibility of active jobs. + * US-405: Now includes failed jobs so users can see OCR errors after refresh. * * @param {string} storedBatchId - The batch ID to restore from - * @returns {Promise<{hasActiveJobs: boolean, jobCount: number}>} Result of restoration + * @returns {Promise<{hasActiveJobs: boolean, jobCount: number, hasFailedJobs: boolean}>} Result of restoration */ async function restoreJobsFromBatch(storedBatchId) { try { @@ -387,26 +426,35 @@ export const useBatchProgressStore = defineStore('batchProgress', () => { if (!data.jobs || data.jobs.length === 0) { console.log(`[BatchProgress] Batch ${storedBatchId} has no jobs, removing from storage`) removeActiveBatch(storedBatchId) - return { hasActiveJobs: false, jobCount: 0 } + return { hasActiveJobs: false, jobCount: 0, hasFailedJobs: false } } - // Count and filter active jobs (pending/processing only) + // US-405: Include failed jobs in restoration (not just pending/processing) + // Failed jobs need to remain visible so users can see and resolve errors + const jobsToRestore = data.jobs.filter( + job => job.status === 'pending' || job.status === 'processing' || job.status === 'failed' + ) + + // Count active jobs (pending/processing) for polling decision const activeJobs = data.jobs.filter( job => job.status === 'pending' || job.status === 'processing' ) - if (activeJobs.length === 0) { - // All jobs are completed or failed - no need to restore to UI - console.log(`[BatchProgress] Batch ${storedBatchId} has no active jobs (all completed/failed), removing from storage`) + // Count failed jobs for return value + const failedJobs = data.jobs.filter(job => job.status === 'failed') + + if (jobsToRestore.length === 0) { + // All jobs are completed - safe to remove batch from storage + console.log(`[BatchProgress] Batch ${storedBatchId} has no jobs to restore (all completed), removing from storage`) removeActiveBatch(storedBatchId) - return { hasActiveJobs: false, jobCount: 0 } + return { hasActiveJobs: false, jobCount: 0, hasFailedJobs: false } } - // Set batch ID and add active jobs to store + // Set batch ID and add jobs to store batchId.value = storedBatchId // Add jobs to the Map (merge with existing if any) - for (const job of activeJobs) { + for (const job of jobsToRestore) { jobs.value.set(job.job_id, { job_id: job.job_id, filename: job.filename, @@ -417,17 +465,22 @@ export const useBatchProgressStore = defineStore('batchProgress', () => { }) } - console.log(`[BatchProgress] Restored ${activeJobs.length} active jobs from batch ${storedBatchId}`) + console.log(`[BatchProgress] Restored ${jobsToRestore.length} jobs from batch ${storedBatchId} (${activeJobs.length} active, ${failedJobs.length} failed)`) - // Start polling for updates - if (!isPolling.value) { + // Only start polling if there are active jobs (pending/processing) + // Failed-only batches don't need polling - they're waiting for user action + if (activeJobs.length > 0 && !isPolling.value) { isPolling.value = true abortController = new AbortController() // Start polling loop in background pollLoop() } - return { hasActiveJobs: true, jobCount: activeJobs.length } + return { + hasActiveJobs: activeJobs.length > 0, + jobCount: jobsToRestore.length, + hasFailedJobs: failedJobs.length > 0 + } } catch (err) { console.error(`[BatchProgress] Error restoring batch ${storedBatchId}:`, err) @@ -437,7 +490,7 @@ export const useBatchProgressStore = defineStore('batchProgress', () => { removeActiveBatch(storedBatchId) } - return { hasActiveJobs: false, jobCount: 0 } + return { hasActiveJobs: false, jobCount: 0, hasFailedJobs: false } } } @@ -578,6 +631,10 @@ export const useBatchProgressStore = defineStore('batchProgress', () => { clearStoredBatch, clearAllStoredBatches, + // US-405: Failed Jobs Cleanup + shouldClearBatch, + clearBatchIfNoFailedJobs, + // US-023: Restore Jobs from Batch restoreJobsFromBatch,