# تقرير اختبار قنوات الإشعارات (Notification Topics)

**التاريخ:** 2026-10-13 · **البرانش:** `feat/notification-topics-2026-10-13` · **5 سبرينتات بطلب دمج واحد**

## 1. الملخص

الإشعارات العامة كانت تنبعت **جهاز جهاز**: إشعار «للكل» = حوالي 8,000 طلب لـ Google بدقيقة لـ 3 دقايق، وكان يفشل منها **25-30%**، و97% من الفشل «خطأ شبكة» من كثرة الطلبات.

صار في **قنوات** (Topics): كل جهاز مشترك بقناته، والإشعار العام بيروح **برسالة وحدة** للقناة، وGoogle بيوصلها لكل المشتركين.

| | قبل | بعد |
|---|---|---|
| رسائل Google لإشعار «للكل» | 7,940 | **1** |
| الوقت | 1-3 دقايق | **3.2 ثانية** (محاكاة على نسخة البيانات) |
| الفشل | 25-30% | **0** |

**يلي ما بتحلّه القنوات:** 64% من الزباين (4,045 من 6,366) **ما عندهم جهاز مسجّل أصلاً**. ما في أي إشعار تطبيق بيوصلهم، والحل تسويقي (تنزيل التطبيق) أو واتساب.

## 2. القنوات

| القناة | الاسم على اللايف | مين فيها |
|---|---|---|
| الكل | `kapitano_shop_all` | كل جهاز عليه التطبيق (ضيف أو زبون) |
| الزباين | `kapitano_shop_customers` | جهاز داخل بحساب زبون فعّال |
| الضيوف | `kapitano_shop_guests` | جهاز بدون حساب |
| التجربة | `kapitano_shop_test` | أجهزة التجربة فقط (بالإيد من صفحة الأدمن) |

اسم القناة بيبدأ باسم السيرفر (`kapitano_app_store_…` على التيست)، لأن التيست واللايف على نفس مشروع Firebase: **مستحيل التيست يبعت لزباين اللايف**.

## 3. شو انعمل بكل سبرينت

| # | السبرينت | الكوميت |
|---|---|---|
| 1 | الاشتراك والإرسال لقناة + صفحة «قنوات الإشعارات» + أداة تجربة بتوصل لقناة التجربة بس | `176e9fac` |
| 2 | كل جهاز بيدخل قنواته ويطلع منها لحاله (فتح التطبيق، دخول، خروج، حظر، حذف حساب، تجديد الرقم) | `f87e5f13` |
| 3 | تسجيل كل الأجهزة الموجودة مرة وحدة (زر بالأدمن أو أمر)، بوضع تجربة، وإعادة محاولة كل ساعة | `ea13ca68` |
| 4 | إشعار الأدمن «لكل الزباين / لكل الضيوف / للكل» بدون فلاتر بيروح رسالة وحدة للقناة | `c0de789a` |
| 5 | اختبار جدول الحالات + هالتقرير | سبرينت 5 |

## 4. جدول الحالات (كل حالة مستخدم × كل نوع إشعار)

مجرّب بالاختبار الآلي `NotificationScenarioMatrixTest` (15 حالة)، من خلال نفس طلبات التطبيق الحقيقية.

- **عام للكل / للزباين / للضيوف:** إشعار الأدمن بدون فلاتر (عبر القنوات).
- **شخصي:** حالة الطلب، تقييم الطلب، أعلمني عند التوفر، نقاط الولاء، تذكير السلة (بيروح لجهاز الحساب `users.device_token`).

| # | الحالة | للكل | للزباين | للضيوف | الشخصي | النتيجة |
|---|---|:-:|:-:|:-:|---|:-:|
| S01 | ضيف جديد فتح التطبيق | ✓ | ✗ | ✓ | ما في (ضيف) | ✓ نجح |
| S02 | ضيف سجّل دخول | ✓ | ✓ | ✗ | لجهازه | ✓ نجح |
| S03 | زبون رجع فتح التطبيق | ✓ | ✓ | ✗ | لجهازه | ✓ نجح |
| S04 | زبون طلع من حسابه (آخر جهاز) | ✓ | ✗ | ✓ | **ما بقى يوصل** | ✓ نجح |
| S05 | زبون على تلفونين وطلع من واحد | التلفونين ✓ | التاني ✓ | الأول ✓ | للتلفون التاني | ✓ نجح |
| S06 | زبون محظور | ✗ | ✗ | ✗ | لجهازه (متل قبل) | ✓ نجح |
| S07 | زبون حذف حسابه | ✗ | ✗ | ✗ | ما في | ✓ نجح |
| S08 | تاجر | ✗ | ✗ | ✗ | لجهازه | ✓ نجح |
| S09 | Google جدّد رقم الجهاز | الجديد ✓ | ✗ | الجديد ✓ | — | ✓ نجح |
| S10 | نسخة تطبيق قديمة | ✓ | بعد الدخول ✓ | قبل الدخول ✓ | بعد الدخول | ✓ نجح |
| S11 | جهاز ما انفتح 90 يوم | ✗ | ✗ | ✗ | — | ✓ نجح |
| S12 | التطبيق انحذف من التلفون | ✗ | ✗ | ✗ | — | ✓ نجح |
| S13 | زبون بدون تطبيق | ✗ | ✗ | ✗ | ما في | ✓ نجح |
| S14 | نفس التلفون انتقل لحساب تاني | ✓ | ✓ | ✗ | للحساب الجديد بس | ✓ نجح |
| S15 | ولا جهاز بيوصله الإشعار العام مرتين | — | — | — | — | ✓ نجح |

