فحص آليّ لنقاط docs/38 مقابل الكود كشف ثغرات لم تظهر بالقراءة. ## تسجيل السائق — أخطر ثغرة لم يكن موجوداً أصلاً: سائق جديد يسجّل دخوله ثم يقف. بُنيت features/onboarding كاملة — تقديم ← ملف ← مركبة ← وثائق ← انتظار الاعتماد. - الخطوة تُشتقّ من حالة الخادم لا من تقدّم محلّي: سائق يعيد تثبيت التطبيق يعود إلى حيث وقف لا إلى البداية - الاعتماد يقع على الخادم تلقائياً حين تكتمل الوثائق؛ التطبيق لا يعتمد أحداً - عند الاعتماد خروج إجباري: الدور يتغيّر على الخادم والتوكن القديم يحمل القديم، فتفشل نقاط السائق بـ403 (مصيدة docs/38 §5) - بوابة توجيه: مستخدم دوره ليس driver يُحجز في /onboarding - الوثائق تُصوَّر بالكاميرا لا من المعرض: أصعب تزويراً ## الدردشة والكوبون - Features.chat كان مفعّلاً بلا ميزة. features/chat باستطلاع كل خمس ثوان — قائمة أحداث الخادم لا تتضمّن الرسائل (docs/38 §9)، فالاستطلاع قيد خادم لا اختيار تصميمي - الكوبون: حقل في ورقة التأكيد خلف طبقتَي الميزات، يُقيَّم على الخادم ## Features.calls أُطفئ صراحةً الخادم يدعم WebRTC والتطبيق لا. عَلَم مفعّل بلا ميزة كذبٌ على القارئ التالي. ## بنية - ApiClient.upload للرفع متعدّد الأجزاء - AuthFailure → ApiFailure في core/api: يخدم المصادقة والملف والتسجيل - tool/sync_from_rider.sh: المزامنة اليدوية بين التوأمين انكسرت أربع مرات، فصارت سكربتاً واحداً يعرّف ما يملكه كل تطبيق flutter analyze نظيف · الراكب 101 ملف/7,809 سطر · السائق 110 ملف/8,116 سطر. فحوص آلية: صفر استيراد بين ميزتين · صفر عَلَم مفعّل بلا ميزة · كل نقاط العقد المخصّصة للتطبيقين مستهلكة. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
363 lines
28 KiB
Markdown
363 lines
28 KiB
Markdown
# 37 — خطة `rider_new` و `driver_new` (Cubit + باك إند NestJS)
|
|
|
|
تاريخ: 2026-08-04 · الحالة: مسودّة للموافقة
|
|
|
|
## 1. القرار المطلوب ومعناه
|
|
|
|
المطلوب: بناء تطبيقين جديدين (`rider_new`, `driver_new`) بـ Bloc/Cubit،
|
|
يعملان على **`backend-archive/` (NestJS)**، بأخذ الأصول والحزم من تطبيقات
|
|
سيرو، مع الحفاظ على `build.gradle` و`Info.plist` كي لا تنكسر هوية النشر.
|
|
|
|
هذا **عكس لقرارين مقفلين**:
|
|
|
|
| القرار | التاريخ | المرجع |
|
|
|---|---|---|
|
|
| الباك إند المعتمد = سيرو PHP، وNestJS مؤرشف | 2026-07-27 | `backend-archive/README-ARCHIVE.md`, `docs/34` |
|
|
| التطبيقات = نقل سيرو GetX بدل إعادة بناء Cubit | 2026-07-21 | `docs/30` |
|
|
|
|
سبب الأرشفة وقتها كان مكتوباً صراحة: «NestJS مكتمل تقنياً لكن **بلا تطبيقات
|
|
تعمل عليه**، وبناء تطبيقات له من الصفر أطول من نقل تطبيقات سيرو».
|
|
الخطة الحالية تقول: نعم، سنبني التطبيقات. هذا يحلّ الاعتراض — بشرط قبول أن
|
|
شغل نقل PHP يتوقّف مؤقتاً.
|
|
|
|
## 2. القاعدة الحاكمة — بناء من الصفر، وسيرو مرجع لا مصدر نسخ
|
|
|
|
قرار المالك 2026-08-04، وهو يعلو على أي اقتراح في هذه الوثيقة:
|
|
|
|
1. **كل كود دارت يُكتب من الصفر.** لا يُورَّث سطر من
|
|
`apps/rider-archive-cubit` (77 ملف) ولا `apps/driver-archive-cubit`
|
|
(53 ملف) — السبب: أداؤها وشكلها مرفوضان، ووراثتها تعني وراثة المشكلة.
|
|
تبقى للقراءة فقط ثم تُحذف بعد المرحلة 3.
|
|
2. **سيرو مرجع بصري وسلوكي، لا مصدر نسخ.** نقرأ منه: كيف بُنيت الشاشة، ما
|
|
ترتيب الخطوات، أي حالات حافة عولجت ميدانياً. لا ننسخ منه ملفات دارت.
|
|
3. **سقف الجودة = شكل تريبز الحالي أو أفضل.** الهدف ليس المعادلة بل التفوّق:
|
|
أداء (60fps، بلا rebuild زائد) وشكل (نظام تصميم واحد متّسق).
|
|
|
|
### أ. الشيء الوحيد الذي يُنقل حرفياً: الطبقة الأصلية
|
|
|
|
لأن كل شيء آخر يُكتب من جديد، تبقى الطبقة الأصلية (`android/`, `ios/`,
|
|
`shorebird.yaml`) هي الوحيدة التي تُنقل كما هي — لأنها هوية النشر ولا يجوز
|
|
إعادة اختراعها. مصدرها محسوم في §2.ج.
|
|
|
|
### ب. `pubspec` — قائمة تُبنى بالطلب، لا تُنسخ
|
|
|
|
`siro_rider/pubspec.yaml` فيه ~60 اعتماد، ومنها `get` و`get_storage` كحزمتين
|
|
**محليّتين معدّلتين** (`./packages/get`) — أي أن نسخه يجرّ GetX كاملاً معه،
|
|
وكذلك `intaleq_maps` و`secure_string_operations` المربوطة بعقد PHP.
|
|
|
|
لذلك `pubspec` الجديد يُكتب من الصفر، وتُضاف الحزمة **فقط عند الحاجة إليها في
|
|
مرحلتها** — لا قائمة مسبقة من 60 اعتماداً. سيرو يجيب على سؤال «أي حزمة نجحت
|
|
ميدانياً لهذه المهمة»، لا «ما القائمة».
|
|
|
|
- ✅ نأخذ منه: الأصول (`assets/`)، والحزم المحليّة غير المرتبطة بـ GetX
|
|
(`secure_string_operations`, `trip_overlay_plugin`, `bubble-master`)،
|
|
والحزم الأصلية (webrtc · geolocator · location · permission_handler ·
|
|
firebase · local_auth · image_cropper · live_activities · quick_actions …)
|
|
لأن هذي هي القيمة الحقيقية المجرّبة ميدانياً.
|
|
- ❌ لا ننسخ: `get`, `get_storage` — تُستبدل بـ `flutter_bloc` +
|
|
`hydrated_bloc`/`shared_preferences`.
|
|
- ⚠️ نراجع: `intaleq_maps` (قرار الخرائط المباشرة لـ map-saas بمفتاح
|
|
`x-api-key` قائم — يبقى)، و`socket_io_client` (NestJS realtime قد يكون
|
|
عقده مختلفاً — يُتحقّق من `backend-archive/src/realtime`).
|
|
|
|
### ج. خطر هوية النشر — يُحسم قبل أي نسخ
|
|
|
|
| التطبيق | applicationId | shorebird app_id |
|
|
|---|---|---|
|
|
| `Siro/siro_driver` | `com.siro.siro_driver` | `f8e9c087-…0120c` |
|
|
| `Tripz/apps/driver` | `com.sefer_driver` | `f8e9c087-…0120c` ← **نفس سيرو** |
|
|
| `apps/driver-archive-cubit` | `com.sefer_driver` | `68cc9345-…5836d` |
|
|
| `Siro/siro_rider` | `com.siro.rider` | `44245793-…305dd` |
|
|
| `Tripz/apps/rider` | `com.mobileapp.store.ride` | `44245793-…305dd` ← **نفس سيرو** |
|
|
| `apps/rider-archive-cubit` | `com.mobileapp.store.ride` | `496cb3ac-…a13ac` |
|
|
|
|
قرار المالك: **لكل تطبيق تريبز هويته الخاصة**، منفصلة تماماً عن سيرو. لذلك
|
|
`apps/driver` و`apps/rider` — اللذان يحملان app_id سيرو نفسه — **أثرُ نسخٍ
|
|
خاطئ عند نقل GetX، وليسا مرجع هوية**. باتش شوربيرد منهما قد يُدفع لتطبيق
|
|
سيرو المنشور. المرجع الصحيح هو `*-archive-cubit`:
|
|
|
|
| | applicationId | shorebird app_id |
|
|
|---|---|---|
|
|
| `driver_new` | `com.sefer_driver` | `68cc9345-…5836d` |
|
|
| `rider_new` | `com.mobileapp.store.ride` | `496cb3ac-…a13ac` |
|
|
|
|
القاعدة: **لا يُنسخ أي `build.gradle` أو `shorebird.yaml` أو `Info.plist` من
|
|
سيرو.** الطبقة الأصلية تُنقل من `*-archive-cubit` (هي وحدها كودٌ لم يكتبه
|
|
النموذج السابق — ملفات إعداد مُولّدة من Flutter ومعدّلة يدوياً)، ويُقرأ من
|
|
سيرو ما يلزم من أذونات وخدمات خلفية و entitlements فيُكتب يدوياً سطراً سطراً.
|
|
|
|
## 3. الخطة بالترتيب
|
|
|
|
### المرحلة 0 — تثبيت الأساس (قبل أي كود) — **نُفّذت 2026-08-04**
|
|
1. ✅ عقد NestJS موثّق في **[`docs/38-api-contract.md`](38-api-contract.md)** —
|
|
37 controller + `e2e-test.mjs`، ومعظم الأشكال **مُتحقَّقة حيّاً** من
|
|
السيرفر المنشور. المصدر الوحيد للحقيقة لطبقة الشبكة.
|
|
2. ⚠️ **جزئي**: الباك إند حيّ ويعمل (`/health` → 200)، والدخول والمستخدم
|
|
والمحفظة وأنواع الرحلات والتعرفة والمسار والتقييم المعلّق كلها تحقّقت
|
|
حيّاً. لكن **`e2e-test.mjs` الكامل لم يُشغَّل**: يحتاج `OTP_DEV_MODE=true`
|
|
وهو مطفأ على المنشور، والـSSH من الماك مرفوض
|
|
(`Permission denied (publickey)`). المتبقّي غير المُتحقَّق مذكور في
|
|
`docs/38` §13. **بلوكر يحتاج المالك.**
|
|
3. ✅ المرجع البصري في **[`docs/39-siro-screens.md`](39-siro-screens.md)** —
|
|
89 شاشة راكب + 113 شاشة سائق، مصنّفة داخل/خارج النطاق، مع ترتيب الخطوات
|
|
وحالات الحافة وملاحظات الأداء. **بلا نسخ كود.**
|
|
|
|
**ما انحسم في م0** (كان مخاطرة، صار حقيقة):
|
|
- `x-app-role` **منفّذ فعلاً**: نفس الرقم أعطى user id مختلفاً للراكب والسائق.
|
|
لا عمل باك إند إضافي في م3.
|
|
- `access_token` = **15 دقيقة فقط** → تجديد استباقي إجباري في طبقة الشبكة.
|
|
- `GET /tenant/config/:slug` يرجّع **خريطة ميزات كاملة** → هي ربط lite/pro/max
|
|
بالخادم، لا أعلام مكتوبة في الكود (يعدّل فهم م6.25).
|
|
- حسابا مراجعة المتاجر `0790000001`/`0790000002` بالرمز `1234` يعملان **بلا**
|
|
`OTP_DEV_MODE` — مدخلنا للاختبار حتى يتوفّر وصول السيرفر.
|
|
- ثغرات مؤكَّدة على المنشور: `/maps/geocode` معطوب (قائمة دول فارغة)،
|
|
و`/tariff/quote` يفشل صامتاً بقيم `null` عند إرسال المعاملات الخطأ.
|
|
|
|
### المرحلة 1 — سقالة `rider_new` و `driver_new` من الصفر — **نُفّذت 2026-08-04، متوقّفة عند البوابة**
|
|
|
|
الحالة: الخطوات 4–8 ✅ · الخطوة 9 (البوابة) ⚠️ جزئية —
|
|
`flutter analyze` **نظيف في التطبيقين (0 مشاكل)**، لكن البناء الفعلي لم
|
|
يُجرَّب: SSH للسيرفر مرفوض (بلوكر م0.2 نفسه).
|
|
|
|
قرارات اتُّخذت أثناء التنفيذ ولم تكن في الخطة:
|
|
- **الأصول مصدرها `*-archive-cubit` لا سيرو.** أصول الأرشيف **مجموعة أشمل**
|
|
من أصول سيرو (كل صور سيرو + `login_hero`/`name_hero`/`onboarding_1..3`/
|
|
`splash_logo`) وخطوطها هي خطوط `docs/26` (IBM Plex Sans Arabic + Inter)
|
|
بينما سيرو عليه `mohanad`/`josefin`. النسخ من سيرو كان سيكون **تراجعاً**.
|
|
استُكمل السائق بعشرة ملفات ناقصة من سيرو (شعارات مزوّدي الدفع + صوتان).
|
|
الأصول ليست كوداً كتبه النموذج السابق، فلا تخالف قاعدة «من الصفر».
|
|
- **أُصلح تعارض هوية في iOS للسائق**: هدف `RunnerTests` في
|
|
`driver_new` كان يحمل `com.mobileapp.store.ride` (bundle الراكب) —
|
|
نُقل إلى `com.sefer.driver.RunnerTests`.
|
|
- `flutter_secure_storage` + `shared_preferences` أُضيفتا لقائمة م1 الدنيا:
|
|
التوكن لا يُخزَّن إلا في التخزين الآمن، وهذا أساس لا إضافة لاحقة.
|
|
4. `flutter create` نظيف لكل تطبيق، ثم **استبدال `android/` و`ios/` و
|
|
`shorebird.yaml` بنسخة من `*-archive-cubit`** (الهوية فقط — §2.ج).
|
|
5. `lib/` فارغ يُبنى من جديد: `core/` (شبكة · تخزين · أخطاء · توجيه · DI)
|
|
ثم `features/` — بلا استيراد أي ملف من الأرشيف.
|
|
6. نقل الأصول من سيرو: `assets/` + الخطوط + الأيقونات + ملفات الترجمة.
|
|
7. `pubspec` بالحد الأدنى: `flutter_bloc` · `dio` · `go_router` ·
|
|
`get_it` · `freezed`. الباقي يُضاف في مرحلته.
|
|
8. مواءمة الأذونات في `AndroidManifest` و`Info.plist` مع ما تحتاجه كل حزمة
|
|
عند إضافتها — بلا لمس المعرّفات.
|
|
9. **بوابة**: يبني على السيرفر، يقلع على المحاكي بشاشة splash،
|
|
و`flutter analyze` نظيف.
|
|
|
|
### المرحلة 2 — نظام التصميم — **نُفّذت 2026-08-04**
|
|
|
|
مُنجز: `core/design/` (tokens · typography · TripzColors · buildTheme) ·
|
|
`core/ui/` عدّة من 11 مكوّناً · `core/l10n/` ARB عربي/إنجليزي مع `gen_l10n` ·
|
|
`SettingsCubit` (المظهر واللغة محفوظان محلياً).
|
|
|
|
**تصحيح بنيوي**: بنية م1 كانت تخالف `docs/23 §1` المُلزِم (`lib/app/` و
|
|
`core/config/` و`core/network/`). أُعيدت للشجرة المفروضة حرفياً:
|
|
`app.dart` · `core/{config,build_config,di,router}.dart` ·
|
|
`core/{design,ui,l10n,api,storage,session}/` · `features/<f>/{cubit,data,view}/`.
|
|
|
|
**قراران يستحقّان المراجعة:**
|
|
1. **لا `packages/tripz_ui`.** خطتي الأصلية (م2.11) اقترحت حزمة مشتركة، لكن
|
|
`docs/23 §1` و`docs/26 §10` كلاهما مُلزِم ويفرض `lib/core/design` و
|
|
`lib/core/ui` **داخل** كل تطبيق. اتُّبعت الوثيقتان، والملفّان متطابقان
|
|
حرفياً بين التطبيقين (يُزامنان بـ`rsync` وأي تباعد يُكتشف بـ`diff -rq`).
|
|
2. **رمز الفشل لا نصّه.** `docs/23 §3` يقول إن الـCubit يبني الرسالة العربية،
|
|
و`docs/26 §4` يوجب مرور كل نص بالترجمة — والـCubit لا يعرف `BuildContext`.
|
|
الحل: الـCubit يُصدر `AuthFailure` كرمز، وتترجمه الواجهة في ملف واحد
|
|
(`auth_failure_text.dart`). الشاشة تبقى بلا منطق، والنص بلغتين.
|
|
|
|
### (الأصل المعتمد للمرحلة 2)
|
|
10. تطبيق `docs/26-flutter-design-system.md` + `docs/23-flutter-conventions.md`:
|
|
theme · ألوان · تايبوغرافي · مسافات · حركة · RTL/LTR بأربع لغات.
|
|
11. حزمة مشتركة `packages/tripz_ui` يستهلكها التطبيقان (تجنّب ازدواج الشاشات).
|
|
12. **ميزانية أداء مكتوبة** تُفحص في كل بوابة بعدها: بلا `setState` فوق شجرة
|
|
كبيرة · `BlocSelector`/`buildWhen` افتراضياً · `const` على كل widget ساكن ·
|
|
قوائم كسولة · بلا عمل ثقيل في `build`. سبب الأداء السيئ سابقاً يُشخّص من
|
|
`*-archive-cubit` **مرة واحدة** ويُكتب هنا كقائمة ممنوعات.
|
|
|
|
### المرحلة 3 — التسجيل والدخول — **نُفّذت 2026-08-04، البوابة معلّقة**
|
|
|
|
مُنجز: طبقة الشبكة (Dio + تجديد استباقي بطلقة واحدة + `x-app-role` +
|
|
`x-device-id` من `device_info_plus`) · `AuthRepository` · `SessionCubit` في
|
|
`core/session/` خلف واجهة `SessionSource` (كي لا يستورد `core` من `features`) ·
|
|
`LoginCubit` بخطواته الخمس · `ProfileCubit` · ست شاشات:
|
|
الشروط ← إذن الموقع ← الهاتف ← الرمز ← إكمال الملف ← هيكل الرئيسية.
|
|
|
|
نُقلت من سيرو كسلوك لا ككود: بوابتان **قبل** أي حقل إدخال · إعادة فحص الإذن
|
|
عند العودة من إعدادات النظام (`didChangeAppLifecycleState`) · تحقّق الهاتف
|
|
الثلاثي (فارغ/صفر بادئ/قصير).
|
|
أُضيف ما ينقص سيرو: **عدّاد إعادة الإرسال** — بدونه تُحظر ثلاث ضغطات المستخدمَ
|
|
خمس دقائق (`docs/38 §2`).
|
|
|
|
`flutter analyze` نظيف في التطبيقين · 48 ملف · 3,140 سطر لكل تطبيق ·
|
|
الفرق بينهما **أربعة ملفات فقط** (`build_config` · `config` · ملفّا ARB).
|
|
البوابة (دخول حقيقي على الخادم) تنتظر التشغيل.
|
|
|
|
### (الأصل المعتمد للمرحلة 3)
|
|
13. طبقة الشبكة: Dio + interceptors + تجديد التوكن + `x-app-role`.
|
|
14. شاشات: onboarding → إدخال الهاتف → OTP → إكمال الملف → استعادة الجلسة.
|
|
15. مراعاة قرار **هوية الراكب/السائق المنفصلة** (قيد `tenant,phone_bidx,role`
|
|
+ ترويسة `x-app-role`) — يجب أن يكون موجوداً في NestJS، وإن لم يكن فهو
|
|
عمل باك إند إضافي يُحسب في هذه المرحلة.
|
|
16. **بوابة**: دخول حقيقي من التطبيقين على NestJS المشتغل + لقطات شاشة تُقارن
|
|
بمرجع سيرو، ولا تُقبل إن كانت أدنى منه.
|
|
|
|
### المرحلة 4 — الخريطة وطلب الرحلة — **نُفّذت 2026-08-04، البوابة معلّقة**
|
|
|
|
**حُسم تعارض الخرائط المسجّل في `docs/38 §7`** لصالح القرار الأحدث
|
|
(2026-07-20): كل الخرائط — البلاطات والبحث والعكسي والمسار — **مباشرة إلى
|
|
`map-saas.intaleqapp.com`** بترويسة `x-api-key`، بلا مرور بباك إند تريبز.
|
|
نقاط `/maps/*` في الباك إند **لا تُستعمل**، وإحداها (`geocode`) معطوبة على
|
|
المنشور أصلاً. تُحقّق العقد حيّاً بـcurl قبل الكتابة.
|
|
|
|
المنجز:
|
|
- `core/api/antlaq_api.dart` — عميل منفصل **بلا** `AuthInterceptor`: توكن
|
|
تريبز لا شأن لخادم الخرائط به، وإرساله إليه تسريبٌ بلا مقابل.
|
|
- `core/realtime/realtime_service.dart` — Socket.IO واحد للتطبيق:
|
|
`trip:update` · `driver:location` · `trip:offer` · `trip:offer_taken`.
|
|
- `core/location/location_service.dart` — نقطة الموقع الوحيدة، بمرشّح 25م.
|
|
- `features/trip/data/` — سبعة نماذج + `MapsRepository` + `TripRepository`
|
|
(متطابق حرفياً بين التوأمين).
|
|
- **الراكب**: `RideCubit` بستّ مراحل + `RidePage` وثمانية ويدجت
|
|
(الخريطة · دبّوس المنتصف · المخطّط · الخط الزمني · اختيار النوع · التأكيد ·
|
|
البحث عن سائق · الرحلة الجارية).
|
|
- **السائق**: `DutyCubit` بأربع مراحل + `DutyPage` وأربعة ويدجت
|
|
(مفتاح الاتصال · العرض بمؤقّت · الرحلة الجارية · بطاقة الراكب).
|
|
|
|
قرارات تصميمية مقصودة:
|
|
1. **`GeoPoint` مستقلّ عن حزمة الخريطة**؛ `intaleq_maps` مستوردة في **ملف
|
|
واحد فقط** لكل تطبيق (`ride_map.dart`). تبديل محرّك الخريطة لا يلمس منطقاً.
|
|
2. **الخريطة خارج الـ`BlocBuilder`** الذي يبني الورقة، ولها `BlocSelector`
|
|
بما تحتاجه وحدها — نبضة موقع كل 25م لا تعيد بناء الشاشة. هذا تطبيق مباشر
|
|
لميزانية الأداء (م2.12).
|
|
3. **مهلة بحث محليّة 90 ثانية + استطلاع كل 5ث**: الخادم قد لا يُطلق
|
|
`expired`/`no_drivers` أبداً (ثغرة R1)، وانتظار حدثٍ قد لا يصل = شاشة
|
|
بحث أبدية. و`offeredDrivers == 0` يُعالَج فوراً بلا انتظار المهلة.
|
|
4. **الأجرة لا تُعرض إلا إن كانت صالحة**: `/tariff/quote` يرجّع 200 بقيم
|
|
`null` صامتة عند المدخلات الخطأ (`docs/38 §7`).
|
|
5. موقع السائق يُرسَل بمسارين عمداً: REST يغذّي مطابقة Redis، والسوكت يغذّي
|
|
خريطة الراكب — سقوط أحدهما لا يُعمي الآخر.
|
|
|
|
`flutter analyze` نظيف · الراكب 70 ملف/5,294 سطر · السائق 67 ملف/4,771 سطر.
|
|
|
|
### (الأصل المعتمد للمرحلة 4)
|
|
17. الخريطة مباشرة لـ map-saas بمفتاح `x-api-key` مقيّد ببصمة التطبيق
|
|
(قرار `maps-direct-decision`) — لا تمرير عبر الباك إند.
|
|
18. الراكب: اختيار المصدر/الوجهة · التسعير المسبق · تأكيد الطلب · انتظار سائق.
|
|
19. السائق: أونلاين/أوفلاين · استقبال العروض · قبول/رفض.
|
|
20. الواقع اللحظي (WebSocket) حسب `backend-archive/src/realtime`.
|
|
21. **بوابة**: رحلة كاملة من الطلب إلى الإنهاء بين جهازين + الخريطة تتحرّك
|
|
بسلاسة أثناء التتبّع (لا اهتزاز، لا إعادة رسم للشاشة كاملة).
|
|
|
|
### المرحلة 5 — مسار الرحلة وما بعدها — **نُفّذت 2026-08-04**
|
|
|
|
- **التقييم مفروض لا اختياري**: `SessionCubit` يفحص
|
|
`GET /trips/rating/pending` بعد إثبات الجلسة، ووجودُ رحلة معلّقة **يحجز
|
|
التطبيق كله** عند `/rating` حتى تُحسم. دورُ المستخدم في الرحلة يأتي من
|
|
الخادم لا من نوع التطبيق (نفس الرقم قد يكون له حسابان — `docs/38 §1`).
|
|
التقييم المزدوج يرجّع 400 وهو **نجاحٌ من منظور المستخدم**: الرحلة مُقيَّمة
|
|
فعلاً، فلا يُحبس في شاشة بلا مخرج.
|
|
- **`core/format/money.dart`**: كل المبالغ تصل نصوصاً، والتحويل والعرض يمرّان
|
|
من نقطة واحدة. عدد المنازل من العملة (`JOD` ثلاث)، والعملة من إعداد
|
|
المستأجر لا من ثابت في التطبيق.
|
|
- **المحفظتان لا تُوحَّدان**: الراكب `WalletPage` (رصيد + شحن = **التزام**
|
|
على المنصّة)، والسائق `EarningsPage` (أرباح + سحب = **إيراد** له). شكلهما
|
|
متقارب وحسابهما مختلف — دمجهما خطأ محاسبي لا اختصار (`docs/39 §4`).
|
|
- **السحب بخطوتين**: `request` يرسل رمزاً **بلا خصم**، و`confirm` هو ما يخصم
|
|
ويحجز. شاشة سيرو كانت خطوة واحدة ولم تُقلَّد. الرمز الخاطئ لا يُرجع
|
|
المستخدم لخطوة المبلغ — الطلب قائم على الخادم أصلاً.
|
|
- **الرصيد التشغيلي معروض منفصلاً** عن الأرباح: منه تُخصم العمولة، ونفاده
|
|
يوقف وصول الطلبات — خلطه بالأرباح يخفي سبب توقّف العمل عن السائق.
|
|
- **`FareSummary` يعرض رقم كلّ طرف**: `price_for_passenger` للراكب و
|
|
`price_for_driver` للسائق — حقلان منفصلان والفرق عمولة (`docs/38 §4`).
|
|
- **الإلغاء بتأكيد صريح مع سبب**. السبب لا يقبله عقد الخادم بعد، لكن السؤال
|
|
يخفض الإلغاء العرضي ويجهّز الحقل.
|
|
|
|
`flutter analyze` نظيف · الراكب 86 ملف/6,280 سطر · السائق 83 ملف/5,818 سطر.
|
|
|
|
### (الأصل المعتمد للمرحلة 5)
|
|
22. حالات الرحلة · التتبّع الحيّ · الإلغاء · الإنهاء · الأجرة · التقييم.
|
|
23. المحفظتان (محفظة الراكب = التزام، محفظة السائق = إيراد — لا تُدمجان).
|
|
|
|
### المرحلة 6 — الدراور والإضافات — **نُفّذت 2026-08-04**
|
|
|
|
- **طبقتا الميزات مُطبَّقتان معاً** في `AppDrawer`:
|
|
`Features.wallet && context.tenantHas('wallet')`. الأولى `const` تقرّر ما
|
|
**يُبنى** في الـbinary (إطفاؤها يحذف الكود فلا يُستخرج — docs/23 §5)،
|
|
والثانية من `GET /tenant/config/:slug` تقرّر ما **يُعرض** ممّا بُني حسب
|
|
اشتراك المستأجر (docs/38 §10). هذا هو ربط lite/pro/max عملياً.
|
|
- `TenantCubit` يعرض **النسخة المخزّنة فوراً** ثم يحدّثها من الشبكة: إقلاعٌ
|
|
بلا إنترنت يعرض التطبيق بميزاته المعروفة آخر مرة، لا شاشة فارغة.
|
|
- الشاشات: الدراور · تعديل الحساب · سجلّ الرحلات · الإعدادات (مظهر + لغة) ·
|
|
المساعدة. الرقم في تعديل الحساب **معطّل مع سبب معروض** لا مخفي: هو هوية
|
|
الحساب على الخادم وتغييره يعني حساباً آخر (docs/38 §1).
|
|
- الإشعارات: `PushService` معزول — **الملف الوحيد الذي يعرف Firebase**.
|
|
التسجيل يقع بعد إثبات الجلسة عبر خطّاف في `SessionCubit`، ويشترك في
|
|
`onTokenRefresh` (بدونه تتوقّف الإشعارات بصمت بعد أسابيع). الإعداد الأصلي
|
|
كان جاهزاً مسبقاً: `google-services.json` منقول و`build.gradle` يطبّق
|
|
`com.google.gms.google-services` أصلاً.
|
|
|
|
**تصحيحات بنيوية أُجريت في هذه المرحلة** (كشفها فحص آلي لا قراءة):
|
|
`docs/23 §1` يمنع أن تستورد ميزةٌ من أخرى، وكان هناك خمس مخالفات:
|
|
1. `features/history` أُدمجت في `features/trip` — سجلّ الرحلات جزء من ميزة
|
|
الرحلة لا ميزة مستقلّة.
|
|
2. `AppDrawer` صعد إلى `core/ui/` — هيكل تطبيق لا ميزة.
|
|
3. `AuthFailure` صعد إلى `core/api/api_failure.dart`، ومترجمه إلى
|
|
`core/ui/failure_text.dart` — مشتركان بين المصادقة والملف.
|
|
4. `AuthHeader` صار `core/ui/form_header.dart`.
|
|
5. `ProfileRepository` مستقلّ عن `AuthRepository`: تعديل الملف ليس مصادقة.
|
|
|
|
`flutter analyze` نظيف · الراكب 98 ملف/7,497 سطر · السائق 95 ملف/6,931 سطر.
|
|
فحوص آلية نظيفة: صفر استيراد بين ميزتين · `core` يستورد `features` من
|
|
`di.dart` و`router.dart` وحدهما · Firebase و`intaleq_maps` و`geolocator` و
|
|
`socket_io` كلٌّ معزول في ملفه.
|
|
|
|
### (الأصل المعتمد للمرحلة 6)
|
|
24. الدراور · الملف الشخصي · الرحلات السابقة · الإشعارات · الدعم · اللغة.
|
|
25. الوحدات المدفوعة السبع (`docs/33`) خلف أعلام `const` حسب نموذج
|
|
lite/pro/max — بناء مولَّد، لا فروع.
|
|
|
|
## 3.7 إكمال التغطية (2026-08-05)
|
|
|
|
فحصٌ آليّ لنقاط `docs/38` مقابل الكود كشف ثغرات لم تظهر بالقراءة:
|
|
|
|
- **تسجيل السائق لم يكن موجوداً أصلاً** — أخطر ثغرة: سائق جديد يسجّل دخوله
|
|
ثم يقف. بُنيت `features/onboarding` كاملة: تقديم ← ملف ← مركبة ← وثائق ←
|
|
انتظار الاعتماد. **الخطوة تُشتقّ من حالة الخادم لا من تقدّم محلّي**، فسائقٌ
|
|
يعيد تثبيت التطبيق يعود إلى حيث وقف. والاعتماد يقع على الخادم تلقائياً حين
|
|
تكتمل الوثائق — التطبيق لا يعتمد أحداً. وعند الاعتماد **خروجٌ إجباري**:
|
|
الدور يتغيّر على الخادم والتوكن القديم يحمل القديم فتفشل نقاط السائق بـ403.
|
|
- **بوابة توجيه**: مستخدمٌ دوره ليس `driver` يُحجز في `/onboarding`.
|
|
- **الدردشة** (`Features.chat` كان مفعّلاً بلا ميزة): `features/chat`
|
|
باستطلاع كل خمس ثوانٍ — قائمة أحداث الخادم لا تتضمّن الرسائل
|
|
(`docs/38 §9`)، فالاستطلاع **قيد خادم لا اختيار تصميمي**.
|
|
- **الكوبون**: حقل في ورقة التأكيد خلف طبقتَي الميزات؛ يُقيَّم على الخادم.
|
|
- **`Features.calls` أُطفئ صراحةً**: الخادم يدعم WebRTC والتطبيق لا. عَلَمٌ
|
|
مفعّل بلا ميزة كذبٌ على القارئ التالي.
|
|
- `ApiClient.upload` للرفع متعدّد الأجزاء · `AuthFailure` صار `ApiFailure`
|
|
في `core/api` لأنه يخدم المصادقة والملف والتسجيل معاً.
|
|
- **`tool/sync_from_rider.sh`**: المزامنة اليدوية بين التوأمين انكسرت أربع
|
|
مرات، فصارت سكربتاً واحداً يعرّف ما يملكه كل تطبيق. تُشغَّل بعده
|
|
`flutter gen-l10n` و`flutter analyze`.
|
|
|
|
`flutter analyze` نظيف · الراكب 101 ملف/7,809 سطر · السائق 110 ملف/8,116 سطر.
|
|
|
|
## 4. مصير المجلدات القائمة
|
|
|
|
| المجلد | المصير |
|
|
|---|---|
|
|
| `apps/rider-archive-cubit`, `driver-archive-cubit` | مصدر الطبقة الأصلية في م1، ومصدر تشخيص الأداء في م2 — ثم **يُحذفان** بعد نجاح بوابة م3 |
|
|
| `apps/rider`, `apps/driver` (نقل GetX) | مرجع سلوك ميداني فقط → `apps/*-archive-getx` بعد م3. **ليسا مرجع هوية** (§2.ج) |
|
|
| `Siro/siro_rider`, `siro_driver` | مرجع خارجي دائم — لا يُعدَّل ولا يُنسخ منه دارت |
|
|
| باك إند PHP (`docs/34`) | يتوقّف العمل عليه — لا يُحذف |
|
|
|
|
## 5. المخاطر
|
|
|
|
| الخطر | التخفيف |
|
|
|---|---|
|
|
| تعارض shorebird app_id بين سيرو وتريبز | الهوية من `*-archive-cubit` حصراً (§2.ج)؛ يُفحص قبل أول باتش |
|
|
| تكرار الأداء السيئ نفسه | م2.12 ميزانية أداء مكتوبة + بوابة أداء في كل مرحلة |
|
|
| «من الصفر» تتحول لنسخ صامت من الأرشيف | ممنوع أي `import` من `*-archive-*`؛ يُفحص بـ grep عند كل بوابة |
|
|
| NestJS «جاهز» لكن غير مختبر منذ 2026-07 | بوابة م0.2 قبل أي شغل تطبيق |
|
|
| هوية الراكب/السائق المنفصلة غير منفّذة في NestJS | يُتحقّق في م0.1 |
|
|
| جرّ GetX بالغلط عبر pubspec | ممنوع `get`/`get_storage` في `*_new` |
|
|
| بناء/اختبار على الماك | كل بناء وهجرة على السيرفر |
|