|
|
|
|
@ -74,6 +74,20 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
|
|
|
|
|
super.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Determines if the invoice allows individual procedure selection
|
|
|
|
|
/// Returns true for Outpatient and ER invoices only
|
|
|
|
|
/// Returns false for Inpatient and Package invoices (whole invoice refund only)
|
|
|
|
|
bool get _canSelectIndividualProcedures {
|
|
|
|
|
// Package invoices: no individual procedure selection
|
|
|
|
|
if (widget.invoice.isPackageInvoice == true) return false;
|
|
|
|
|
|
|
|
|
|
// Inpatient invoices: no individual procedure selection
|
|
|
|
|
if (widget.invoice.isOutPatient == false) return false;
|
|
|
|
|
|
|
|
|
|
// Outpatient and ER invoices: allow individual procedure selection
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void _loadInvoiceDetails() {
|
|
|
|
|
final invoiceNo = widget.invoice.invoiceNo;
|
|
|
|
|
final projectId = widget.invoice.projectId;
|
|
|
|
|
@ -90,9 +104,17 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
|
|
|
|
|
patientPaidAmount: item.patientPaidAmount ?? 0.0,
|
|
|
|
|
apiLineItemNo: item.lineItemNo,
|
|
|
|
|
apiProcedureId: item.procedureId,
|
|
|
|
|
// For Inpatient/Package invoices: auto-select all procedures (can't deselect)
|
|
|
|
|
isSelected: !_canSelectIndividualProcedures,
|
|
|
|
|
// For Inpatient/Package invoices: set empty string as refund reason (mandatory field)
|
|
|
|
|
refundReason: !_canSelectIndividualProcedures ? "Inpatient/Package invoice" : null,
|
|
|
|
|
))
|
|
|
|
|
.toList();
|
|
|
|
|
_refundRequestViewModel.setProcedures(procedures, needsITGApproval: details.needITGApproval ?? false);
|
|
|
|
|
_refundRequestViewModel.setProcedures(
|
|
|
|
|
procedures,
|
|
|
|
|
needsITGApproval: details.needITGApproval ?? false,
|
|
|
|
|
isWholeInvoiceRefund: !_canSelectIndividualProcedures,
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -123,12 +145,11 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Step 1 — exactly mirrors RechargeWalletPage hospital row ─────────────
|
|
|
|
|
// ── Step 1 — Select Procedures (or whole invoice for Inpatient/Package) ──
|
|
|
|
|
|
|
|
|
|
Widget _buildStep1Content(RefundRequestViewModel vm) {
|
|
|
|
|
return Consumer<RefundRequestViewModel>(
|
|
|
|
|
builder: (context, refundVM, _) {
|
|
|
|
|
final isArabic = _appState.isArabic();
|
|
|
|
|
return Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
@ -164,39 +185,41 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
// Select All row
|
|
|
|
|
Material(
|
|
|
|
|
color: Colors.transparent,
|
|
|
|
|
child: InkWell(
|
|
|
|
|
onTap: () => _refundRequestViewModel.toggleSelectAll(),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 24.w,
|
|
|
|
|
height: 24.w,
|
|
|
|
|
child: Transform.scale(
|
|
|
|
|
scale: 24.w / 22,
|
|
|
|
|
child: Checkbox(
|
|
|
|
|
value: vm.isAllSelected,
|
|
|
|
|
onChanged: (_) => _refundRequestViewModel.toggleSelectAll(),
|
|
|
|
|
activeColor: AppColors.primaryRedColor,
|
|
|
|
|
side: BorderSide(color: AppColors.checkBoxBorderColor, width: 1),
|
|
|
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
|
|
|
visualDensity: VisualDensity.compact,
|
|
|
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.r)),
|
|
|
|
|
// Select All row - only show for Outpatient/ER invoices
|
|
|
|
|
if (_canSelectIndividualProcedures)
|
|
|
|
|
Material(
|
|
|
|
|
color: Colors.transparent,
|
|
|
|
|
child: InkWell(
|
|
|
|
|
onTap: () => _refundRequestViewModel.toggleSelectAll(),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
|
child: Row(
|
|
|
|
|
children: [
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 24.w,
|
|
|
|
|
height: 24.w,
|
|
|
|
|
child: Transform.scale(
|
|
|
|
|
scale: 24.w / 22,
|
|
|
|
|
child: Checkbox(
|
|
|
|
|
value: vm.isAllSelected,
|
|
|
|
|
onChanged: (_) => _refundRequestViewModel.toggleSelectAll(),
|
|
|
|
|
activeColor: AppColors.primaryRedColor,
|
|
|
|
|
side: BorderSide(color: AppColors.checkBoxBorderColor, width: 1),
|
|
|
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
|
|
|
visualDensity: VisualDensity.compact,
|
|
|
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.r)),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
|
LocaleKeys.selectAll.tr(context: context).toText16(isBold: true),
|
|
|
|
|
],
|
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
|
LocaleKeys.selectAll.tr(context: context).toText16(isBold: true),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Divider(color: AppColors.bottomNAVBorder, height: 1, thickness: 1).paddingSymmetrical(16.w, 0.w),
|
|
|
|
|
if (_canSelectIndividualProcedures)
|
|
|
|
|
Divider(color: AppColors.bottomNAVBorder, height: 1, thickness: 1).paddingSymmetrical(16.w, 0.w),
|
|
|
|
|
...List.generate(
|
|
|
|
|
vm.procedures.length,
|
|
|
|
|
(index) => _buildProcedureCard(vm.procedures[index], index, vm.procedures.length),
|
|
|
|
|
@ -331,11 +354,12 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Step 2 — procedure checkboxes ─────────────────────────────────────────
|
|
|
|
|
// ── Procedure Card ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
Widget _buildProcedureCard(RefundableProcedure procedure, int index, int length) {
|
|
|
|
|
final isArabic = _appState.isArabic();
|
|
|
|
|
final name = isArabic && procedure.procedureNameN != null ? procedure.procedureNameN! : procedure.procedureName;
|
|
|
|
|
final canSelect = _canSelectIndividualProcedures;
|
|
|
|
|
|
|
|
|
|
return AnimationConfiguration.staggeredList(
|
|
|
|
|
position: index,
|
|
|
|
|
@ -349,81 +373,88 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
|
|
|
|
|
child: Material(
|
|
|
|
|
color: Colors.transparent,
|
|
|
|
|
child: InkWell(
|
|
|
|
|
onTap: () => _refundRequestViewModel.toggleProcedureSelection(procedure),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 24.w,
|
|
|
|
|
height: 24.w,
|
|
|
|
|
child: Transform.scale(
|
|
|
|
|
scale: 24.w / 22,
|
|
|
|
|
child: Checkbox(
|
|
|
|
|
value: procedure.isSelected,
|
|
|
|
|
onChanged: (_) => _refundRequestViewModel.toggleProcedureSelection(procedure),
|
|
|
|
|
activeColor: AppColors.primaryRedColor,
|
|
|
|
|
side: BorderSide(color: AppColors.checkBoxBorderColor, width: 1),
|
|
|
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
|
|
|
visualDensity: VisualDensity.compact,
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
borderRadius: BorderRadius.circular(4.r),
|
|
|
|
|
// Disable tap for Inpatient/Package invoices
|
|
|
|
|
onTap: canSelect ? () => _refundRequestViewModel.toggleProcedureSelection(procedure) : null,
|
|
|
|
|
child: Opacity(
|
|
|
|
|
// Reduce opacity for disabled items
|
|
|
|
|
opacity: canSelect ? 1.0 : 0.7,
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
SizedBox(
|
|
|
|
|
width: 24.w,
|
|
|
|
|
height: 24.w,
|
|
|
|
|
child: Transform.scale(
|
|
|
|
|
scale: 24.w / 22,
|
|
|
|
|
child: Checkbox(
|
|
|
|
|
value: procedure.isSelected,
|
|
|
|
|
// Disable checkbox for Inpatient/Package invoices
|
|
|
|
|
onChanged: canSelect ? (_) => _refundRequestViewModel.toggleProcedureSelection(procedure) : null,
|
|
|
|
|
activeColor: AppColors.primaryRedColor,
|
|
|
|
|
side: BorderSide(color: AppColors.checkBoxBorderColor, width: 1),
|
|
|
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
|
|
|
visualDensity: VisualDensity.compact,
|
|
|
|
|
shape: RoundedRectangleBorder(
|
|
|
|
|
borderRadius: BorderRadius.circular(4.r),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
|
Expanded(child: name.toText16(isBold: true)),
|
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
|
Utils.getPaymentAmountWithSymbol(
|
|
|
|
|
procedure.patientPaidAmount.toStringAsFixed(2).toText20(
|
|
|
|
|
isBold: true,
|
|
|
|
|
isEnglishOnly: true,
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
),
|
|
|
|
|
AppColors.textColor,
|
|
|
|
|
10.f,
|
|
|
|
|
isSaudiCurrency: true,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
if (procedure.isSelected) ...[
|
|
|
|
|
InkWell(
|
|
|
|
|
onTap: () => showRefundReasonBottomSheet(
|
|
|
|
|
context,
|
|
|
|
|
selectedReason: procedure.refundReason,
|
|
|
|
|
onDone: (reason) => _refundRequestViewModel.setRefundReasonForProcedure(procedure, reason),
|
|
|
|
|
),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.only(top: 16.h),
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
LocaleKeys.refundReason.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true),
|
|
|
|
|
(procedure.refundReason ?? LocaleKeys.selectRefundReason.tr(context: context))
|
|
|
|
|
.toText14(color: AppColors.textColor, isBold: true),
|
|
|
|
|
],
|
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
|
Expanded(child: name.toText16(isBold: true)),
|
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
|
Utils.getPaymentAmountWithSymbol(
|
|
|
|
|
procedure.patientPaidAmount.toStringAsFixed(2).toText20(
|
|
|
|
|
isBold: true,
|
|
|
|
|
isEnglishOnly: true,
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
|
|
|
|
|
],
|
|
|
|
|
AppColors.textColor,
|
|
|
|
|
10.f,
|
|
|
|
|
isSaudiCurrency: true,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
// Only show refund reason selector for Outpatient/ER invoices
|
|
|
|
|
if (canSelect && procedure.isSelected) ...[
|
|
|
|
|
InkWell(
|
|
|
|
|
onTap: () => showRefundReasonBottomSheet(
|
|
|
|
|
context,
|
|
|
|
|
selectedReason: procedure.refundReason,
|
|
|
|
|
onDone: (reason) => _refundRequestViewModel.setRefundReasonForProcedure(procedure, reason),
|
|
|
|
|
),
|
|
|
|
|
child: Padding(
|
|
|
|
|
padding: EdgeInsets.only(top: 16.h),
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
LocaleKeys.refundReason.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true),
|
|
|
|
|
(procedure.refundReason ?? LocaleKeys.selectRefundReason.tr(context: context))
|
|
|
|
|
.toText14(color: AppColors.textColor, isBold: true),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
if (index != length - 1) ...[
|
|
|
|
|
Divider(color: AppColors.bottomNAVBorder, height: 1, thickness: 1).paddingOnly(top: 16.h),
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
if (index != length - 1) ...[
|
|
|
|
|
Divider(color: AppColors.bottomNAVBorder, height: 1, thickness: 1).paddingOnly(top: 16.h),
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
@ -492,7 +523,10 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
|
|
|
|
|
? (refundRequestViewModel.selectedPaymentMethod == null ||
|
|
|
|
|
(refundRequestViewModel.selectedPaymentMethod == RefundTabType.invoices &&
|
|
|
|
|
(_ibanController.text.trim().isEmpty || _descriptionController.text.trim().isEmpty)))
|
|
|
|
|
: (refundRequestViewModel.selectedProcedures.isEmpty || !refundRequestViewModel.allSelectedProceduresHaveReasons),
|
|
|
|
|
: (refundRequestViewModel.selectedProcedures.isEmpty ||
|
|
|
|
|
// For Outpatient/ER: require refund reasons
|
|
|
|
|
// For Inpatient/Package: skip refund reason validation
|
|
|
|
|
(_canSelectIndividualProcedures && !refundRequestViewModel.allSelectedProceduresHaveReasons)),
|
|
|
|
|
).paddingSymmetrical(24.h, 24.h),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
|