Added invoice type checks

faiz_dev
faizatflutter 3 weeks ago
parent 7ec72460e7
commit 34a55396b0

@ -209,7 +209,8 @@ class ApiClientImp implements ApiClient {
} }
body['TokenID'] = "@dm!n"; body['TokenID'] = "@dm!n";
body['PatientID'] = 4774167; // body['PatientID'] = 4770714;
body['PatientID'] = 4771727;
// body['PatientID'] = 89754444; // body['PatientID'] = 89754444;
// body['PatientTypeID'] = 1; // body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0; // body['PatientOutSA'] = 0;

@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts { class ApiConsts {
static const maxSmallScreen = 660; static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -292,7 +292,7 @@ class ApiConsts {
// ************ static values for Api **************** // ************ static values for Api ****************
// static final double appVersionID = 21.2; // static final double appVersionID = 21.2;
static final double appVersionID = 50.7; static final double appVersionID = 21.4;
static final int appChannelId = 3; static final int appChannelId = 3;
static final String appIpAddress = "10.20.10.20"; static final String appIpAddress = "10.20.10.20";
static final String appGeneralId = "Cs2020@2016\$2958"; static final String appGeneralId = "Cs2020@2016\$2958";

@ -19,6 +19,10 @@ class GetInvoicesListResponseModel {
String? doctorImageUrl; String? doctorImageUrl;
int? doctorRate; int? doctorRate;
bool? isAllowRefund; bool? isAllowRefund;
int? invoiceType;
String? invoiceTypeDescription;
bool? isPackageInvoice;
bool? isOutPatient;
int? patientNumber; int? patientNumber;
String? projectName; String? projectName;
@ -41,6 +45,10 @@ class GetInvoicesListResponseModel {
this.doctorImageUrl, this.doctorImageUrl,
this.doctorRate, this.doctorRate,
this.isAllowRefund, this.isAllowRefund,
this.invoiceType,
this.invoiceTypeDescription,
this.isPackageInvoice,
this.isOutPatient,
this.patientNumber, this.patientNumber,
this.projectName, this.projectName,
}); });
@ -68,6 +76,10 @@ class GetInvoicesListResponseModel {
doctorImageUrl: json["DoctorImageURL"], doctorImageUrl: json["DoctorImageURL"],
doctorRate: json["DoctorRate"], doctorRate: json["DoctorRate"],
isAllowRefund: json["IsAllowRefund"], isAllowRefund: json["IsAllowRefund"],
invoiceType: json["InvoiceType"],
invoiceTypeDescription: json["InvoiceTypeDescription"],
isPackageInvoice: json["IsPackageInvoice"],
isOutPatient: json['InvoiceType'] != 75,
patientNumber: json["PatientNumber"], patientNumber: json["PatientNumber"],
projectName: json["ProjectName"], projectName: json["ProjectName"],
); );
@ -91,6 +103,10 @@ class GetInvoicesListResponseModel {
"DoctorImageURL": doctorImageUrl, "DoctorImageURL": doctorImageUrl,
"DoctorRate": doctorRate, "DoctorRate": doctorRate,
"IsAllowRefund": isAllowRefund, "IsAllowRefund": isAllowRefund,
"invoiceType": invoiceType,
"invoiceDescription": invoiceTypeDescription,
"isOutPatient": isOutPatient,
"isPackageInvoice": isPackageInvoice,
"PatientNumber": patientNumber, "PatientNumber": patientNumber,
"ProjectName": projectName, "ProjectName": projectName,
}; };

@ -142,6 +142,7 @@ class InvoiceRefundProcedure {
final String? procedureId; final String? procedureId;
final String? procedureName; final String? procedureName;
final String? refundReason; final String? refundReason;
final bool? isApproved;
InvoiceRefundProcedure({ InvoiceRefundProcedure({
this.isLocked, this.isLocked,
@ -150,6 +151,7 @@ class InvoiceRefundProcedure {
this.procedureId, this.procedureId,
this.procedureName, this.procedureName,
this.refundReason, this.refundReason,
this.isApproved,
}); });
factory InvoiceRefundProcedure.fromJson(Map<String, dynamic> json) { factory InvoiceRefundProcedure.fromJson(Map<String, dynamic> json) {
@ -160,6 +162,7 @@ class InvoiceRefundProcedure {
procedureId: json['ProcedureID'] as String?, procedureId: json['ProcedureID'] as String?,
procedureName: json['ProcedureName'] as String?, procedureName: json['ProcedureName'] as String?,
refundReason: json['RefundReason'] as String?, refundReason: json['RefundReason'] as String?,
isApproved: json['IsApproved'] as bool?,
); );
} }
} }

