Files
musadaq-saas/newplan.md
2026-05-06 01:38:39 +03:00

30 KiB
Raw Blame History

خطة تنفيذ مُصادَق — Flutter Mobile App

خارطة الطريق الشاملة بالأولويات والمكتبات والتفاصيل


أولاً: قرارات المكتبات النهائية

هيكل pubspec.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)

// 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)]
// features/auth/controllers/auth_controller.dart
class AuthController extends GetxController {
  final _biometricService = Get.find<BiometricService>();
  final _authService = Get.find<AuthService>();
  final _deviceService = Get.find<DeviceService>();

  Future<void> requestOtp(String phone) async {
    final deviceFingerprint = await _deviceService.getFingerprint();
    // إرسال OTP مع fingerprint للتحقق منه في Backend
    await _authService.requestOtp(phone: phone, deviceId: deviceFingerprint);
  }

  Future<void> 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<void> loginWithBiometric() async {
    final authenticated = await _biometricService.authenticate();
    if (authenticated) {
      // التحقق من JWT مخزن + تجديده إذا انتهت صلاحيته
      final token = await SecureStorage.read(BoxName.jwt);
      if (token != null) Get.offAllNamed(AppRoutes.dashboard);
    }
  }
}
// core/services/device_service.dart
class DeviceService extends GetxService {
  Future<String> 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<DeviceCapability> 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:

// 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 المطلوب إضافته:

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)

// features/scanner/controllers/batch_scanner_controller.dart
class BatchScannerController extends GetxController {
  final images = <File>[].obs;
  final isProcessing = false.obs;
  final processedCount = 0.obs;
  
  // Pipeline: Scan → Process → Queue → Upload
  Future<void> 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<void> 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 كامل

// core/services/image_processor.dart
class ImageProcessor {
  /// Full pipeline: Edge → Perspective → Grayscale → Threshold → Compress
  static Future<File> 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<File> 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 من الصور

// core/services/pdf_generator.dart
class PdfGenerator {
  static Future<File> fromImages(
    List<File> 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

// 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

// features/voice/controllers/voice_controller.dart
class VoiceController extends GetxController {
  final _recorder = AudioRecorder();
  final isRecording = false.obs;
  final lastCommand = ''.obs;
  final commandResult = Rxn<VoiceCommandResult>();
  
  static const maxDurationSeconds = 15;
  Timer? _autoStopTimer;
  
  Future<void> 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<void> 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<void> _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

// features/voice/services/voice_service.dart
class VoiceService {
  static Future<VoiceCommandResult> 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<String> _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<VoiceCommandResult> _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

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:

-- جدول جديد: 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

// 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 عند أول تشغيل

// features/auth/views/device_check_screen.dart
class DeviceCheckScreen extends GetView<DeviceCheckController> {
  @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<void> _runCheck() async {
    final result = await DeviceService().checkCapability();
    
    // حفظ النتيجة محلياً
    GetStorage().write(BoxName.deviceCapability, result.name);
    
    capability.value = result;
    isChecking.value = false;
  }
}

5.2 WorkManager للـ Background Sync

// 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