Update: 2026-08-07 05:56:50
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
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<Map<String, dynamic>>();
|
||||
|
||||
/// نتيجة التنفيذ. وجودها يقفل زر الصرف — لا تُنفَّذ الدورة مرتين.
|
||||
final executed = Rxn<Map<String, dynamic>>();
|
||||
|
||||
@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<String, String> get _payload => {
|
||||
'courier_id': courierCtrl.text.trim(),
|
||||
'period_start': startCtrl.text.trim(),
|
||||
'period_end': endCtrl.text.trim(),
|
||||
};
|
||||
|
||||
/// يحسب الصافي بلا أي أثر على القاعدة أو المحفظة.
|
||||
Future<void> 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<void> 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<String, dynamic>? _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<String, dynamic>.from(data) : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user