prepare(" SELECT id, company_id, status, total_images FROM invoice_batches WHERE id = ? AND tenant_id = ? AND uploaded_by = ? "); $stmt->execute([$batchId, $tenantId, $userId]); $batch = $stmt->fetch(); if (!$batch) { json_error('الدفعة غير موجودة أو ليس لديك صلاحية', 404); } if ($batch['status'] !== 'uploading') { json_error('لا يمكن إضافة صور لدفعة تمت معالجتها', 400); } // 3. Validate file type $allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/heic', 'image/heif']; $mimeType = $_FILES['image']['type']; if (!in_array($mimeType, $allowedTypes)) { json_error('نوع الملف غير مدعوم. المسموح: JPEG, PNG, WebP, HEIC', 422); } // 4. Validate file size (max 10MB) $maxSize = 10 * 1024 * 1024; if ($_FILES['image']['size'] > $maxSize) { json_error('حجم الصورة أكبر من 10 ميغابايت', 422); } // 5. Save file $companyId = $batch['company_id']; $uploadDir = STORAGE_PATH . '/invoices/' . $tenantId . '/' . $companyId . '/batches/' . $batchId; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } $extension = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION) ?: 'jpg'; $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. Run AI Extraction $fileContent = file_get_contents($targetPath); $base64Data = base64_encode($fileContent); $extracted = AI::extractInvoiceData($base64Data, $mimeType); if (!$extracted) { // Save as raw upload if AI fails $invoiceId = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4)); $stmt = $db->prepare("INSERT INTO invoices (id, tenant_id, company_id, uploaded_by, original_file_path, status, created_at) VALUES (?, ?, ?, ?, ?, 'uploaded', NOW())"); $stmt->execute([$invoiceId, $tenantId, $companyId, $userId, $targetPath]); } else { // Save Extracted Data $db->beginTransaction(); try { $supplierTin = $extracted['supplier']['tin'] ?? ''; $invoiceNum = $extracted['invoice_number'] ?? ''; $invoiceDate = $extracted['invoice_date'] ?? ''; $validDate = (!empty($invoiceDate) && strtotime($invoiceDate)) ? $invoiceDate : null; $invoiceId = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4)); $stmt = $db->prepare(" INSERT INTO invoices ( id, tenant_id, company_id, uploaded_by, original_file_path, status, invoice_number, invoice_date, invoice_type, invoice_category, supplier_tin, supplier_name, supplier_address, buyer_tin, buyer_name, buyer_national_id, subtotal, tax_amount, discount_total, grand_total, currency_code, created_at ) VALUES ( ?, ?, ?, ?, ?, 'extracted', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW() ) "); $stmt->execute([ $invoiceId, $tenantId, $companyId, $userId, $targetPath, $invoiceNum, $validDate, $extracted['invoice_type'] ?? 'cash', $extracted['invoice_category'] ?? 'simplified', Encryption::encrypt($supplierTin), Encryption::encrypt($extracted['supplier']['name'] ?? ''), Encryption::encrypt($extracted['supplier']['address'] ?? ''), Encryption::encrypt($extracted['buyer']['tin'] ?? ''), Encryption::encrypt($extracted['buyer']['name'] ?? ''), Encryption::encrypt($extracted['buyer']['national_id'] ?? ''), $extracted['subtotal'] ?? 0, $extracted['tax_amount'] ?? 0, $extracted['discount_total'] ?? 0, $extracted['grand_total'] ?? 0, $extracted['currency_code'] ?? 'JOD' ]); if (!empty($extracted['lines']) && is_array($extracted['lines'])) { $lineStmt = $db->prepare("INSERT INTO invoice_lines (id, invoice_id, line_number, description, quantity, unit_price, tax_rate, line_total) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); foreach ($extracted['lines'] as $index => $item) { $lineId = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4)); $lineStmt->execute([ $lineId, $invoiceId, $item['line_number'] ?? ($index + 1), $item['description'] ?? 'بدون وصف', $item['quantity'] ?? 1, $item['unit_price'] ?? 0, $item['tax_rate'] ?? 0.16, $item['line_total'] ?? 0 ]); } } $db->commit(); QuotaMiddleware::incrementInvoiceUsage($tenantId); } catch (Exception $e) { $db->rollBack(); error_log("Batch Upload Error: " . $e->getMessage()); } } // 7. Update batch image count $stmt = $db->prepare(" UPDATE invoice_batches SET total_images = total_images + 1, processed_images = processed_images + 1, updated_at = NOW() WHERE id = ? "); $stmt->execute([$batchId]); // Count processed so far in batch $stmt = $db->prepare("SELECT processed_images FROM invoice_batches WHERE id = ?"); $stmt->execute([$batchId]); $uploadedCount = (int)$stmt->fetchColumn(); json_success([ 'uploaded' => $uploadedCount, 'file_name' => $fileName, ], "تم رفع الصورة بنجاح ({$uploadedCount} صور في الدفعة)");