@ -109,6 +109,7 @@ class RefundRequestViewModel extends ChangeNotifier {
currentStep = 0; currentStep = 0;
procedures.clear(); procedures.clear();
needITGApproval = false; needITGApproval = false;
isWholeInvoiceRefund = false;
selectedPaymentMethod = null; selectedPaymentMethod = null;
selectedRefundReason = null; selectedRefundReason = null;
isProceduresLoading = false; isProceduresLoading = false;
@ -208,15 +209,20 @@ class RefundRequestViewModel extends ChangeNotifier {
List<RefundableProcedure> procedures = []; List<RefundableProcedure> procedures = [];
bool isProceduresLoading = false; bool isProceduresLoading = false;
bool needITGApproval = false; bool needITGApproval = false;
bool isWholeInvoiceRefund = false; // For Inpatient/Package invoices
void setProcedures(List<RefundableProcedure> list, {bool needsITGApproval = false}) { void setProcedures(List<RefundableProcedure> list, {bool needsITGApproval = false, bool isWholeInvoiceRefund = false}) {
procedures = list; procedures = list;
needITGApproval = needsITGApproval; needITGApproval = needsITGApproval;
this.isWholeInvoiceRefund = isWholeInvoiceRefund;
isProceduresLoading = false; isProceduresLoading = false;
notifyListeners(); notifyListeners();
} }
void toggleProcedureSelection(RefundableProcedure procedure) { void toggleProcedureSelection(RefundableProcedure procedure) {
// Prevent selection toggle for whole invoice refunds
if (isWholeInvoiceRefund) return;
procedure.isSelected = !procedure.isSelected; procedure.isSelected = !procedure.isSelected;
notifyListeners(); notifyListeners();
} }
@ -229,6 +235,9 @@ class RefundRequestViewModel extends ChangeNotifier {
bool get isAllSelected => procedures.isNotEmpty && procedures.every((p) => p.isSelected); bool get isAllSelected => procedures.isNotEmpty && procedures.every((p) => p.isSelected);
void toggleSelectAll() { void toggleSelectAll() {
// Prevent toggle for whole invoice refunds
if (isWholeInvoiceRefund) return;
final selectAll = !isAllSelected; final selectAll = !isAllSelected;
for (final p in procedures) { for (final p in procedures) {
p.isSelected = selectAll; p.isSelected = selectAll;

@ -24,9 +24,9 @@ import 'package:provider/provider.dart';
class InvoiceListCard extends StatelessWidget { class InvoiceListCard extends StatelessWidget {
final GetInvoicesListResponseModel getInvoicesListResponseModel; final GetInvoicesListResponseModel getInvoicesListResponseModel;
Function? onTap; final Function? onTap;
InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap}); const InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap});
void _handleRefundInvoiceTap(BuildContext context) { void _handleRefundInvoiceTap(BuildContext context) {
final invoiceNo = getInvoicesListResponseModel.invoiceNo; final invoiceNo = getInvoicesListResponseModel.invoiceNo;
@ -70,7 +70,6 @@ class InvoiceListCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
AppState appState = getIt<AppState>();
return Container( return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: Padding( child: Padding(
@ -90,12 +89,15 @@ class InvoiceListCard extends StatelessWidget {
labelText: LocaleKeys.walkin.tr(), labelText: LocaleKeys.walkin.tr(),
textColor: AppColors.textColor, textColor: AppColors.textColor,
), ),
if (getInvoicesListResponseModel.invoiceTypeDescription != null &&
getInvoicesListResponseModel.invoiceTypeDescription!.isNotEmpty) ...[
AppCustomChipWidget( AppCustomChipWidget(
labelText: appState.isArabic() ? "العيادات الخارجيه" : "Out Patient", labelText: getInvoicesListResponseModel.invoiceTypeDescription ?? "",
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1),
textColor: AppColors.warningColorYellow, textColor: AppColors.warningColorYellow,
), ),
], ],
],
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Row( Row(

@ -74,6 +74,20 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
super.dispose(); 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() { void _loadInvoiceDetails() {
final invoiceNo = widget.invoice.invoiceNo; final invoiceNo = widget.invoice.invoiceNo;
final projectId = widget.invoice.projectId; final projectId = widget.invoice.projectId;
@ -90,9 +104,17 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
patientPaidAmount: item.patientPaidAmount ?? 0.0, patientPaidAmount: item.patientPaidAmount ?? 0.0,
apiLineItemNo: item.lineItemNo, apiLineItemNo: item.lineItemNo,
apiProcedureId: item.procedureId, 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(); .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) { Widget _buildStep1Content(RefundRequestViewModel vm) {
return Consumer<RefundRequestViewModel>( return Consumer<RefundRequestViewModel>(
builder: (context, refundVM, _) { builder: (context, refundVM, _) {
final isArabic = _appState.isArabic();
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -164,7 +185,8 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// Select All row // Select All row - only show for Outpatient/ER invoices
if (_canSelectIndividualProcedures)
Material( Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@ -196,6 +218,7 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
), ),
), ),
), ),
if (_canSelectIndividualProcedures)
Divider(color: AppColors.bottomNAVBorder, height: 1, thickness: 1).paddingSymmetrical(16.w, 0.w), Divider(color: AppColors.bottomNAVBorder, height: 1, thickness: 1).paddingSymmetrical(16.w, 0.w),
...List.generate( ...List.generate(
vm.procedures.length, 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) { Widget _buildProcedureCard(RefundableProcedure procedure, int index, int length) {
final isArabic = _appState.isArabic(); final isArabic = _appState.isArabic();
final name = isArabic && procedure.procedureNameN != null ? procedure.procedureNameN! : procedure.procedureName; final name = isArabic && procedure.procedureNameN != null ? procedure.procedureNameN! : procedure.procedureName;
final canSelect = _canSelectIndividualProcedures;
return AnimationConfiguration.staggeredList( return AnimationConfiguration.staggeredList(
position: index, position: index,
@ -349,7 +373,11 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: () => _refundRequestViewModel.toggleProcedureSelection(procedure), // 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( child: Padding(
padding: EdgeInsets.all(16.h), padding: EdgeInsets.all(16.h),
child: Column( child: Column(
@ -365,7 +393,8 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
scale: 24.w / 22, scale: 24.w / 22,
child: Checkbox( child: Checkbox(
value: procedure.isSelected, value: procedure.isSelected,
onChanged: (_) => _refundRequestViewModel.toggleProcedureSelection(procedure), // Disable checkbox for Inpatient/Package invoices
onChanged: canSelect ? (_) => _refundRequestViewModel.toggleProcedureSelection(procedure) : null,
activeColor: AppColors.primaryRedColor, activeColor: AppColors.primaryRedColor,
side: BorderSide(color: AppColors.checkBoxBorderColor, width: 1), side: BorderSide(color: AppColors.checkBoxBorderColor, width: 1),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
@ -391,7 +420,8 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
), ),
], ],
), ),
if (procedure.isSelected) ...[ // Only show refund reason selector for Outpatient/ER invoices
if (canSelect && procedure.isSelected) ...[
InkWell( InkWell(
onTap: () => showRefundReasonBottomSheet( onTap: () => showRefundReasonBottomSheet(
context, context,
@ -431,6 +461,7 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
), ),
), ),
), ),
),
); );
} }
@ -492,7 +523,10 @@ class _RefundRequestCreatePageState extends State<RefundRequestCreatePage> {
? (refundRequestViewModel.selectedPaymentMethod == null || ? (refundRequestViewModel.selectedPaymentMethod == null ||
(refundRequestViewModel.selectedPaymentMethod == RefundTabType.invoices && (refundRequestViewModel.selectedPaymentMethod == RefundTabType.invoices &&
(_ibanController.text.trim().isEmpty || _descriptionController.text.trim().isEmpty))) (_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), ).paddingSymmetrical(24.h, 24.h),
], ],
), ),

@ -186,8 +186,28 @@ class RefundRequestDetailScreen extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
LocaleKeys.procedure.tr(context: context).toText12(color: AppColors.textColor, isBold: true), Expanded(
LocaleKeys.amountOnly.tr(context: context).toText12(color: AppColors.textColor, isBold: true), flex: 3,
child: LocaleKeys.procedure.tr(context: context).toText12(color: AppColors.textColor, isBold: true),
),
SizedBox(width: 8.w),
SizedBox(
width: 70.w,
child: LocaleKeys.approved.tr(context: context).toText12(
color: AppColors.textColor,
isBold: true,
textAlignment: TextAlign.center,
),
),
SizedBox(width: 8.w),
SizedBox(
width: 60.w,
child: LocaleKeys.amountOnly.tr(context: context).toText12(
color: AppColors.textColor,
isBold: true,
textAlignment: TextAlign.end,
),
),
], ],
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -221,6 +241,8 @@ class RefundRequestDetailScreen extends StatelessWidget {
} }
Widget _buildProcedureRow(BuildContext context, InvoiceRefundProcedure procedure) { Widget _buildProcedureRow(BuildContext context, InvoiceRefundProcedure procedure) {
final isApproved = procedure.isApproved ?? false;
return Padding( return Padding(
padding: EdgeInsets.symmetric(vertical: 6.h), padding: EdgeInsets.symmetric(vertical: 6.h),
child: Row( child: Row(
@ -232,7 +254,23 @@ class RefundRequestDetailScreen extends StatelessWidget {
child: (procedure.procedureName ?? '-').toText12(color: AppColors.textColor), child: (procedure.procedureName ?? '-').toText12(color: AppColors.textColor),
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
(procedure.patientPaidAmount?.toString() ?? '-').toText12(color: AppColors.textColor, isBold: true), 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.end,
),
),
], ],
), ),
); );

Loading…
Cancel
Save