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:
co-authored by
Claude Fable 5
parent
de4bbd00ac
commit
4ca932dd1f
@@ -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).
|
||||
|
||||
@@ -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** — عليهما مشاكل ولا أولوية لهما الآن. يُنظر فيهما لاحقاً.
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user