diff --git a/lib/controller/finance/add_payment_request_controller.dart b/lib/controller/finance/add_payment_request_controller.dart index 475eaf4..1ff0a4e 100644 --- a/lib/controller/finance/add_payment_request_controller.dart +++ b/lib/controller/finance/add_payment_request_controller.dart @@ -42,6 +42,7 @@ class AddPaymentRequestController extends GetxController { final dueDateController = TextEditingController(); final amountController = TextEditingController(); final descriptionController = TextEditingController(); + final removedAttachments = >[].obs; // Attachments final attachments = [].obs; @@ -187,20 +188,47 @@ class AddPaymentRequestController extends GetxController { void selectCurrency(Currency currency) => selectedCurrency.value = currency; void addAttachment(File file) => attachments.add(file); - void removeAttachment(File file) => attachments.remove(file); + void removeAttachment(File file) { + if (attachments.contains(file)) { + attachments.remove(file); + } + } + + void removeExistingAttachment(Map existingAttachment) { + final index = existingAttachments.indexWhere( + (e) => e['id'] == existingAttachment['id']); // match by normalized id + + if (index != -1) { + // Mark as inactive + existingAttachments[index]['isActive'] = false; + existingAttachments.refresh(); + + // Add to removedAttachments to inform API + removedAttachments.add({ + "documentId": existingAttachment['id'], // ensure API receives id + "isActive": false, + }); + + // Show snackbar feedback + showAppSnackbar( + title: 'Removed', + message: 'Attachment has been removed.', + type: SnackbarType.success, + ); + } + } /// Build attachment payload Future>> buildAttachmentPayload() async { final existingPayload = existingAttachments .map((e) => { - "documentId": e['documentId'], + "documentId": e['id'], // use the normalized id "fileName": e['fileName'], "contentType": e['contentType'] ?? 'application/octet-stream', "fileSize": e['fileSize'] ?? 0, "description": "", "url": e['url'], "isActive": e['isActive'] ?? true, - "base64Data": "", }) .toList(); @@ -215,7 +243,78 @@ class AddPaymentRequestController extends GetxController { }; })); - return [...existingPayload, ...newPayload]; + // Combine active + removed attachments + return [...existingPayload, ...newPayload, ...removedAttachments]; + } + + /// Submit edited payment request + Future submitEditedPaymentRequest({required String requestId}) async { + if (isSubmitting.value) return false; + + try { + isSubmitting.value = true; + + // Validate form + if (!_validateForm()) return false; + + // Build attachment payload + final billAttachments = await buildAttachmentPayload(); + + final payload = { + "id": requestId, + "title": titleController.text.trim(), + "projectId": selectedProject.value?['id'] ?? '', + "expenseCategoryId": selectedCategory.value?.id ?? '', + "amount": double.tryParse(amountController.text.trim()) ?? 0, + "currencyId": selectedCurrency.value?.id ?? '', + "description": descriptionController.text.trim(), + "payee": selectedPayee.value, + "dueDate": selectedDueDate.value?.toIso8601String(), + "isAdvancePayment": isAdvancePayment.value, + "billAttachments": billAttachments.map((a) { + return { + "documentId": a['documentId'], + "fileName": a['fileName'], + "base64Data": a['base64Data'] ?? "", + "contentType": a['contentType'], + "fileSize": a['fileSize'], + "description": a['description'] ?? "", + "isActive": a['isActive'] ?? true, + }; + }).toList(), + }; + + logSafe("💡 Submitting Edited Payment Request: ${jsonEncode(payload)}"); + + final success = await ApiService.editExpensePaymentRequestApi( + id: payload['id'], + title: payload['title'], + projectId: payload['projectId'], + expenseCategoryId: payload['expenseCategoryId'], + amount: payload['amount'], + currencyId: payload['currencyId'], + description: payload['description'], + payee: payload['payee'], + dueDate: payload['dueDate'] ?? '', + isAdvancePayment: payload['isAdvancePayment'], + billAttachments: payload['billAttachments'], + ); + + logSafe("💡 Edit Payment Request API Response: $success"); + + if (success == true) { + logSafe("✅ Payment request edited successfully."); + return true; + } else { + return _errorSnackbar("Failed to edit payment request."); + } + } catch (e, st) { + logSafe("💥 Submit Edited Payment Request Error: $e\n$st", + level: LogLevel.error); + return _errorSnackbar("Something went wrong. Please try again later."); + } finally { + isSubmitting.value = false; + } } /// Submit payment request (Project API style) @@ -314,5 +413,6 @@ class AddPaymentRequestController extends GetxController { isAdvancePayment.value = false; attachments.clear(); existingAttachments.clear(); + removedAttachments.clear(); } } diff --git a/lib/helpers/services/api_endpoints.dart b/lib/helpers/services/api_endpoints.dart index 9c4c59a..de12e95 100644 --- a/lib/helpers/services/api_endpoints.dart +++ b/lib/helpers/services/api_endpoints.dart @@ -21,9 +21,9 @@ class ApiEndpoints { "/Expense/payment-request/filter"; static const String updateExpensePaymentRequestStatus = "/Expense/payment-request/action"; - static const String createExpenseforPR = - "/expense/payment-request/action"; - + static const String createExpenseforPR = "/expense/payment-request/action"; + static const String getExpensePaymentRequestEdit = + "/expense/payment-request/edit"; static const String getDashboardProjectProgress = "/dashboard/progression"; static const String getDashboardTasks = "/dashboard/tasks"; diff --git a/lib/helpers/services/api_service.dart b/lib/helpers/services/api_service.dart index a413c4f..25a46e5 100644 --- a/lib/helpers/services/api_service.dart +++ b/lib/helpers/services/api_service.dart @@ -302,6 +302,69 @@ class ApiService { } } + /// Edit Expense Payment Request + static Future editExpensePaymentRequestApi({ + required String id, + required String title, + required String description, + required String payee, + required String currencyId, + required double amount, + required String dueDate, + required String projectId, + required String expenseCategoryId, + required bool isAdvancePayment, + List> billAttachments = const [], + }) async { + final endpoint = "${ApiEndpoints.getExpensePaymentRequestEdit}/$id"; + + final body = { + "id": id, + "title": title, + "description": description, + "payee": payee, + "currencyId": currencyId, + "amount": amount, + "dueDate": dueDate, + "projectId": projectId, + "expenseCategoryId": expenseCategoryId, + "isAdvancePayment": isAdvancePayment, + "billAttachments": billAttachments, + }; + + try { + final response = await _putRequest(endpoint, body); + + if (response == null) { + logSafe("Edit Expense Payment Request failed: null response", + level: LogLevel.error); + return false; + } + + logSafe( + "Edit Expense Payment Request response status: ${response.statusCode}"); + logSafe("Edit Expense Payment Request response body: ${response.body}"); + + final json = jsonDecode(response.body); + if (json['success'] == true) { + logSafe( + "Expense Payment Request edited successfully: ${json['data'] ?? 'No data'}"); + return true; + } else { + logSafe( + "Failed to edit Expense Payment Request: ${json['message'] ?? 'Unknown error'}", + level: LogLevel.warning, + ); + return false; + } + } catch (e, stack) { + logSafe("Exception during editExpensePaymentRequestApi: $e", + level: LogLevel.error); + logSafe("StackTrace: $stack", level: LogLevel.debug); + return false; + } + } + /// Create Expense for Payment Request static Future createExpenseForPRApi({ required String paymentModeId, diff --git a/lib/model/finance/add_payment_request_bottom_sheet.dart b/lib/model/finance/add_payment_request_bottom_sheet.dart index 138f7b1..9a5f022 100644 --- a/lib/model/finance/add_payment_request_bottom_sheet.dart +++ b/lib/model/finance/add_payment_request_bottom_sheet.dart @@ -73,17 +73,14 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet> controller.isAdvancePayment.value = data["isAdvancePayment"] ?? false; // 🕒 Wait until categories & currencies are loaded before setting them - everAll([ - controller.categories, - controller.currencies, - ], (_) { + everAll([controller.categories, controller.currencies], (_) { controller.selectedCategory.value = controller.categories .firstWhereOrNull((c) => c.id == data["expenseCategoryId"]); controller.selectedCurrency.value = controller.currencies .firstWhereOrNull((c) => c.id == data["currencyId"]); }); - // 🖇 Attachments - Safe parsing (avoids null or wrong type) + // 🖇 Attachments final attachmentsData = data["attachments"]; if (attachmentsData != null && attachmentsData is List && @@ -91,12 +88,13 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet> final attachments = attachmentsData .whereType>() .map((a) => { - "id": a["id"], + "id": a["documentId"] ?? a["id"], // map documentId to id "fileName": a["fileName"], "url": a["url"], "thumbUrl": a["thumbUrl"], "fileSize": a["fileSize"] ?? 0, "contentType": a["contentType"] ?? "", + "isActive": true, // ensure active by default }) .toList(); controller.existingAttachments.assignAll(attachments); @@ -106,7 +104,6 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet> } }); } - @override Widget build(BuildContext context) { return Obx(() => Form( @@ -120,7 +117,21 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet> submitText: "Save as Draft", onSubmit: () async { if (_formKey.currentState!.validate() && _validateSelections()) { - final success = await controller.submitPaymentRequest(); + bool success = false; + if (widget.isEdit && widget.existingData != null) { + final requestId = + widget.existingData!['id']?.toString() ?? ''; + if (requestId.isNotEmpty) { + success = await controller.submitEditedPaymentRequest( + requestId: requestId); + } else { + _showError("Invalid Payment Request ID"); + return; + } + } else { + success = await controller.submitPaymentRequest(); + } + if (success) { Get.back(); if (widget.onUpdated != null) widget.onUpdated!(); diff --git a/lib/view/finance/payment_request_detail_screen.dart b/lib/view/finance/payment_request_detail_screen.dart index a9fc907..acd46e5 100644 --- a/lib/view/finance/payment_request_detail_screen.dart +++ b/lib/view/finance/payment_request_detail_screen.dart @@ -72,6 +72,7 @@ class _PaymentRequestDetailScreenState extends State showPaymentRequestBottomSheet( isEdit: true, existingData: { + "id": request.id, "paymentRequestId": request.paymentRequestUID, "title": request.title, "projectId": request.project.id,