prepare(" SELECT id, tenant_id, status, total_images FROM invoice_batches WHERE id = ? AND uploaded_by = ? "); $stmt->execute([$batchId, $userId]); $batch = $stmt->fetch(); if (!$batch || ($decoded['role'] !== 'super_admin' && $batch['tenant_id'] !== $tenantId)) { json_error('الدفعة غير موجودة', 404); } if ($batch['status'] !== 'uploading') { json_error('تم إنهاء هذه الدفعة مسبقاً', 400); } if ($batch['total_images'] == 0) { json_error('لا يمكن إنهاء دفعة فارغة', 400); } // 2. Mark as processing $stmt = $db->prepare(" UPDATE invoice_batches SET status = 'processing', updated_at = NOW() WHERE id = ? "); $stmt->execute([$batchId]); // 3. Send response IMMEDIATELY to mobile app // We manually build the response instead of using json_success() because it calls exit() $responsePayload = json_encode([ 'success' => true, 'data' => [ 'batch_id' => $batchId, 'status' => 'processing', 'total_images' => $batch['total_images'] ], 'message' => 'تم إنهاء الدفعة بنجاح وبدء المعالجة الفورية', 'timestamp' => date('c') ], JSON_UNESCAPED_UNICODE); // Set headers header('Content-Type: application/json; charset=utf-8'); header('Content-Length: ' . strlen($responsePayload)); http_response_code(200); // Flush ALL output buffers to send response to client NOW echo $responsePayload; // Flush PHP output buffers if (ob_get_level() > 0) { ob_end_flush(); } flush(); // Log the API call for app.log (mimicking json_response behavior) $logEntry = sprintf( "API %s %s | 200 | OK | %s", $_SERVER['REQUEST_METHOD'] ?? 'CLI', $_SERVER['REQUEST_URI'] ?? '', 'تم إنهاء الدفعة بنجاح وبدء المعالجة الفورية' ); error_log($logEntry); @file_put_contents( STORAGE_PATH . '/logs/app.log', "[" . date('Y-m-d H:i:s') . "] " . $logEntry . "\n", FILE_APPEND ); // 4. Tell PHP-FPM: "The client response is done. But keep this PHP process alive." if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } // 5. Now process in the background (client has already received the response) ignore_user_abort(true); set_time_limit(300); // 5 minutes max $bgLog = function(string $msg) { @file_put_contents( STORAGE_PATH . '/logs/worker.log', "[" . date('Y-m-d H:i:s') . "] [finalize-bg] " . $msg . "\n", FILE_APPEND ); }; $bgLog("Background processing started for batch: $batchId"); try { $queueStmt = $db->prepare("SELECT id FROM invoice_processing_queue WHERE batch_id = ? AND status = 'pending' ORDER BY created_at ASC"); $queueStmt->execute([$batchId]); $items = $queueStmt->fetchAll(\PDO::FETCH_COLUMN); $bgLog("Found " . count($items) . " pending item(s) for batch $batchId"); foreach ($items as $queueId) { $bgLog("Processing queue item: $queueId"); try { $success = InvoiceProcessor::processQueueItem((int)$queueId); $bgLog("Queue item $queueId: " . ($success ? "SUCCESS" : "FAILED")); } catch (\Throwable $e) { $bgLog("Queue item $queueId EXCEPTION: " . $e->getMessage()); } } $bgLog("Background processing finished for batch: $batchId"); } catch (\Throwable $e) { $bgLog("FATAL ERROR in background processing: " . $e->getMessage()); } exit;