marco.pms.mobileapp/lib/controller/expense/expense_detail_controller.dart
2025-12-08 16:54:03 +05:30

241 lines
7.8 KiB
Dart

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<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;
// 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<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}");
// 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<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 expense with reimbursement info and status
Future<bool> 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<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;
}
}
}