refund module feedback changes completed

faiz_dev
faizatflutter 3 days ago
parent 3d4609a1a9
commit e7f3095308

@ -1873,6 +1873,7 @@
"cash": "نقدي",
"selectRefundReason": "اختر سبب الاسترداد",
"refundReason": "سبب الاسترداد",
"rejectionReason": "سبب الرفض",
"done": "تم",
"refundReason1": "تم معالجة مريض الشركة كمدفوع ذاتي وإعادة فوترته",
"refundReason2": "المريض لا يريد إجراء سريرياً",

@ -1865,6 +1865,7 @@
"cash": "Cash",
"selectRefundReason": "Select Refund Reason",
"refundReason": "Refund Reason",
"rejectionReason": "Rejection Reason",
"done": "Done",
"refundReason1": "Company patient treated as self-paying and re-invoiced",
"refundReason2": "Patient does not want a clinical procedure",

@ -212,6 +212,43 @@ class HabibWalletViewModel extends ChangeNotifier {
return isValid;
}
// ==================== WITHDRAW ATTACHMENTS ====================================
List<String> withdrawAttachmentList = []; // Stores base64 strings
String? _withdrawAttachmentError;
static const int maxAttachments = 4;
static const int maxFileSizeBytes = 2 * 1024 * 1024; // 2MB
String? get withdrawAttachmentError => _withdrawAttachmentError;
void addWithdrawAttachment(String base64String) {
if (withdrawAttachmentList.length >= maxAttachments) {
_withdrawAttachmentError = LocaleKeys.maximumFilesReached.tr();
notifyListeners();
return;
}
withdrawAttachmentList.add(base64String);
_withdrawAttachmentError = null;
notifyListeners();
}
void removeWithdrawAttachment(int index) {
if (index >= 0 && index < withdrawAttachmentList.length) {
withdrawAttachmentList.removeAt(index);
_withdrawAttachmentError = null;
notifyListeners();
}
}
void clearWithdrawAttachmentError() {
_withdrawAttachmentError = null;
notifyListeners();
}
void setWithdrawAttachmentError(String error) {
_withdrawAttachmentError = error;
notifyListeners();
}
// Validate recharge form - shows only first error
bool validateRechargeForm(String amount) {
// Clear previous errors
@ -335,6 +372,7 @@ class HabibWalletViewModel extends ChangeNotifier {
refundTotalAmount: selectedAdvanceBalance,
refundProceduresList: const [],
refundAdvancesList: advances,
refundAttachmentsBase64List: withdrawAttachmentList,
);
final result = await refundRequestRepo.submitRefund(request: request);
@ -401,8 +439,11 @@ class HabibWalletViewModel extends ChangeNotifier {
isSubmittingWithdrawal = false;
_ibanError = null;
_withdrawDescriptionError = null;
_withdrawAttachmentError = null;
withdrawAttachmentList.clear();
currentWithdrawStep = 0;
selectedAdvance = null;
selectedWithdrawMethod = null;
if (!silent) notifyListeners();
}

@ -142,6 +142,7 @@ class InvoiceRefundProcedure {
final String? procedureId;
final String? procedureName;
final String? refundReason;
final String? rejectionReason;
final bool? isApproved;
InvoiceRefundProcedure({
@ -151,6 +152,7 @@ class InvoiceRefundProcedure {
this.procedureId,
this.procedureName,
this.refundReason,
this.rejectionReason,
this.isApproved,
});
@ -162,6 +164,7 @@ class InvoiceRefundProcedure {
procedureId: json['ProcedureID'] as String?,
procedureName: json['ProcedureName'] as String?,
refundReason: json['RefundReason'] as String?,
rejectionReason: json['RejectionReason'] as String?,
isApproved: json['IsApproved'] as bool?,
);
}

@ -1859,6 +1859,7 @@ abstract class LocaleKeys {
static const bankTransfer = 'bankTransfer';
static const selectRefundReason = 'selectRefundReason';
static const refundReason = 'refundReason';
static const rejectionReason = 'rejectionReason';
static const refundReason1 = 'refundReason1';
static const refundReason2 = 'refundReason2';
static const refundReason3 = 'refundReason3';

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
@ -20,6 +22,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/image_picker.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/radio_list_tile_widget.dart';
@ -293,6 +296,33 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
),
),
],
if (vm.selectedWithdrawMethod == RefundTabType.cash) ...[
SizedBox(height: 16.h),
Container(
padding: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(24.r),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.alertSquare,
height: 24.h,
width: 24.h,
iconColor: AppColors.textColor,
),
SizedBox(width: 12.w),
Expanded(
child: LocaleKeys.cashRefundApprovalInstructions.tr(context: context).toText12(
color: AppColors.greyInfoTextColor,
),
),
],
),
),
],
],
);
}
@ -329,7 +359,7 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (isStep2 && vm.selectedAdvance != null) ...[
if (vm.selectedAdvance != null) ...[
Padding(
padding: EdgeInsets.fromLTRB(24.w, 16.h, 24.w, 0),
child: Row(
@ -351,7 +381,8 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
),
],
CustomButton(
text: isStep2 ? LocaleKeys.submitRequest.tr(context: context) : LocaleKeys.next.tr(context: context),
icon: isStep2 ? AppAssets.refundIcon : null,
text: isStep2 ? LocaleKeys.submit.tr(context: context) : LocaleKeys.next.tr(context: context),
isDisabled: isStep2 ? !isStep2Valid : !isStep1Valid,
onPressed: () {
if (!isStep2) {
@ -525,6 +556,160 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
);
}
// Image Upload Section
Widget _buildImageUploadSection(HabibWalletViewModel vm) {
return Consumer<HabibWalletViewModel>(
builder: (context, viewModel, _) {
final hasError = viewModel.withdrawAttachmentError != null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Upload button
InkWell(
onTap: () {
if (viewModel.withdrawAttachmentList.length >= 4) {
viewModel.setWithdrawAttachmentError(LocaleKeys.maximumFilesReached.tr(context: context));
return;
}
ImageOptions.showImageOptionsNew(
context,
true,
(String base64Image, File file) async {
// Validate file size (2MB max)
final fileSize = await file.length();
if (fileSize > HabibWalletViewModel.maxFileSizeBytes) {
viewModel.setWithdrawAttachmentError(LocaleKeys.fileSizeTooLarge.tr(context: context));
return;
}
// Check max files limit
if (viewModel.withdrawAttachmentList.length >= HabibWalletViewModel.maxAttachments) {
viewModel.setWithdrawAttachmentError(LocaleKeys.maximumFilesReached.tr(context: context));
return;
}
// Add attachment
viewModel.addWithdrawAttachment(base64Image);
},
title: LocaleKeys.selectAttachment.tr(context: context),
allowMultiple: true, // Enable multiple selection
);
},
child: Container(
padding: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(24.r),
border: hasError ? Border.all(color: AppColors.primaryRedBorderColor, width: 1) : null,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
height: 40.h,
width: 40.h,
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
borderRadius: 12.r,
color: AppColors.greyColor,
),
child: Utils.buildSvgWithAssets(icon: AppAssets.selectImageIcon, applyThemeColor: false),
),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.uploadImage.tr(context: context).toText12(
color: AppColors.inputLabelTextColor,
isBold: true,
),
LocaleKeys.selectImage.tr(context: context).toText14(
color: AppColors.inputLabelTextColor,
isBold: true,
),
],
),
],
),
if (hasError) ...[
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
viewModel.withdrawAttachmentError!,
style: TextStyle(
color: AppColors.primaryRedColor,
fontSize: 12.f,
fontWeight: FontWeight.w600,
),
),
],
),
],
],
),
),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
],
),
),
),
// Selected images list
if (viewModel.withdrawAttachmentList.isNotEmpty) ...[
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
child: ListView.separated(
padding: EdgeInsets.all(16.h),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.withdrawAttachmentList.length,
separatorBuilder: (context, index) => SizedBox(height: 12.h),
itemBuilder: (BuildContext context, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.attach_file,
color: Color(0xff2B353E),
size: 20.h,
),
SizedBox(width: 8.w),
"${LocaleKeys.image.tr(context: context)} ${index + 1}".toText14(),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.cancel_circle_icon, width: 20.h, height: 20.h).onPress(() {
viewModel.removeWithdrawAttachment(index);
}),
],
);
},
),
),
],
],
);
},
);
}
// Submit
void _handleWalletWithdrawalSubmit(HabibWalletViewModel vm) {
@ -718,6 +903,10 @@ class _WithdrawRequestCreatePageState extends State<WithdrawRequestCreatePage> {
SizedBox(height: 16.h),
_buildBankInputDetails(),
],
if (habibWalletViewModel.selectedWithdrawMethod != null) ...[
SizedBox(height: 16.h),
_buildImageUploadSection(habibWalletViewModel),
],
],
),
),