## 5. نتائج الاختبارات الآلية

| المجموعة | الاختبارات | النتيجة |
|---|---:|:-:|
| القنوات: الاشتراك والإرسال (`FcmTopicsTest`) | 22 | ✓ |
| التسجيل التلقائي (`TopicMembershipTest`) | 22 | ✓ |
| تسجيل الأجهزة الموجودة (`TopicSeederTest`) | 14 | ✓ |
| الإرسال العام عبر القنوات (`TopicBroadcastTest`) | 9 | ✓ |
| جدول الحالات (`NotificationScenarioMatrixTest`) | 15 | ✓ |
| الإشعار المخصص: الجمهور والمستلمين | 26 | ✓ |
| تقييم الطلب (إشعار) | 25 | ✓ |
| أعلمني عند التوفر (إشعار + واتساب) | 25 | ✓ |
| إشعار جهاز الضيف (السلة المتروكة) | 14 | ✓ |
| إشعارات التاجر (لوحة التاجر) | 24 | ✓ |
| أرقام الأجهزة والتكرار | 26 | ✓ |
| حذف الحساب | 24 | ✓ |
| **كل المشروع** | **2,286** | **✓ صفر فشل** |

**اختبارات «تخريب» قصدي:** خرّبت الكود قصداً مرتين، وبالمرتين فشل الاختبار ولقط الغلطة:
- أداة التجربة صارت تبعت لـ «الكل».
- الخروج من الحساب صار ما يفك الجهاز.

## 6. حالات الفشل المختبرة

| الحالة | الاختبار |
|---|---|
| Google واقع، أو بطيء، أو ما رد أبداً، أو جوابه خربان | ✓ التطبيق بيكمل، والجهاز بيتسجّل «فشل» وبيتجرّب كل ساعة |
| مفاتيح Firebase ناقصة أو مرفوضة | ✓ ما بيطلع أي طلب، والسبب مكتوب |
| رقم جهاز ميت | ✓ بينشال من القنوات وبينعدّ بالتقرير |
| Google رفض دفعة كاملة | ✓ الدفعات التانية بتكمل |
| أكتر من 1000 جهاز | ✓ دفعات 1000 |
| الإرسال للقناة انرفض | ✓ بيرجع يبعت جهاز جهاز، فما حدا بيضيع عليه، وما حدا بيوصله مرتين |
| التسجيل انقطع بالنص | ✓ منعيده، وبيكمل بدون تكرار |
| إعادة التسجيل | ✓ صفر طلب لـ Google |
| سيرفر عنوانه غلط بالإعدادات | ✓ تحذير أحمر، والتسجيل والإرسال بيرفضوا |
| الميزة مطفية، أو الجدول لسا ما انعمل | ✓ كلشي متل قبل |
| تغيير انلغى (rollback) | ✓ ما بيوصل شي |

## 7. المحاكاة على البيانات الحقيقية (نسخة `kapitano_dev`)

| | النتيجة |
|---|---|
| تسجيل تجريبي (عدّ فقط) | 7,942 جهاز، 1.8 ثانية، **صفر** طلب لـ Google |
| التسجيل الحقيقي | **10.8 ثانية**، 18 دفعة (أبطأها 0.8 ث)، **34 طلب** لـ Google، صفر فشل |
| الأرقام | 2,321 جهاز زبون، 5,619 جهاز ضيف |
| إعادة التسجيل | **صفر** طلب لـ Google |
| إشعار «للكل» | **رسالة وحدة**، 3.2 ثانية، 2,321 سطر بقائمة إشعارات التطبيق، و7,940 «أُرسل عبر القناة» بالتصدير |

