optimized code
This commit is contained in:
parent
7ce0a8555a
commit
fbfc54159c
@ -4,6 +4,9 @@ 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/expense/expense_detail_model.dart';
|
||||||
import 'package:on_field_work/model/employees/employee_model.dart';
|
import 'package:on_field_work/model/employees/employee_model.dart';
|
||||||
import 'package:flutter/material.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 {
|
class ExpenseDetailController extends GetxController {
|
||||||
final Rx<ExpenseDetailModel?> expense = Rx<ExpenseDetailModel?>(null);
|
final Rx<ExpenseDetailModel?> expense = Rx<ExpenseDetailModel?>(null);
|
||||||
@ -17,6 +20,22 @@ class ExpenseDetailController extends GetxController {
|
|||||||
final employeeSearchController = TextEditingController();
|
final employeeSearchController = TextEditingController();
|
||||||
final isSearchingEmployees = false.obs;
|
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
|
/// Call this once from the screen (NOT inside build) to initialize
|
||||||
void init(String expenseId) {
|
void init(String expenseId) {
|
||||||
if (_isInitialized) return;
|
if (_isInitialized) return;
|
||||||
@ -31,6 +50,36 @@ class ExpenseDetailController extends GetxController {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
/// Generic method to handle API calls with loading and error states
|
||||||
Future<T?> _apiCallWrapper<T>(
|
Future<T?> _apiCallWrapper<T>(
|
||||||
Future<T?> Function() apiCall, String operationName) async {
|
Future<T?> Function() apiCall, String operationName) async {
|
||||||
@ -63,6 +112,8 @@ class ExpenseDetailController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
expense.value = ExpenseDetailModel.fromJson(result);
|
expense.value = ExpenseDetailModel.fromJson(result);
|
||||||
logSafe("Expense details loaded successfully: ${expense.value?.id}");
|
logSafe("Expense details loaded successfully: ${expense.value?.id}");
|
||||||
|
// Call permission check after data is loaded
|
||||||
|
checkPermissionToSubmit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMessage.value = 'Failed to parse expense details: $e';
|
errorMessage.value = 'Failed to parse expense details: $e';
|
||||||
logSafe("Parse error in fetchExpenseDetails: $e",
|
logSafe("Parse error in fetchExpenseDetails: $e",
|
||||||
@ -75,8 +126,6 @@ class ExpenseDetailController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
List<String> parsePermissionIds(dynamic permissionData) {
|
||||||
if (permissionData == null) return [];
|
if (permissionData == null) return [];
|
||||||
if (permissionData is List) {
|
if (permissionData is List) {
|
||||||
@ -131,8 +180,6 @@ class ExpenseDetailController extends GetxController {
|
|||||||
allEmployees.clear();
|
allEmployees.clear();
|
||||||
logSafe("No employees found.", level: LogLevel.warning);
|
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
|
/// Update expense with reimbursement info and status
|
||||||
|
|||||||
@ -11,14 +11,12 @@ import 'package:on_field_work/model/expense/comment_bottom_sheet.dart';
|
|||||||
import 'package:on_field_work/model/expense/expense_detail_model.dart';
|
import 'package:on_field_work/model/expense/expense_detail_model.dart';
|
||||||
import 'package:on_field_work/model/expense/reimbursement_bottom_sheet.dart';
|
import 'package:on_field_work/model/expense/reimbursement_bottom_sheet.dart';
|
||||||
import 'package:on_field_work/controller/expense/add_expense_controller.dart';
|
import 'package:on_field_work/controller/expense/add_expense_controller.dart';
|
||||||
import 'package:on_field_work/helpers/services/app_logger.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
import 'package:on_field_work/helpers/widgets/my_refresh_indicator.dart';
|
||||||
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
|
import 'package:on_field_work/helpers/widgets/custom_app_bar.dart';
|
||||||
import 'package:on_field_work/helpers/widgets/expense/expense_detail_helpers.dart';
|
import 'package:on_field_work/helpers/widgets/expense/expense_detail_helpers.dart';
|
||||||
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
|
import 'package:on_field_work/helpers/widgets/my_spacing.dart';
|
||||||
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
import 'package:on_field_work/helpers/widgets/my_text.dart';
|
||||||
import 'package:on_field_work/helpers/services/storage/local_storage.dart';
|
|
||||||
import 'package:on_field_work/model/employees/employee_info.dart';
|
import 'package:on_field_work/model/employees/employee_info.dart';
|
||||||
import 'package:timeline_tile/timeline_tile.dart';
|
import 'package:timeline_tile/timeline_tile.dart';
|
||||||
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
import 'package:on_field_work/helpers/utils/mixins/ui_mixin.dart';
|
||||||
@ -37,15 +35,14 @@ class _ExpenseDetailScreenState extends State<ExpenseDetailScreen>
|
|||||||
final projectController = Get.find<ProjectController>();
|
final projectController = Get.find<ProjectController>();
|
||||||
final permissionController = Get.put(PermissionController());
|
final permissionController = Get.put(PermissionController());
|
||||||
|
|
||||||
EmployeeInfo? employeeInfo;
|
// Removed local employeeInfo, canSubmit, and _checkedPermission
|
||||||
final RxBool canSubmit = false.obs;
|
|
||||||
bool _checkedPermission = false;
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
controller = Get.put(ExpenseDetailController(), tag: widget.expenseId);
|
controller = Get.put(ExpenseDetailController(), tag: widget.expenseId);
|
||||||
|
// EmployeeInfo loading and permission checking is now handled inside controller.init()
|
||||||
controller.init(widget.expenseId);
|
controller.init(widget.expenseId);
|
||||||
_loadEmployeeInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -54,35 +51,10 @@ class _ExpenseDetailScreenState extends State<ExpenseDetailScreen>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadEmployeeInfo() async {
|
// Removed _loadEmployeeInfo and _checkPermissionToSubmit
|
||||||
final info = await LocalStorage.getEmployeeInfo();
|
|
||||||
employeeInfo = info;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _checkPermissionToSubmit(ExpenseDetailModel expense) {
|
|
||||||
const allowedNextStatusId = '6537018f-f4e9-4cb3-a210-6c3b2da999d7';
|
|
||||||
|
|
||||||
final isCreatedByCurrentUser = employeeInfo?.id == expense.createdBy.id;
|
|
||||||
final nextStatusIds = expense.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: ${expense.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Color appBarColor = contentTheme.primary;
|
final Color appBarColor = contentTheme.primary;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -119,9 +91,7 @@ Widget build(BuildContext context) {
|
|||||||
return Center(child: MyText.bodyMedium("No data to display."));
|
return Center(child: MyText.bodyMedium("No data to display."));
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
// Permission logic moved to controller (no need for postFrameCallback here)
|
||||||
_checkPermissionToSubmit(expense);
|
|
||||||
});
|
|
||||||
|
|
||||||
final statusColor = getExpenseStatusColor(
|
final statusColor = getExpenseStatusColor(
|
||||||
expense.status.name,
|
expense.status.name,
|
||||||
@ -135,8 +105,7 @@ Widget build(BuildContext context) {
|
|||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
12, 12, 12, 30 + MediaQuery.of(context).padding.bottom
|
12, 12, 12, 30 + MediaQuery.of(context).padding.bottom),
|
||||||
),
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 520),
|
constraints: const BoxConstraints(maxWidth: 520),
|
||||||
@ -162,9 +131,11 @@ Widget build(BuildContext context) {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
MyText.bodyMedium('Amount', fontWeight: 600),
|
MyText.bodyMedium('Amount',
|
||||||
|
fontWeight: 600),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
MyText.bodyLarge(
|
MyText.bodyLarge(
|
||||||
formattedAmount,
|
formattedAmount,
|
||||||
@ -223,21 +194,16 @@ Widget build(BuildContext context) {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
floatingActionButton: Obx(() {
|
floatingActionButton: Obx(() {
|
||||||
if (controller.isLoading.value) return buildLoadingSkeleton();
|
|
||||||
|
|
||||||
final expense = controller.expense.value;
|
final expense = controller.expense.value;
|
||||||
if (controller.errorMessage.isNotEmpty || expense == null) {
|
if (controller.errorMessage.isNotEmpty || expense == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_checkedPermission) {
|
// Removed _checkedPermission and its logic
|
||||||
_checkedPermission = true;
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_checkPermissionToSubmit(expense);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ExpensePermissionHelper.canEditExpense(employeeInfo, expense)) {
|
if (!ExpensePermissionHelper.canEditExpense(
|
||||||
|
controller.employeeInfo, // Use controller's employeeInfo
|
||||||
|
expense)) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +271,7 @@ Widget build(BuildContext context) {
|
|||||||
|
|
||||||
final isSubmitStatus = next.id == submitStatusId;
|
final isSubmitStatus = next.id == submitStatusId;
|
||||||
final isCreatedByCurrentUser =
|
final isCreatedByCurrentUser =
|
||||||
employeeInfo?.id == expense.createdBy.id;
|
controller.employeeInfo?.id == expense.createdBy.id; // Use controller's employeeInfo
|
||||||
|
|
||||||
if (isSubmitStatus) return isCreatedByCurrentUser;
|
if (isSubmitStatus) return isCreatedByCurrentUser;
|
||||||
return permissionController.hasAnyPermission(parsedPermissions);
|
return permissionController.hasAnyPermission(parsedPermissions);
|
||||||
@ -317,8 +283,7 @@ Widget build(BuildContext context) {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Widget _statusButton(BuildContext context, ExpenseDetailController controller,
|
Widget _statusButton(BuildContext context, ExpenseDetailController controller,
|
||||||
ExpenseDetailModel expense, dynamic next) {
|
ExpenseDetailModel expense, dynamic next) {
|
||||||
@ -346,7 +311,8 @@ Widget build(BuildContext context) {
|
|||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(5))),
|
borderRadius:
|
||||||
|
BorderRadius.vertical(top: Radius.circular(5))),
|
||||||
builder: (context) => ReimbursementBottomSheet(
|
builder: (context) => ReimbursementBottomSheet(
|
||||||
expenseId: expense.id,
|
expenseId: expense.id,
|
||||||
statusId: next.id,
|
statusId: next.id,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user