feat: المجموعة J — الرصيد التشغيلي + تصحيح نموذج العمولة (B3/B6)
فلسفة العمولة الصحيحة (docs/18): السائق يقبض أجرة الراكب **كاملة**،
والعمولة تُخصم من رصيد تشغيلي مدفوع سلفاً. ما بُني في f3fdc1b كان نموذج
أوبر (اقتطاع من الأرباح) — عكس المطلوب.
- J1: driver_credits + credit_txns. خصم ذرّي كنمط I1 لكن **بلا شرط رصيد**
ولا قيد >= 0: الدين مسموح عمداً (قرار المالك: الرحلة لا تُقطع أبداً)
- J2: خصم العمولة عند completed لا paid — الرحلة تمّت فالعمولة استُحقّت.
قاعدة واحدة للكاش والمحفظة (§5.5): لا تفريع حسب وسيلة الدفع
- J3: price_for_driver = price_for_passenger. TariffEngine.split →
commission، وبلا سقف بالأجرة لأن الخصم على رصيد منفصل يجوز أن يسلب
- J4: الحجب عند تجاوز credit_floor فقط، عند setOnline و accept — لا أثناء
رحلة. الحجب يمنع الاتصال أصلاً وإلا عُرضت رحلات يُرفض قبولها
- J6: مكافأة التسجيل عند الاعتماد (3 JOD / 300 SYP جديدة / 300 EGP).
حارسان: فحص تطبيقي للحالة الشائعة + فهرس فريد جزئي للسباق
- J7: credit_floor لكل عملة
- J8: GET /credit + /credit/transactions
تصحيح B3: مشوار الوصول تعويضُ إلغاء بعد انقضاء الانتظار المجاني — لا بند
في كل أجرة. أُزيل مفتاح pickup_leg.charge.
هجرة: DriverCredit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2edd8f6916
commit
d9b0faab2d
@@ -97,8 +97,11 @@ export class TariffEngine {
|
||||
}
|
||||
|
||||
/**
|
||||
* رسم مشوار وصول السائق للراكب (docs/17 — B3).
|
||||
* يرجع صفراً ما لم يُفعِّله المالك صراحةً في التعرفة.
|
||||
* قيمة مشوار وصول السائق للراكب (docs/17 — B3).
|
||||
*
|
||||
* **ليست بنداً في أجرة الرحلة.** تُحسب دائماً وتُخزَّن، لكنها لا تُحصَّل إلا
|
||||
* كـ**تعويض إلغاء**: إن انتظر السائق دقائقه المجانية ثم أُلغيت الرحلة، يقبض
|
||||
* قيمة المشوار الذي قطعه بلا مقابل.
|
||||
*/
|
||||
static pickupCharge(
|
||||
def: TariffDefinition,
|
||||
@@ -106,7 +109,6 @@ export class TariffEngine {
|
||||
durationMin: number,
|
||||
at: Date = new Date(),
|
||||
): number {
|
||||
if (!def.pickup_leg?.charge) return 0;
|
||||
const win = TariffEngine.pickWindow(def.windows, at);
|
||||
const charge =
|
||||
Math.max(0, distanceKm ?? 0) * win.per_km + Math.max(0, durationMin ?? 0) * win.per_min;
|
||||
@@ -114,30 +116,27 @@ export class TariffEngine {
|
||||
}
|
||||
|
||||
/**
|
||||
* فصل السعر (docs/17 — B6): يقسم ما يدفعه الراكب إلى عمولة المنصة وحصة
|
||||
* السائق. العمولة لا تتجاوز الأجرة مهما كانت الإعدادات — وإلا صار دخل
|
||||
* السائق سالباً.
|
||||
* حساب العمولة (docs/18).
|
||||
*
|
||||
* **العمولة لا تُقتطع من أجرة السائق** — السائق يقبض الأجرة كاملة من الراكب،
|
||||
* والعمولة تُخصم من رصيده التشغيلي المدفوع سلفاً. هذه الدالة تحسب المبلغ
|
||||
* المستحق فقط؛ من يخصمه هو `DriverCreditService`.
|
||||
*
|
||||
* لذلك **لا سقف** يمنع تجاوز العمولة للأجرة هنا: الخصم يقع على رصيد منفصل
|
||||
* يجوز أن يصير سالباً، لا على دخل السائق.
|
||||
*/
|
||||
static split(
|
||||
static commission(
|
||||
def: TariffDefinition,
|
||||
passengerTotal: number,
|
||||
): { forPassenger: number; forDriver: number; commission: number; rate: number } {
|
||||
): { amount: number; rate: number } {
|
||||
const total = Math.max(0, passengerTotal ?? 0);
|
||||
const c = def.commission;
|
||||
if (!c) {
|
||||
return { forPassenger: TariffEngine.n(total), forDriver: TariffEngine.n(total), commission: 0, rate: 0 };
|
||||
}
|
||||
if (!c) return { amount: 0, rate: 0 };
|
||||
|
||||
const percent = c.percent ?? 0;
|
||||
let commission = total * (percent / 100) + (c.flat ?? 0);
|
||||
if (c.min != null && commission < c.min) commission = c.min;
|
||||
if (commission > total) commission = total;
|
||||
commission = TariffEngine.n(commission);
|
||||
return {
|
||||
forPassenger: TariffEngine.n(total),
|
||||
forDriver: TariffEngine.n(total - commission),
|
||||
commission,
|
||||
rate: percent,
|
||||
};
|
||||
let amount = total * (percent / 100) + (c.flat ?? 0);
|
||||
if (c.min != null && amount < c.min) amount = c.min;
|
||||
return { amount: TariffEngine.n(amount), rate: percent };
|
||||
}
|
||||
|
||||
private static pickWindow(windows: TariffWindow[], at: Date): TariffWindow {
|
||||
|
||||
Reference in New Issue
Block a user