Files
tripz-llc/backend/src/database/migrations/1721840000000-TripPricingAndTimestamps.ts
T
Hamza-AyedandClaude Opus 4.8 f3fdc1bc20 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>
2026-07-17 03:11:36 +03:00

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);
}
}
}