import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../controller/admin/courier_settlement_controller.dart'; /// شاشة تسوية أرباح سائق التوصيل. /// /// المسار: إدخال → معاينة → تنفيذ. زر التنفيذ لا يظهر قبل المعاينة. class CourierSettlementPage extends StatelessWidget { const CourierSettlementPage({super.key}); @override Widget build(BuildContext context) { final c = Get.put(CourierSettlementController()); return Directionality( textDirection: TextDirection.rtl, child: Scaffold( appBar: AppBar(title: const Text('تسوية أرباح التوصيل')), body: ListView( padding: const EdgeInsets.all(16), children: [ _Card( title: 'الفترة والسائق', icon: Icons.tune, child: Column( children: [ TextField( controller: c.courierCtrl, decoration: const InputDecoration( labelText: 'معرّف السائق', hintText: 'driver id', ), ), const SizedBox(height: 12), TextField( controller: c.startCtrl, decoration: const InputDecoration( labelText: 'من', hintText: 'YYYY-MM-DD HH:MM:SS', ), ), const SizedBox(height: 12), TextField( controller: c.endCtrl, decoration: const InputDecoration( labelText: 'إلى', hintText: 'YYYY-MM-DD HH:MM:SS', ), ), const SizedBox(height: 16), Obx(() => SizedBox( width: double.infinity, height: 44, child: OutlinedButton.icon( onPressed: c.isLoading.value ? null : c.runPreview, icon: c.isLoading.value ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Icons.calculate_outlined), label: Text(c.isLoading.value ? 'جارٍ الحساب…' : 'احسب الصافي'), ), )), ], ), ), const SizedBox(height: 16), Obx(() => c.preview.value == null ? const SizedBox.shrink() : _Result(c: c)), ], ), ), ); } } class _Result extends StatelessWidget { const _Result({required this.c}); final CourierSettlementController c; @override Widget build(BuildContext context) { final p = c.preview.value!; final done = c.executed.value; final num net = p['net_decimal'] ?? 0; final String cur = p['currency']?.toString() ?? ''; final bool willPay = net > 0; return _Card( title: 'الصافي', icon: Icons.receipt_long_outlined, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _line('الطلبات المسلَّمة', '${p['orders_count'] ?? 0}'), const Divider(height: 20), _line('أجور التوصيل', '+ ${_dec(p['payout_total'])} $cur', color: Colors.green.shade700), _line('دَين النقد', '− ${_dec(p['cash_debt_total'])} $cur', color: Colors.orange.shade800), if ((p['carried_over'] ?? 0) != 0) _line('مُرحَّل من دورة سابقة', '− ${_dec(p['carried_over'])} $cur', color: Colors.orange.shade800), const Divider(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text('الصافي', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), Text('$net $cur', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: willPay ? Colors.green.shade700 : Colors.red.shade700, )), ], ), const SizedBox(height: 8), Text( willPay ? 'سيُصرف هذا المبلغ لمحفظة السائق.' // ‏السالب لا يُخصم من محفظته: قد تكون فارغة، والخصم القسري // ‏يفاجئه ويوقفه عن العمل. الدَين لا يضيع — يُطرح لاحقاً. : 'الصافي سالب — يُرحَّل للدورة القادمة ولا يُخصم من محفظته.', style: const TextStyle(fontSize: 12, height: 1.5), ), const SizedBox(height: 16), if (done == null) Obx(() => SizedBox( width: double.infinity, height: 46, child: ElevatedButton.icon( onPressed: c.isExecuting.value ? null : () => _confirm(context, c, net, cur, willPay), icon: c.isExecuting.value ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) : Icon(willPay ? Icons.payments_outlined : Icons.archive_outlined), label: Text(willPay ? 'ثبّت واصرف' : 'ثبّت ورحّل'), ), )) else Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: done['status'] == 'failed' ? Colors.red.withOpacity(0.08) : Colors.green.withOpacity(0.08), borderRadius: BorderRadius.circular(10), ), child: Text( 'تسوية #${done['settlement_id']} — ${_statusLabel(done['status'])}', style: const TextStyle(fontWeight: FontWeight.bold), ), ), ], ), ); } /// ‏تأكيد قبل الصرف. التثبيت لا يُلغى، والدورة لا تُنفَّذ مرتين — فالضغطة /// الواحدة يجب أن تكون مقصودة. void _confirm(BuildContext context, CourierSettlementController c, num net, String cur, bool willPay) { Get.defaultDialog( title: 'تأكيد التسوية', middleText: willPay ? 'سيُصرف $net $cur لمحفظة السائق. لا يمكن التراجع.' : 'سيُثبَّت دَين $net $cur ويُرحَّل للدورة القادمة.', textConfirm: 'تأكيد', textCancel: 'إلغاء', onConfirm: () { Get.back(); c.runExecute(); }, ); } String _statusLabel(dynamic s) => switch (s) { 'paid' => 'صُرفت بنجاح', 'carried' => 'رُحّلت للدورة القادمة', 'failed' => 'ثُبّتت والتحويل فشل — تحتاج تسوية يدوية', _ => '$s', }; String _dec(dynamic smallestUnit) { final v = int.tryParse('$smallestUnit') ?? 0; // ‏الفلس الأردني: ثلاث خانات عشرية. يطابق FOOD_CURRENCY_DIVISOR=1000. return (v / 1000).toStringAsFixed(3); } Widget _line(String label, String value, {Color? color}) => Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: const TextStyle(fontSize: 13)), Text(value, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: color)), ], ), ); } class _Card extends StatelessWidget { const _Card({required this.title, required this.icon, required this.child}); final String title; final IconData icon; final Widget child; @override Widget build(BuildContext context) => Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(icon, size: 18), const SizedBox(width: 8), Text(title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 15)), ]), const SizedBox(height: 14), child, ], ), ), ); }