@ -286,7 +286,7 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
Container(
padding: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: AppColors.warningColorYellow.withOpacity(0.1),
color: AppColors.warningColorYellow.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: AppColors.warningColorYellow, width: 1),
),
@ -304,9 +304,38 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
),
),
],
if (vm.selectedPaymentMethod == RefundTabType.cash) ...[
SizedBox(height: 16.h),
Container(
padding: EdgeInsets.all(16.h),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(24.r),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.alertSquare,
height: 24.h,
width: 24.h,
iconColor: AppColors.textColor,
),
SizedBox(width: 12.w),
Expanded(
child: LocaleKeys.cashRefundApprovalInstructions.tr(context: context).toText12(
color: AppColors.greyInfoTextColor,
),
),
],
),
),
],
if (vm.selectedPaymentMethod == RefundTabType.invoices) ...[
SizedBox(height: 16.h),
_buildBankInputDetails(vm),
],
if (vm.selectedPaymentMethod != null) ...[
SizedBox(height: 16.h),
_buildImageUploadSection(vm),
],

@ -10,9 +10,7 @@ import 'package:hmg_patient_app_new/features/refund_request/models/resp_models/r
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
class RefundRequestDetailScreen extends StatelessWidget {
final RefundInvoiceModel invoice;
@ -242,39 +240,65 @@ class RefundRequestDetailScreen extends StatelessWidget {
);
}
Widget _buildProcedureRow(BuildContext context, InvoiceRefundProcedure procedure) {
final isApproved = procedure.isApproved ?? false;
final hasRejectionReason = procedure.rejectionReason != null && procedure.rejectionReason!.isNotEmpty;
return Padding(
padding: EdgeInsets.symmetric(vertical: 6.h),
child: Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 3,
child: (procedure.procedureName ?? '-').toText12(color: AppColors.textColor),
),
SizedBox(width: 8.w),
SizedBox(
width: 70.w,
child: (isApproved ? LocaleKeys.yes : LocaleKeys.no).tr(context: context).toText12(
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
flex: 3,
child: (procedure.procedureName ?? '-').toText12(color: AppColors.textColor),
),
SizedBox(width: 8.w),
SizedBox(
width: 70.w,
child: (isApproved ? LocaleKeys.yes : LocaleKeys.no).tr(context: context).toText12(
color: AppColors.textColor,
isBold: true,
textAlignment: TextAlign.center,
),
),
SizedBox(width: 8.w),
SizedBox(
width: 60.w,
child: (procedure.patientPaidAmount?.toStringAsFixed(2) ?? '-').toText12(
color: AppColors.textColor,
isBold: true,
textAlignment: TextAlign.center,
textAlignment: TextAlign.end,
),
),
],
),
SizedBox(width: 8.w),
SizedBox(
width: 60.w,
child: (procedure.patientPaidAmount?.toStringAsFixed(2) ?? '-').toText12(
color: AppColors.textColor,
isBold: true,
textAlignment: TextAlign.end,
if (hasRejectionReason) ...[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10.h),
"${LocaleKeys.rejectionReason.tr(context: context)}:".toText10(
color: AppColors.greyInfoTextColor,
isBold: true,
),
Row(
children: [
Expanded(
child: procedure.rejectionReason!.toText10(
color: AppColors.greyInfoTextColor,
),
),
],
),
],
),
),
],
],
),
);

Loading…
Cancel
Save