feat: المجموعة B (شريحة 1) — فصل السعر + الانتظار + مشوار الوصول + الطوابع
B6 فصل السعر (الأهم — أساس كل عمل مالي لاحق):
- price_for_passenger / price_for_driver / commission_amount / commission_rate
- العمولة تُعرَّف في التعرفة: commission { percent, flat, min }، ولا تتجاوز
الأجرة أبداً (دخل السائق لا يصير سالباً)
- **إصلاح**: تسوية المحفظة كانت تُضيف للسائق كامل أجرة الراكب — أي أن
العمولة كانت تضيع. صار الراكب يُخصم passenger والسائق يُضاف له driver
- العرض يحمل earnings (ما يقبضه السائق) لا أجرة الراكب
B2 الانتظار: يبدأ عند driver_arrived ويُحتسب عند in_progress. أول 5 دقائق
مجانية (free_waiting_min قابل للضبط لكل تعرفة)، والزائد بسعر النافذة.
B3 مشوار الوصول: يُقاس من موقع السائق الحيّ لحظة القبول حتى نقطة الالتقاط.
لا يُحتسب على الراكب ما لم يُفعّله المالك (pickup_leg.charge) — تفعيله يرفع
كل أجرة فهو قرار لا افتراض صامت. فشل القياس لا يُفشل القبول.
B7 الطوابع: driver_going_at · arrived_at · started_at (+ completed_at).
كشف «الإنهاء السريع» صار يقيس من بدء الرحلة لا إسنادها — المشوار والانتظار
ليسا جزءاً من زمنها.
B1: كان منفَّذاً أصلاً — driver_arrived و in_progress حالتان منفصلتان منذ P1.
معلَّق على I2: العمولة محسوبة ومسجّلة لكنها لا تُقيَّد في محفظة المنصة
(لم تُبنَ بعد).
هجرة: TripPricingAndTimestamps (تملأ الرحلات القائمة: دفع = قبض).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7e24c63dd8
commit
f3fdc1bc20
@@ -77,6 +77,69 @@ export class TariffEngine {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* رسم الانتظار بعد وصول السائق (docs/17 — B2). الدقائق المجانية (5 قانوناً)
|
||||
* تُخصم أولاً، والباقي يُحسب بسعر انتظار النافذة الفعّالة.
|
||||
*/
|
||||
static waitingCharge(
|
||||
def: TariffDefinition,
|
||||
waitedMin: number,
|
||||
at: Date = new Date(),
|
||||
): { freeMin: number; billableMin: number; charge: number } {
|
||||
const win = TariffEngine.pickWindow(def.windows, at);
|
||||
const freeMin = def.free_waiting_min ?? 5;
|
||||
const billableMin = Math.max(0, (waitedMin ?? 0) - freeMin);
|
||||
return {
|
||||
freeMin,
|
||||
billableMin: TariffEngine.n(billableMin),
|
||||
charge: TariffEngine.n(billableMin * (win.per_min_waiting ?? 0)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* رسم مشوار وصول السائق للراكب (docs/17 — B3).
|
||||
* يرجع صفراً ما لم يُفعِّله المالك صراحةً في التعرفة.
|
||||
*/
|
||||
static pickupCharge(
|
||||
def: TariffDefinition,
|
||||
distanceKm: number,
|
||||
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;
|
||||
return TariffEngine.n(charge);
|
||||
}
|
||||
|
||||
/**
|
||||
* فصل السعر (docs/17 — B6): يقسم ما يدفعه الراكب إلى عمولة المنصة وحصة
|
||||
* السائق. العمولة لا تتجاوز الأجرة مهما كانت الإعدادات — وإلا صار دخل
|
||||
* السائق سالباً.
|
||||
*/
|
||||
static split(
|
||||
def: TariffDefinition,
|
||||
passengerTotal: number,
|
||||
): { forPassenger: number; forDriver: number; commission: 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 };
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
private static pickWindow(windows: TariffWindow[], at: Date): TariffWindow {
|
||||
const mins = at.getUTCHours() * 60 + at.getUTCMinutes();
|
||||
for (const w of windows) {
|
||||
|
||||
Reference in New Issue
Block a user