- Updated import paths for employee model files to reflect new directory structure. - Deleted obsolete models: JobRecentApplicationModel, LeadReportModel, Product, ProductOrderModal, ProjectSummaryModel, RecentOrderModel, TaskListModel, TimeLineModel, User, VisitorByChannelsModel. - Introduced new AttendanceLogModel, AttendanceLogViewModel, AttendanceModel, TaskModel, TaskListModel, EmployeeInfo, and EmployeeModel with comprehensive fields and JSON serialization methods. - Enhanced data handling in attendance and task management features.
		
			
				
	
	
		
			188 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			188 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:get/get.dart';
 | |
| import 'package:marco/helpers/services/api_service.dart';
 | |
| import 'package:marco/helpers/services/app_logger.dart';
 | |
| import 'package:marco/model/expense/expense_detail_model.dart';
 | |
| import 'package:marco/model/employees/employee_model.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| 
 | |
| class ExpenseDetailController extends GetxController {
 | |
|   final Rx<ExpenseDetailModel?> expense = Rx<ExpenseDetailModel?>(null);
 | |
|   final RxBool isLoading = false.obs;
 | |
|   final RxString errorMessage = ''.obs;
 | |
|   final Rx<EmployeeModel?> selectedReimbursedBy = Rx<EmployeeModel?>(null);
 | |
|   final RxList<EmployeeModel> allEmployees = <EmployeeModel>[].obs;
 | |
|   final RxList<EmployeeModel> employeeSearchResults = <EmployeeModel>[].obs;
 | |
|   late String _expenseId;
 | |
|   bool _isInitialized = false;
 | |
|   final employeeSearchController = TextEditingController();
 | |
|   final isSearchingEmployees = false.obs;
 | |
| 
 | |
|   /// 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(),
 | |
|     ]);
 | |
|   }
 | |
| 
 | |
|   /// Generic method to handle API calls with loading and error states
 | |
|   Future<T?> _apiCallWrapper<T>(
 | |
|       Future<T?> 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<void> 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}");
 | |
|       } 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);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // This method seems like a utility and might be better placed in a helper or utility class
 | |
|   // if it's used across multiple controllers. Keeping it here for now as per original code.
 | |
|   List<String> 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<void> 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<void> 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()` is typically not needed for RxList directly unless you have specific GetBuilder/Obx usage that requires it
 | |
|     // If you are using Obx widgets, `allEmployees.assignAll` will automatically trigger a rebuild.
 | |
|   }
 | |
| 
 | |
|   /// Update expense with reimbursement info and status
 | |
|   Future<bool> updateExpenseStatusWithReimbursement({
 | |
|     required String comment,
 | |
|     required String reimburseTransactionId,
 | |
|     required String reimburseDate,
 | |
|     required String reimburseById,
 | |
|     required String statusId,
 | |
|   }) async {
 | |
|     final success = await _apiCallWrapper(
 | |
|       () => ApiService.updateExpenseStatusApi(
 | |
|         expenseId: _expenseId,
 | |
|         statusId: statusId,
 | |
|         comment: comment,
 | |
|         reimburseTransactionId: reimburseTransactionId,
 | |
|         reimburseDate: reimburseDate,
 | |
|         reimbursedById: reimburseById,
 | |
|       ),
 | |
|       "submit reimbursement",
 | |
|     );
 | |
| 
 | |
|     if (success == true) {
 | |
|       // Explicitly check for true as _apiCallWrapper returns T?
 | |
|       await fetchExpenseDetails(); // Refresh details after successful update
 | |
|       return true;
 | |
|     } else {
 | |
|       errorMessage.value = "Failed to submit reimbursement.";
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /// Update status for this specific expense
 | |
|   Future<bool> 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;
 | |
|     }
 | |
|   }
 | |
| }
 |