prepare(" SELECT id, tenant_id, company_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); } // Override tenantId with the actual batch's tenantId $tenantId = $batch['tenant_id']; if ($batch['status'] !== 'uploading') { json_error('لا يمكن إضافة صور لدفعة تمت معالجتها', 400); } // 3. Validate file type. // Sniff the real type off the temp file - $_FILES[...]['type'] is supplied by the // client and can claim anything. $allowedTypes = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/heic' => 'heic', 'image/heif' => 'heif', 'application/pdf' => 'pdf', ]; $mimeType = @mime_content_type($_FILES['image']['tmp_name']) ?: ''; if (!isset($allowedTypes[$mimeType])) { json_error('نوع الملف غير مدعوم. المسموح: صور (JPG, PNG, WEBP, HEIC) و PDF', 422); } // 4. Validate file size (max 10MB) $maxSize = 10 * 1024 * 1024; if ($_FILES['image']['size'] > $maxSize) { json_error('حجم الصورة أكبر من 10 ميغابايت', 422); } // 4b. Enforce quota per image, not just once per batch. Checking only at // batches/create let a 50-image batch through on a single remaining credit. $queuedStmt = $db->prepare("SELECT COUNT(*) FROM invoice_processing_queue WHERE batch_id = ?"); $queuedStmt->execute([$batchId]); $alreadyQueued = (int)$queuedStmt->fetchColumn(); if ($decoded['role'] !== 'super_admin') { \App\Middleware\QuotaMiddleware::checkInvoiceQuota($tenantId, $alreadyQueued + 1); } // 5. Save file $companyId = $batch['company_id']; $uploadDir = STORAGE_PATH . '/invoices/' . $tenantId . '/' . $companyId . '/batches/' . $batchId; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } $extension = $allowedTypes[$mimeType]; $fileName = sprintf('img_%03d_%s.%s', $imageOrder, bin2hex(random_bytes(4)), $extension); $targetPath = $uploadDir . '/' . $fileName; if (!move_uploaded_file($_FILES['image']['tmp_name'], $targetPath)) { json_error('فشل في حفظ الصورة', 500); } // 6. Add to processing queue $stmt = $db->prepare(" INSERT INTO invoice_processing_queue (batch_id, tenant_id, company_id, image_path, image_order, status) VALUES (?, ?, ?, ?, ?, 'pending') "); $stmt->execute([$batchId, $tenantId, $companyId, $targetPath, $imageOrder]); // 7. Update batch image count $stmt = $db->prepare(" UPDATE invoice_batches SET total_images = total_images + 1, updated_at = NOW() WHERE id = ? "); $stmt->execute([$batchId]); // Count uploaded so far $stmt = $db->prepare("SELECT COUNT(*) FROM invoice_processing_queue WHERE batch_id = ?"); $stmt->execute([$batchId]); $uploadedCount = (int)$stmt->fetchColumn(); json_success([ 'uploaded' => $uploadedCount, 'file_name' => $fileName, ], "تم رفع الصورة بنجاح ({$uploadedCount} صور في الدفعة)");