import 'package:get/get.dart'; import 'package:on_field_work/helpers/services/api_service.dart'; import 'package:on_field_work/helpers/services/app_logger.dart'; import 'package:on_field_work/model/expense/expense_detail_model.dart'; import 'package:on_field_work/model/employees/employee_model.dart'; import 'package:flutter/material.dart'; import 'package:on_field_work/model/employees/employee_info.dart'; import 'package:on_field_work/helpers/services/storage/local_storage.dart'; class ExpenseDetailController extends GetxController { final Rx expense = Rx(null); final RxBool isLoading = false.obs; final RxString errorMessage = ''.obs; final Rx selectedReimbursedBy = Rx(null); final RxList allEmployees = [].obs; final RxList employeeSearchResults = [].obs; late String _expenseId; bool _isInitialized = false; final employeeSearchController = TextEditingController(); final isSearchingEmployees = false.obs; // NEW: Holds the logged-in user info for permission checks EmployeeInfo? employeeInfo; final RxBool canSubmit = false.obs; @override void onInit() { super.onInit(); _loadEmployeeInfo(); // Load employee info on init } void _loadEmployeeInfo() async { final info = await LocalStorage.getEmployeeInfo(); employeeInfo = info; } /// Call this once from the screen (NOT inside build) to initialize void init(String expenseId) { if (_isInitialized) return; _isInitialized = true; _expenseId = expenseId; // Use Future.wait to fetch details and employees concurrently Future.wait([ fetchExpenseDetails(), fetchAllEmployees(), ]); } /// NEW: Logic to check if the current user can submit the expense void checkPermissionToSubmit() { final expenseData = expense.value; if (employeeInfo == null || expenseData == null) { canSubmit.value = false; return; } // Status ID for 'Submit' (Hardcoded ID from the original screen logic) const allowedNextStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7'; final isCreatedByCurrentUser = employeeInfo?.id == expenseData.createdBy.id; final nextStatusIds = expenseData.nextStatus.map((e) => e.id).toList(); final hasRequiredNextStatus = nextStatusIds.contains(allowedNextStatusId); final result = isCreatedByCurrentUser && hasRequiredNextStatus; logSafe( '🐛 Checking submit permission:\n' '🐛 - Logged-in employee ID: ${employeeInfo?.id}\n' '🐛 - Expense created by ID: ${expenseData.createdBy.id}\n' '🐛 - Next Status IDs: $nextStatusIds\n' '🐛 - Has Required Next Status ID ($allowedNextStatusId): $hasRequiredNextStatus\n' '🐛 - Final Permission Result: $result', level: LogLevel.debug, ); canSubmit.value = result; } /// Generic method to handle API calls with loading and error states Future _apiCallWrapper( Future Function() apiCall, String operationName) async { isLoading.value = true; errorMessage.value = ''; // Clear previous errors try { logSafe("Initiating $operationName..."); final result = await apiCall(); logSafe("$operationName completed successfully."); return result; } catch (e, stack) { errorMessage.value = 'An unexpected error occurred during $operationName.'; logSafe("Exception in $operationName: $e", level: LogLevel.error); logSafe("StackTrace: $stack", level: LogLevel.debug); return null; } finally { isLoading.value = false; } } /// Fetch expense details by stored ID Future fetchExpenseDetails() async { final result = await _apiCallWrapper( () => ApiService.getExpenseDetailsApi(expenseId: _expenseId), "fetch expense details"); if (result != null) { try { expense.value = ExpenseDetailModel.fromJson(result); logSafe("Expense details loaded successfully: ${expense.value?.id}"); // Call permission check after data is loaded checkPermissionToSubmit(); } catch (e) { errorMessage.value = 'Failed to parse expense details: $e'; logSafe("Parse error in fetchExpenseDetails: $e", level: LogLevel.error); } } else { errorMessage.value = 'Failed to fetch expense details from server.'; logSafe("fetchExpenseDetails failed: null response", level: LogLevel.error); } } List parsePermissionIds(dynamic permissionData) { if (permissionData == null) return []; if (permissionData is List) { return permissionData .map((e) => e.toString().trim()) .where((e) => e.isNotEmpty) .toList(); } if (permissionData is String) { final clean = permissionData.replaceAll(RegExp(r'[\[\]]'), ''); return clean .split(',') .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toList(); } return []; } Future searchEmployees(String query) async { if (query.trim().isEmpty) return employeeSearchResults.clear(); isSearchingEmployees.value = true; try { final data = await ApiService.searchEmployeesBasic(searchString: query.trim()); employeeSearchResults.assignAll( (data ?? []).map((e) => EmployeeModel.fromJson(e)), ); } catch (e) { logSafe("Error searching employees: $e", level: LogLevel.error); employeeSearchResults.clear(); } finally { isSearchingEmployees.value = false; } } /// Fetch all employees Future fetchAllEmployees() async { final response = await _apiCallWrapper( () => ApiService.getAllEmployees(), "fetch all employees"); if (response != null && response.isNotEmpty) { try { allEmployees.assignAll(response.map((e) => EmployeeModel.fromJson(e))); logSafe("All Employees fetched: ${allEmployees.length}", level: LogLevel.info); } catch (e) { errorMessage.value = 'Failed to parse employee data: $e'; logSafe("Parse error in fetchAllEmployees: $e", level: LogLevel.error); } } else { allEmployees.clear(); logSafe("No employees found.", level: LogLevel.warning); } } /// Update expense with reimbursement info and status Future updateExpenseStatusWithReimbursement({ required String comment, required String reimburseTransactionId, required String reimburseDate, required String reimburseById, required String statusId, double? baseAmount, double? taxAmount, double? tdsPercent, double? netPayable, }) async { final success = await _apiCallWrapper( () => ApiService.updateExpenseStatusApi( expenseId: _expenseId, statusId: statusId, comment: comment, reimburseTransactionId: reimburseTransactionId, reimburseDate: reimburseDate, reimbursedById: reimburseById, baseAmount: baseAmount, taxAmount: taxAmount, tdsPercent: tdsPercent, netPayable: netPayable, ), "submit reimbursement", ); if (success == true) { await fetchExpenseDetails(); return true; } else { errorMessage.value = "Failed to submit reimbursement."; return false; } } /// Update status for this specific expense Future updateExpenseStatus(String statusId, {String? comment}) async { final success = await _apiCallWrapper( () => ApiService.updateExpenseStatusApi( expenseId: _expenseId, statusId: statusId, comment: comment, ), "update expense status", ); if (success == true) { await fetchExpenseDetails(); return true; } else { errorMessage.value = "Failed to update expense status."; return false; } } }