## 8. تغييرات بالسلوك (لازم يعرفها الفريق)

1. **الخروج من الحساب على آخر جهاز:** التلفون بيرجع «ضيف»، وما عاد توصله إشعارات طلبات الحساب. قبل كانت تضل توصل لتلفون طلع صاحبه منه.
2. **فتح التطبيق وإنت داخل بحساب ما إلو جهاز محفوظ:** التلفون بيصير جهاز الحساب.
3. **قناة «الزباين»** فيها كل زبون فعّال عنده التطبيق. الإرسال القديم كان بيطلب كمان «إيميل مفعّل»، وعلى نسخة البيانات كل الزباين يلي عندهم تطبيق مفعّلين، فما في فرق فعلي.

## 9. مشاكل موجودة لقيناها (ما إلها علاقة بالقنوات، وما تصلحت هون)

| # | المشكلة | الأثر |
|---|---|---|
| 1 | إشعار «تم استلام طلبك» ما بينبعت أبداً، لأن ما في كود بيستدعيه صح | الزبون ما بيوصله إشعار عند الطلب |
| 2 | إشعارات تغيير حالة الطلب من الأدمن معلّقة بالكود (commented out) | ما بتوصل من الأدمن (بتوصل من التاجر بس) |
| 3 | تذكير السلة بيتشغّل من رابط عام بدون حماية، وبدون منع تكرار | ممكن حدا يشغّله ويبعت تذكيرات مكررة |
| 4 | نسخة قديمة من كود الإرسال (`NotificationUtility`) مكتوبة غلط، وبتسجّل نجاح بدون إرسال | حالياً ما حدا بيستدعيها، بس خطر إذا انستعملت |
| 5 | ملف مفاتيح Firebase (`kapitano.json`) مرفوع على git | خطر أمني: الأفضل يتغيّر المفتاح وينشال الملف |
| 6 | الإرسال القديم بيشيل بس الأرقام `UNREGISTERED`، والأنواع الميتة التانية بتضل | أرقام ميتة بتضل بالقاعدة |

## 10. التشغيل على السيرفر (التيست، وبعدين اللايف)

1. تنزيل البرانش، وبعدها `php artisan migrate` و`php artisan cache:clear`.
2. الأدمن ← الإشعار ← **قنوات الإشعارات**:
   - التأكد إنو ما في تحذير أحمر، وإنو الأسماء بتبدأ باسم السيرفر الصح.
   - **أداة التجربة:** رقم جهاز تلفون الفريق ← «اشتراك بقناة التجربة» ← «إرسال لقناة التجربة» ← لازم يوصل الإشعار.
3. تشغيل **«إبقاء الأجهزة بقنواتها»** ← حفظ.
4. **«احسب أولاً»** للأرقام، وبعدين **«تسجيل كل الأجهزة بقنواتها»**.
5. تشغيل **«إرسال الإشعارات المخصصة لكل الزبائن / لكل الضيوف كرسالة واحدة»** ← حفظ.
6. إشعار مخصص تجريبي «للكل» من التيست، والتأكد إنو وصل للتلفونات، ومن سجل المستلمين.

**الرجوع:** بنطفي المفتاحين من نفس الصفحة، وكلشي بيرجع متل قبل (جهاز جهاز). ما في شي بينمسح.

## 11. اختبار سيرفر التيست (بيتعبّى وقت التجربة)

| # | الخطوة | المتوقع | النتيجة |
|---|---|---|---|
| T1 | فتح صفحة قنوات الإشعارات | بدون تحذيرات، والأسماء `kapitano_app_store_…` | |
| T2 | اشتراك رقم جهاز بقناة التجربة | «تم: 1، فشل: 0» | |
| T3 | إرسال لقناة التجربة | الإشعار وصل للتلفون | |
| T4 | تشغيل المفتاح الأول + «احسب أولاً» | أرقام قريبة من عدد الأجهزة | |
| T5 | تسجيل كل الأجهزة | فشل = 0 أو قليل، والعدادات تعبّت | |
| T6 | ضيف يفتح التطبيق | بيدخل الكل + الضيوف | |
| T7 | تسجيل دخول | بيطلع من الضيوف وبيدخل الزباين | |
| T8 | خروج من الحساب | بيرجع ضيف | |
| T9 | إشعار مخصص «للكل» | وصل لتلفونات الضيف والزبون، مرة وحدة | |
| T10 | إشعار مخصص لمدينة معيّنة | انبعت جهاز جهاز متل قبل | |
| T11 | إشعار شخصي (أعلمني / حالة طلب) | وصل لجهاز الحساب | |
