diff --git a/app/cron/process_batches.php b/app/cron/process_batches.php index 15c4459..e72480a 100644 --- a/app/cron/process_batches.php +++ b/app/cron/process_batches.php @@ -12,6 +12,7 @@ use App\Core\Database; use App\Core\AI; use App\Core\Encryption; use App\Middleware\QuotaMiddleware; +use App\Services\NotificationService; // Prevent multiple instances (Lock file) $lockFile = STORAGE_PATH . '/logs/process_batches.lock'; @@ -138,12 +139,18 @@ try { } // Check if batch is complete - $stmt = $db->prepare("SELECT total_images, processed_images FROM invoice_batches WHERE id = ?"); + $stmt = $db->prepare("SELECT total_images, processed_images, uploaded_by FROM invoice_batches WHERE id = ?"); $stmt->execute([$batchId]); $batch = $stmt->fetch(); if ($batch && $batch['processed_images'] >= $batch['total_images']) { $db->prepare("UPDATE invoice_batches SET status = 'done', completed_at = NOW() WHERE id = ?")->execute([$batchId]); echo "Batch $batchId Complete!\n"; + + // Send Notification to user + $notifier = new NotificationService(); + $title = "اكتملت معالجة الدفعة"; + $body = "تمت معالجة جميع الفواتير بنجاح. يمكنك الآن مراجعتها وتدقيقها في لوحة التحكم قبل اعتمادها."; + $notifier->sendNotification($batch['uploaded_by'], $title, $body, ['batch_id' => $batchId]); } } diff --git a/app/modules_app/batches/finalize.php b/app/modules_app/batches/finalize.php index 996e4c8..ec75786 100644 --- a/app/modules_app/batches/finalize.php +++ b/app/modules_app/batches/finalize.php @@ -49,16 +49,16 @@ if ($batch['total_images'] == 0) { json_error('لا يمكن إنهاء دفعة فارغة', 400); } -// 2. Mark as done since AI processing is now synchronous +// 2. Mark as processing $stmt = $db->prepare(" UPDATE invoice_batches - SET status = 'done', completed_at = NOW(), updated_at = NOW() + SET status = 'processing', updated_at = NOW() WHERE id = ? "); $stmt->execute([$batchId]); json_success([ 'batch_id' => $batchId, - 'status' => 'done', + 'status' => 'processing', 'total_images' => $batch['total_images'] -], 'تم رفع ومعالجة الدفعة بنجاح'); +], 'تم إنهاء الدفعة بنجاح وإرسالها للمعالجة'); diff --git a/app/modules_app/batches/upload_image.php b/app/modules_app/batches/upload_image.php index b116815..3bc4dbd 100644 --- a/app/modules_app/batches/upload_image.php +++ b/app/modules_app/batches/upload_image.php @@ -11,9 +11,6 @@ declare(strict_types=1); use App\Core\Database; use App\Middleware\AuthMiddleware; -use App\Core\AI; -use App\Core\Encryption; -use App\Middleware\QuotaMiddleware; $decoded = AuthMiddleware::check(); $tenantId = $decoded['tenant_id']; @@ -77,77 +74,23 @@ 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()); - } -} +// 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, processed_images = processed_images + 1, updated_at = NOW() + SET total_images = total_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 = ?"); +// Count uploaded so far +$stmt = $db->prepare("SELECT COUNT(*) FROM invoice_processing_queue WHERE batch_id = ?"); $stmt->execute([$batchId]); $uploadedCount = (int)$stmt->fetchColumn(); diff --git a/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart b/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart index 5da487e..8012580 100644 --- a/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart +++ b/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; @@ -95,10 +96,24 @@ class ScannerController extends GetxController { ); if (batchId != null) { - AppSnackbar.showSuccess('نجاح', 'تم رفع ${capturedImages.length} فواتير للمعالجة بنجاح'); capturedImages.clear(); uploadProgress.value = 0.0; - Get.back(); // Go back to dashboard or previous screen + + Get.defaultDialog( + title: 'جاري المعالجة ⏳', + middleText: 'تم استلام الفواتير بنجاح وسيتم إشعارك فور الانتهاء من تدقيقها عبر الذكاء الاصطناعي.', + textConfirm: 'حسناً', + confirmTextColor: Colors.white, + buttonColor: const Color(0xFF0F4C81), + onConfirm: () { + if (Get.isDialogOpen ?? false) Get.back(); // close dialog + Get.back(); // go back to dashboard + }, + barrierDismissible: false, + titleStyle: const TextStyle(fontFamily: 'El Messiri', fontWeight: FontWeight.bold, fontSize: 18), + middleTextStyle: const TextStyle(fontFamily: 'El Messiri', fontSize: 14), + radius: 12, + ); } else { AppSnackbar.showError('خطأ', 'فشل رفع الفواتير، يرجى المحاولة لاحقاً'); }