الإشعارات
كل إشعار ينطلق من حدث يُكتب في صندوق الصادر (outbox) داخل معاملة قاعدة البيانات نفسها التي تُجري التغيير. لا يستدعي كود الطلبات أو الدفع أو التوصيل خدمة الإشعارات مباشرة، فلا يستطيع خادم بريد بطيء التراجع عن طلب مدفوع، ولا يضيع بريد طلبٍ تم حفظه بسبب تعطل مفاجئ.
القنوات ومزوّدو الخدمة
| القناة | المزوّد |
|---|---|
| البريد الإلكتروني | SMTP |
| الإشعارات الفورية | Firebase Cloud Messaging (رموز الأجهزة تسجلها التطبيقات) |
| الرسائل النصية | Twilio — وتحمل أيضًا رموز الدخول ورموز التسليم |
| داخل التطبيق | بلا مزوّد: الإشعار المحفوظ هو نفسه التسليم، ويظهر في قائمة إشعارات الحساب. |
القناة التي لا مزوّد مضبوطًا لها تُبقي إشعاراتها في الانتظار بدل إسقاطها. يوجد مزوّد «طرفية» للتطوير ويُرفض عند التشغيل في بيئة الإنتاج.
قواعد تحكم الإرسال
- ساعات الهدوء. تؤجَّل الإشعارات الروتينية إلى صباح المستلم — تأخير لا إسقاط. أما العاجلة (فشل دفع، مهمة السائق التالية، استرجاع بنكي) فتُرسل في كل الأحوال.
- التجميع. تُجمع التدفقات الكثيفة: البائع الذي لديه أربعون طلبًا يتلقى «40 طلبًا جديدًا» وليس أربعين إشعارًا.
- اللغة. كل قالب متوفر بالعربية والإنجليزية. تُحدَّد اللغة من الجهاز، ثم من ملف المستخدم، ثم الإنجليزية.
- التفضيلات. يستطيع المشتري تشغيل الإشعارات أو إيقافها لكل حدث وقناة.
- القوالب. يستطيع المسؤول تعديل نص أي قالب من لوحة الإدارة (قوالب الرسائل).
ما يُرسل اليوم
يضم الكتالوج (المولَّد في docs/notifications.md) 33 قاعدة لـ32 حدثًا عبر ست فئات. رموز الدخول وإعادة تعيين كلمة المرور تُرسل بالبريد أيضًا.
المشتري
| الحدث | القنوات | ساعات الهدوء | تجميع | الرسالة |
|---|---|---|---|---|
order.placed | EMAIL · IN_APP | تُرسل دائمًا | فردية | تأكيد الطلب |
order.paid | EMAIL · IN_APP | تُرسل دائمًا | فردية | استلام الدفع |
order.payment_failed | PUSH · EMAIL · IN_APP | تُرسل دائمًا | فردية | مشكلة في الدفع |
order.shipped | PUSH · EMAIL · IN_APP | تؤجَّل للصباح | مجمّعة | الطلب في الطريق |
delivery.out_for_delivery | PUSH · IN_APP | تُرسل دائمًا | فردية | الوصول قريبًا مع الوقت المتوقع |
delivery.otp_issued | SMS · IN_APP | تُرسل دائمًا | فردية | رمز التسليم الخاص بك |
delivery.completed | PUSH · IN_APP | تؤجَّل للصباح | مجمّعة | تم التسليم |
delivery.failed | PUSH · SMS · IN_APP | تُرسل دائمًا | فردية | تعذّر التسليم |
order.cancelled | EMAIL · IN_APP | تؤجَّل للصباح | فردية | أُلغي الطلب |
refund.settled | EMAIL · IN_APP | تؤجَّل للصباح | فردية | أُرسل المبلغ المسترد |
ticket.replied | PUSH · EMAIL · IN_APP | تؤجَّل للصباح | فردية | ردّ الدعم |
البائع
| الحدث | القنوات | ساعات الهدوء | تجميع | الرسالة |
|---|---|---|---|---|
order.group_placed | PUSH · EMAIL · IN_APP | تُرسل دائمًا | مجمّعة | طلب جديد |
order.accept_deadline_missed | PUSH · EMAIL · IN_APP | تُرسل دائمًا | فردية | طلب تجاوز مهلة القبول |
payout.paid | EMAIL · IN_APP | تؤجَّل للصباح | فردية | أُرسلت الدفعة |
review.published | IN_APP | تؤجَّل للصباح | مجمّعة | تقييم جديد |
catalog.product_rejected | EMAIL · IN_APP | تؤجَّل للصباح | مجمّعة | لم يُعتمد المنتج |
السائق
| الحدث | القنوات | ساعات الهدوء | تجميع | الرسالة |
|---|---|---|---|---|
delivery.task_assigned | PUSH | تُرسل دائمًا | فردية | مهمة جديدة |
delivery.task_reassigned | PUSH · IN_APP | تُرسل دائمًا | فردية | أُعيد إسناد المهمة |
driver.earning_recorded | IN_APP | تؤجَّل للصباح | مجمّعة | سُجّل لك ربح |
الإدارة والمالية والدعم
| الحدث | القنوات | ساعات الهدوء | تجميع | الرسالة |
|---|---|---|---|---|
order.accept_deadline_missed | EMAIL · IN_APP | تُرسل دائمًا | مجمّعة | طلب بلا رد |
payment.chargeback_opened | EMAIL · IN_APP | تُرسل دائمًا | فردية | فُتح استرجاع بنكي |
seller.application_submitted | IN_APP | تؤجَّل للصباح | مجمّعة | بائعون بانتظار الاعتماد |
review.flagged | IN_APP | تؤجَّل للصباح | مجمّعة | تقييمات تحتاج مراجعة |
cod.settlement_discrepancy | EMAIL · IN_APP | تُرسل دائمًا | مجمّعة | عجز في تسوية الدفع عند الاستلام |
ticket.sla_breached | EMAIL · IN_APP | تُرسل دائمًا | فردية | تجاوز مهلة الدعم |
لهذه الأحداث قوالب، لكن لا شيء في الكود يطلقها حتى الآن، فلا تُرسل أبدًا: inventory.low_stock وmessage.received وticket.opened وorder.accept_deadline_near وledger.reconciliation_failed وdelivery.cash_limit_near.
التتبع المباشر
قناة فورية (Socket.IO على المسار /realtime) تتيح للمشتري الاشتراك في طلبه وتلقي تحديثات موقع السائق، وتتيح للمرسِلين الاشتراك في الأسطول. وتُرجع GET /tracking/orders/{orderNumber} آخر موقع معروف ووقت الوصول المتوقع. لا يرسل تطبيق السائق إشارات الموقع بعد، لذا لا تتدفق المواقع فعليًا حاليًا.