import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constant/links.dart'; import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; /// تسوية أرباح سائق التوصيل. /// /// خطوتان لا واحدة: **معاينة** تحسب وتعرض بلا أثر، ثم **تنفيذ** يصرف. /// صرف المال يجب أن يسبقه اطّلاع، لا أن يكون أثراً جانبياً لفتح شاشة. class CourierSettlementController extends GetxController { final CRUD _crud = CRUD(); final courierCtrl = TextEditingController(); final startCtrl = TextEditingController(); final endCtrl = TextEditingController(); final isLoading = false.obs; final isExecuting = false.obs; /// نتيجة المعاينة. فارغة = لم تُطلب بعد. final preview = Rxn>(); /// نتيجة التنفيذ. وجودها يقفل زر الصرف — لا تُنفَّذ الدورة مرتين. final executed = Rxn>(); @override void onInit() { super.onInit(); // ‏الافتراضي: الأسبوع المنتهي. أشيع فترة تسوية، ويوفّر كتابة يدوية. final now = DateTime.now(); final weekAgo = now.subtract(const Duration(days: 7)); startCtrl.text = _fmt(weekAgo, startOfDay: true); endCtrl.text = _fmt(now); } @override void onClose() { courierCtrl.dispose(); startCtrl.dispose(); endCtrl.dispose(); super.onClose(); } String _fmt(DateTime d, {bool startOfDay = false}) { final s = '${d.year}-${_2(d.month)}-${_2(d.day)}'; return startOfDay ? '$s 00:00:00' : '$s ${_2(d.hour)}:${_2(d.minute)}:00'; } String _2(int n) => n.toString().padLeft(2, '0'); bool get _valid { if (courierCtrl.text.trim().isEmpty) { mySnackbarError('أدخل معرّف السائق'); return false; } if (startCtrl.text.trim().isEmpty || endCtrl.text.trim().isEmpty) { mySnackbarError('حدّد الفترة'); return false; } return true; } Map get _payload => { 'courier_id': courierCtrl.text.trim(), 'period_start': startCtrl.text.trim(), 'period_end': endCtrl.text.trim(), }; /// يحسب الصافي بلا أي أثر على القاعدة أو المحفظة. Future runPreview() async { if (!_valid || isLoading.value) return; isLoading.value = true; // ‏تغيير السائق أو الفترة يُبطل نتيجة تنفيذ سابقة — وإلا بقيت معروضة // ‏فأوهمت الموظف أن الدورة الجديدة صُرفت. executed.value = null; preview.value = null; try { final res = await _crud.post( link: AppLink.foodCourierSettlement, payload: {..._payload, 'action': 'preview'}, ); final data = _extract(res); if (data == null) return; preview.value = data; } catch (e) { mySnackbarError('تعذّر الاتصال بالخادم'); } finally { isLoading.value = false; } } /// يثبّت الدورة ويصرف إن كان الصافي موجباً. Future runExecute() async { if (!_valid || isExecuting.value || preview.value == null) return; isExecuting.value = true; try { final res = await _crud.post( link: AppLink.foodCourierSettlement, payload: {..._payload, 'action': 'execute'}, ); final data = _extract(res); if (data == null) return; executed.value = data; switch (data['status']) { case 'paid': mySnackbarSuccess('صُرفت التسوية #${data['settlement_id']}'); break; case 'carried': mySnackbarSuccess('الصافي سالب — رُحّل للدورة القادمة'); break; default: // ‏فشل التحويل يُعرض صراحةً: الدورة مثبّتة والمال لم يصل. mySnackbarError('ثُبّتت الدورة لكن التحويل فشل — تحتاج تسوية يدوية'); } } catch (e) { mySnackbarError('تعذّر الاتصال بالخادم'); } finally { isExecuting.value = false; } } Map? _extract(dynamic res) { final decoded = res is String ? jsonDecode(res) : res; if (decoded is! Map) { mySnackbarError('رد غير مفهوم من الخادم'); return null; } if (decoded['status'] != 'success') { mySnackbarError(decoded['message']?.toString() ?? 'فشلت العملية'); return null; } final data = decoded['message'] ?? decoded['data']; return data is Map ? Map.from(data) : null; } }