// food_delivery_models.dart — نماذج بيانات وحدة التوصيل (جهة السائق) int _int(dynamic v) => int.tryParse(v?.toString() ?? '') ?? 0; double? _doubleOrNull(dynamic v) => double.tryParse(v?.toString() ?? ''); double _double(dynamic v) => double.tryParse(v?.toString() ?? '') ?? 0; DateTime? _date(dynamic v) => DateTime.tryParse(v?.toString() ?? ''); class FoodDeliveryTask { final int id; final String status; // courier_assigned | picked_up final int merchantId; final String merchantNameAr; final double? merchantLat; final double? merchantLng; final String? merchantAddress; final int deliveryFee; final int itemsTotal; final int serviceFee; final int discount; final int grandTotal; final String paymentMethod; // wallet | cash final int cashToCollect; final int courierOwes; final int itemsCount; final String? customerNote; final String? deliveryAddress; final double deliveryLat; final double deliveryLng; final DateTime? createdAt; final DateTime? readyAt; final DateTime? courierAssignedAt; final DateTime? pickedUpAt; FoodDeliveryTask({ required this.id, required this.status, required this.merchantId, required this.merchantNameAr, required this.deliveryFee, required this.deliveryLat, required this.deliveryLng, this.itemsTotal = 0, this.serviceFee = 0, this.discount = 0, this.grandTotal = 0, this.paymentMethod = 'wallet', this.cashToCollect = 0, this.courierOwes = 0, this.itemsCount = 0, this.customerNote, this.merchantLat, this.merchantLng, this.merchantAddress, this.deliveryAddress, this.createdAt, this.readyAt, this.courierAssignedAt, this.pickedUpAt, }); bool get isPickedUp => status == 'picked_up'; bool get isCash => paymentMethod == 'cash'; factory FoodDeliveryTask.fromJson(Map j) => FoodDeliveryTask( id: _int(j['id']), status: j['status']?.toString() ?? '', merchantId: _int(j['merchant_id']), merchantNameAr: j['merchant_name_ar']?.toString() ?? '', merchantLat: _doubleOrNull(j['merchant_lat']), merchantLng: _doubleOrNull(j['merchant_lng']), merchantAddress: j['merchant_address']?.toString(), deliveryFee: _int(j['delivery_fee']), itemsTotal: _int(j['items_total']), serviceFee: _int(j['service_fee']), discount: _int(j['discount']), grandTotal: _int(j['grand_total']), paymentMethod: j['payment_method']?.toString() ?? 'wallet', cashToCollect: _int(j['cash_to_collect']), courierOwes: _int(j['courier_owes']), itemsCount: _int(j['items_count']), customerNote: j['customer_note']?.toString(), deliveryAddress: j['delivery_address']?.toString(), deliveryLat: _double(j['delivery_lat']), deliveryLng: _double(j['delivery_lng']), createdAt: _date(j['created_at']), readyAt: _date(j['ready_at']), courierAssignedAt: _date(j['courier_assigned_at']), pickedUpAt: _date(j['picked_up_at']), ); } // عرض توصيل وارد — يصل عبر سوكيت الطعام ('food_delivery_offer') وبنفس الحقول // من courier/pending_offers.php عند انقطاع السوكيت (foodBuildCourierOfferPayload). class FoodDeliveryOffer { final int orderId; final int merchantId; final String merchantNameAr; final String? merchantAddress; final double? merchantLat; final double? merchantLng; final double deliveryLat; final double deliveryLng; final int deliveryFee; final int itemsCount; final String paymentMethod; final int cashToCollect; final int ttlSeconds; final DateTime receivedAt; final DateTime? offeredAt; FoodDeliveryOffer({ required this.orderId, required this.merchantId, required this.merchantNameAr, required this.deliveryFee, this.merchantAddress, this.merchantLat, this.merchantLng, this.deliveryLat = 0, this.deliveryLng = 0, this.itemsCount = 0, this.paymentMethod = 'wallet', this.cashToCollect = 0, this.ttlSeconds = 20, DateTime? receivedAt, this.offeredAt, }) : receivedAt = receivedAt ?? DateTime.now(); bool get isCash => paymentMethod == 'cash'; factory FoodDeliveryOffer.fromJson(Map j) => FoodDeliveryOffer( orderId: _int(j['order_id']), merchantId: _int(j['merchant_id']), merchantNameAr: j['merchant_name_ar']?.toString() ?? '', merchantAddress: j['merchant_address']?.toString(), merchantLat: _doubleOrNull(j['merchant_lat']), merchantLng: _doubleOrNull(j['merchant_lng']), deliveryLat: _double(j['delivery_lat']), deliveryLng: _double(j['delivery_lng']), deliveryFee: _int(j['delivery_fee']), itemsCount: _int(j['items_count']), paymentMethod: j['payment_method']?.toString() ?? 'wallet', cashToCollect: _int(j['cash_to_collect']), ttlSeconds: _int(j['offer_ttl_seconds']) == 0 ? 20 : _int(j['offer_ttl_seconds']), offeredAt: _date(j['offered_at']), ); // العدّاد للعرض فقط — الخادم هو الحكم الفعلي ويرفض offer_respond بعد المهلة. // نبدأ من offered_at إن جاءت (مسار polling)، وإلا من لحظة الاستلام (مسار السوكيت). int get secondsRemaining { final start = offeredAt ?? receivedAt; final elapsed = DateTime.now().difference(start).inSeconds; return (ttlSeconds - elapsed).clamp(0, ttlSeconds); } bool get isExpired => secondsRemaining <= 0; } // صنف داخل الطلب — يراه السائق ليتأكد من محتوى الكيس عند الاستلام من المطعم class FoodOrderItem { final String nameAr; final int quantity; final int unitPrice; final int lineTotal; FoodOrderItem({ required this.nameAr, required this.quantity, required this.unitPrice, required this.lineTotal, }); factory FoodOrderItem.fromJson(Map j) => FoodOrderItem( nameAr: j['name_ar']?.toString() ?? '', quantity: _int(j['quantity']), unitPrice: _int(j['unit_price']), lineTotal: _int(j['line_total']), ); } class FoodOrderDetails { final FoodDeliveryTask task; final List items; final String? merchantPhone; FoodOrderDetails({required this.task, required this.items, this.merchantPhone}); factory FoodOrderDetails.fromJson(Map j) => FoodOrderDetails( task: FoodDeliveryTask.fromJson(j), items: (j['items'] is List) ? (j['items'] as List) .map((i) => FoodOrderItem.fromJson(Map.from(i))) .toList() : [], merchantPhone: j['merchant_phone']?.toString(), ); } class FoodCourierStatus { final bool deliveryModeEnabled; final int activeOrdersCount; final int todayOrdersCount; final int todayEarnings; FoodCourierStatus({ required this.deliveryModeEnabled, this.activeOrdersCount = 0, this.todayOrdersCount = 0, this.todayEarnings = 0, }); factory FoodCourierStatus.fromJson(Map j) => FoodCourierStatus( deliveryModeEnabled: j['delivery_mode_enabled'] == true || j['delivery_mode_enabled']?.toString() == '1' || j['delivery_mode_enabled']?.toString() == 'true', activeOrdersCount: _int(j['active_orders_count']), todayOrdersCount: _int(j['today_orders_count']), todayEarnings: _int(j['today_earnings']), ); } class FoodEarningsDay { final String day; final int ordersCount; final int earnings; FoodEarningsDay({required this.day, required this.ordersCount, required this.earnings}); factory FoodEarningsDay.fromJson(Map j) => FoodEarningsDay( day: j['day']?.toString() ?? '', ordersCount: _int(j['orders_count']), earnings: _int(j['earnings']), ); } class FoodEarnings { final int todayEarnings; final int todayOrders; final int weekEarnings; final int weekOrders; final int monthEarnings; final int monthOrders; final int totalEarnings; final int totalOrders; final int pendingCashOwed; final List daily; FoodEarnings({ required this.todayEarnings, required this.todayOrders, required this.weekEarnings, required this.weekOrders, required this.monthEarnings, required this.monthOrders, required this.totalEarnings, required this.totalOrders, required this.pendingCashOwed, required this.daily, }); factory FoodEarnings.fromJson(Map j) => FoodEarnings( todayEarnings: _int(j['today_earnings']), todayOrders: _int(j['today_orders']), weekEarnings: _int(j['week_earnings']), weekOrders: _int(j['week_orders']), monthEarnings: _int(j['month_earnings']), monthOrders: _int(j['month_orders']), totalEarnings: _int(j['total_earnings']), totalOrders: _int(j['total_orders']), pendingCashOwed: _int(j['pending_cash_owed']), daily: (j['daily'] is List) ? (j['daily'] as List) .map((d) => FoodEarningsDay.fromJson(Map.from(d))) .toList() : [], ); } class FoodHistoryEntry { final int id; final String merchantNameAr; final String? merchantAddress; final int deliveryFee; final int grandTotal; final String paymentMethod; final int itemsCount; final int? rating; final int? durationMinutes; final DateTime? deliveredAt; FoodHistoryEntry({ required this.id, required this.merchantNameAr, required this.deliveryFee, this.merchantAddress, this.grandTotal = 0, this.paymentMethod = 'wallet', this.itemsCount = 0, this.rating, this.durationMinutes, this.deliveredAt, }); factory FoodHistoryEntry.fromJson(Map j) => FoodHistoryEntry( id: _int(j['id']), merchantNameAr: j['merchant_name_ar']?.toString() ?? '', merchantAddress: j['merchant_address']?.toString(), deliveryFee: _int(j['delivery_fee']), grandTotal: _int(j['grand_total']), paymentMethod: j['payment_method']?.toString() ?? 'wallet', itemsCount: _int(j['items_count']), rating: j['rating'] == null ? null : _int(j['rating']), durationMinutes: j['duration_minutes'] == null ? null : _int(j['duration_minutes']), deliveredAt: _date(j['delivered_at']), ); } const int foodCurrencyDivisor = 1000; String foodFormatPrice(int smallestUnit, {String currencySymbol = 'د.أ'}) { final decimal = smallestUnit / foodCurrencyDivisor; return '${decimal.toStringAsFixed(3)} $currencySymbol'; }