Update: 2026-07-30 02:27:45

This commit is contained in:
Hamza-Ayed
2026-07-30 02:27:45 +03:00
parent 5f62455113
commit ca4a7c2e70
56 changed files with 3391 additions and 709 deletions
+24 -6
View File
@@ -48,14 +48,19 @@ if ($batch['total_images'] == 0) {
json_error('لا يمكن إنهاء دفعة فارغة', 400);
}
// 2. Mark as processing
// 2. Mark as processing - atomically, so two concurrent finalize calls cannot
// both start a background worker for the same batch.
$stmt = $db->prepare("
UPDATE invoice_batches
SET status = 'processing', updated_at = NOW()
WHERE id = ?
UPDATE invoice_batches
SET status = 'processing', updated_at = NOW()
WHERE id = ? AND status = 'uploading'
");
$stmt->execute([$batchId]);
if ($stmt->rowCount() !== 1) {
json_error('تم إنهاء هذه الدفعة مسبقاً', 400);
}
// 3. Send response IMMEDIATELY to mobile app
// We manually build the response instead of using json_success() because it calls exit()
$responsePayload = json_encode([
@@ -117,7 +122,16 @@ $bgLog = function(string $msg) {
$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");
// processQueueItem() claims each row atomically, so it is safe for the cron
// worker to be walking the same batch at the same time - whoever claims a row
// first owns it and the other simply skips it.
$queueStmt = $db->prepare("
SELECT id FROM invoice_processing_queue
WHERE batch_id = ?
AND status = 'pending'
AND attempts < COALESCE(max_attempts, 3)
ORDER BY image_order ASC, created_at ASC
");
$queueStmt->execute([$batchId]);
$items = $queueStmt->fetchAll(\PDO::FETCH_COLUMN);
@@ -127,12 +141,16 @@ try {
$bgLog("Processing queue item: $queueId");
try {
$success = InvoiceProcessor::processQueueItem((int)$queueId);
$bgLog("Queue item $queueId: " . ($success ? "SUCCESS" : "FAILED"));
$bgLog("Queue item $queueId: " . ($success ? "SUCCESS" : "FAILED/SKIPPED"));
} catch (\Throwable $e) {
$bgLog("Queue item $queueId EXCEPTION: " . $e->getMessage());
}
}
// Final sweep: if every item ended up terminal, close the batch here rather
// than waiting up to a minute for the cron to notice.
InvoiceProcessor::checkBatchCompletion($batchId);
$bgLog("Background processing finished for batch: $batchId");
} catch (\Throwable $e) {