fix: عمود overview + قرارات المالك (الخرائط · فلسفة المال · تنظيف المستودع)

إصلاح انحدار أدخلتُه: /admin/overview كان يستعلم عن `t.created_at` وهو غير
موجود في كيان الرحلة (الاسم `requested_at`)، فترجع النقطة 500 ويُفرَّغ جدول
المستأجرين في لوحة السوبر-أدمن. فحص الأنواع لم يمسكه لأن استعلامات
QueryBuilder نصوص، والمحكّ الذي كان سيمسكه لم يُشغَّل.

قرارات المالك 2026-07-18:
- الخرائط: `intaleq_maps` حصراً (SDK انطلق على MapLibre). تصحيح قرار سابق
  خاطئ: flutter_map + latlong2 طبقة منافسة تعطي نوعَي LatLng متضاربين.
  السبب ليس عمل جوجل في سوريا — بل قِدَم بياناتها في المنطقة.
- docs/24 جديد: فلسفة المال — فصل الإيراد (شحن السائق · رسوم العمليات) عن
  الأمانة (شحن الراكب)، بمحفظتين لا بحقل حالة، ودفتر مضاف فقط.
- P موسّعة (بوابات · تسوية بالرسائل لكليك/شام كاش · رسوم بالدولة · تقارير)
  و O5 (ترتيب المهام المجدولة)، وتأجيل Android Auto/CarPlay.
- الحزم: نسخ سيرو الأحدث + live_activities لشاشة القفل في iOS.

