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>
53 lines
2.2 KiB
TypeScript
53 lines
2.2 KiB
TypeScript
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
|
|
/**
|
|
* نموذج الرحلة (docs/17 — B2/B3/B6/B7):
|
|
* - فصل السعر: ما يدفعه الراكب مقابل ما يقبضه السائق + العمولة.
|
|
* - الانتظار: الدقائق ورسمها.
|
|
* - مشوار وصول السائق للراكب: مسافته وزمنه ورسمه.
|
|
* - طوابع دقيقة: انطلاق نحو الراكب · الوصول · بدء الرحلة.
|
|
*/
|
|
export class TripPricingAndTimestamps1721840000000 implements MigrationInterface {
|
|
public async up(q: QueryRunner): Promise<void> {
|
|
const add = (col: string, type: string) =>
|
|
q.query(`ALTER TABLE tripz_trips ADD COLUMN IF NOT EXISTS ${col} ${type}`);
|
|
|
|
await add('price_for_passenger', 'numeric(12,3)');
|
|
await add('price_for_driver', 'numeric(12,3)');
|
|
await add('commission_amount', 'numeric(12,3)');
|
|
await add('commission_rate', 'numeric(5,2)');
|
|
|
|
await add('waiting_min', 'numeric(6,1)');
|
|
await add('waiting_charge', 'numeric(12,3)');
|
|
|
|
await add('pickup_distance_km', 'numeric(8,3)');
|
|
await add('pickup_duration_min', 'numeric(8,1)');
|
|
await add('pickup_charge', 'numeric(12,3)');
|
|
|
|
await add('driver_going_at', 'timestamptz');
|
|
await add('arrived_at', 'timestamptz');
|
|
await add('started_at', 'timestamptz');
|
|
|
|
// رحلات قائمة: الراكب دفع = السائق قبض (لم تكن هناك عمولة أصلاً).
|
|
await q.query(`
|
|
UPDATE tripz_trips
|
|
SET price_for_passenger = COALESCE(price_for_passenger, final_fare, quoted_fare),
|
|
price_for_driver = COALESCE(price_for_driver, final_fare, quoted_fare)
|
|
WHERE quoted_fare IS NOT NULL OR final_fare IS NOT NULL
|
|
`);
|
|
}
|
|
|
|
public async down(q: QueryRunner): Promise<void> {
|
|
const drop = (col: string) =>
|
|
q.query(`ALTER TABLE tripz_trips DROP COLUMN IF EXISTS ${col}`);
|
|
for (const c of [
|
|
'started_at', 'arrived_at', 'driver_going_at',
|
|
'pickup_charge', 'pickup_duration_min', 'pickup_distance_km',
|
|
'waiting_charge', 'waiting_min',
|
|
'commission_rate', 'commission_amount', 'price_for_driver', 'price_for_passenger',
|
|
]) {
|
|
await drop(c);
|
|
}
|
|
}
|
|
}
|