Skip to content
msalemmlemine-sketchPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

تام موبايل (tam_mobile)

تطبيق أندرويد يعمل بدون إنترنت بالكامل لإدارة منتسبي نقابة تحالف أساتذة موريتانيا، مبني بـ Flutter + Dart + SQLite، كبديل لنظام tam_members_registry_v1 (PHP/MySQL) الأصلي.

تحديث أخير: أُضيف خط عربي حقيقي (Amiri Regular/Bold، مرخَّص SIL OFL) مضمَّنًا فعليًا في assets/fonts/ لتصدير PDF، بالإضافة إلى مجموعة اختبارات تكامل حقيقية (test/integration/) تشغّل قاعدة بيانات SQLite فعلية (عبر sqflite_common_ffi) لا مجرد دوال معزولة، وتغطي: المنتسبين والمؤسسات والمقاطعات، الاشتراكات وترحيل رصيد الصندوق بين السنوات، تسجيل الدخول والقفل بعد محاولات فاشلة، النسخ الاحتياطي/الاستعادة (بما في ذلك اكتشاف وإصلاح علّة حقيقية أثناء كتابة الاختبار — انظر القسم أدناه)، واستيراد CSV كاملًا من التحليل حتى الاعتماد النهائي.

1. طريقة تشغيل المشروع

هذا المستودع يحتوي على lib/ وpubspec.yaml وtest/ فقط (بيئة التطوير الحالية لا تملك Flutter SDK لتوليد مجلدات android/ وios/ الأصلية). لتشغيله:

# أنشئ هيكل مشروع Flutter فارغ يحتوي على android/ الرسمية
flutter create --org com.tam.mobile --platforms=android tam_mobile_shell

# انسخ محتوى هذا المستودع فوقه (استبدل lib/ و pubspec.yaml و test/)
cp -r lib pubspec.yaml test tam_mobile_shell/
cd tam_mobile_shell

flutter pub get
flutter run

2. طريقة بناء APK

flutter build apk --release
# الناتج: build/app/outputs/flutter-apk/app-release.apk

flutter build appbundle --release
# الناتج: build/app/outputs/bundle/release/app-release.aab

3. مكان قاعدة البيانات

قاعدة البيانات SQLite تُنشأ تلقائيًا عند أول تشغيل داخل مجلد بيانات التطبيق الخاص (getApplicationDocumentsDirectory())، باسم tam.db. لا صلاحية إنترنت مطلوبة، ويمكن حذف صلاحية INTERNET من android/app/src/main/AndroidManifest.xml بأمان.

4/5. النسخ الاحتياطي والاستعادة

مبنيان فعليًا (lib/services/backup_service.dart + lib/screens/settings/backup_screen.dart، من تبويب "المزيد"):

  • نسخة احتياطية: ينسخ ملف tam.db الحالي إلى ملف باسم TAM_backup_YYYY-MM-DD.db، ثم يفتح قائمة المشاركة النظامية (واتساب/تيليجرام/بلوتوث/أي تطبيق آخر) — بدون إنترنت.
  • الاستعادة: يختار المستخدم ملفًا عبر منتقي الملفات، يتحقق التطبيق من توقيع SQLite (Magic Bytes) قبل أي شيء، يعرض حجم الملف، يطلب تأكيدًا صريحًا، ينشئ نسخة أمان تلقائية من البيانات الحالية أولًا، ثم يستبدل الملف ويعيد فتح الاتصال.

6. استيراد البيانات القديمة

مبني فعليًا (lib/services/subscription_importer.dart + lib/screens/subscriptions/import_screen.dart، من "المزيد ← استيراد بيانات الاشتراكات"). يدعم نفس أعمدة ملف المنسقية الأصلي (السنة، الدفعة، الاسم، المبلغ، تفاصيل الاشتراك، دفع مباشر للتنفيذي...)، ويطابق تلقائيًا حسب الاسم/الدليل المالي، ويعرض معاينة قابلة للربط اليدوي قبل الاعتماد النهائي، ويمنع تكرار الاستيراد عبر بصمة SHA-256 لكل سجل (مطابقةً لـ row_hash الأصلي).

بقايا صغيرة (ليست وظائف مفقودة، بل تفاصيل تحتاج قرارًا منك)

  • تصدير Excel الحقيقي (.xlsx عبر حزمة excel الموجودة في pubspec.yaml): التقارير الحالية تُصدَّر CSV (يفتح مباشرة في إكسل) بدل .xlsx فعلي — أسرع للتنفيذ وكافٍ عمليًا، لكن أخبرني إن كنت تريد ملف .xlsx حرفيًا. السجلات التاريخية غير المرتبطة بمنتسب حالي (نوع "historical" في الاستيراد) تُحتسب في الملخص لكن لا تُدرَج كدفعة فعلية في قاعدة البيانات (لأن member_id مفتاح أجنبي إلزامي) — تبقى بيانات الملف الأصلي قابلة لإعادة الاستيراد بعد إنشاء المنتسب أو تصحيح اسمه.

7. طريقة تحديث قاعدة البيانات

أي تعديل مستقبلي في البنية يُضاف كشرط جديد داخل _onUpgrade في lib/core/database/app_database.dart مع رفع schemaVersion — لا يُعدَّل _onCreate مباشرة حتى لا تُفقد بيانات المستخدمين الحاليين.

8. بيانات الدخول الافتراضية

اسم المستخدم: admin كلمة المرور الافتراضية: admin123

يُجبر التطبيق على تغييرها عند أول تسجيل دخول (must_change_password=1 في جدول users). بعد 5 محاولات فاشلة متتالية يُقفل الحساب مؤقتًا لمدة 5 دقائق.

القرارات المتفق عليها صراحة مع صاحب المشروع

  • الصندوق: أُعيد بناؤه كمفهوم سنوي مرحّل (رصيد افتتاحي لكل سنة = الرصيد الختامي للسنة السابقة)، خلافًا لمنطق الكود الأصلي الذي كان حسابًا تراكميًا واحدًا بلا فصل سنوي. انظر lib/services/fund_service.dart.
  • الصلاحيات: أُبقيت بسيطة (مستخدم واحد بلا أدوار)، مطابقة للأصل.

بنية المشروع

lib/
  core/database/     # فتح قاعدة البيانات + الترحيلات (migrations)
  core/theme/         # Material 3 + RTL
  models/             # كائنات البيانات (Member, SubscriptionPayment...)
  repositories/        # كل SQL هنا فقط — لا SQL داخل الشاشات
  services/            # منطق الأعمال الصِّرف (حساب الديون، الصندوق)
  screens/             # الواجهات (الرئيسية، المنتسبون، الاشتراكات، الصندوق)
test/
  subscription_calculator_test.dart   # اختبارات الحسابات المالية

اختبارات التكامل (test/integration/)

هذه اختبارات حقيقية — لا تُحاكي قاعدة البيانات، بل تشغّل SQLite فعليًا عبر حزمة sqflite_common_ffi (تنفيذ أصلي بلغة C لنفس محرك SQLite) في ملف مؤقت جديد لكل اختبار، عبر نفس طبقة AppDatabase/Repositories/Services المستخدمة في التطبيق دون أي تعديل. الشيء الوحيد المُحاكى هو قناة منصّة path_provider (لأنها تحتاج جهازًا حقيقيًا)، ويُستبدل فقط بمسار المجلد المؤقت للاختبار نفسه.

الملفات:

  • member_repository_integration_test.dart — المقاطعات/المؤسسات/ المنتسبين، البحث المفهرَس، الأرشفة، وسلامة القيود (FK RESTRICT).
  • subscription_and_fund_integration_test.dart — تراكم المدفوعات، حساب مداخيل الصندوق من حصة الجهوي فقط (باستثناء الدفعات المباشرة للتنفيذي)، وأهم نقطة: ترحيل الرصيد الختامي كرصيد افتتاحي للسنة التالية بالضبط (وهي بالضبط النقطة التي طلبتَ اختبارها بعناية في المواصفات الأصلية).
  • auth_service_integration_test.dart — دخول ناجح، كلمة مرور خاطئة، تغيير كلمة المرور، والقفل بعد 5 محاولات فاشلة.
  • backup_service_integration_test.dart — نسخة احتياطية حقيقية، رفض ملف غير صالح، واستعادة تُعيد فعليًا الحالة القديمة للبيانات.
  • subscription_importer_integration_test.dart — مطابقة تلقائية، فصل رسم البطاقة، منع التكرار عند استيراد نفس الملف مرتين، والربط اليدوي لسجل غير مطابق.

علّة حقيقية اكتُشفت وأُصلحت أثناء كتابة هذه الاختبارات

اختبار الاستعادة كشف أن restoreBackup كانت تستدعي createBackup نفسها لأخذ نسخة أمان قبل الاستبدال — وبما أن اسم ملف النسخة كان مبنيًا على التاريخ فقط (TAM_backup_2026-09-12.db)، فإن نسخة الأمان الداخلية كانت أحيانًا تحمل نفس اسم النسخة التي يجري استعادتها منها، فتستبدلها بمحتوى قاعدة البيانات الحالية قبل قراءتها فعليًا — أي أن الاستعادة كانت يمكن أن تُعيد نفس البيانات الحالية بدل النسخة القديمة المطلوبة، في حال إنشاء نسختين في نفس اليوم. تم إصلاحها بجعل اسم نسخة الأمان الداخلية مبنيًا على طابع زمني دقيق (بالثانية) + رقم فريد، وبادئة مختلفة تمامًا (TAM_prerestore_) لا يمكن أبدًا أن تتطابق مع اسم أي نسخة يدوية.

تشغيل الاختبارات

flutter test

هذا الأمر يشغّل تلقائيًا كل الاختبارات (الوحدة + التكامل) لأنها كلها تحت مجلد test/ — لا حاجة لأمر منفصل لاختبارات التكامل.

Financial engine v8

  • الديون تبدأ من يناير 2026 بدل تاريخ أول دفعة.
  • الدفعة الواحدة يمكن أن تغطي شهرًا أو عدة أشهر أو السنة كاملة.
  • توزيع تلقائي من أقدم استحقاق غير مسدد، مع دعم الدفعة الجزئية.
  • payment_allocations يربط كل مبلغ مدفوع بالأشهر المغطاة.
  • subscription_rates يحفظ قيمة الاشتراك حسب السنة والشهر بدل تغيير التاريخ بأثر رجعي.
  • لا يوجد أي جدول أو خدمة لإرسال رسائل واتساب في هذا التطبيق.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages