# خطة تنفيذ مُصادَق — Flutter Mobile App ## خارطة الطريق الشاملة بالأولويات والمكتبات والتفاصيل --- ## أولاً: قرارات المكتبات النهائية ### هيكل `pubspec.yaml` الكامل ```yaml dependencies: flutter: sdk: flutter # ─── State Management ─────────────────────────────── get: ^4.6.6 # GetX — state + routing + DI # ─── Networking ───────────────────────────────────── dio: ^5.4.0 # HTTP client flutter_secure_storage: ^9.0.0 # تخزين JWT + secrets آمن # ─── Local Database (Isar > Hive للمشروع هذا) ────── isar: ^3.1.0+1 # قاعدة بيانات محلية — أسرع من Hive isar_flutter_libs: ^3.1.0+1 # Flutter bindings path_provider: ^2.1.2 # مسارات الملفات # ─── Authentication & Security ────────────────────── local_auth: ^2.1.8 # Fingerprint + FaceID device_info_plus: ^10.1.0 # Device fingerprinting crypto: ^3.0.3 # HMAC-SHA256 # ─── Camera & Scanning ────────────────────────────── camerawesome: ^2.0.0 # كاميرا متقدمة مع تحكم كامل cunning_document_scanner: ^0.2.0 # Edge detection + Auto-crop (iOS/Android) image_picker: ^1.0.7 # Gallery fallback # ─── Image Processing ─────────────────────────────── image: ^4.1.7 # معالجة صور Dart-native (offline) opencv_dart: ^1.3.2 # Adaptive thresholding + advanced ops flutter_image_compress: ^2.1.0 # ضغط JPEG بجودة قابلة للضبط # ─── PDF Generation ───────────────────────────────── pdf: ^3.10.8 # إنشاء PDF بدعم العربية printing: ^5.12.0 # مشاركة + طباعة + preview # ─── Voice & Audio ────────────────────────────────── record: ^5.1.0 # تسجيل صوتي (OGG/WAV) — أخف من flutter_sound permission_handler: ^11.3.0 # صلاحيات Mic + Camera + Storage # ─── Connectivity & Background ────────────────────── connectivity_plus: ^6.0.3 # كشف الإنترنت workmanager: ^0.5.2 # Background sync jobs # ─── UI & UX ──────────────────────────────────────── cached_network_image: ^3.3.1 # صور من الشبكة shimmer: ^3.0.0 # Loading skeleton lottie: ^3.1.0 # Animations # ─── Utilities ────────────────────────────────────── uuid: ^4.3.3 # Batch IDs intl: ^0.19.0 # تنسيق التواريخ والأرقام العربية package_info_plus: ^8.0.0 # App version dev_dependencies: isar_generator: ^3.1.0+1 build_runner: ^2.4.8 flutter_test: sdk: flutter ``` ### لماذا Isar وليس Hive؟ | المعيار | Hive | Isar | |---------|------|------| | السرعة | جيد | أسرع 10x على الـ queries | | Type Safety | يدوي (TypeAdapters) | تلقائي عبر code generation | | Queries | محدود | Full query engine مع indexes | | الحجم | خفيف | أكبر قليلاً | | الاستخدام | بسيط جداً | يحتاج build_runner مرة واحدة | | الاختيار | MVP بسيط | **مُصادَق** — لأننا نحتاج queries معقدة على الفواتير المحلية | --- ## المرحلة 1 — أساس المشروع والأمان (الأسبوع 1-3) ### 1.1 هيكل مجلدات Flutter ``` lib/ ├── main.dart ├── app/ │ ├── bindings/ # GetX bindings │ ├── routes/ # AppPages + AppRoutes │ └── theme/ # AppColors, AppStyles ├── core/ │ ├── network/ │ │ ├── dio_client.dart # Dio instance │ │ └── hmac_interceptor.dart # HMAC signing │ ├── storage/ │ │ ├── isar_service.dart # Isar instance singleton │ │ └── secure_storage.dart # flutter_secure_storage wrapper │ ├── services/ │ │ ├── auth_service.dart │ │ ├── biometric_service.dart │ │ └── device_service.dart # fingerprint + hardware check │ └── constants/ │ ├── app_link.dart # API endpoints │ ├── app_color.dart │ └── box_name.dart # Isar collection names ├── features/ │ ├── auth/ # Login + OTP + Biometric │ ├── scanner/ # Batch camera + image processing │ ├── invoices/ # Invoice list + detail │ ├── voice/ # Voice assistant │ └── dashboard/ # Main dashboard └── shared/ ├── widgets/ └── models/ ``` ### 1.2 HMAC Interceptor (أمان الـ API) ```dart // core/network/hmac_interceptor.dart class HmacInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { final timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final body = options.data != null ? jsonEncode(options.data) : ''; final apiSecret = SecureStorage.read(BoxName.apiSecret); // HMAC-SHA256: timestamp + method + path + body final message = '$timestamp|${options.method}|${options.path}|$body'; final hmac = Hmac(sha256, utf8.encode(apiSecret ?? '')); final signature = hmac.convert(utf8.encode(message)).toString(); options.headers['X-Timestamp'] = timestamp; options.headers['X-Signature'] = signature; options.headers['Authorization'] = 'Bearer ${SecureStorage.read(BoxName.jwt)}'; handler.next(options); } } ``` ### 1.3 نظام المصادقة — SMS OTP + Biometric **التدفق الكامل:** ``` [المدير يُضيف محاسب + رقم هاتفه في Web Dashboard] ↓ [المحاسب يفتح التطبيق → يدخل رقم الهاتف] ↓ [Backend يرسل OTP عبر SMS (Twilio أو منصة محلية أردنية)] ↓ [المحاسب يدخل OTP → Backend يتحقق → يرجع JWT] ↓ [التطبيق يحفظ JWT + يطلب تفعيل البصمة/FaceID] ↓ [عمليات الدخول التالية: بصمة فقط (أو PIN كـ fallback)] ``` ```dart // features/auth/controllers/auth_controller.dart class AuthController extends GetxController { final _biometricService = Get.find(); final _authService = Get.find(); final _deviceService = Get.find(); Future requestOtp(String phone) async { final deviceFingerprint = await _deviceService.getFingerprint(); // إرسال OTP مع fingerprint للتحقق منه في Backend await _authService.requestOtp(phone: phone, deviceId: deviceFingerprint); } Future verifyOtp(String phone, String otp) async { final deviceFingerprint = await _deviceService.getFingerprint(); final result = await _authService.verifyOtp( phone: phone, otp: otp, deviceId: deviceFingerprint, ); // حفظ JWT في secure storage await SecureStorage.write(BoxName.jwt, result.token); await SecureStorage.write(BoxName.apiSecret, result.apiSecret); // عرض شاشة تفعيل البصمة if (await _biometricService.isAvailable()) { Get.toNamed(AppRoutes.biometricSetup); } } Future loginWithBiometric() async { final authenticated = await _biometricService.authenticate(); if (authenticated) { // التحقق من JWT مخزن + تجديده إذا انتهت صلاحيته final token = await SecureStorage.read(BoxName.jwt); if (token != null) Get.offAllNamed(AppRoutes.dashboard); } } } ``` ```dart // core/services/device_service.dart class DeviceService extends GetxService { Future getFingerprint() async { final info = DeviceInfoPlugin(); if (Platform.isAndroid) { final android = await info.androidInfo; // Combination of stable device identifiers final raw = '${android.brand}|${android.model}|${android.id}'; return sha256.convert(utf8.encode(raw)).toString(); } else { final ios = await info.iosInfo; final raw = '${ios.model}|${ios.identifierForVendor}'; return sha256.convert(utf8.encode(raw)).toString(); } } Future checkCapability() async { final info = DeviceInfoPlugin(); if (Platform.isAndroid) { final android = await info.androidInfo; final sdkInt = android.version.sdkInt; // Android 10+ (API 29) = متوافق if (sdkInt >= 29) return DeviceCapability.full; if (sdkInt >= 26) return DeviceCapability.limited; return DeviceCapability.unsupported; } // iOS 14+ = متوافق return DeviceCapability.full; } } enum DeviceCapability { full, limited, unsupported } ``` **Backend PHP — التحقق من OTP + Device:** ```php // api/auth/verify_otp.php function verifyOtpAndIssueToken($phone, $otp, $deviceId) { // 1. تحقق من OTP $storedOtp = Redis::get("otp:$phone"); if ($storedOtp !== $otp) return ['error' => 'OTP غير صحيح']; // 2. جلب المستخدم $user = User::where('phone', $phone)->first(); if (!$user) return ['error' => 'المستخدم غير موجود']; // 3. تسجيل/تحديث الجهاز UserDevice::updateOrCreate( ['user_id' => $user->id, 'device_fingerprint' => $deviceId], ['last_seen' => now(), 'is_trusted' => true] ); // 4. توليد JWT مع device_id مضمّن $payload = [ 'user_id' => $user->id, 'tenant_id' => $user->tenant_id, 'device_id' => $deviceId, 'exp' => time() + (30 * 24 * 3600) // 30 يوم ]; $token = JWT::encode($payload, config('jwt_secret'), 'HS256'); // 5. توليد API Secret خاص بهذا الجهاز للـ HMAC $apiSecret = hash('sha256', $user->id . $deviceId . config('app_key')); Redis::del("otp:$phone"); return ['token' => $token, 'api_secret' => $apiSecret]; } ``` **جدول `user_devices` المطلوب إضافته:** ```sql CREATE TABLE user_devices ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, device_fingerprint VARCHAR(64) NOT NULL, device_name VARCHAR(100), is_trusted TINYINT(1) DEFAULT 0, last_seen TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), UNIQUE KEY unique_device (user_id, device_fingerprint) ); ``` --- ## المرحلة 2 — الماسح الذكي ومعالجة الصور (الأسبوع 3-7) ### 2.1 وضع الدفعة (Batch Scan Mode) ```dart // features/scanner/controllers/batch_scanner_controller.dart class BatchScannerController extends GetxController { final images = [].obs; final isProcessing = false.obs; final processedCount = 0.obs; // Pipeline: Scan → Process → Queue → Upload Future addAndProcessImage(File rawImage) async { final capability = await DeviceService().checkCapability(); File processed; if (capability == DeviceCapability.full) { // معالجة كاملة على الجهاز processed = await ImageProcessor.processLocally(rawImage); } else { // ضغط فقط — المعالجة على السيرفر processed = await ImageProcessor.compressOnly(rawImage); } images.add(processed); processedCount.value = images.length; } Future generateAndUpload(String companyId) async { isProcessing.value = true; // 1. دمج الصور في PDF final pdf = await PdfGenerator.fromImages(images, companyName: companyId); // 2. حفظ في Isar كـ pending batch final batch = InvoiceBatch( id: Uuid().v4(), companyId: companyId, pdfPath: pdf.path, status: BatchStatus.pending, imageCount: images.length, createdAt: DateTime.now(), ); await IsarService.instance.writeTxn(() async { await IsarService.instance.invoiceBatchs.put(batch); }); // 3. محاولة رفع فورية إذا في إنترنت final hasConnection = await ConnectivityService.isConnected(); if (hasConnection) { await _uploadBatch(batch); } // وإلا WorkManager سيتولى الرفع لاحقاً isProcessing.value = false; } } ``` ### 2.2 معالجة الصور — Pipeline كامل ```dart // core/services/image_processor.dart class ImageProcessor { /// Full pipeline: Edge → Perspective → Grayscale → Threshold → Compress static Future processLocally(File input) async { // 1. تحميل الصورة final bytes = await input.readAsBytes(); img.Image image = img.decodeImage(bytes)!; // 2. تدرج الرمادي image = img.grayscale(image); // 3. زيادة التباين (يساعد OCR) image = img.adjustColor(image, contrast: 1.5); // 4. تحسين الحدة image = img.sharpen(image, amount: 1.0); // 5. Binarization بسيط عبر image package image = _applyThreshold(image, threshold: 128); // ملاحظة: لـ Adaptive Thresholding الحقيقي → استخدم opencv_dart // final mat = await opencv.imread(input.path); // final processed = await opencv.adaptiveThreshold(mat, 255, // opencv.ADAPTIVE_THRESH_GAUSSIAN_C, opencv.THRESH_BINARY, 11, 2); // 6. ضغط JPEG بجودة 85% final compressed = img.encodeJpg(image, quality: 85); // حجم متوقع: 4MB → ~200KB final output = File('${input.parent.path}/processed_${input.path.split('/').last}'); await output.writeAsBytes(compressed); return output; } static img.Image _applyThreshold(img.Image src, {int threshold = 128}) { return img.adjustColor(src, hueRotation: 0, saturation: 0, exposure: 0, gamma: threshold / 128.0, ); } static Future compressOnly(File input) async { final compressed = await FlutterImageCompress.compressAndGetFile( input.path, '${input.parent.path}/compressed_${input.path.split('/').last}', quality: 70, minWidth: 1200, minHeight: 1600, ); return compressed ?? input; } } ``` ### 2.3 توليد PDF من الصور ```dart // core/services/pdf_generator.dart class PdfGenerator { static Future fromImages( List images, { required String companyName, }) async { final pdf = pw.Document(); for (final imageFile in images) { final imageBytes = await imageFile.readAsBytes(); final pdfImage = pw.MemoryImage(imageBytes); pdf.addPage(pw.Page( pageFormat: PdfPageFormat.a4, margin: const pw.EdgeInsets.all(0), build: (ctx) => pw.Image(pdfImage, fit: pw.BoxFit.contain), )); } final date = DateTime.now().toString().split(' ')[0]; final filename = 'فواتير_${companyName}_$date.pdf'; final dir = await getApplicationDocumentsDirectory(); final file = File('${dir.path}/$filename'); await file.writeAsBytes(await pdf.save()); return file; } } ``` ### 2.4 Isar Schema للـ Offline Queue ```dart // shared/models/invoice_batch.dart @collection class InvoiceBatch { Id get isarId => fastHash(id); late String id; // UUID late String companyId; late String pdfPath; late int imageCount; @enumerated late BatchStatus status; // pending | uploading | done | failed late DateTime createdAt; DateTime? uploadedAt; int retryCount = 0; String? errorMessage; } @collection class LocalInvoice { Id get isarId => fastHash(id); late String id; late String batchId; late String imagePath; late String companyId; @enumerated late InvoiceStatus status; // pending | extracted | validated | submitted // بيانات مستخرجة من AI String? invoiceNumber; double? totalAmount; double? taxAmount; String? supplierName; late DateTime createdAt; bool isSynced = false; } ``` --- ## المرحلة 3 — المساعد الصوتي (الأسبوع 7-10) ### 3.1 معمارية كاملة: Record → Groq → Gemini → Command ```dart // features/voice/controllers/voice_controller.dart class VoiceController extends GetxController { final _recorder = AudioRecorder(); final isRecording = false.obs; final lastCommand = ''.obs; final commandResult = Rxn(); static const maxDurationSeconds = 15; Timer? _autoStopTimer; Future startRecording() async { if (!await _recorder.hasPermission()) return; final dir = await getTemporaryDirectory(); final path = '${dir.path}/voice_${DateTime.now().millisecondsSinceEpoch}.m4a'; await _recorder.start( const RecordConfig(encoder: AudioEncoder.aacLc, sampleRate: 16000), path: path, ); isRecording.value = true; // إيقاف تلقائي بعد 15 ثانية _autoStopTimer = Timer(Duration(seconds: maxDurationSeconds), stopAndProcess); } Future stopAndProcess() async { _autoStopTimer?.cancel(); if (!isRecording.value) return; final path = await _recorder.stop(); isRecording.value = false; if (path == null) return; final file = File(path); final result = await VoiceService.processCommand(file); commandResult.value = result; await _executeCommand(result); } Future _executeCommand(VoiceCommandResult result) async { switch (result.action) { case VoiceAction.listInvoices: Get.toNamed(AppRoutes.invoices, arguments: result.params); break; case VoiceAction.checkQuota: Get.toNamed(AppRoutes.subscription); break; case VoiceAction.openScanner: Get.toNamed(AppRoutes.scanner, arguments: result.params); break; // ... باقي الأوامر } } } ``` ### 3.2 Voice Service — Groq STT + Gemini Intent ```dart // features/voice/services/voice_service.dart class VoiceService { static Future processCommand(File audioFile) async { // Step 1: STT via Groq Whisper final text = await _groqTranscribe(audioFile); if (text.isEmpty) return VoiceCommandResult.failed('لم أفهم الأمر'); // Step 2: Intent parsing via Gemini Flash Lite Latest final intent = await _parseIntent(text); return intent; } static Future _groqTranscribe(File audio) async { final dio = DioClient.instance; // بدون HMAC (Groq خارجي) final formData = FormData.fromMap({ 'file': await MultipartFile.fromFile(audio.path, filename: 'voice.m4a'), 'model': 'whisper-large-v3-turbo', 'language': 'ar', 'response_format': 'text', }); final response = await Dio().post( 'https://api.groq.com/openai/v1/audio/transcriptions', data: formData, options: Options(headers: { 'Authorization': 'Bearer ${AppConfig.groqApiKey}', }), ); return response.data.toString().trim(); } static Future _parseIntent(String text) async { const systemPrompt = ''' أنت محلل أوامر لنظام مُصادَق للفوترة الأردني. استخرج النية والمعاملات من النص وأرجع JSON فقط. الأوامر المتاحة: - list_invoices: { company?: string, from?: date, to?: date, status?: string } - check_quota: {} - open_scanner: { company?: string } - search_invoice: { amount?: number, company?: string, number?: string } - get_report: { type: "tax"|"monthly", period?: string } - check_status: { invoice_id?: string, company?: string } - export_pdf: { invoice_id?: string, company?: string } - navigate: { screen: string } أرجع: { "action": "...", "params": {...}, "confirmation": "نص قصير تأكيد" } '''; final response = await Dio().post( 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-lite-latest:generateContent', queryParameters: {'key': AppConfig.geminiApiKey}, data: { 'contents': [{'parts': [{'text': text}]}], 'systemInstruction': {'parts': [{'text': systemPrompt}]}, 'generationConfig': { 'responseMimeType': 'application/json', 'maxOutputTokens': 200, }, }, ); final json = jsonDecode( response.data['candidates'][0]['content']['parts'][0]['text'] ); return VoiceCommandResult( action: VoiceAction.fromString(json['action']), params: json['params'] ?? {}, confirmation: json['confirmation'] ?? '', rawText: text, ); } } ``` ### 3.3 الأوامر الصوتية الـ 10 ```dart enum VoiceAction { listInvoices, // "فواتير شركة X لشهر كذا" checkQuota, // "كم باقي من رصيدي" openScanner, // "صور فاتورة لشركة X" searchInvoice, // "ابحث عن فاتورة 150 دينار" getReport, // "تقرير ضريبة الشهر الماضي" checkStatus, // "حالة فاتورة شركة X" exportPdf, // "حوّل فاتورة 300 لـ PDF" listRejected, // "الفواتير المرفوضة هذا الأسبوع" subscriptionInfo,// "حالة اشتراكي" navigate, // "افتح لوحة التحكم" unknown, } ``` --- ## المرحلة 4 — AI Pre-Audit الضريبي (الأسبوع 10-13) ### 4.1 تحديث Backend — نظام Queue للفواتير **المطلوب إضافته في الـ schema:** ```sql -- جدول جديد: invoice_processing_queue CREATE TABLE invoice_processing_queue ( id INT AUTO_INCREMENT PRIMARY KEY, batch_id VARCHAR(36) NOT NULL, invoice_id INT, -- بعد الاستخراج tenant_id INT NOT NULL, image_path VARCHAR(500) NOT NULL, status ENUM('pending','processing','done','failed') DEFAULT 'pending', attempts INT DEFAULT 0, error_message TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, processed_at TIMESTAMP NULL, INDEX idx_status_tenant (status, tenant_id), INDEX idx_batch (batch_id) ); -- جدول جديد: invoice_batches CREATE TABLE invoice_batches ( id VARCHAR(36) PRIMARY KEY, tenant_id INT NOT NULL, company_id INT NOT NULL, pdf_path VARCHAR(500), total_images INT, processed_images INT DEFAULT 0, status ENUM('uploading','processing','done','partial_fail') DEFAULT 'uploading', created_by INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP NULL ); ``` ### 4.2 تحديث InvoiceExtractionService.php ```php // services/InvoiceExtractionService.php class InvoiceExtractionService { // قواعد الضريبة الأردنية private array $taxRules = [ // معفيات صفر 'zero_rated' => ['أدوية', 'دواء', 'خبز', 'طحين', 'سكر', 'أرز'], // ضريبة خاصة 'special' => ['سيارات' => 0.15, 'سجائر' => 0.32, 'كحول' => 0.30], // معدل عام 'standard_rate' => 0.16, // 16% ضريبة مبيعات أردن ]; public function extractAndAudit(string $imagePath, int $tenantId): array { // 1. استخراج البيانات من Gemini Vision $extracted = $this->extractWithVision($imagePath); // 2. AI Pre-Audit $audit = $this->performPreAudit($extracted); // 3. حساب Hash لمنع التكرار $hash = $this->calculateInvoiceHash($extracted); // 4. التحقق من التكرار $duplicate = Invoice::where('invoice_hash', $hash) ->where('tenant_id', $tenantId)->first(); if ($duplicate) { $audit['warnings'][] = [ 'code' => 'DUPLICATE_INVOICE', 'message' => 'هذه الفاتورة مرفوعة مسبقاً (رقم ' . $duplicate->id . ')', 'severity' => 'critical' ]; } return [ 'extracted' => $extracted, 'audit' => $audit, 'hash' => $hash, 'jofotara_readiness' => $this->assessJoFotaraReadiness($extracted, $audit), ]; } private function performPreAudit(array $data): array { $warnings = []; $errors = []; // فحص الضريبة if (isset($data['items'])) { foreach ($data['items'] as $item) { $expectedTax = $this->calculateExpectedTax($item['name'], $item['amount']); if (abs($item['tax'] - $expectedTax) > 0.01) { $warnings[] = [ 'code' => 'TAX_MISMATCH', 'message' => "ضريبة غير صحيحة لـ '{$item['name']}': المتوقع {$expectedTax} الموجود {$item['tax']}", 'severity' => 'high' ]; } } } // فحص اكتمال البيانات الإلزامية $required = ['supplier_name', 'invoice_number', 'invoice_date', 'total_amount']; foreach ($required as $field) { if (empty($data[$field])) { $errors[] = ['code' => 'MISSING_FIELD', 'field' => $field, 'severity' => 'critical']; } } return ['warnings' => $warnings, 'errors' => $errors]; } private function assessJoFotaraReadiness(array $data, array $audit): array { $errorCount = count($audit['errors']); $highWarnings = array_filter($audit['warnings'], fn($w) => $w['severity'] === 'high'); $score = 100 - ($errorCount * 30) - (count($highWarnings) * 15); return [ 'score' => max(0, $score), 'ready' => $score >= 70, 'message' => $score >= 70 ? 'جاهزة للإرسال' : 'تحتاج مراجعة قبل الإرسال' ]; } } ``` --- ## المرحلة 5 — فحص الجهاز + Offline Architecture (الأسبوع 12-14) ### 5.1 Device Capability Check عند أول تشغيل ```dart // features/auth/views/device_check_screen.dart class DeviceCheckScreen extends GetView { @override Widget build(BuildContext context) { return Scaffold( body: Obx(() { if (controller.isChecking.value) { return _buildCheckingUI(); } return _buildResultUI(controller.capability.value); }), ); } Widget _buildResultUI(DeviceCapability cap) { return switch (cap) { DeviceCapability.full => _buildFullModeUI(), DeviceCapability.limited => _buildLimitedModeUI(), DeviceCapability.unsupported => _buildUnsupportedUI(), }; } } class DeviceCheckController extends GetxController { final isChecking = true.obs; final capability = DeviceCapability.full.obs; @override void onInit() { super.onInit(); _runCheck(); } Future _runCheck() async { final result = await DeviceService().checkCapability(); // حفظ النتيجة محلياً GetStorage().write(BoxName.deviceCapability, result.name); capability.value = result; isChecking.value = false; } } ``` ### 5.2 WorkManager للـ Background Sync ```dart // main.dart void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { switch (task) { case 'syncPendingBatches': await _syncPendingBatches(); break; case 'retryFailedUploads': await _retryFailedUploads(); break; } return Future.value(true); }); } // في main(): Workmanager().initialize(callbackDispatcher); Workmanager().registerPeriodicTask( 'syncTask', 'syncPendingBatches', frequency: const Duration(minutes: 15), constraints: Constraints(networkType: NetworkType.connected), ); ``` --- ## الملخص التنفيذي بالأسابيع | الأسبوع | التركيز | المخرجات | |---------|---------|----------| | 1-2 | هيكل المشروع + HMAC + Isar setup | بنية مجلدات نظيفة، DioClient محمي | | 2-3 | SMS OTP + Biometric Auth | تسجيل دخول آمن كامل | | 3-5 | Batch Scanner + camerawesome | تصوير متسلسل يعمل | | 5-7 | Image Processing (image + opencv) | صور مضغوطة ومحسّنة | | 7-8 | PDF Generator + Offline Queue | دفعات محفوظة محلياً | | 8-10 | Voice: Record → Groq → Gemini | 10 أوامر صوتية تعمل | | 10-12 | AI Pre-Audit في Backend | تحذيرات ضريبية قبل JoFotara | | 12-13 | Device Check + WorkManager Sync | مزامنة خلفية موثوقة | | 13-14 | Testing + Beta (10 مكاتب عمان) | إطلاق تجريبي | --- ## قرارات نهائية سريعة | القرار | الاختيار | السبب | |--------|---------|-------| | STT | Groq Whisper | الأرخص 9x + الأسرع | | Intent LLM | Gemini 2.0 Flash Lite Latest | ما ينوقف، مجاني بحد معقول | | Local DB | Isar | Queries معقدة على الفواتير | | Camera | camerawesome | تحكم أفضل في Batch Mode | | Image Processing | image (Dart) + opencv_dart | مرونة: Dart للبسيط، OpenCV للمعقد | | PDF | pdf (pub.dev) | دعم RTL + عربية | | Recording | record | أخف من flutter_sound، كافٍ للمهمة | | Background Sync | WorkManager | الأكثر موثوقية لـ iOS + Android | | Security Storage | flutter_secure_storage | Keychain/Keystore native |