diff --git a/lib/model/finance/payment_request_rembursement_bottom_sheet.dart b/lib/model/finance/payment_request_rembursement_bottom_sheet.dart index e212d95..d4d273e 100644 --- a/lib/model/finance/payment_request_rembursement_bottom_sheet.dart +++ b/lib/model/finance/payment_request_rembursement_bottom_sheet.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; - import 'package:marco/controller/finance/payment_request_detail_controller.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; @@ -24,10 +23,10 @@ class UpdatePaymentRequestWithReimbursement extends StatefulWidget { @override State createState() => - _UpdatePaymentRequestWithReimbursement(); + _UpdatePaymentRequestWithReimbursementState(); } -class _UpdatePaymentRequestWithReimbursement +class _UpdatePaymentRequestWithReimbursementState extends State { final PaymentRequestDetailController controller = Get.find(); @@ -37,7 +36,18 @@ class _UpdatePaymentRequestWithReimbursement final TextEditingController tdsCtrl = TextEditingController(text: '0'); final TextEditingController baseAmountCtrl = TextEditingController(); final TextEditingController taxAmountCtrl = TextEditingController(); + final RxString dateStr = ''.obs; + final RxDouble tdsAmount = 0.0.obs; + final RxDouble netPayable = 0.0.obs; + + @override + void initState() { + super.initState(); + baseAmountCtrl.addListener(_recalculateTds); + taxAmountCtrl.addListener(_recalculateTds); + tdsCtrl.addListener(_recalculateTds); + } @override void dispose() { @@ -49,7 +59,21 @@ class _UpdatePaymentRequestWithReimbursement super.dispose(); } - /// Employee selection bottom sheet + void _recalculateTds() { + final double base = double.tryParse(baseAmountCtrl.text.trim()) ?? 0.0; + final double gst = double.tryParse(taxAmountCtrl.text.trim()) ?? 0.0; + final double tdsPercent = double.tryParse(tdsCtrl.text.trim()) ?? 0.0; + + final double calculatedTds = (base * tdsPercent) / 100; + final double roundedTds = double.parse(calculatedTds.toStringAsFixed(2)); + + final double net = (base + gst) - roundedTds; + final double roundedNet = double.parse(net.toStringAsFixed(2)); + + tdsAmount.value = roundedTds; + netPayable.value = roundedNet; + } + void _showEmployeeList() async { await showModalBottomSheet( context: context, @@ -67,7 +91,6 @@ class _UpdatePaymentRequestWithReimbursement ), ); - // Optional cleanup controller.employeeSearchController.clear(); controller.employeeSearchResults.clear(); } @@ -94,6 +117,29 @@ class _UpdatePaymentRequestWithReimbursement ); } + Widget _readOnlyValueBox(String label, String value, Color color) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + border: Border.all(color: color.withOpacity(0.3)), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MyText.labelMedium(label), + MyText.bodyMedium( + "₹$value", + color: color, + fontWeight: 700, + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return Obx(() { @@ -105,7 +151,6 @@ class _UpdatePaymentRequestWithReimbursement Navigator.pop(context); }, onSubmit: () async { - // Mandatory fields validation if (commentCtrl.text.trim().isEmpty || txnCtrl.text.trim().isEmpty || dateStr.value.isEmpty || @@ -120,27 +165,24 @@ class _UpdatePaymentRequestWithReimbursement } try { - // Parse inputs final parsedDate = DateFormat('dd-MM-yyyy').parse(dateStr.value, true); - final baseAmount = double.tryParse(baseAmountCtrl.text.trim()) ?? 0; - final taxAmount = double.tryParse(taxAmountCtrl.text.trim()) ?? 0; + final baseAmt = double.tryParse(baseAmountCtrl.text.trim()) ?? 0; + final taxAmt = double.tryParse(taxAmountCtrl.text.trim()) ?? 0; final tdsPercentage = tdsCtrl.text.trim().isEmpty ? null : tdsCtrl.text.trim(); - // Call API final success = await controller.updatePaymentRequestStatus( statusId: widget.statusId, comment: commentCtrl.text.trim(), paidTransactionId: txnCtrl.text.trim(), paidById: controller.selectedReimbursedBy.value?.id, paidAt: parsedDate, - baseAmount: baseAmount, - taxAmount: taxAmount, + baseAmount: baseAmt, + taxAmount: taxAmt, tdsPercentage: tdsPercentage, ); - // Show snackbar showAppSnackbar( title: success ? 'Success' : 'Error', message: success @@ -150,12 +192,11 @@ class _UpdatePaymentRequestWithReimbursement ); if (success) { - // Ensure bottom sheet closes and callback is called - widget.onClose(); // optional callback for parent refresh + widget.onClose(); if (Navigator.canPop(context)) { Navigator.pop(context); } else { - Get.close(1); // fallback if Navigator can't pop + Get.close(1); } } } catch (e, st) { @@ -167,103 +208,141 @@ class _UpdatePaymentRequestWithReimbursement ); } }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.labelMedium("Transaction ID*"), - MySpacing.height(8), - TextField( - controller: txnCtrl, - decoration: _inputDecoration("Enter transaction ID"), - ), - MySpacing.height(16), - MyText.labelMedium("Transaction Date*"), - MySpacing.height(8), - GestureDetector( - onTap: () async { - final today = DateTime.now(); - final firstDate = DateTime(2020); - final lastDate = today; + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.labelMedium("Transaction ID*"), + MySpacing.height(8), + TextField( + controller: txnCtrl, + decoration: _inputDecoration("Enter transaction ID"), + ), + MySpacing.height(16), - final picked = await showDatePicker( - context: context, - initialDate: today, - firstDate: firstDate, - lastDate: lastDate, - ); - - if (picked != null) { - dateStr.value = DateFormat('dd-MM-yyyy').format(picked); - } - }, - child: AbsorbPointer( - child: TextField( - controller: TextEditingController(text: dateStr.value), - decoration: _inputDecoration("Select Date").copyWith( - suffixIcon: const Icon(Icons.calendar_today), + MyText.labelMedium("Transaction Date*"), + MySpacing.height(8), + GestureDetector( + onTap: () async { + final picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2020), + lastDate: DateTime.now(), + ); + if (picked != null) { + dateStr.value = DateFormat('dd-MM-yyyy').format(picked); + } + }, + child: AbsorbPointer( + child: TextField( + controller: TextEditingController(text: dateStr.value), + decoration: _inputDecoration("Select Date").copyWith( + suffixIcon: const Icon(Icons.calendar_today), + ), ), ), ), - ), - MySpacing.height(16), - MyText.labelMedium("Paid By (Optional)"), - MySpacing.height(8), - GestureDetector( - onTap: _showEmployeeList, - child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - decoration: BoxDecoration( - color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade300), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - controller.selectedReimbursedBy.value == null - ? "Select Paid By" - : '${controller.selectedReimbursedBy.value?.firstName ?? ''} ${controller.selectedReimbursedBy.value?.lastName ?? ''}', - style: const TextStyle(fontSize: 14), - ), - const Icon(Icons.arrow_drop_down, size: 22), - ], + MySpacing.height(16), + + MyText.labelMedium("Paid By (Optional)"), + MySpacing.height(8), + GestureDetector( + onTap: _showEmployeeList, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + controller.selectedReimbursedBy.value == null + ? "Select Paid By" + : '${controller.selectedReimbursedBy.value?.firstName ?? ''} ${controller.selectedReimbursedBy.value?.lastName ?? ''}', + style: const TextStyle(fontSize: 14), + ), + const Icon(Icons.arrow_drop_down, size: 22), + ], + ), ), ), - ), - MySpacing.height(16), - MyText.labelMedium("TDS Percentage (Optional)"), - MySpacing.height(8), - TextField( - controller: tdsCtrl, - keyboardType: TextInputType.number, - decoration: _inputDecoration("Enter TDS Percentage"), - ), - MySpacing.height(16), - MyText.labelMedium("Base Amount*"), - MySpacing.height(8), - TextField( - controller: baseAmountCtrl, - keyboardType: TextInputType.number, - decoration: _inputDecoration("Enter Base Amount"), - ), - MySpacing.height(16), - MyText.labelMedium("Tax Amount*"), - MySpacing.height(8), - TextField( - controller: taxAmountCtrl, - keyboardType: TextInputType.number, - decoration: _inputDecoration("Enter Tax Amount"), - ), - MySpacing.height(16), - MyText.labelMedium("Comment*"), - MySpacing.height(8), - TextField( - controller: commentCtrl, - decoration: _inputDecoration("Enter comment"), - ), - ], + MySpacing.height(16), + + MyText.labelMedium("Base Amount"), + MySpacing.height(8), + TextField( + controller: baseAmountCtrl, + keyboardType: TextInputType.number, + decoration: _inputDecoration("Enter Base Amount"), + ), + MySpacing.height(16), + + MyText.labelMedium("GST Amount"), + MySpacing.height(8), + TextField( + controller: taxAmountCtrl, + keyboardType: TextInputType.number, + decoration: _inputDecoration("Enter GST Amount"), + ), + MySpacing.height(16), + + MyText.labelMedium("TDS Percent"), + MySpacing.height(8), + TextField( + controller: tdsCtrl, + keyboardType: TextInputType.number, + decoration: _inputDecoration("Enter TDS Percent").copyWith( + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 12), + child: Icon(Icons.percent, + size: 20, color: Colors.grey.shade600), + ), + suffixIconConstraints: + const BoxConstraints(minWidth: 0, minHeight: 0), + ), + onChanged: (_) => _recalculateTds(), + ), + + MySpacing.height(4), + MyText.bodySmall( + "TDS is applied on base amount only.", + color: Colors.grey.shade600, + ), + MySpacing.height(16), + + // ✅ Proper display section for TDS and Net Payable + Obx(() => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _readOnlyValueBox( + "TDS Amount", + tdsAmount.value.toStringAsFixed(2), + Colors.orange, + ), + MySpacing.height(12), + _readOnlyValueBox( + "Net Payable", + netPayable.value.toStringAsFixed(2), + Colors.green, + ), + ], + )), + MySpacing.height(20), + + MyText.labelMedium("Comment*"), + MySpacing.height(8), + TextField( + controller: commentCtrl, + maxLines: 2, + decoration: _inputDecoration("Enter comment"), + ), + ], + ), ), ); }); diff --git a/lib/view/finance/payment_request_screen.dart b/lib/view/finance/payment_request_screen.dart index 8a42758..8d1e6bd 100644 --- a/lib/view/finance/payment_request_screen.dart +++ b/lib/view/finance/payment_request_screen.dart @@ -76,11 +76,10 @@ class _PaymentRequestMainScreenState extends State return isHistory ? filtered - .where((e) => e.dueDate.isBefore(DateTime(now.year, now.month))) + .where((e) => e.dueDate.isBefore(DateTime(now.year, now.month, 1))) .toList() : filtered - .where((e) => - e.dueDate.month == now.month && e.dueDate.year == now.year) + .where((e) => e.dueDate.isAfter(DateTime(now.year, now.month, 0))) .toList(); }