Commit c3a255cf authored by Mahmoud Aglan's avatar Mahmoud Aglan

docs(pricing): rewrite discounts/offers tutorial with full business logic detail

Complete guide covering board offers lifecycle, special discounts, subscription
adjustments, accumulation rules, edge cases, scenarios, and anti-patterns.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent bf80fb97
# العروض والتخفيضات في السيستم — دليل خطوة بخطوة # دورة العروض والخصومات — الدليل الكامل
## 1. إنشاء خصم خاص (التعريف) ## ملخص سريع
**المسار:** القائمة الجانبية ← **التسعير****الخصومات الخاصة** النظام فيه 3 طبقات منفصلة للخصومات:
أو مباشرة: `/pricing/special-discounts` | الطبقة | الغرض | من يستفيد | تطبيق |
|--------|--------|-----------|-------|
| عروض مجلس الإدارة | شروط دفع (خصم كاش + أقساط مخصصة) | كل الأعضاء خلال الفترة | تلقائي |
| الخصومات الخاصة | خصم على قيمة العضوية | عضو محدد أو بشرط | يدوي أو تلقائي بشرط |
| تعديل الاشتراك السنوي | نسبة على الاشتراك | كل المشتركين في سنة معينة | تلقائي عند التوليد |
1. اضغط **"إنشاء خصم جديد"** ---
2. املأ البيانات:
- **اسم الخصم** (مثال: "خصم أبناء شهداء") ## 1. عروض مجلس الإدارة (Board Offers)
- **النوع**: نسبة مئوية / مبلغ ثابت / سنوات مجانية
- **النسبة أو المبلغ** ### الفكرة
- **الشروط**: تاريخ بداية ونهاية، فرع معين، يحتاج مستند إثبات أم لا
- **سنوات مجانية** (bonus): عدد السنوات التي يُعفى فيها العضو من الاشتراك السنوي قرار مجلس إدارة بشروط دفع خاصة لفترة محدودة. مثلاً: "عرض الصيف — خصم 10% كاش أو تقسيط على 36 شهر بفائدة 18%".
3. احفظ ← الخصم يظهر في القائمة ويمكن تفعيله/تعطيله
### المسار: التسعير ← عروض مجلس الإدارة
`/pricing/board-offers`
### ماذا يحتوي العرض
1. **مسار الكاش** (اختياري) — خصم يُطبَّق لو العضو دفع كامل نقداً:
- نوع: نسبة % أو مبلغ ثابت
- مثال: خصم 10% = عضوية 150,000 تصبح 135,000
2. **مسار التقسيط** (اختياري) — يتجاوز الإعدادات الافتراضية:
- نسبة المقدم (الافتراضي 25%)
- عدد الأشهر (الافتراضي 30)
- نسبة الفائدة (الافتراضي 22%)
- فترة سماح:
- `first_n_months`: أول N شهر بدون فوائد (بعدها فائدة عادية أو مخصصة)
- `full_free_under_n`: إعفاء كامل من الفوائد لو عدد الأقساط ≤ N
3. **بيانات القرار**: رقم القرار، تاريخه، الفرع (أو كل الفروع)
4. **فترة السريان**: effective_from → effective_to (مطلوبين)
### كيف يُطبَّق تلقائياً
1. العضو يفتح صفحته `/members/{id}`
2. الكنترولر يستدعي `BoardOfferService::getBestOffer($branchId)`
3. لو فيه عرض ساري:
- يظهر بانر أصفر "عرض مجلس الإدارة: {title}"
- الكاش يظهر بالمبلغ بعد الخصم + "وفّرت X"
- التقسيط يظهر بالشروط الجديدة (مقدم، أشهر، فائدة، سماح)
4. عند الضغط "إرسال للخزينة":
- يُخزن `board_offer_id` + `offer_snapshot_json` في `payment_requests`
- الـ snapshot يحفظ شروط العرض لحظة الطلب — تغيير العرض بعدها لا يؤثر
### الأولوية
- عرض بـ `branch_id` محدد يتقدم على عرض بدون فرع (كل الفروع)
- لو فيه أكثر من عرض ساري → الأكثر تحديداً يفوز (أحدث effective_from)
- عرض واحد فقط يُطبَّق في المرة الواحدة
### Edge Cases
- **العرض انتهى أثناء ما الطلب في الخزينة**: الـ snapshot محفوظ — الخصم يُطبَّق
- **العرض اتعطّل (is_active=0)**: لا يظهر للأعضاء الجدد، لكن الطلبات القديمة ما تتأثر
- **عرض مستقبلي (effective_from > today)**: لا يظهر — ينشط تلقائياً عند الموعد
- **عرض بدون مسار كاش ولا تقسيط**: لا يُطبَّق (الفاليديشن يمنع حفظه أصلاً)
- **عضو واحد يدفع كاش والتاني تقسيط**: كل واحد يأخذ المسار اللي اختاره
--- ---
## 2. تطبيق الخصم على عضو ## 2. الخصومات الخاصة (Special Discounts)
### الفكرة
خصومات على **قيمة العضوية نفسها** — إما يدوية (تُعيَّن لعضو) أو تلقائية (بشرط).
### المسار: التسعير ← الخصومات الخاصة
`/pricing/special-discounts`
### أنواع الخصم
| النوع | التأثير |
|-------|---------|
| `percentage` | يخصم X% من قيمة العضوية |
| `fixed_amount` | يخصم مبلغ ثابت |
| `free_subscription` | لا يخصم من العضوية — يمنح اشتراكات مجانية فقط |
### شروط التطبيق
| الشرط | المعنى |
|-------|--------|
| `none` | بدون شرط — يُطبَّق يدوياً عند تعيينه لعضو |
| `full_payment` | يُطبَّق تلقائياً لو العضو سدّد كامل قيمة العضوية |
| `min_payment` | يُطبَّق تلقائياً لو العضو سدّد مبلغ ≥ الحد الأدنى |
### كيف يُعيَّن لعضو (يدوي)
1. صفحة العضو `/members/{id}`
2. قسم "خصم خاص" (يظهر فقط قبل التفعيل)
3. اختر الخصم من القائمة → احفظ
4. يُخزن `special_discount_id` في جدول `members`
5. `BillingService::getMemberBill()` يحسب الخصم ويُنقصه من الفاتورة
**المسار:** الأعضاء ← اختر العضو ← صفحة العضو ### كيف يُطبَّق تلقائياً (بشرط)
1. افتح صفحة العضو (`/members/{id}`) 1. `SpecialDiscountService::evaluateForMember($memberId, $membershipValue, $totalPaid)` يُستدعى أثناء حساب الفاتورة
2. ابحث عن قسم **"خصم خاص"** (يظهر فقط إذا كان العضو في حالة `accepted` أو `payment_pending`) 2. يفحص: هل العضو معيّن له خصم؟ → يستخدمه
3. اختر الخصم من القائمة المنسدلة 3. لو لا → يبحث في الخصومات الشرطية (`condition_type != 'none'`)
4. إذا كان يحتاج مستند ← ارفع ملف الإثبات 4. يطابق الشرط (سداد كامل / حد أدنى) → يُطبَّق أعلى خصم مطابق
5. اضغط **"تطبيق الخصم"**
6. يظهر ✅ باسم الخصم والمبلغ المخصوم — ويمكنك إزالته بزر ❌ ### المكافأة: سنوات اشتراك مجانية
- حقل `bonus_free_subscription_years` (0-5)
- بعد تفعيل العضوية (event: `member.activated`):
- `SpecialDiscountService::getBonusFreeYears($memberId)` يحسب
- `applyFreeSubscriptionBonus()` يحول اشتراكات pending إلى paid (بـ receipt = 'FREE-BONUS')
### Edge Cases
- **خصم بدون فترة (effective_from/to = null)**: ساري دائماً
- **خصم percentage = 100%**: العضوية مجاناً — ممكن لأبناء شهداء مثلاً
- **خصم condition=full_payment + bonus=1 year**: الأكثر شيوعاً — ادفع كاش واحصل على اشتراك سنة مجاني
- **عضو بخصم يدوي + خصم شرطي**: اليدوي يفوز (يُفحص أولاً)
- **خصم معطّل (is_active=0) ومعيّن لعضو**: لا يُطبَّق — يُتجاهل
---
## 3. تعديل الاشتراك السنوي (Subscription Year Adjustment)
### الفكرة
خصم/زيادة على الاشتراكات السنوية لكل المشتركين في سنة مالية معينة.
### المسار: التسعير ← لوحة التسعير
`/pricing`
### كيف يعمل
- Rule Engine key: `SUBSCRIPTION_YEAR_ADJUSTMENT_{year}` (مثل `SUBSCRIPTION_YEAR_ADJUSTMENT_2026`)
- يحتوي `discount_percentage` (مثل 50 = خصم 50% على الاشتراك)
- `SubscriptionSyncService` و `SubscriptionGenerator` يطبقان الخصم عند إنشاء صف الاشتراك
### متى يُستخدم
- سنة كورونا: خصم 100% (اشتراك مجاني)
- سنة تأخير افتتاح: خصم 50%
- لا يؤثر على اشتراكات سبق إنشاؤها — فقط الجديدة
--- ---
## 3. عروض مجلس الإدارة (Board Offers) ## 4. الفرق بين Board Offer و Special Discount
**الملف:** `app/Modules/Members/Services/BoardOfferService.php` | | Board Offer | Special Discount |
|---|---|---|
| **يخصم من** | المبلغ المدفوع (كاش) أو يغيّر شروط التقسيط | قيمة العضوية في الفاتورة |
| **يُطبَّق على** | كل من يدفع خلال الفترة | عضو معيّن أو بشرط |
| **مصدره** | قرار مجلس إدارة | إدارة النادي |
| **يتراكم مع الآخر** | نعم — الاتنين ممكن ينطبقوا على نفس العضو | نعم |
| **أين يُحفظ** | `payment_requests.board_offer_id` + snapshot | `members.special_discount_id` |
| **تأثير على الفاتورة** | لا (معلوماتي فقط حتى الدفع) | نعم (ينقّص total_pending) |
هذه عروض تُطبق **تلقائياً** على جميع الأعضاء المستوفين خلال فترة العرض. ### مثال تراكم
- تُخزن في جدول `board_offers` عضو عضويته 150,000:
- تحتوي على: رقم القرار، تاريخ القرار، نوع العرض (خصم نقدي / خصم اشتراك / تغيير أقساط) 1. عليه خصم خاص 10% → الفاتورة تصبح 135,000
- تُطبق تلقائياً أثناء الفوترة (عبر `BillingService`) 2. عرض مجلس إدارة كاش 10% → يدفع 121,500
3. مكافأة سنة اشتراك مجانية → الاشتراك السنوي (492 + 35) = 0
---
> ملاحظة: حالياً لا يوجد UI لإدارة عروض مجلس الإدارة — يتم إدخالها في الداتابيز مباشرة. ## 5. دورة حياة العرض الكاملة (Board Offer Lifecycle)
```
إنشاء العرض (/pricing/board-offers/create)
[is_active=1, effective_from ≤ today ≤ effective_to]
↓ يظهر تلقائياً
صفحة العضو ← بانر أصفر + أزرار كاش/تقسيط محدّثة
↓ العضو يختار مسار الدفع
إرسال طلب دفع ← board_offer_id + snapshot يُحفظ
الخزينة تستلم الطلب ← تعالجه ← يُنشئ payment
payment.completed event ← يقرأ snapshot ← يطبّق الشروط
installment_plan يُنشأ بشروط العرض (لو تقسيط)
```
--- ---
## 4. خصومات الاشتراكات السنوية (سنوية تلقائية) ## 6. دورة حياة الخصم الخاص (Special Discount Lifecycle)
**المسار:** القائمة الجانبية ← **التسعير****لوحة التسعير** (`/pricing`) ```
إنشاء الخصم (/pricing/special-discounts/create)
[حالة 1: بدون شرط]
الموظف يعيّنه يدوياً لعضو ← members.special_discount_id = X
BillingService.getMemberBill() يحسب الخصم ← total_pending ينقص
- في لوحة التسعير، يمكنك ضبط **نسبة تعديل سنوية** لكل سنة مالية (مثلاً: خصم 50% لسنة 2023/2024) [حالة 2: بشرط full_payment]
- تُطبق تلقائياً عند توليد الاشتراكات السنوية عبر `SubscriptionGenerator` العضو يسدّد كامل ← evaluateForMember() يكتشف الشرط متحقق
- يتم تعريفها كـ Rule Engine key: `SUBSCRIPTION_YEAR_ADJUSTMENT_2024_2025`
الخصم يُطبَّق تلقائياً في الفاتورة
member.activated event ← getBonusFreeYears() ← applyFreeSubscriptionBonus()
```
--- ---
## 5. خصومات الأنشطة الرياضية (الأكاديميات) ## 7. الملفات المسؤولة
| الملف | الدور |
|-------|-------|
| `Pricing/Controllers/BoardOfferController.php` | CRUD عروض مجلس الإدارة |
| `Pricing/Controllers/SpecialDiscountController.php` | CRUD الخصومات الخاصة |
| `Members/Services/BoardOfferService.php` | جلب العرض الأفضل + حساب خصم كاش + شروط تقسيط |
| `Pricing/Services/SpecialDiscountService.php` | تقييم خصم عضو + تطبيق مكافأة سنوات |
| `Members/Services/BillingService.php` (line 649) | يعرض العرض كبند في الفاتورة |
| `Members/Controllers/MemberController.php` (line 514) | يحفظ snapshot العرض عند إرسال طلب الدفع |
| `Pricing/Services/PricingEngine.php` | حساب أسعار العضوية + رسوم الأبناء + الأقساط |
| `Pricing/bootstrap.php` (line 35) | event listener: يطبّق مكافأة الاشتراك بعد التفعيل |
تُطبق تلقائياً عند التسجيل في الأكاديميات: ---
- **خصم الدفع المقدم**: دفع 3+ شهور مقدماً = 15% ## 8. جداول قاعدة البيانات
- **خصم الأشقاء**: أخ/أخت مسجل في نفس النشاط = خصم محدد
هذه تُدار من جدول `sa_discount_rules` ولا تحتاج تدخل يدوي. ### `board_offers`
```
id, title_ar, title_en, description,
cash_discount_type (percentage|fixed_amount|NULL),
cash_discount_value,
inst_down_payment_pct, inst_months, inst_interest_rate,
inst_grace_type (first_n_months|full_free_under_n|NULL),
inst_grace_months, inst_post_grace_rate,
branch_id (FK|NULL), applies_to (membership_fee|all),
effective_from, effective_to, is_active,
board_decision_number, board_decision_date, notes,
created_by, updated_by, created_at, updated_at
```
### `special_discounts`
```
id, name_ar, name_en,
discount_type (percentage|fixed_amount|free_subscription),
discount_percentage, fixed_amount,
applies_to (membership_fee|subscription|all),
effective_from (nullable), effective_to (nullable),
condition_type (none|full_payment|min_payment),
condition_min_amount,
bonus_free_subscription_years (0-5),
requires_document (0|1),
description, is_active,
created_at, updated_at, created_by, updated_by
```
### `payment_requests` (الحقول المتعلقة)
```
board_offer_id (FK|NULL),
offer_snapshot_json (JSON string of offer at request time)
```
### `members` (الحقول المتعلقة)
```
special_discount_id (FK|NULL)
```
--- ---
## 6. خصومات العضوية الموسمية ## 9. سيناريوهات شائعة
### "عايزين عرض كاش 10% لمدة 3 شهور"
1. `/pricing/board-offers/create`
2. العنوان: "عرض الصيف — خصم 10% كاش"
3. فعّل مسار الكاش → percentage → 10
4. التواريخ: اليوم → بعد 3 شهور
5. احفظ ← كل عضو يدفع كاش خلال الفترة يحصل على 10% خصم تلقائي
### "عايزين تقسيط على 48 شهر بفائدة 15% بدل 30 شهر بـ 22%"
1. `/pricing/board-offers/create`
2. فعّل مسار التقسيط → 48 شهر → 15% فائدة
3. ممكن تفعّل الكاش أيضاً (العرض يقدم المسارين)
### "عايزين إعفاء من الفوائد لو دفع في 6 شهور أو أقل"
1. مسار تقسيط → grace_type = `full_free_under_n` → grace_months = 6
2. لو العضو اختار 6 أقساط أو أقل → فائدة = 0%
3. لو اختار 7+ → الفائدة العادية تُطبَّق
تُطبق تلقائياً عند إنشاء عضوية موسمية: ### "عايزين خصم 10% لأي عضو يدفع كامل"
1. `/pricing/special-discounts/create`
2. نوع: percentage → 10%
3. شرط: full_payment
4. مجال: membership_fee
5. ← أي عضو يسدّد كامل العضوية نقداً يحصل على 10% تلقائياً
- **خصم عائلي** (أكثر من شخص في نفس العائلة) ### "عايزين سنة اشتراك مجاناً لأي عضو يدفع كاش"
- **خصم بدون مؤهل**: 10% 1. نفس الخصم أعلاه + bonus_free_subscription_years = 1
- **خصم عضو مؤهل**: 20% 2. بعد تفعيل العضوية → اشتراك السنة الحالية يتحول لـ "paid" تلقائياً
--- ---
## ملخص المسارات ## 10. ما لا يجب فعله
| العملية | المسار في السيستم | - **لا تنشئ board offer + special discount بنفس النسبة** — هيتراكموا والعضو يأخذ خصم مضاعف
| ------------------------------ | ----------------------------- | - **لا تعدّل عرض ساري والطلبات في الخزينة** — الطلبات القديمة محمية بالـ snapshot، بس ممكن يسبب لخبطة في التقارير
| إدارة الخصومات الخاصة | التسعير ← الخصومات الخاصة | - **لا تحذف خصم معيّن لعضو** — عطّله (is_active=0) بدل ما تحذفه، عشان السجل
| تطبيق خصم على عضو | صفحة العضو ← قسم "خصم خاص" | - **لا تضع effective_to = null في board offer** — مطلوب؛ العروض لازم لها نهاية
| خصومات سنوية (rate adjustment) | التسعير ← لوحة التسعير | - **لا تنسى إن الفائدة سنوية مش شهرية** — 22% سنوي × (شهور/12) هي المعادلة
| عروض مجلس الإدارة | (تلقائية — لا يوجد UI حالياً) |
| خصومات الأكاديميات | (تلقائية عند التسجيل) |
| خصومات الموسمي | (تلقائية عند الإنشاء) |
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment