diff --git a/app/modules_app/auth/login.php b/app/modules_app/auth/login.php index 9f702c1..11affd5 100644 --- a/app/modules_app/auth/login.php +++ b/app/modules_app/auth/login.php @@ -39,8 +39,71 @@ if (!$user || !password_verify($password, $user['password_hash'])) { json_error('بيانات الدخول غير صحيحة', 401); } -// 3. Handle device registration if provided (for mobile app login) $deviceId = $data['device_id'] ?? null; +$isReviewer = (strtolower($email) === 'reviewer@musadaq.jo'); + +if ($deviceId && !$isReviewer) { + // Generate and send WhatsApp OTP + $phone = $user['phone'] ? (\App\Core\Encryption::decrypt($user['phone']) ?: $user['phone']) : null; + if (empty($phone)) { + json_error('رقم الهاتف غير مسجل لهذا المستخدم. يرجى التواصل مع المسؤول.', 403); + } + + $phone = preg_replace('/[^0-9+]/', '', $phone); + $phone = ltrim($phone, '+'); + if (str_starts_with($phone, '07')) { + $phone = '962' . substr($phone, 1); + } elseif (str_starts_with($phone, '7')) { + $phone = '962' . $phone; + } + + $otp = str_pad((string)random_int(100000, 999999), 6, '0', STR_PAD_LEFT); + $otpHash = password_hash($otp, PASSWORD_DEFAULT); + $phoneHash = hash('sha256', $phone); + + $cacheDir = STORAGE_PATH . '/cache/otp'; + if (!is_dir($cacheDir)) { + mkdir($cacheDir, 0755, true); + } + + $otpData = [ + 'hash' => $otpHash, + 'user_id' => $user['id'], + 'attempts' => 0, + 'max_attempts' => 5, + 'expires_at' => time() + 300, + 'created_at' => time(), + ]; + + $fp = fopen($cacheDir . '/otp_' . $phoneHash . '.json', 'w'); + if ($fp) { + flock($fp, LOCK_EX); + fwrite($fp, json_encode($otpData)); + flock($fp, LOCK_UN); + fclose($fp); + } + + $whatsappService = new \App\Services\WhatsAppProxyService(); + $message = "رمز التحقق لتطبيق مُصادَق:\n*{$otp}*\n\nصالح لمدة 5 دقائق."; + $result = $whatsappService->sendMessage($phone, $message); + + if (!$result['success']) { + error_log("ERROR: Failed to send OTP WhatsApp to phone: {$phone}"); + json_error('عذراً، فشل في إرسال رمز التحقق. يرجى المحاولة مرة أخرى.', 500); + } + + if (env('APP_DEBUG', 'false') === 'true') { + error_log("DEV OTP for {$phone}: {$otp}"); + } + + json_success([ + 'otp_required' => true, + 'phone' => $phone, + ], 'تم إرسال رمز التحقق إلى رقم هاتفك المسجل عبر واتساب'); + exit; +} + +// 3. Handle device registration if provided (for mobile app login) $deviceName = $data['device_name'] ?? 'Web Browser'; $deviceSecret = null; diff --git a/app/modules_app/tenants/create.php b/app/modules_app/tenants/create.php index fba38ad..2d23447 100644 --- a/app/modules_app/tenants/create.php +++ b/app/modules_app/tenants/create.php @@ -18,8 +18,8 @@ $data = input(); $errors = Validator::validate($data, [ 'name' => 'required', 'email' => 'required|email', + 'phone' => 'required', 'manager_name' => 'required', - 'manager_email' => 'required|email', 'manager_password' => 'required' ]); @@ -43,12 +43,23 @@ try { $encryptedTenantName = \App\Core\Encryption::encrypt($data['name']); $encryptedTenantEmail = \App\Core\Encryption::encrypt($data['email']); + $phone = preg_replace('/[^0-9+]/', '', $data['phone']); + $phone = ltrim($phone, '+'); + if (str_starts_with($phone, '07')) { + $phone = '962' . substr($phone, 1); + } elseif (str_starts_with($phone, '7')) { + $phone = '962' . $phone; + } + + $encryptedPhone = \App\Core\Encryption::encrypt($phone); + $phoneHash = hash('sha256', $phone); + $stmt = $db->prepare("INSERT INTO tenants (id, name, email, phone, status, created_at) VALUES (?, ?, ?, ?, 'active', NOW())"); $stmt->execute([ $tenantId, $encryptedTenantName, $encryptedTenantEmail, - $data['phone'] ?? null + $phone ]); // Generate User UUID @@ -60,17 +71,19 @@ try { // Encrypt sensitive user data $encryptedName = \App\Core\Encryption::encrypt($data['manager_name']); - $encryptedEmail = \App\Core\Encryption::encrypt($data['manager_email']); - $emailHash = hash('sha256', strtolower($data['manager_email'])); + $encryptedEmail = \App\Core\Encryption::encrypt($data['email']); + $emailHash = hash('sha256', strtolower($data['email'])); // 2. Create Initial Manager (Admin) for this Tenant - $stmtUser = $db->prepare("INSERT INTO users (id, tenant_id, name, email, email_hash, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?, ?, 'admin', NOW())"); + $stmtUser = $db->prepare("INSERT INTO users (id, tenant_id, name, email, email_hash, phone, phone_hash, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'admin', NOW())"); $stmtUser->execute([ $userId, $tenantId, $encryptedName, $encryptedEmail, $emailHash, + $encryptedPhone, + $phoneHash, password_hash($data['manager_password'], PASSWORD_DEFAULT) ]); diff --git a/musadaq-app/android/app/src/main/AndroidManifest.xml b/musadaq-app/android/app/src/main/AndroidManifest.xml index 823bd76..2c9ae3a 100644 --- a/musadaq-app/android/app/src/main/AndroidManifest.xml +++ b/musadaq-app/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,6 @@ - diff --git a/musadaq-app/lib/features/auth/controllers/auth_controller.dart b/musadaq-app/lib/features/auth/controllers/auth_controller.dart index a019ec7..520493b 100644 --- a/musadaq-app/lib/features/auth/controllers/auth_controller.dart +++ b/musadaq-app/lib/features/auth/controllers/auth_controller.dart @@ -12,7 +12,7 @@ import '../../../core/services/push_notification_service.dart'; class AuthController extends GetxController { final Dio _dio = DioClient().client; final SecureStorage _storage = SecureStorage(); - + var isLoading = false.obs; var phone = ''.obs; @@ -23,20 +23,20 @@ class AuthController extends GetxController { return; } isLoading.value = true; - + // Normalize phone number String normalizedPhone = phoneNumber.replaceAll(RegExp(r'[^0-9+]'), ''); if (normalizedPhone.startsWith('+')) { normalizedPhone = normalizedPhone.substring(1); } if (normalizedPhone.startsWith('07')) { - normalizedPhone = '962' + normalizedPhone.substring(1); + normalizedPhone = '962${normalizedPhone.substring(1)}'; } else if (normalizedPhone.startsWith('7')) { - normalizedPhone = '962' + normalizedPhone; + normalizedPhone = '962$normalizedPhone'; } - + phone.value = normalizedPhone; - + final response = await _dio.post('auth/mobile/request-otp', data: { 'phone': normalizedPhone, }); @@ -60,12 +60,12 @@ class AuthController extends GetxController { Future verifyOtp(String otp) async { try { isLoading.value = true; - + // Get device info final deviceInfo = DeviceInfoPlugin(); String deviceId = ''; String deviceName = ''; - + if (Platform.isAndroid) { final androidInfo = await deviceInfo.androidInfo; deviceId = androidInfo.id; @@ -92,16 +92,16 @@ class AuthController extends GetxController { if (response.statusCode == 200) { AppLogger.print('OTP Verify Success. Tokens received.'); final data = response.data['data']; - + // Save secure data await _storage.saveToken(data['access_token']); await _storage.saveDeviceSecret(data['device_secret']); if (data['user']['email'] != null) { await _storage.saveEmail(data['user']['email']); } - + AppSnackbar.showSuccess('مرحباً بك', 'تم تسجيل الدخول بنجاح'); - + // Navigate to Biometric Setup (unless it's the reviewer) if (data['user']['email'] == 'reviewer@musadaq.jo') { Get.offAllNamed(AppRoutes.MAIN); @@ -111,7 +111,8 @@ class AuthController extends GetxController { } } on DioException catch (e, stackTrace) { AppLogger.error('OTP Verify Failed', e.response?.data, stackTrace); - AppSnackbar.showError('خطأ', e.response?.data['message'] ?? 'رمز التحقق غير صحيح'); + AppSnackbar.showError( + 'خطأ', e.response?.data['message'] ?? 'رمز التحقق غير صحيح'); } finally { isLoading.value = false; } @@ -120,7 +121,8 @@ class AuthController extends GetxController { Future loginWithEmail(String email, String password) async { try { if (email.trim().isEmpty || password.trim().isEmpty) { - AppSnackbar.showError('خطأ', 'الرجاء إدخال البريد الإلكتروني وكلمة المرور'); + AppSnackbar.showError( + 'خطأ', 'الرجاء إدخال البريد الإلكتروني وكلمة المرور'); return; } isLoading.value = true; @@ -129,7 +131,7 @@ class AuthController extends GetxController { final deviceInfo = DeviceInfoPlugin(); String deviceId = ''; String deviceName = ''; - + if (Platform.isAndroid) { final androidInfo = await deviceInfo.androidInfo; deviceId = androidInfo.id; @@ -150,22 +152,31 @@ class AuthController extends GetxController { }); if (response.statusCode == 200) { - AppLogger.print('Email Login Success. Tokens received.'); final data = response.data['data']; - + + if (data['otp_required'] == true) { + AppLogger.print('Email Login verification required via OTP.'); + phone.value = data['phone'] ?? ''; + AppSnackbar.showSuccess('نجاح', 'تم إرسال رمز التحقق إلى رقم هاتفك المسجل'); + Get.toNamed(AppRoutes.OTP_VERIFY); + return; + } + + AppLogger.print('Email Login Success. Tokens received.'); + // Save secure data await _storage.saveToken(data['access_token']); // Note: auth/login might not return device_secret, handle if missing if (data['device_secret'] != null) { await _storage.saveDeviceSecret(data['device_secret']); } - + if (data['user']['email'] != null) { await _storage.saveEmail(data['user']['email']); } - + AppSnackbar.showSuccess('مرحباً بك', 'تم تسجيل الدخول بنجاح'); - + // Navigate to Dashboard for reviewer, else Biometric Setup if (email == 'reviewer@musadaq.jo') { Get.offAllNamed(AppRoutes.MAIN); diff --git a/musadaq-app/lib/features/auth/views/phone_input_view.dart b/musadaq-app/lib/features/auth/views/phone_input_view.dart index 6dfca44..812462f 100644 --- a/musadaq-app/lib/features/auth/views/phone_input_view.dart +++ b/musadaq-app/lib/features/auth/views/phone_input_view.dart @@ -6,7 +6,7 @@ class PhoneInputView extends StatelessWidget { PhoneInputView({super.key}); final AuthController controller = Get.put(AuthController()); - final TextEditingController phoneController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); @override @@ -37,47 +37,38 @@ class PhoneInputView extends StatelessWidget { ), const SizedBox(height: 8), const Text( - 'أدخل رقم هاتفك أو البريد الإلكتروني لتسجيل الدخول', + 'أدخل البريد الإلكتروني وكلمة المرور لتسجيل الدخول', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey), ), const SizedBox(height: 32), TextField( - controller: phoneController, + controller: emailController, keyboardType: TextInputType.emailAddress, textDirection: TextDirection.ltr, onChanged: (val) => controller.phone.value = val, decoration: InputDecoration( - labelText: 'رقم الهاتف أو البريد الإلكتروني', - prefixIcon: const Icon(Icons.person_outline), + labelText: 'البريد الإلكتروني', + prefixIcon: const Icon(Icons.email_outlined), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), ), const SizedBox(height: 16), - Obx(() { - final isEmail = controller.phone.value.contains('@'); - if (!isEmail) return const SizedBox.shrink(); - - return Column( - children: [ - TextField( - controller: passwordController, - obscureText: true, - textDirection: TextDirection.ltr, - decoration: InputDecoration( - labelText: 'كلمة المرور', - prefixIcon: const Icon(Icons.lock_outline), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - const SizedBox(height: 24), - ], - ); - }), + TextField( + controller: passwordController, + obscureText: true, + textDirection: TextDirection.ltr, + decoration: InputDecoration( + labelText: 'كلمة المرور', + prefixIcon: const Icon(Icons.lock_outline), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + const SizedBox(height: 24), Obx(() => ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), @@ -88,22 +79,16 @@ class PhoneInputView extends StatelessWidget { onPressed: controller.isLoading.value ? null : () { - if (controller.phone.value.contains('@')) { - controller.loginWithEmail( - controller.phone.value, - passwordController.text - ); - } else { - controller.requestOtp(phoneController.text); - } + controller.loginWithEmail( + emailController.text, + passwordController.text + ); }, child: controller.isLoading.value ? const CircularProgressIndicator(color: Colors.white) - : Text( - controller.phone.value.contains('@') - ? 'تسجيل الدخول' - : 'إرسال رمز التحقق', - style: const TextStyle(fontSize: 16) + : const Text( + 'تسجيل الدخول', + style: TextStyle(fontSize: 16) ), )), ], diff --git a/musadaq-app/lib/features/tenants/controllers/add_tenant_controller.dart b/musadaq-app/lib/features/tenants/controllers/add_tenant_controller.dart index d9d9625..5eebd53 100644 --- a/musadaq-app/lib/features/tenants/controllers/add_tenant_controller.dart +++ b/musadaq-app/lib/features/tenants/controllers/add_tenant_controller.dart @@ -9,8 +9,8 @@ import 'tenants_management_controller.dart'; class AddTenantController extends GetxController { final nameController = TextEditingController(); final emailController = TextEditingController(); + final phoneController = TextEditingController(); final managerNameController = TextEditingController(); - final managerEmailController = TextEditingController(); final managerPasswordController = TextEditingController(); var isSubmitting = false.obs; @@ -20,8 +20,8 @@ class AddTenantController extends GetxController { void onClose() { nameController.dispose(); emailController.dispose(); + phoneController.dispose(); managerNameController.dispose(); - managerEmailController.dispose(); managerPasswordController.dispose(); super.onClose(); } @@ -29,11 +29,11 @@ class AddTenantController extends GetxController { Future submit() async { final name = nameController.text.trim(); final email = emailController.text.trim(); + final phone = phoneController.text.trim(); final managerName = managerNameController.text.trim(); - final managerEmail = managerEmailController.text.trim(); final managerPassword = managerPasswordController.text; - if (name.isEmpty || email.isEmpty || managerName.isEmpty || managerEmail.isEmpty || managerPassword.isEmpty) { + if (name.isEmpty || email.isEmpty || phone.isEmpty || managerName.isEmpty || managerPassword.isEmpty) { AppSnackbar.showWarning('تنبيه', 'الرجاء إدخال جميع البيانات المطلوبة'); return; } @@ -43,8 +43,8 @@ class AddTenantController extends GetxController { final response = await _dio.post('tenants/create', data: { 'name': name, 'email': email, + 'phone': phone, 'manager_name': managerName, - 'manager_email': managerEmail, 'manager_password': managerPassword, }); diff --git a/musadaq-app/lib/features/tenants/views/add_tenant_view.dart b/musadaq-app/lib/features/tenants/views/add_tenant_view.dart index d2b4e5a..5ef4383 100644 --- a/musadaq-app/lib/features/tenants/views/add_tenant_view.dart +++ b/musadaq-app/lib/features/tenants/views/add_tenant_view.dart @@ -36,29 +36,29 @@ class AddTenantView extends StatelessWidget { const SizedBox(height: 16), _buildTextField( controller: controller.emailController, - label: 'البريد الإلكتروني للمكتب', + label: 'البريد الإلكتروني للعمل', icon: Icons.email, keyboardType: TextInputType.emailAddress, isDark: isDark, ), const SizedBox(height: 24), const Text( - 'بيانات مدير المكتب', + 'بيانات مدير المكتب المسؤول', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), _buildTextField( controller: controller.managerNameController, - label: 'اسم المدير', + label: 'اسم المدير الكامل', icon: Icons.person, isDark: isDark, ), const SizedBox(height: 16), _buildTextField( - controller: controller.managerEmailController, - label: 'البريد الإلكتروني للمدير', - icon: Icons.alternate_email, - keyboardType: TextInputType.emailAddress, + controller: controller.phoneController, + label: 'رقم هاتف المدير (لتسجيل الدخول OTP)', + icon: Icons.phone, + keyboardType: TextInputType.phone, isDark: isDark, ), const SizedBox(height: 16), diff --git a/musadaq-app/pubspec.lock b/musadaq-app/pubspec.lock index 5cdb4b1..874239c 100644 --- a/musadaq-app/pubspec.lock +++ b/musadaq-app/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.dev" source: hosted - version: "88.0.0" + version: "93.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: bda3b7b55958bfd867addc40d067b4b11f7b8846d57671f5b5a6e7f9a56fe3ad + sha256: "8f89e371e2883de35cdc78f648e725fa4da5f3b6c927269f00fa68f1ea92b598" url: "https://pub.dev" source: hosted - version: "1.3.69" + version: "1.3.71" analyzer: dependency: transitive description: name: analyzer - sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f" + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.dev" source: hosted - version: "8.1.1" + version: "10.0.1" archive: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" barcode: dependency: transitive description: @@ -77,18 +77,18 @@ packages: dependency: transitive description: name: build - sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.6" build_config: dependency: transitive description: name: build_config - sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" build_daemon: dependency: transitive description: @@ -97,30 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.1" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 - url: "https://pub.dev" - source: hosted - version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" url: "https://pub.dev" source: hosted - version: "2.7.1" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" - url: "https://pub.dev" - source: hosted - version: "9.3.1" + version: "2.15.0" built_collection: dependency: transitive description: @@ -217,14 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_builder: + code_assets: dependency: transitive description: - name: code_builder - sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" url: "https://pub.dev" source: hosted - version: "4.11.1" + version: "1.0.0" collection: dependency: transitive description: @@ -293,18 +277,18 @@ packages: dependency: "direct main" description: name: cunning_document_scanner - sha256: bf590e8c8c8a4903ba7873c4f22b67e976604853f11065f594cb19b408bd25ef + sha256: de0c0705799f7d5cc9b82b67bfb8b3e965a1fbff4afbd70ea10cd1dad4f3a98c url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.4.0" dart_style: dependency: transitive description: name: dart_style - sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697 + sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.7" dbus: dependency: transitive description: @@ -405,10 +389,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" url: "https://pub.dev" source: hosted - version: "0.9.4+4" + version: "0.9.5" file_selector_platform_interface: dependency: transitive description: @@ -429,50 +413,50 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: d5a94b884dcb1e6d3430298e94bfe002238094cdfd5e29202d536ee2120f9158 + sha256: "93a5bde9775fd5adcc937f39dfa04ae0bc89c4d79bea6abc49de3f7b049d9ff6" url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.9.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce" + sha256: "4a120366dbf7d5a8ee9438978530b664b855728fb8dcc3a201017660817e555b" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "7.0.1" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: dc5096257cd67292d34d78ceeb90836f02a4be921b5f3934311a02bb2376118c + sha256: "7c98f10b8c8e5adedc0b810b66a877120696675e2c22d9ca9caca092da0d9e57" url: "https://pub.dev" source: hosted - version: "3.6.0" + version: "3.7.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: e5c93e8e7a9b0513f94bb684d2cf100e32e7dcdf2949574386b1955fc9a9b96a + sha256: "8d0dc81a31cd030170508dc3e89bfd14355b20a1b991340af5f018e37daab5d7" url: "https://pub.dev" source: hosted - version: "16.2.0" + version: "16.2.2" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "8cbb7d842e5071bba836452aff262f7db4b14bb3a0d00c1896cf176df886d65a" + sha256: "37abb0b0535c5497605ee94c12470e1ebbbe47e71a22d0c20bffcc912311f8cb" url: "https://pub.dev" source: hosted - version: "4.7.9" + version: "4.7.11" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "8750bacf50573c0383535fc3f9c58c6a2f9dff5320a16a82c30631b9dad894f1" + sha256: "54e22b43e2c26a2728a3f68c188de0f9011993ae19ae959a06d476dad935c776" url: "https://pub.dev" source: hosted - version: "4.1.5" + version: "4.1.7" fixnum: dependency: transitive description: @@ -485,10 +469,10 @@ packages: dependency: transitive description: name: flat_buffers - sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3" + sha256: "7c1de2d6eb5f3e61e5c50040841109f509deaaf2b12ec0d57b92456d9ea50345" url: "https://pub.dev" source: hosted - version: "23.5.26" + version: "25.9.23" flutter: dependency: "direct main" description: flutter @@ -570,26 +554,26 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" url: "https://pub.dev" source: hosted - version: "2.0.31" + version: "2.0.34" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "8b302d17096ba88f911b7eb317c71d5e691da60a259549f42b38c658d1776d87" + sha256: "6848263f9744072d0977347c383fb8b57d9780319a6bf5238b5a2866a029de62" url: "https://pub.dev" source: hosted - version: "10.1.0" + version: "10.2.0" flutter_secure_storage_darwin: dependency: transitive description: name: flutter_secure_storage_darwin - sha256: "3af15a3cb2bf5b8b776832bd01776f8018766aece55623176e28b406481fb320" + sha256: "67cd1ff671add31dc13e45194398187a04bb63804b37fa47866afae296d73fcb" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.1" flutter_secure_storage_linux: dependency: transitive description: @@ -636,18 +620,10 @@ packages: dependency: "direct main" description: name: freerasp - sha256: "76a3fb6f8e3fdd7d83e224866998523e7fb79d5779321983e484a6cfbf4b01b5" + sha256: "5b9a3402a7a30d928897e2264e2700a30db1df14de289f500b3a0cf50dc19df2" url: "https://pub.dev" source: hosted - version: "6.12.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" + version: "7.5.1" get: dependency: "direct main" description: @@ -672,6 +648,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + url: "https://pub.dev" + source: hosted + version: "1.0.3" html: dependency: transitive description: @@ -708,26 +692,26 @@ packages: dependency: "direct main" description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.8.0" image_picker: dependency: "direct main" description: name: image_picker - sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.13+1" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: @@ -740,10 +724,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 url: "https://pub.dev" source: hosted - version: "0.8.13" + version: "0.8.13+6" image_picker_linux: dependency: transitive description: @@ -756,10 +740,10 @@ packages: dependency: transitive description: name: image_picker_macos - sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: @@ -792,14 +776,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" json_annotation: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.12.0" leak_tracker: dependency: transitive description: @@ -844,18 +844,18 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3" + sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 url: "https://pub.dev" source: hosted - version: "1.0.52" + version: "1.0.56" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" + sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.6.1" local_auth_platform_interface: dependency: transitive description: @@ -884,18 +884,18 @@ packages: dependency: "direct main" description: name: lottie - sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950 + sha256: "8b6359a7422167014aa73ce763fa133fb832065dcc0ac4d1dec1f603a5cef7d0" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.3.3" matcher: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.18" material_color_utilities: dependency: transitive description: @@ -924,10 +924,18 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" nm: dependency: transitive description: @@ -940,26 +948,34 @@ packages: dependency: "direct main" description: name: objectbox - sha256: "3cc186749178a3556e1020c9082d0897d0f9ecbdefcc27320e65c5bc650f0e57" + sha256: "83d58e0ab5c4180a2f67086c449a4f2d1475932b31819271923dfc82a76f73c6" url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "5.3.1" objectbox_flutter_libs: dependency: "direct main" description: name: objectbox_flutter_libs - sha256: cd754766e04229a4f51250f121813d9a3c1a74fc21cd68e48b3c6085cbcd6c85 + sha256: fd4e8ed03e2b2bfeb8aa965435d7c5523ec7e962f1ffecf6e7da6c1f4d170419 url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "5.3.1" objectbox_generator: dependency: "direct dev" description: name: objectbox_generator - sha256: "71a3f6948e631be5c7160d512ad2a8cb7471cdbcf1731ec6baf2a794b82386d7" + sha256: daa95f21c7140c619ffc1abee2465541d4f0317b8c3bbf2141be1a9e5c507a1d url: "https://pub.dev" source: hosted - version: "4.3.1" + version: "5.3.1" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" octo_image: dependency: transitive description: @@ -1020,18 +1036,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" url: "https://pub.dev" source: hosted - version: "2.2.19" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -1060,10 +1076,10 @@ packages: dependency: "direct main" description: name: pdf - sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" + sha256: e47a275b267873d5944ad5f5ff0dcc7ac2e36c02b3046a0ffac9b72fd362c44b url: "https://pub.dev" source: hosted - version: "3.11.3" + version: "3.12.0" pdf_widget_wrapper: dependency: transitive description: @@ -1072,30 +1088,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" - url: "https://pub.dev" - source: hosted - version: "1.11.1" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 url: "https://pub.dev" source: hosted - version: "11.4.0" + version: "12.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "13.0.1" permission_handler_apple: dependency: transitive description: @@ -1256,6 +1264,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" record_web: dependency: transitive description: @@ -1337,66 +1353,66 @@ packages: dependency: transitive description: name: source_gen - sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.2.3" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" speech_to_text: dependency: "direct main" description: name: speech_to_text - sha256: c07557664974afa061f221d0d4186935bea4220728ea9446702825e8b988db04 + sha256: "75587f7400f485fdf166beacd471549d98fe5d58e634f708916bb65dec05d6a4" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.4.0" speech_to_text_platform_interface: dependency: transitive description: name: speech_to_text_platform_interface - sha256: a1935847704e41ee468aad83181ddd2423d0833abe55d769c59afca07adb5114 + sha256: a7e16e02853853ed7534ac2bde9a1c4f39c8879970a7974ac6ff832d4bdaa4b0 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" speech_to_text_windows: dependency: transitive description: name: speech_to_text_windows - sha256: "2c9846d18253c7bbe059a276297ef9f27e8a2745dead32192525beb208195072" + sha256: "2d1d10565b23262386b453b33656299608dc7a66784453735d6c1318f13f44d7" url: "https://pub.dev" source: hosted - version: "1.0.0+beta.8" + version: "1.0.1" sqflite: dependency: transitive description: name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.2+1" sqflite_android: dependency: transitive description: name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2+3" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465" url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.8" sqflite_darwin: dependency: transitive description: @@ -1449,10 +1465,10 @@ packages: dependency: transitive description: name: synchronized - sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.4.0+1" term_glyph: dependency: transitive description: @@ -1465,18 +1481,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.10" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" + version: "0.7.9" typed_data: dependency: transitive description: @@ -1497,18 +1505,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" url: "https://pub.dev" source: hosted - version: "6.3.20" + version: "6.3.29" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "6.4.1" url_launcher_linux: dependency: transitive description: @@ -1521,10 +1529,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -1537,10 +1545,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1569,10 +1577,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.2.0" watcher: dependency: transitive description: @@ -1646,5 +1654,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.9.0-0 <4.0.0" - flutter: ">=3.32.0" + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.38.4" diff --git a/musadaq-app/pubspec.yaml b/musadaq-app/pubspec.yaml index 85e3280..b5b47d2 100644 --- a/musadaq-app/pubspec.yaml +++ b/musadaq-app/pubspec.yaml @@ -1,7 +1,7 @@ name: musadaq_app description: Jordanian E-Invoicing Automation SaaS publish_to: 'none' -version: 1.0.5+5 +version: 1.0.6+6 environment: sdk: '>=3.2.0 <4.0.0' @@ -18,8 +18,8 @@ dependencies: flutter_secure_storage: ^10.1.0 # ─── Local Database (ObjectBox) ───────────────────── - objectbox: ^4.0.1 - objectbox_flutter_libs: any + objectbox: ^5.3.1 + objectbox_flutter_libs: ^5.3.1 path_provider: ^2.1.2 # ─── Authentication & Security ────────────────────── @@ -28,8 +28,8 @@ dependencies: crypto: ^3.0.3 # ─── Camera & Scanning ────────────────────────────── - camerawesome: ^2.0.0 - cunning_document_scanner: ^1.2.3 + camerawesome: ^2.5.0 + cunning_document_scanner: ^1.4.0 image_picker: ^1.0.7 file_picker: ^8.1.2 @@ -46,7 +46,7 @@ dependencies: # ─── Voice & Audio ────────────────────────────────── speech_to_text: ^7.3.0 record: ^6.2.0 - permission_handler: ^11.3.0 + permission_handler: ^12.0.1 # ─── Connectivity & Background ────────────────────── connectivity_plus: ^6.0.3 @@ -70,13 +70,13 @@ dependencies: shorebird_code_push: ^2.0.0 # ─── Security (Root/Jailbreak/Tamper Detection) ───── - freerasp: ^6.6.0 + freerasp: ^7.5.1 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 - objectbox_generator: any + objectbox_generator: ^5.3.1 build_runner: ^2.4.8 flutter_launcher_icons: ^0.13.1 diff --git a/public/shell.php b/public/shell.php index 1d9bdd4..0c8cafa 100644 --- a/public/shell.php +++ b/public/shell.php @@ -2728,10 +2728,9 @@ class="form-input" required>
- + + class="form-input" required>
@@ -2747,13 +2746,8 @@ -
- - -
-
- +
+
@@ -2950,7 +2944,7 @@ newUser: { name: '', email: '', password: '', role: 'accountant', tenant_id: '' }, newCompany: { name: '', tax_identification_number: '', commercial_registration_number: '', address: '', tenant_id: '' }, - newTenant: { name: '', email: '', phone: '', manager_name: '', manager_email: '', manager_password: '' }, + newTenant: { name: '', email: '', phone: '', manager_name: '', manager_password: '' }, connectData: { client_id: '', secret_key: '', income_source_sequence: '1' }, uploadData: { company_id: '' }, currentCompany: null, currentInvoice: null, companyStats: null, @@ -3121,7 +3115,7 @@ const res = await this.apiRequest('v1/tenants/create', 'POST', this.newTenant); if (res) { this.showAddTenantModal = false; - this.newTenant = { name: '', email: '', phone: '', manager_name: '', manager_email: '', manager_password: '' }; + this.newTenant = { name: '', email: '', phone: '', manager_name: '', manager_password: '' }; this.loadAll(); alert('تم إضافة المكتب المحاسبي والمدير المسؤول بنجاح'); }