// earnings_models.dart — نماذج شاشة الأرباح // // ‏كل الأرقام تصل من نقطة واحدة (`ride/earnings/summary.php`) بختم زمني // ‏واحد، فلا مجال لأن يتناقض رقمان في الشاشة نفسها. class PeriodEarnings { final int trips; /// ‏صافي السائق — حصته المثبَّتة لحظة عرض السعر، أي ما وُعد به قبل القبول. final double net; /// ‏إجمالي ما دفعه الركاب. final double gross; /// ‏عمولة المنصة. تُعرض صراحةً لا تُطرح بصمت: السائق الذي يرى تركيبة /// ‏المبلغ يفهم، والذي يرى الصافي وحده يخمّن ويشكّ. final double commission; final double distance; final int minutes; PeriodEarnings({ required this.trips, required this.net, required this.gross, required this.commission, required this.distance, required this.minutes, }); double get hours => minutes / 60.0; factory PeriodEarnings.fromJson(Map j) => PeriodEarnings( trips: int.tryParse(j['trips']?.toString() ?? '0') ?? 0, net: double.tryParse(j['net']?.toString() ?? '0') ?? 0, gross: double.tryParse(j['gross']?.toString() ?? '0') ?? 0, commission: double.tryParse(j['commission']?.toString() ?? '0') ?? 0, distance: double.tryParse(j['distance']?.toString() ?? '0') ?? 0, minutes: int.tryParse(j['minutes']?.toString() ?? '0') ?? 0, ); static PeriodEarnings empty() => PeriodEarnings( trips: 0, net: 0, gross: 0, commission: 0, distance: 0, minutes: 0); } class DayEarnings { final String day; final int trips; final double net; final int minutes; DayEarnings( {required this.day, required this.trips, required this.net, required this.minutes}); factory DayEarnings.fromJson(Map j) => DayEarnings( day: j['day']?.toString() ?? '', trips: int.tryParse(j['trips']?.toString() ?? '0') ?? 0, net: double.tryParse(j['net']?.toString() ?? '0') ?? 0, minutes: int.tryParse(j['minutes']?.toString() ?? '0') ?? 0, ); } class DeductionItem { final String product; final String name; final double outstanding; DeductionItem( {required this.product, required this.name, required this.outstanding}); factory DeductionItem.fromJson(Map j) => DeductionItem( product: j['product']?.toString() ?? '', name: j['name']?.toString() ?? '', outstanding: double.tryParse(j['outstanding']?.toString() ?? '0') ?? 0, ); } /// ‏الاقتطاعات (تأمين، وقود) تُعرض بجانب الأرباح التي اقتُطعت منها. /// ‏اقتطاعٌ لا يظهر في مكانه يُقرأ سرقةً مهما كان مشروعاً. class Deductions { final double today; final double week; final double month; final double outstanding; final List items; Deductions({ required this.today, required this.week, required this.month, required this.outstanding, required this.items, }); bool get hasAny => today > 0 || outstanding > 0; factory Deductions.fromJson(Map j) => Deductions( today: double.tryParse(j['today']?.toString() ?? '0') ?? 0, week: double.tryParse(j['week']?.toString() ?? '0') ?? 0, month: double.tryParse(j['month']?.toString() ?? '0') ?? 0, outstanding: double.tryParse(j['outstanding']?.toString() ?? '0') ?? 0, items: (j['items'] is List) ? (j['items'] as List) .map((i) => DeductionItem.fromJson(Map.from(i))) .toList() : [], ); } class EarningsSummary { final PeriodEarnings today; final PeriodEarnings week; final PeriodEarnings month; final PeriodEarnings prevWeek; /// ‏نسبة التغيّر عن الأسبوع الماضي، أو null إن لم يكن هناك ما يُقارَن به. /// ‏المقارنة بالذات لا بالسائقين الآخرين: ترتيبٌ يُحبط أكثر من يحتاج /// ‏التحفيز — فهو من سيقع في أسفله. final double? trendPercent; final List daily; final DayEarnings? bestDay; final Deductions deductions; final String asOf; EarningsSummary({ required this.today, required this.week, required this.month, required this.prevWeek, required this.daily, required this.deductions, required this.asOf, this.trendPercent, this.bestDay, }); factory EarningsSummary.fromJson(Map j) => EarningsSummary( today: PeriodEarnings.fromJson(Map.from(j['today'] ?? {})), week: PeriodEarnings.fromJson(Map.from(j['week'] ?? {})), month: PeriodEarnings.fromJson(Map.from(j['month'] ?? {})), prevWeek: PeriodEarnings.fromJson(Map.from(j['prev_week'] ?? {})), trendPercent: j['trend_percent'] == null ? null : double.tryParse(j['trend_percent'].toString()), daily: (j['daily'] is List) ? (j['daily'] as List) .map((d) => DayEarnings.fromJson(Map.from(d))) .toList() : [], bestDay: j['best_day'] is Map ? DayEarnings.fromJson(Map.from(j['best_day'])) : null, deductions: Deductions.fromJson(Map.from(j['deductions'] ?? {})), asOf: j['as_of']?.toString() ?? '', ); } class TripBreakdown { final double gross; final double commission; final double bonus; final double net; /// ‏إعفاء العمولة (سلسلة ٠٪) — هدية تدفع المنصة ثمنها ولم تكن تظهر /// ‏للسائق في أي شاشة. نميّزها لتُعرض مكسباً لا سطراً فارغاً. final bool zeroCommission; TripBreakdown({ required this.gross, required this.commission, required this.bonus, required this.net, required this.zeroCommission, }); factory TripBreakdown.fromJson(Map j) => TripBreakdown( gross: double.tryParse(j['gross']?.toString() ?? '0') ?? 0, commission: double.tryParse(j['commission']?.toString() ?? '0') ?? 0, bonus: double.tryParse(j['bonus']?.toString() ?? '0') ?? 0, net: double.tryParse(j['net']?.toString() ?? '0') ?? 0, zeroCommission: j['zero_commission'] == true, ); } class TripEarning { final int rideId; final String at; final String from; final String to; final String carType; final String paymentMethod; final double distanceKm; final int minutes; final TripBreakdown breakdown; TripEarning({ required this.rideId, required this.at, required this.from, required this.to, required this.carType, required this.paymentMethod, required this.distanceKm, required this.minutes, required this.breakdown, }); factory TripEarning.fromJson(Map j) => TripEarning( rideId: int.tryParse(j['ride_id']?.toString() ?? '0') ?? 0, at: j['at']?.toString() ?? '', from: j['from']?.toString() ?? '', to: j['to']?.toString() ?? '', carType: j['car_type']?.toString() ?? '', paymentMethod: j['payment_method']?.toString() ?? '', distanceKm: double.tryParse(j['distance_km']?.toString() ?? '0') ?? 0, minutes: int.tryParse(j['minutes']?.toString() ?? '0') ?? 0, breakdown: TripBreakdown.fromJson(Map.from(j['breakdown'] ?? {})), ); }