Update codebase
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
// 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<FoodOrderItem> items;
|
||||
final String? merchantPhone;
|
||||
|
||||
FoodOrderDetails({required this.task, required this.items, this.merchantPhone});
|
||||
|
||||
factory FoodOrderDetails.fromJson(Map<String, dynamic> j) => FoodOrderDetails(
|
||||
task: FoodDeliveryTask.fromJson(j),
|
||||
items: (j['items'] is List)
|
||||
? (j['items'] as List)
|
||||
.map((i) => FoodOrderItem.fromJson(Map<String, dynamic>.from(i)))
|
||||
.toList()
|
||||
: <FoodOrderItem>[],
|
||||
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<String, dynamic> 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<String, dynamic> 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<FoodEarningsDay> 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<String, dynamic> 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<String, dynamic>.from(d)))
|
||||
.toList()
|
||||
: <FoodEarningsDay>[],
|
||||
);
|
||||
}
|
||||
|
||||
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<String, dynamic> 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';
|
||||
}
|
||||
Reference in New Issue
Block a user