Update: 2026-08-03 11:32:12

This commit is contained in:
Hamza-Ayed
2026-08-03 11:32:12 +03:00
parent 2b9f696372
commit 0334f9881f
40 changed files with 86899 additions and 84841 deletions
@@ -10,6 +10,11 @@ class FinancialV2Controller extends GetxController {
Map<String, dynamic> stats = {};
List<dynamic> settlements = [];
/// ملخّص محفظة سيرو القادم من خادم المدفوعات.
/// المفاتيح: month_to_date / previous_month / all_time / growth_percent
/// / by_payment_method — ولكل نافذة: credits و debits و net و tx_count.
Map<String, dynamic> walletSummary = {};
@override
void onInit() {
super.onInit();
@@ -23,12 +28,28 @@ class FinancialV2Controller extends GetxController {
await Future.wait([
fetchStats(),
fetchSettlements(),
fetchWalletSummary(),
]);
isLoading = false;
update();
}
Future<void> fetchWalletSummary() async {
try {
var res =
await CRUD().getWallet(link: AppLink.siroWalletSummary, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success' && d['data'] is Map) {
walletSummary = Map<String, dynamic>.from(d['data']);
}
}
} catch (e) {
Log.print('Error fetching Siro wallet summary: $e');
}
}
Future<void> fetchStats() async {
try {
var res =
@@ -0,0 +1,103 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/views/widgets/snackbar.dart';
import '../../print.dart';
/// إدارة المطاعم (وحدة الطعام).
/// الباك إند كان جاهزاً بالكامل في backend/food/admin/ منذ البداية
/// (merchants / merchant_approve / payouts) دون أي واجهة في اللوحة.
class FoodMerchantsController extends GetxController {
bool isLoading = true;
List<dynamic> merchants = [];
String filter = 'all';
/// المطعم قيد المعالجة — لتعطيل أزراره أثناء الطلب
int? busyId;
/// الحالات كما تقبلها backend/food/admin/merchants.php حرفياً
static const statuses = <String, String>{
'all': 'الكل',
'pending_approval': 'بانتظار الاعتماد',
'active': 'نشط',
'paused': 'موقوف مؤقتاً',
'suspended': 'معلّق',
'rejected': 'مرفوض',
};
@override
void onInit() {
super.onInit();
fetchMerchants();
}
Future<void> changeFilter(String next) async {
if (filter == next) return;
filter = next;
await fetchMerchants();
}
Future<void> fetchMerchants() async {
isLoading = true;
update();
try {
var res = await CRUD()
.post(link: AppLink.foodMerchants, payload: {'status': filter});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
// jsonSuccess يغلّف الحمولة في message لا data
final payload = d['message'] ?? d['data'];
if (payload is Map && payload['merchants'] is List) {
merchants = payload['merchants'];
} else {
merchants = [];
}
} else {
mySnackbarError('${d['message'] ?? 'تعذّر جلب المطاعم'}');
}
}
} catch (e) {
Log.print('Error fetching merchants: $e');
mySnackbarError('$e');
}
isLoading = false;
update();
}
/// approve أو reject — الخادم يقبلهما فقط لمطعم بحالة pending_approval.
Future<void> decide(int merchantId, String action) async {
busyId = merchantId;
update();
try {
var res = await CRUD().post(
link: AppLink.foodMerchantApprove,
payload: {
'merchant_id': merchantId.toString(),
'action': action,
},
);
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
mySnackbarSuccess(
action == 'approve' ? 'تم اعتماد المطعم' : 'تم رفض المطعم');
busyId = null;
await fetchMerchants();
return;
}
mySnackbarError('${d['message'] ?? 'تعذّر تنفيذ الإجراء'}');
}
} catch (e) {
Log.print('Error deciding merchant: $e');
mySnackbarError('$e');
}
busyId = null;
update();
}
}
@@ -0,0 +1,92 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/views/widgets/snackbar.dart';
import '../../print.dart';
/// طلبات سحب أرصدة السائقين (جدول driver_withdrawal_requests على خادم
/// المدفوعات). لم تكن لها واجهة إطلاقاً قبل هذا: السائق يرسل الطلب فيُخزَّن
/// وتُرسل رسالة واتساب لحظية، ثم لا يقرأ الجدول أحد.
class WithdrawalController extends GetxController {
bool isLoading = true;
List<dynamic> requests = [];
Map<String, dynamic> totals = {};
String filter = 'pending';
/// المعرّف قيد المعالجة — لتعطيل أزرار الصف أثناء الطلب ومنع النقر المزدوج
int? busyId;
@override
void onInit() {
super.onInit();
fetchRequests();
}
Future<void> changeFilter(String next) async {
if (filter == next) return;
filter = next;
await fetchRequests();
}
Future<void> fetchRequests() async {
isLoading = true;
update();
try {
var res = await CRUD().getWallet(
link: AppLink.withdrawalRequests,
payload: {'action': 'list', 'status': filter},
);
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success' && d['data'] is Map) {
requests = d['data']['requests'] ?? [];
totals = Map<String, dynamic>.from(d['data']['totals'] ?? {});
} else {
mySnackbarError('${d['message'] ?? 'تعذّر جلب طلبات السحب'}');
}
}
} catch (e) {
Log.print('Error fetching withdrawal requests: $e');
mySnackbarError('$e');
}
isLoading = false;
update();
}
/// تغيير الحالة يتطلّب super_admin على الخادم؛ الخادم يرفض أيضاً أي انتقال
/// غير مسموح (مثل إعادة فتح طلب مرفوض) بـ 409، فنعرض رسالته كما هي.
Future<void> updateStatus(int id, String status) async {
busyId = id;
update();
try {
var res = await CRUD().getWallet(
link: AppLink.withdrawalRequests,
payload: {
'action': 'update_status',
'id': id.toString(),
'status': status,
},
);
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
mySnackbarSuccess('تم تحديث حالة الطلب');
busyId = null;
await fetchRequests();
return;
}
mySnackbarError('${d['message'] ?? 'تعذّر تحديث الحالة'}');
}
} catch (e) {
Log.print('Error updating withdrawal status: $e');
mySnackbarError('$e');
}
busyId = null;
update();
}
}