fixed add remove doscument

This commit is contained in:
Vaibhav Surve 2025-11-10 16:10:45 +05:30
parent a88d085001
commit 502bb1e2d9
5 changed files with 190 additions and 15 deletions

View File

@ -42,6 +42,7 @@ class AddPaymentRequestController extends GetxController {
final dueDateController = TextEditingController(); final dueDateController = TextEditingController();
final amountController = TextEditingController(); final amountController = TextEditingController();
final descriptionController = TextEditingController(); final descriptionController = TextEditingController();
final removedAttachments = <Map<String, dynamic>>[].obs;
// Attachments // Attachments
final attachments = <File>[].obs; final attachments = <File>[].obs;
@ -187,20 +188,47 @@ class AddPaymentRequestController extends GetxController {
void selectCurrency(Currency currency) => selectedCurrency.value = currency; void selectCurrency(Currency currency) => selectedCurrency.value = currency;
void addAttachment(File file) => attachments.add(file); 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<String, dynamic> 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 /// Build attachment payload
Future<List<Map<String, dynamic>>> buildAttachmentPayload() async { Future<List<Map<String, dynamic>>> buildAttachmentPayload() async {
final existingPayload = existingAttachments final existingPayload = existingAttachments
.map((e) => { .map((e) => {
"documentId": e['documentId'], "documentId": e['id'], // use the normalized id
"fileName": e['fileName'], "fileName": e['fileName'],
"contentType": e['contentType'] ?? 'application/octet-stream', "contentType": e['contentType'] ?? 'application/octet-stream',
"fileSize": e['fileSize'] ?? 0, "fileSize": e['fileSize'] ?? 0,
"description": "", "description": "",
"url": e['url'], "url": e['url'],
"isActive": e['isActive'] ?? true, "isActive": e['isActive'] ?? true,
"base64Data": "",
}) })
.toList(); .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<bool> 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) /// Submit payment request (Project API style)
@ -314,5 +413,6 @@ class AddPaymentRequestController extends GetxController {
isAdvancePayment.value = false; isAdvancePayment.value = false;
attachments.clear(); attachments.clear();
existingAttachments.clear(); existingAttachments.clear();
removedAttachments.clear();
} }
} }

View File

@ -21,9 +21,9 @@ class ApiEndpoints {
"/Expense/payment-request/filter"; "/Expense/payment-request/filter";
static const String updateExpensePaymentRequestStatus = static const String updateExpensePaymentRequestStatus =
"/Expense/payment-request/action"; "/Expense/payment-request/action";
static const String createExpenseforPR = static const String createExpenseforPR = "/expense/payment-request/action";
"/expense/payment-request/action"; static const String getExpensePaymentRequestEdit =
"/expense/payment-request/edit";
static const String getDashboardProjectProgress = "/dashboard/progression"; static const String getDashboardProjectProgress = "/dashboard/progression";
static const String getDashboardTasks = "/dashboard/tasks"; static const String getDashboardTasks = "/dashboard/tasks";

View File

@ -302,6 +302,69 @@ class ApiService {
} }
} }
/// Edit Expense Payment Request
static Future<bool> 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<Map<String, dynamic>> 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 /// Create Expense for Payment Request
static Future<bool> createExpenseForPRApi({ static Future<bool> createExpenseForPRApi({
required String paymentModeId, required String paymentModeId,

View File

@ -73,17 +73,14 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet>
controller.isAdvancePayment.value = data["isAdvancePayment"] ?? false; controller.isAdvancePayment.value = data["isAdvancePayment"] ?? false;
// 🕒 Wait until categories & currencies are loaded before setting them // 🕒 Wait until categories & currencies are loaded before setting them
everAll([ everAll([controller.categories, controller.currencies], (_) {
controller.categories,
controller.currencies,
], (_) {
controller.selectedCategory.value = controller.categories controller.selectedCategory.value = controller.categories
.firstWhereOrNull((c) => c.id == data["expenseCategoryId"]); .firstWhereOrNull((c) => c.id == data["expenseCategoryId"]);
controller.selectedCurrency.value = controller.currencies controller.selectedCurrency.value = controller.currencies
.firstWhereOrNull((c) => c.id == data["currencyId"]); .firstWhereOrNull((c) => c.id == data["currencyId"]);
}); });
// 🖇 Attachments - Safe parsing (avoids null or wrong type) // 🖇 Attachments
final attachmentsData = data["attachments"]; final attachmentsData = data["attachments"];
if (attachmentsData != null && if (attachmentsData != null &&
attachmentsData is List && attachmentsData is List &&
@ -91,12 +88,13 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet>
final attachments = attachmentsData final attachments = attachmentsData
.whereType<Map<String, dynamic>>() .whereType<Map<String, dynamic>>()
.map((a) => { .map((a) => {
"id": a["id"], "id": a["documentId"] ?? a["id"], // map documentId to id
"fileName": a["fileName"], "fileName": a["fileName"],
"url": a["url"], "url": a["url"],
"thumbUrl": a["thumbUrl"], "thumbUrl": a["thumbUrl"],
"fileSize": a["fileSize"] ?? 0, "fileSize": a["fileSize"] ?? 0,
"contentType": a["contentType"] ?? "", "contentType": a["contentType"] ?? "",
"isActive": true, // ensure active by default
}) })
.toList(); .toList();
controller.existingAttachments.assignAll(attachments); controller.existingAttachments.assignAll(attachments);
@ -106,7 +104,6 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet>
} }
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() => Form( return Obx(() => Form(
@ -120,7 +117,21 @@ class _PaymentRequestBottomSheetState extends State<_PaymentRequestBottomSheet>
submitText: "Save as Draft", submitText: "Save as Draft",
onSubmit: () async { onSubmit: () async {
if (_formKey.currentState!.validate() && _validateSelections()) { 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) { if (success) {
Get.back(); Get.back();
if (widget.onUpdated != null) widget.onUpdated!(); if (widget.onUpdated != null) widget.onUpdated!();

View File

@ -72,6 +72,7 @@ class _PaymentRequestDetailScreenState extends State<PaymentRequestDetailScreen>
showPaymentRequestBottomSheet( showPaymentRequestBottomSheet(
isEdit: true, isEdit: true,
existingData: { existingData: {
"id": request.id,
"paymentRequestId": request.paymentRequestUID, "paymentRequestId": request.paymentRequestUID,
"title": request.title, "title": request.title,
"projectId": request.project.id, "projectId": request.project.id,