تنظيف: إخراج 463 ملف بناء NDK من التتبّع، وإخراج شهادات التوقيع ومفتاح
App Store من المستودع (تُولَّد من جديد عند الحاجة) مع تحديث .gitignore.
الملفات باقية على القرص — أُزيلت من الفهرس فقط.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-18 14:37:26 +03:00
co-authored by Claude Fable 5
parent de4bbd00ac
commit 4ca932dd1f
475 changed files with 144 additions and 62603 deletions
+10 -4
View File
@@ -128,13 +128,19 @@ Shorebird يرقّع Dart فقط. بتوحيد Kotlin/الصلاحيات/NDK ت
| O2 | **نظام المواصلات** (`transit` + وضع الباص): خطوط · محطات · بثّ موقع الباص منفصل. add-on. |
| O3 | **محرّك التسويق** (`marketing_engine`): بوتات آلية · مولّد تعليقات Gemini · مهام مجدولة · تقارير. add-on. |
| O4 | **البوتات** (`socialBot` · `android_bot`): نسخ + تهيئة كـadd-ons مدفوعة خلف الاستحقاقات. |
| O5 | **مراجعة المهام المجدولة (cron) وترتيبها** — قرار المالك 2026-07-18. تُجرد كل المهام الدورية (تسويات · تقارير · تجميع التقييمات · تنظيف · استخبار السوق والأخبار) في مكان واحد بجدولة موحّدة عبر BullMQ (بادئة `tripz_`). المطلوب: مهمة واحدة لا تعمل مرتين عند تشغيل أكثر من نسخة، وسجلّ تنفيذ يُظهر آخر نجاح/فشل — لا مهام صامتة. |
### المجموعة P — إكمال المدفوعات (بوابات حقيقية) 💰
> **تُقرأ [24-tenant-wallet-revenue](24-tenant-wallet-revenue.md) أولاً** — فلسفة المال (إيراد مقابل أمانة) تحكم كل بند هنا.
### المجموعة P — إكمال المدفوعات (بوابات حقيقية)
| # | البند |
|---|-------|
| P1 | **بوابات فعلية**: PayMob (مصر) · CliQ (الأردن) · MTN/SyriaTel/شام كاش (سوريا) — من I3 (جدول لكل طريقة) + محوّل موحّد. |
| P2 | **webhook رسائل + Gemini** (I8): تسوية الدفع من رسائل المزوّد للأسواق بلا API. |
| P3 | **محفظة المنصة/تقرير الإيراد** (I2). |
| P0 | **محفظتا المستأجر**: `tenant_wallet` (إيراد) و`tenant_pending` (أمانات) كدفترين مضافَين فقط. الفصل بمحفظتين لا بحقل حالة — استعلامٌ ينسى الشرط يجعل المالك يسحب من مال الركّاب. |
| P1 | **بوابات فعلية**: PayMob (مصر) · CliQ (الأردن) · MTN/SyriaTel/شام كاش (سوريا) — جدول لكل وسيلة + محوّل موحّد. |
| P2 | **تسوية بالرسائل** (كليك/شام كاش بلا API): تطبيق أندرويد يرفع الرسالة **خاماً** → webhook → استخراج بـGemini → مطابقة فاتورة. تفرّد على مرجع العملية، وطابور مراجعة لما لا يُطابَق. |
| P3 | **رسم العملية إيراداً**: 35 ل.س · 5 ج.م · 20 قرشاً — **من إعداد المستأجر لا من الكود** (سيرو كتب الرقم مرتين فتناقضا وسرّب مالاً). |
| P4 | **تقارير المستأجر**: الدخل حسب الوسيلة/اليوم · شحن السائقين مقابل الأمانات · العمولات · المعلّق غير المسوّى. |
| P5 | **تصحيح `revenue` في `/admin/overview`**: يحسب عمولة الرحلات فقط؛ الإيراد الحقيقي = عمولة الرحلات + شحن السائقين + رسوم العمليات، والأمانات تُستثنى. |
### المجموعة Q — تطبيق فلاتر (إعادة بناء كاملة) 📱
> **إعادة استعمال الأصيل الثابت** (Kotlin/iOS من التطبيق القديم Ride/Tripz)، **وإعادة بناء Dart من الصفر** بـCubit+Bloc (لا GetX).
+23 -5
View File
@@ -120,11 +120,17 @@ if (Features.chat) routes.add(GoRoute(path: '/chat', builder: ...));
**القاعدة الحاكمة**: `الفعّال = BuildConfig ∩ المانيفست الريموت ∩ استحقاقات السيرفر`.
التطبيق **لا يمنح** صلاحية — يخفي فقط. من فكّك التطبيق وفعّل كل شيء يصطدم بـ403 من `FeatureGuard`.
## 6. الخرائط
## 6. الخرائط — `intaleq_maps` حصراً
- **`flutter_map` + بلاطات انطلق** — لا `google_maps_flutter`.
- البلاطات: التطبيق → انطلق **مباشرة** (حجم كبير، بلا أسرار).
- الجيوكودنغ/المسارات/الأماكن: التطبيق → **سيرفرنا** → انطلق (يحمي المفتاح ويكيّش ويقيس الحصص).
**القرار (المالك، 2026-07-18، مصحَّح):** تُستعمل حزمة **`intaleq_maps`** (SDK انطلق، إصدار 2.2.0، مبنية على MapLibre GL) — وهي حزمة المالك نفسه ومنشورة على pub.dev.
> ⚠️ **تصحيح قرار سابق**: كان مكتوباً «`flutter_map` + بلاطات انطلق». هذا **خطأ**: `flutter_map` و`latlong2` طبقة منافسة تُعرّف `LatLng` خاصاً بها، فيجتمع في المشروع نوعان بنفس الاسم. `intaleq_maps` تُصدِّر `LatLng` و`Marker` و`Polyline` و`CameraUpdate` بنفسها كبديل drop-in لـ`google_maps_flutter`. **لا يُضاف `flutter_map` ولا `latlong2` إطلاقاً.**
**لماذا انطلق لا جوجل** (تصحيح فهم شائع): خرائط جوجل **تعمل** في سوريا كـAPI وكرندر؛ غير العامل جزئياً هو تطبيق جوجل الرسمي — وهذا لا يعنينا لأن التوجيه داخل تطبيقنا. السبب الحقيقي أن **بيانات جوجل في المنطقة قديمة (لم تُحدَّث منذ 2011)**، بينما انطلق مُحدَّثة وبمستوى تجاري وأدقّ ميدانياً.
الخريطة كواجهة تأتي من الحزمة؛ ويبقى ما هو **سيرفري** على حاله: الجيوكودنغ والمسارات والأماكن تمرّ عبر **سيرفرنا** (يحمي المفتاح ويكيّش ويقيس الحصص لكل مستأجر).
**مفتاح الخريطة** يأتي من البيئة/إعداد المستأجر — لا يُكتب في الكود ولا في المستودع.
## 7. ما يُعاد استعماله من التطبيق القديم — لا يُعاد بناؤه
@@ -154,7 +160,7 @@ if (Features.chat) routes.add(GoRoute(path: '/chat', builder: ...));
| الحقن | `get_it` |
| الشبكة | `dio` |
| التنقّل | `go_router` |
| الخرائط | `flutter_map` + `latlong2` |
| الخرائط | **`intaleq_maps`** (لا `flutter_map` ولا `latlong2` ولا `google_maps_flutter`) |
| التخزين الآمن | `flutter_secure_storage` |
| الزمن الحقيقي | `socket_io_client` |
| الإشعارات | `firebase_messaging` + `flutter_local_notifications` |
@@ -190,3 +196,15 @@ if (Features.chat) routes.add(GoRoute(path: '/chat', builder: ...));
- [ ] النصوص عربية وعبر طبقة الترجمة
- [ ] لا استيراد متقاطع بين الميزات
- [ ] السيرفر يفرض ما يخفيه التطبيق (لا حماية بالواجهة وحدها)
---
## 13. الاختبار على بناء release لا debug ⚠️
**حذف كود الميزة المقفلة يحدث في بناء release/AOT فقط.** في debug (JIT) يبقى الكود كله موجوداً ولو كان العلم `const false`.
فالتحقّق من أن نسخة lite لا تحتوي فعلاً على كود النسخ الأعلى **لا يصحّ إلا على بناء release** — وأي فحص على debug يعطي نتيجة مضلِّلة. القياس المعتمد: بناء release لكل طبقة ومقارنة الحجم + تفتيش الرموز.
## 14. مؤجَّل بقرار المالك (2026-07-18)
- **Android Auto** (موجود في سيرو: `MyCarSession` · `MyCarScreen` · `MyCarAppService`) و**CarPlay** — عليهما مشاكل ولا أولوية لهما الآن. يُنظر فيهما لاحقاً.
+78
View File
@@ -0,0 +1,78 @@
# 24 — محفظة المستأجر وإيرادات المنصة (فلسفة المال)
> قرارات المالك 2026-07-18. هذا المستند يحكم **كل** ما يتعلّق بحركة المال، ويسبق أي كود في المجموعة P.
> ذو صلة: [18](18-driver-credit-commission.md) (الرصيد والعمولة) · [22](22-full-product-roadmap.md) §P · [[siro-reference]].
---
## 1. القاعدة الحاكمة: مالٌ لنا، ومالٌ نحتفظ به لغيرنا
الخطأ الذي يقع فيه أغلب التطبيقات — **وسيرو منها** — هو خلط النوعين في وعاء واحد، فيبدو الرصيد ضخماً بينما أغلبه دَيْن على المنصة لا ملكاً لها.
| النوع | مثاله | مِلك مَن؟ | في الميزانية |
|---|---|---|---|
| **إيراد** | شحن السائق لرصيده التشغيلي · عمولة كل عملية دفع | **المستأجر** (مالك التطبيق) | دخل مُحقَّق |
| **أمانة (pending)** | شحن الراكب لمحفظته · أجرة رحلة لم تُسوَّ بعد | **الراكب/السائق** | **التزام**، لا دخل |
> **المحصّلة العملية**: نقود الأمانة تمرّ عبر حسابنا لكنها ليست لنا — سنسلّمها للسائق أو نردّها للراكب. عدّها إيراداً يعني تقريراً ماليّاً كاذباً، وقراراً بالإنفاق من مالٍ ليس لنا.
## 2. محفظتا المستأجر
لكل مستأجر (مثال: `siro`) محفظتان منفصلتان — لا حقل واحد بحالتين:
| المحفظة | يدخلها | يخرج منها |
|---|---|---|
| **`tenant_wallet`** (محفظة المستأجر — «سيرو والت») | شحن الرصيد التشغيلي للسائقين · عمولة عمليات الدفع · عمولة الرحلات | سحب المالك لأرباحه |
| **`tenant_pending`** (أمانات) | شحن الراكب · أجرة محصَّلة لم تُسلَّم | تسوية للسائق · ردّ للراكب |
**لماذا محفظتان لا حقل `status`**: التقارير والسحب يقرآن `tenant_wallet` وحده. لو كان الفصل بحقلٍ في صفوف واحدة، فاستعلامٌ واحد ينسى شرط الحالة يجعل المالك يسحب من أموال الركّاب — وهذا خطأ يقع مرة واحدة ويكلّف ثقة السوق.
**دفتر مضاف فقط (append-only)** لكل محفظة: كل حركة صفٌّ جديد بمرجعها وسببها، لا تحديثٌ لرصيد. الرصيد مجموعٌ مشتق. (سيرو خزّن المال في `varchar(10)` وجمع نصوصاً — لا يُنقل هذا إطلاقاً.)
## 3. مسارات المال الثلاثة
### 3.1 شحن السائق رصيده التشغيلي ← **إيراد مباشر**
هذا هو نموذج العمل الأساسي (docs/18). السائق يدفع مقدَّماً ليعمل؛ المبلغ الحقيقي يصل → **`tenant_wallet`** فور نجاح الدفع. لا مرحلة أمانة: المال صار للمستأجر مقابل خدمة تشغيلية.
### 3.2 شحن الراكب محفظته ← **أمانة**
المال يدخل حسابنا لكنه **يبقى ملك الراكب** → **`tenant_pending`** + رصيد الراكب. حين يركب، يُخصم من رصيده وتُسوَّى الأجرة للسائق (ناقص العمولة). الجزء الوحيد الذي يتحوّل إلى إيراد هو **العمولة**.
### 3.3 عمولة عملية الدفع ← **إيراد**
رسم ثابت على **كل** عملية تحويل/دفع، محدَّد بالدولة (`countryPack`):
| الدولة | الرسم لكل عملية |
|---|---|
| سوريا | **35 ل.س** (بالعملة الجديدة) |
| مصر | **5 ج.م** |
| الأردن | **20 قرشاً** (0.20 د.أ) |
يُقتطع عند العملية ويُقيَّد في `tenant_wallet`. يُخزَّن **في إعداد المستأجر لا في الكود** — سيرو كتب الرقم مرتين في مكانين فتناقضا وتسرّب المال (docs/21).
## 4. جدول لكل وسيلة دفع + تقارير
نمط سيرو الصحيح: جدول مستقل لكل وسيلة (`cliq_invoices` · `invoices_shamcash` · `mtn_invoices` · `ecash_transactions` …) لأن لكل مزوّد حقولَه ومرجعَه وسلوكَ تأكيده. فوقها **محوّل موحّد** يعطي الباقي واجهة واحدة.
التقارير المطلوبة للوحة المستأجر: الدخل حسب الوسيلة · حسب اليوم · شحن السائقين مقابل الأمانات · العمولات المحصَّلة · المعلّق غير المسوّى.
## 5. الأسواق بلا API (كليك · شام كاش) — تسوية بالرسائل
كليك وشام كاش **لا توفّران API**. الحل المُثبَت في سيرو ويُنقل:
1. تطبيق أندرويد صغير على جهاز مخصّص يلتقط رسائل التأكيد الواردة من المزوّد.
2. يرفعها **خاماً** إلى webhook عندنا (`raw_sms_log`) — تُحفظ كما وصلت، لا تُحلَّل على الجهاز.
3. يُستخرج المبلغ والمرجع والمرسل (**Gemini** كما في `process_with_gemini.php`) ثم تُطابَق مع فاتورة معلّقة وتُسوَّى المحفظة.
**شروط لا تُتنازل عنها**: الرسالة الخام تُحفظ دائماً (أثر للنزاع) · المطابقة **مرة واحدة** بمفتاح تفرّد على مرجع العملية (وإلا سوّت رسالةٌ مكرّرة الفاتورة مرتين) · وما لا يُطابَق آلياً يذهب لطابور مراجعة بشرية لا يُهمَل بصمت.
## 6. ما لا يُنقل من سيرو (مُدقَّق — docs/21)
- **IDOR في `request_payout.php`**: هوية السائق من الطلب لا من التوكن → سحبُ رصيد الغير. (Tripz سليم: من التوكن.)
- **بلا حجز رصيد عند طلب السحب** → طلبات متزامنة تمرّ كلها = صرفٌ مزدوج.
- **رسم 3500 متناقض**: الفحص يشترط `amount + 3500` والتسوية تخصم `amount − 3500` → تسريب مال.
- **`finalizePayout` خمس كتابات بلا معاملة** → فشلٌ في المنتصف يترك سحباً نصف مسوّى.
- **المال في `varchar(10)`**.
- **جدولان متداخلان** لنفس المفهوم (`payout_requests` و`driver_withdrawal_requests`).
## 7. أثره على ما هو مبنيّ الآن
`GET /admin/overview` يحسب `revenue` = مجموع عمولة الرحلات فقط. بعد هذا المستند يصير الإيراد الحقيقي = **عمولة الرحلات + شحن السائقين + عمولات عمليات الدفع**، والأمانات تُستثنى صراحةً. النقطة تحتاج تحديثاً ضمن P.