diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 911856ab..06c9d2e5 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1374,6 +1374,8 @@ "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "ليس لديك أي مواعيد لطلب تقرير طبي.", "areYouSureYouWantToRequestMedicalReport": "هل أنت متأكد أنك تريد طلب تقرير طبي لهذا الموعد؟", "yourMedicalReportRequestSubmittedSuccessfully": "تم إرسال طلب التقرير الطبي بنجاح.", + "alreadyRequested": "تم الطلب مسبقاً", + "medicalReportAlreadyRequestedMessage": "لديك طلب تقرير طبي معلق بالفعل لهذا الطبيب والعيادة. يمكنك تقديم طلب جديد بمجرد أن يصبح التقرير السابق جاهزاً.", "monthlyHealthSummaryReportDisclaimer": "يعكس تقرير الملخص الصحي الشهري هذا المؤشرات الصحية ونتائج التحليل لأحدث الزيارات. يرجى ملاحظة أن هذا سيتم إرساله تلقائيًا من النظام ولا يعتبر تقريرًا رسميًا لذا لا ينبغي اتخاذ أي قرار طبي بناءً عليه", "updatingMonthlyReportStatus": "جاري تحديث حالة التقرير الشهري...", "monthlyReportStatusUpdatedSuccessfully": "تم تحديث حالة التقرير الشهري بنجاح", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index aaafb124..10d8a627 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1366,6 +1366,8 @@ "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "You do not have any appointments to request a medical report.", "areYouSureYouWantToRequestMedicalReport": "Are you sure you want to request a medical report for this appointment?", "yourMedicalReportRequestSubmittedSuccessfully": "Your medical report request has been successfully submitted.", + "alreadyRequested": "Already Requested", + "medicalReportAlreadyRequestedMessage": "You already have a pending medical report request for this doctor and clinic. You can submit another request once the previous report becomes ready.", "monthlyHealthSummaryReportDisclaimer": "This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as a official report so no medical decision should be taken based on it", "updatingMonthlyReportStatus": "Updating Monthly Report Status...", "monthlyReportStatusUpdatedSuccessfully": "Monthly Report Status Updated Successfully", diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 29f653ec..db3e6919 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,7 +1,9 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; @@ -748,7 +750,37 @@ class MedicalFileViewModel extends ChangeNotifier { }); } + /// Checks if there is an existing pending medical report request for the same clinic and doctor. + /// According to CR#22303: + /// - If an existing request for the same clinic and doctor is not Active (status == 1, Requested), + /// the system prevents the patient from submitting another Medical Report request. + /// - Once the previous request becomes Active (status == 2, Ready) or Canceled (status == 4), + /// the patient is allowed to submit a new request for the same clinic and doctor. + bool hasPendingMedicalReportRequest({dynamic doctorId, dynamic clinicId}) { + if (doctorId == null || clinicId == null) return false; + final String docIdStr = doctorId.toString().trim(); + final String clinicIdStr = clinicId.toString().trim(); + + if (docIdStr.isEmpty || clinicIdStr.isEmpty) return false; + + return patientMedicalReportList.any((report) { + final String repDocIdStr = (report.doctorID ?? '').toString().trim(); + final String repClinicIdStr = (report.clinicID ?? '').toString().trim(); + + final bool isSameClinicAndDoctor = repDocIdStr == docIdStr && repClinicIdStr == clinicIdStr; + + // Status 1 (Requested) indicates the report has been requested and is pending (not Active). + final bool isPending = report.status == 1; + + return isSameClinicAndDoctor && isPending; + }); + } + Future getPatientMedicalReportAppointmentsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (patientMedicalReportList.isEmpty || isMedicalReportsDataNeedsReloading) { + isMedicalReportsDataNeedsReloading = true; + await getPatientMedicalReportList(); + } patientMedicalReportAppointmentHistoryList.clear(); notifyListeners(); @@ -773,6 +805,17 @@ class MedicalFileViewModel extends ChangeNotifier { } Future insertRequestForMedicalReport({Function(dynamic)? onSuccess, Function(String)? onError}) async { + if (patientMedicalReportSelectedAppointment != null && + hasPendingMedicalReportRequest( + doctorId: patientMedicalReportSelectedAppointment!.doctorID, + clinicId: patientMedicalReportSelectedAppointment!.clinicID, + )) { + if (onError != null) { + onError(LocaleKeys.medicalReportAlreadyRequestedMessage.tr()); + } + return; + } + final result = await medicalFileRepo.insertRequestForMedicalReport(appointmentHistoryResponseModel: patientMedicalReportSelectedAppointment!); result.fold( @@ -783,6 +826,7 @@ class MedicalFileViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { + isMedicalReportsDataNeedsReloading = true; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart index 71fb165d..4fd67b17 100644 --- a/lib/generated/codegen_loader.g.dart +++ b/lib/generated/codegen_loader.g.dart @@ -1348,6 +1348,8 @@ abstract class LocaleKeys { static const youDoNotHaveAnyAppointmentsToRequestMedicalReport = 'youDoNotHaveAnyAppointmentsToRequestMedicalReport'; static const areYouSureYouWantToRequestMedicalReport = 'areYouSureYouWantToRequestMedicalReport'; static const yourMedicalReportRequestSubmittedSuccessfully = 'yourMedicalReportRequestSubmittedSuccessfully'; + static const alreadyRequested = 'alreadyRequested'; + static const medicalReportAlreadyRequestedMessage = 'medicalReportAlreadyRequestedMessage'; static const monthlyHealthSummaryReportDisclaimer = 'monthlyHealthSummaryReportDisclaimer'; static const updatingMonthlyReportStatus = 'updatingMonthlyReportStatus'; static const monthlyReportStatusUpdatedSuccessfully = 'monthlyReportStatusUpdatedSuccessfully'; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 7dbcfd75..86b4680e 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1371,6 +1371,8 @@ abstract class LocaleKeys { static const youDoNotHaveAnyAppointmentsToRequestMedicalReport = 'youDoNotHaveAnyAppointmentsToRequestMedicalReport'; static const areYouSureYouWantToRequestMedicalReport = 'areYouSureYouWantToRequestMedicalReport'; static const yourMedicalReportRequestSubmittedSuccessfully = 'yourMedicalReportRequestSubmittedSuccessfully'; + static const alreadyRequested = 'alreadyRequested'; + static const medicalReportAlreadyRequestedMessage = 'medicalReportAlreadyRequestedMessage'; static const monthlyHealthSummaryReportDisclaimer = 'monthlyHealthSummaryReportDisclaimer'; static const updatingMonthlyReportStatus = 'updatingMonthlyReportStatus'; static const monthlyReportStatusUpdatedSuccessfully = 'monthlyReportStatusUpdatedSuccessfully'; diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 1eeb842c..be0f9785 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -356,6 +356,62 @@ class _AppointmentCardState extends State { ); } + Widget _buildMedicalReportButton(BuildContext context) { + if (widget.isForEyeMeasurements) { + return SizedBox.shrink(); + } + + final bool isAlreadyRequested = !widget.isForFeedback && + (widget.medicalFileViewModel?.hasPendingMedicalReportRequest( + doctorId: widget.patientAppointmentHistoryResponseModel.doctorID, + clinicId: widget.patientAppointmentHistoryResponseModel.clinicID, + ) ?? + false); + + return CustomButton( + text: isAlreadyRequested + ? LocaleKeys.alreadyRequested.tr(context: context) + : LocaleKeys.selectAppointment.tr(context: context), + onPressed: () { + if (widget.isForFeedback) { + widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); + Navigator.pop(context, false); + } else if (isAlreadyRequested) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.medicalReportAlreadyRequestedMessage.tr(context: context), + showOkButton: true, + onConfirmTap: () { + Navigator.pop(context); + }, + ), + ); + } else { + widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); + Navigator.pop(context, false); + } + }, + backgroundColor: isAlreadyRequested + ? AppColors.greyColor.withValues(alpha: 0.15) + : AppColors.secondaryLightRedColor, + borderColor: isAlreadyRequested + ? AppColors.greyColor.withValues(alpha: 0.15) + : AppColors.secondaryLightRedColor, + textColor: isAlreadyRequested + ? AppColors.greyTextColor + : AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: isAlreadyRequested ? null : AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } + Widget _buildActionArea(BuildContext context, AppState appState) { if ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || @@ -441,28 +497,22 @@ class _AppointmentCardState extends State { // ], // ); // } else { + if (widget.isFromMedicalReport) { + return _buildMedicalReportButton(context); + } return CustomButton( - text: widget.isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), + text: LocaleKeys.viewDetails.tr(context: context), onPressed: () { - if (widget.isFromMedicalReport) { - if (widget.isForFeedback) { - widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); - } else { - widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); - } - Navigator.pop(context, false); - } else { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((_) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - } + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, @@ -473,40 +523,14 @@ class _AppointmentCardState extends State { padding: EdgeInsets.symmetric(horizontal: 10.w), // height: isTablet || isFoldable ? 46.h : 40.h, height: 40.h, - icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null, + icon: null, iconColor: AppColors.primaryRedColor, iconSize: 16.h, ); // } } else { if (widget.isFromMedicalReport) { - if (widget.isForEyeMeasurements) { - return SizedBox.shrink(); - } else { - return CustomButton( - text: LocaleKeys.selectAppointment.tr(context: context), - onPressed: () { - if (widget.isForFeedback) { - widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); - } else { - widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); - } - Navigator.pop(context, false); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: isTablet || isFoldable ? 46.h : 40.h, - height: 40.h, - icon: AppAssets.checkmark_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); - } + return _buildMedicalReportButton(context); } return (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) ? (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index e1ac3969..278368d0 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -355,7 +355,24 @@ class _MedicalReportsPageState extends State { direction: AxisDirection.down, ), ); + if (!mounted) return; if (value != null) { + final selectedAppointment = medicalFileViewModel.patientMedicalReportSelectedAppointment; + if (selectedAppointment != null && + medicalFileViewModel.hasPendingMedicalReportRequest( + doctorId: selectedAppointment.doctorID, + clinicId: selectedAppointment.clinicID, + )) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.medicalReportAlreadyRequestedMessage.tr(context: context), + showOkButton: true, + onConfirmTap: () => Navigator.pop(context), + ), + ); + return; + } showConfirmRequestMedicalReportBottomSheet(); } }, onError: (err) {