From 499639e476e1047d69ae0ae82563c8f17a56f45e Mon Sep 17 00:00:00 2001 From: FaizHashmi Date: Mon, 9 Nov 2026 16:36:10 +0300 Subject: [PATCH] added invoice category and beneficiaryName --- assets/langs/ar-SA.json | 40 +++++-------------- assets/langs/en-US.json | 32 ++++----------- lib/core/utils/validation_utils.dart | 5 +++ .../habib_wallet/habib_wallet_view_model.dart | 24 ++++++++++- .../get_invoices_list_response_model.dart | 4 ++ .../submit_refund_request_model.dart | 11 ++++- .../refund_request_view_model.dart | 20 +++++++++- lib/generated/locale_keys.g.dart | 3 ++ .../withdraw_request_create_page.dart | 38 +++++++++++++++++- .../refund_request_create_page.dart | 39 +++++++++++++++++- 10 files changed, 156 insertions(+), 60 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index e4ca14bf..4ba5c8c0 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -57,7 +57,7 @@ "patientInfo": "معلومات المريض", "doctorFilter": "سيتم تصفية الأطباء بناءً على جنسك وعمرك", "bookSuccess": "تم الحجز بنجاح", - "patientShare": "نسبة المريض", + "patientShare": "نصيب المريض", "patientShareWithTax": "نسبة المريض مع الضريبة", "confirmAppo": "تأكيد الموعد", "confirmLiveCare": "تأكيد ال لايف كير", @@ -224,7 +224,6 @@ "allowView": "السماح", "rejectView": "رفض", "deleteView": "حذف", - "myFamily": "عائلتي", "approvals": "الموافقات", "approvalNo": "رقم الموافقة:", "companyName": "اسم الشركة:", @@ -386,7 +385,7 @@ "iAgreeHealthInfoCanBeUsed": "أوافق على استخدام معلوماتي الصحية للمقابلة. مزيد من المعلومات في ", "privacyPolicyText": "سياسة الخصوصية", "outPatient": "العيادات الخارجيه", - "thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة لاسترداد الأموال", + "thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة للاسترداد", "save": "حفظ", "userAgreement": "اتفاقية المستخدم", "updateSuccessfully": "تم التحديث بنجاح", @@ -478,7 +477,7 @@ "orders": "سجل الطلبات", "lakum": "لكم", "lakumMsg": "لا توجد تفاصيل عن النقاط", - "lakumPoint": "نقطة", + "lakumPoint": "نقاط لكم", "wishlist": "قائمة الرغبات", "products": "المنتجات", "reviews": "التعليقات", @@ -614,7 +613,6 @@ "waitinggained": "انتظار المكتسب", "expired": "منتهي", "willBeExpired": "سينتهي", - "lakumPoint": "نقاط لكم", "activateLAKUMAccount": "تفعيل حساب لكم", "checkBeneficiary": "تحقق من المستفيد", "beneficiaryName": "اسم المستفيد", @@ -648,7 +646,6 @@ "viewSubCategorise": "عرض جميع الفئات الفرعية", "categorise": "الفئات", "wishList": "قائمة الرغبات", - "myAccount": "المستفيد", "cart": "عربة التسوق", "alhabibapp": "تطبيق الحبيب", "searchProductHere": "ابحث عن المنتج هنا", @@ -959,7 +956,6 @@ "insuranceExpiredOrInactive": "التأمين غير ساري (منتهي او غير مفعل)", "totalAmountToPay": "المبلغ الإجمالي", "vat15": "ضريبة القيمة المضافة (15%)", - "general": "عام", "liveCare": "لايف كير", "recentVisits": "قائمة الزيارات", "favouriteDoctors": "الأطباء المفضلون", @@ -1141,7 +1137,7 @@ "insideTheHome": "من المنزل", "haveAnyAppointment": "هل لديك أي موعد", "amountPaidAtHospital": "الدفع عند الوصول للمستشفى", - "submitRequest": "تأكيد الطلب", + "submitRequest": "تقديم الطلب", "enterPickupLocationManually": "تحديد موقع الاستلام", "enterPickupLocation": "أدخل موقع الاستلام", "trackingDetails": "استدعاء سيارة إسعاف", @@ -1263,11 +1259,10 @@ "healthTrackers": "متتبعات الصحة", "monitorBloodPressureLevels": "راقب مستويات ضغط الدم لديك، وتتبع القراءات الانقباضية والانبساطية، وحافظ على صحة قلبك.", "trackWeightProgress": "تتبع تقدم وزنك، وضع الأهداف، وحافظ على كتلة جسم صحية من أجل العافية الشاملة.", - "bookAppointment": "حجز\nموعد", + "bookAppointment": "حجز موعد", "completeCheckup": "الفحص \n الشامل", "indoorNavigation": "الملاحة الداخلية", "eReferralServices": "الإحالة \n الإلكترونية", - "bloodDonation": "التبرع بالدم", "dailyWaterMonitor": "مراقب الماء اليومي", "fetchingYourWaterIntakeDetails": "يرجى الانتظار…", "healthCalculatorsServices": "حاسبات\nالصحة", @@ -1338,7 +1333,7 @@ "generatingReport": "جارٍ إنشاء التقرير، يرجى الانتظار...", "noLabResults": "لا يوجد نتائج", "labResultDetails": "تفاصيل نتائج المختبر", - "resultOf": "نتيجة", + "resultOf": "نتيجة {date}", "whatIsThisResult": "ما هي هذه النتيجة؟", "lastTested": "آخر اختبار", "byVisit": "حسب الزيارة", @@ -1466,7 +1461,6 @@ "colorScience": "دلالات الألوان", "noNeedToVisitDoctor": "لا حاجة لزيارة الطبيب", "commonSymptom": "الأعراض الشائعة", - "bookAppointment": "حجز موعد", "notApplicable": "لا ينطبق", "emergencyTriage": "طوارئ", "emergencyEvidenceDetected": "تم اكتشاف دليل طوارئ. يرجى طلب العناية الطبية.", @@ -1568,7 +1562,6 @@ "healthy": "صحي", "warning": "تحذير", "vitalSignDetails": "تفاصيل العلامات الحيوية", - "resultOf": "نتيجة {date}", "resultOfNoDate": "نتيجة --", "referenceRangeBetween": "النطاق المرجعي: {low} – {high} {unit}", "referenceRangeMin": "النطاق المرجعي: ≥ {low} {unit}", @@ -1606,7 +1599,6 @@ "waterConsumption": "استهلاك المياه", "selectNumberOfReminders": "حدد عدد التذكيرات", "h2oSettings": "الاعدادات", - "settingsSavedSuccessfully": "تم حفظ الإعدادات بنجاح", "yourName": "اسمك", "ageYears": "العمر (11-120) سنة", "numberOfRemindersInADay": "عدد التذكيرات في اليوم", @@ -1661,7 +1653,7 @@ "bodyFatObeseDescription": "نسبة الدهون في جسمك أعلى من المعدل الصحي الموصى به. قد يزيد هذا من المخاطر الصحية، بما في ذلك أمراض القلب والأوعية الدموية، والسكري، ومشاكل المفاصل. يُنصح باستشارة طبيب أو أخصائي تغذية معتمد لوضع خطة علاجية شخصية تشمل التغذية المتوازنة، والنشاط البدني المنتظم، وتعديلات نمط الحياة لتحقيق إدارة مستدامة للوزن.", "bmrNormalDescription": "معدل الأيض الأساسي لديك ضمن النطاق المتوقع لحالتك. معدل الأيض الأساسي هو الطاقة التي يحتاجها جسمك في حالة الراحة للحفاظ على وظائفه الحيوية (التنفس، الدورة الدموية، درجة الحرارة). استخدم هذه القيمة كأساس لتقدير إجمالي احتياجاتك اليومية من الطاقة من خلال تطبيق مُعامل النشاط. حافظ على نظام غذائي متوازن وممارسة النشاط البدني بانتظام لدعم صحة التمثيل الغذائي.", "bmrLowDescription": "معدل الأيض الأساسي لديك أقل من المعدل الطبيعي لحالتك. تشمل الأسباب المحتملة انخفاض كتلة العضلات، أو تراجع التمثيل الغذائي المرتبط بالتقدم في السن، أو التكيف الأيضي الناتج عن تقييد السعرات الحرارية لفترة طويلة. يُنصح بالتركيز على تمارين المقاومة لبناء العضلات والحفاظ عليها، والتأكد من تناول كميات كافية من البروتين والمغذيات الدقيقة، واستشارة أخصائي رعاية صحية إذا كان هذا الأمر غير متوقع.", - "bmrHighDescription": "معدل الأيض الأساسي لديك أعلى من المتوسط \u200B\u200Bبالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.", + "bmrHighDescription": "معدل الأيض الأساسي لديك أعلى من المتوسط ​​بالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.", "ibwAtTargetDescription": "وزنك في النطاق المثالي. حافظ على عاداتك الحالية — نظام غذائي متوازن ونشاط بدني منتظم — للاستمرار على هذا المستوى.", "ibwBelowTargetDescription": "أنتِ أقل من الوزن المثالي بمقدار {kg} كجم. يُنصح بزيادة الوزن تدريجيًا من خلال فائضٍ بسيط في السعرات الحرارية، وتناول أطعمة غنية بالعناصر الغذائية، مع ممارسة تمارين المقاومة.", "ibwAboveTargetDescription": "أنتِ أعلى من الوزن المثالي بمقدار {kg} كجم. يُنصح باتباع خفضٍ تدريجي ومستدام للوزن من خلال عجزٍ معتدل في السعرات الحرارية، وزيادة مستوى النشاط البدني، واتباع تغذية متوازنة.", @@ -1698,7 +1690,7 @@ "reschedulingAppo": "إعادة جدولة الموعد، يرجى الانتظار...", "invalidEligibility": "لا يمكنك إجراء الدفع عبر الإنترنت لأنك غير مؤهل لاستخدام الخدمة المقدمة.", "invalidInsurance": "لا يمكنك إجراء الدفع عبر الإنترنت لأنه ليس لديك تأمين صالح.", - "continueCash": "تواصل نقدا", + "continueCash": "متابعة الدفع نقدًا", "applewatch": "ساعة آبل", "applehealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Apple Health على هاتفك", "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "لا يمكننا اكتشاف التطبيق المثبت على جهازك. يرجى العودة إلى هنا بمجرد تثبيت هذا التطبيق.", @@ -1713,7 +1705,6 @@ "whoophealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Whoop Health على هاتفك", "whoopwatchshouldbeconnected": "يجب توصيل ساعة Whoop", "updatetheinformation": "سيتيح ذلك جمع أحدث المعلومات من ساعة آبل الخاصة بك", - "continueCash": "متابعة الدفع نقدًا", "timeFor": "الوقت", "hmgPolicies": "سياسات مجموعة الحبيب الطبية", "darkMode": "الوضع الداكن", @@ -1791,7 +1782,6 @@ "underWeight": "وزن ناقص", "nearestGate": "أقرب بوابة", "RRTDDetails": "يقدم فريق الاستجابة السريع رعاية صحية متكاملة لجميع الحالات الطارئة والمستقرة.", - "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", "normalMapType": "افتراضي", "satelliteMapType": "قمر صناعي", "terrainMapType": "تضاريس", @@ -1817,7 +1807,6 @@ "selectFromPhotoLibrary": "اختر من مكتبة الصور", "selectDocumentFiles": "اختر ملفات المستندات", "profilePicture": "صورة الملف الشخصي", - "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", "sendingAskDrRequest": "جارٍ إرسال الطلب...", "askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً.", "onlineCheckIn24HoursOnly": "لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.", @@ -1920,30 +1909,26 @@ "selectOptions": "اختر الخيارات", "iban": "رقم الآيبان", "addIbanDetails": "أضف تفاصيل الآيبان", - "description": "الوصف", "enterDescription": "أدخل الوصف", - "submitRequest": "تقديم الطلب", "ibanIsRequired": "حقل رقم الآيبان مطلوب", "descriptionIsRequired": "الوصف مطلوب", "invalidIbanFormat": "رقم الآيبان غير صالح. يرجى إدخال رقم آيبان صالح (مثال: SA0380000000608010167519)", + "enterBeneficiaryName": "أدخل اسم المستفيد", + "beneficiaryNameIsRequired": "اسم المستفيد مطلوب", + "invalidBeneficiaryNameFormat": "يرجى إدخال اسم صالح (حروف فقط)", "refundHistory": "سجل الاسترداد", "noRefundHistoryFound": "لا يوجد سجل استرداد حتى الآن.", - "invoiceNo": "رقم الفاتورة", - "totalAmount": "المبلغ الإجمالي", - "patientShare": "نصيب المريض", "selectProcedure": "حدد العمليات", "noProceduresAvailableForRefund": "لا توجد عمليات متاحة للاسترداد", "selectAll": "تحديد الكل", "selectRefundMethod": "اختر طريقة الاسترداد", "hmgWallet": "محفظة HMG", "bankTransfer": "تحويل بنكي", - "cash": "نقدي", "selectRefundReason": "اختر سبب الاسترداد", "refundReason": "سبب الاسترداد", "optional": "(اختياري)", "requestRejectionReason": "سبب رفض الطلب", "procedureRejectionReason": "سبب رفض الإجراء", - "done": "تم", "refundReason1": "تم معالجة مريض الشركة كمدفوع ذاتي وإعادة فوترته", "refundReason2": "المريض لا يريد إجراء سريرياً", "refundReason3": "المريض لم ينتظر الاستشارة / يريد طبيباً آخر", @@ -1957,7 +1942,6 @@ "symptomsCheckerNotDiagnosis": "• فاحص الأعراض ليس تشخيصًا. إنه للمعلومات الخاصة بك فقط وليس رأيًا طبيًا مؤهلاً.", "symptomsCheckerNotEmergency": "• فاحص الأعراض ليس للطوارئ. اتصل برقم الطوارئ المحلي فورًا عند وجود حالة طوارئ صحية.", "yourDataIsSafe": "• بياناتك آمنة. تضمن تدابير الخصوصية حماية المعلومات التي تشاركها.", - "submitEReferral": "تقديم الطلب", "redeem": "استبدال", "points": "نقاط", "transactions": "المعاملات", @@ -1966,7 +1950,6 @@ "earned": "مكتسب", "getParkingQR": "الحصول على رمز الاستجابة السريعة لوقوف السيارات", "cannotOpenThisFile": "لا يمكن فتح هذا الملف", - "thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة للاسترداد", "refundDetails": "تفاصيل الاسترداد", "refund": "استرداد", "requestNo": "رقم الطلب.", @@ -1996,7 +1979,6 @@ "doctorSchedule": "جدول الطبيب", "weeklyShift": "الفترات الأسبوعية", "dayOff": "إجازة", - "notAvailable": "غير متاح", "hide": "إخفاء", "viewPatientQR": "عرض رمز الاستجابة السريعة للمريض", "patientQR": "رمز الاستجابة السريعة للمريض", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index fdb873d0..ca29591b 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -381,7 +381,7 @@ "termsAndConditions": "Terms and Conditions", "iAgreeHealthInfoCanBeUsed": "I agree that my health information can be used for the interview. More information in the ", "privacyPolicyText": "Privacy Policy", - "outPatient": "Out Patient", + "outPatient": "OutPatient", "thisInvoiceIsNotEligibleForRefund": "This invoice is not eligible for refund", "save": "Save", "userAgreement": "User Agreement", @@ -760,7 +760,7 @@ "failedToAccessHmgServices": "Connected with HMG Network,\n\nBut failed to access HMG services", "offerAndPackages": "Offers and Packages", "offerAndPackagesDetails": "This service allows you to view all HMG Offers:", - "invoiceNo": "Invoice No", + "invoiceNo": "Invoice No.", "invoiceDate": "Invoice Date", "specialResult": "Special Result", "generalResult": "General Result", @@ -950,7 +950,6 @@ "insuranceExpiredOrInactive": "Insurance expired or inactive", "totalAmountToPay": "Total Amount", "vat15": "VAT 15%", - "general": "General", "liveCare": "LiveCare", "recentVisits": "Recent Visits", "favouriteDoctors": "Favourite Doctors", @@ -1254,7 +1253,7 @@ "healthTrackers": "Health Trackers", "monitorBloodPressureLevels": "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.", "trackWeightProgress": "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.", - "bookAppointment": "New\nAppointment", + "bookAppointment": "Book Appointment", "completeCheckup": "Complete Checkup", "indoorNavigation": "Indoor Navigation", "eReferralServices": "E-Referral Services", @@ -1290,7 +1289,7 @@ "selectLocationPrescriptionDelivery": "Please select the location for prescription delivery", "noRadiologyOrders": "No Radiology Orders Found", "ageIsRequired": "Age is required", - "ibanIsRequired": "IBAN field is required", + "ibanIsRequired": "IBAN is required", "descriptionIsRequired": "Description is required", "invalidAge": "Invalid age", "ageMustBeBetween11And120": "Age must be between 11 and 120", @@ -1376,13 +1375,12 @@ "wantsToAddYouAsTheirFamilyMember": "wants to add you as their family member", "rejectedYourRequestToBeYourFamilyMember": "{status} your request to be your family member", "rejectedYourFamilyMemberRequest": "{status} your family member request", - "notAvailable": "N/A", + "notAvailable": "Not Available", "selectAProfile": "Please select a profile", "switchFamilyFile": "Switch from the below list of medical files", "medicalFiles": "Medical Files", "addANewFamilyMember": "Add a new family member", "viewInvoiceDetails": "View invoice details", - "outPatient": "OutPatient", "invoiceDetails": "Invoice Details", "sendingEmailPleaseWait": "Sending email, Please wait...", "emailSentSuccessfullyMessage": "Email sent successfully.", @@ -1459,7 +1457,6 @@ "colorScience": "Color Science", "noNeedToVisitDoctor": "No need to visit doctor", "commonSymptom": "Common Symptom", - "bookAppointment": "Book Appointment", "notApplicable": "Not Applicable", "emergencyTriage": "Emergency", "emergencyEvidenceDetected": "Emergency evidence detected. Please seek medical attention.", @@ -1598,7 +1595,6 @@ "waterConsumption": "Water Consumption", "selectNumberOfReminders": "Select Number of Reminders", "h2oSettings": "H20 Settings", - "settingsSavedSuccessfully": "Settings saved successfully", "yourName": "Your Name", "ageYears": "Age (11-120) yrs", "numberOfRemindersInADay": "Number of reminders in a day", @@ -1785,7 +1781,6 @@ "underWeight": "UnderWeight", "nearestGate": "Nearest Gate", "RRTDDetails": "Request a specialized team that offers comprehensive medical services for urgent and stable cases.", - "fromHospitalSubtitle": "Transfer from the hospital back to your home", "normalMapType": "Default", "satelliteMapType": "Satellite", "terrainMapType": "Terrain", @@ -1811,7 +1806,6 @@ "selectFromPhotoLibrary": "Select from your photo library", "selectDocumentFiles": "Select document files", "profilePicture": "Profile Picture", - "aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", "sendingAskDrRequest": "Sending Request...", "askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon.", "onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time.", @@ -1913,30 +1907,24 @@ "selectOptions": "Select Options", "iban": "IBAN", "addIbanDetails": "Add IBAN Details", - "description": "Description", "enterDescription": "Enter Description", - "submitRequest": "Submit Request", - "ibanIsRequired": "IBAN is required", - "descriptionIsRequired": "Description is required", "invalidIbanFormat": "Invalid IBAN. Please enter a valid IBAN (e.g. SA0380000000608010167519)", + "enterBeneficiaryName": "Enter Beneficiary Name", + "beneficiaryNameIsRequired": "Beneficiary Name is required", + "invalidBeneficiaryNameFormat": "Please enter a valid name (letters only)", "refundHistory": "Refund History", "noRefundHistoryFound": "You don't have any refund history yet.", - "invoiceNo": "Invoice No.", - "totalAmount": "Total Amount", - "patientShare": "Patient Share", "selectProcedure": "Select Procedure", "noProceduresAvailableForRefund": "No procedures available for refund", "selectAll": "Select all", "selectRefundMethod": "Select Refund Method", "hmgWallet": "HMG Wallet", "bankTransfer": "Bank Transfer", - "cash": "Cash", "selectRefundReason": "Select Refund Reason", "refundReason": "Refund Reason", "optional": "(Optional)", "requestRejectionReason": "Request Rejection Reason", "procedureRejectionReason": "Procedure Rejection Reason", - "done": "Done", "refundReason1": "Company patient treated as self-paying and re-invoiced", "refundReason2": "Patient does not want a clinical procedure", "refundReason3": "Patient does not wait for consultation/wants another doctor", @@ -1987,7 +1975,6 @@ "weeklyShift": "Weekly Shift", "dayOff": "Off", "inpatientRefundValidationMessage": "Your Inpatient invoice refund request requires validation because of inpatient validations related procedure / medications. We will review and process the request within 5 working days after confirming eligibility.", - "notAvailable": "Not Available", "hide": "Hide", "viewPatientQR": "View Patient QR Code", "patientQR": "Patient QR Code", @@ -2082,6 +2069,3 @@ "pendingMedicalReportRequest": "You already have a pending medical report request for this appointment. Please wait for the request to be completed before submitting a new one.", "allergyReactions": "Allergy Reactions" } - - - diff --git a/lib/core/utils/validation_utils.dart b/lib/core/utils/validation_utils.dart index ca644e0c..b8c224d1 100644 --- a/lib/core/utils/validation_utils.dart +++ b/lib/core/utils/validation_utils.dart @@ -226,4 +226,9 @@ class ValidationUtils { return remainder == 1; } + + /// Letters (any language) and spaces only — used for beneficiary name fields. + static bool validateBeneficiaryName(String name) { + return RegExp(r'^[\p{L}\s]+$', unicode: true).hasMatch(name.trim()); + } } diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 84f8183f..e31b889d 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -205,10 +205,13 @@ class HabibWalletViewModel extends ChangeNotifier { // ==================== WITHDRAW VALIDATION ERROR TRACKING ==================== String? _ibanError; + String? _beneficiaryNameError; String? _withdrawDescriptionError; String? get ibanError => _ibanError; + String? get beneficiaryNameError => _beneficiaryNameError; + String? get withdrawDescriptionError => _withdrawDescriptionError; void clearIbanError() { @@ -216,16 +219,30 @@ class HabibWalletViewModel extends ChangeNotifier { notifyListeners(); } + void clearBeneficiaryNameError() { + _beneficiaryNameError = null; + notifyListeners(); + } + void clearWithdrawDescriptionError() { _withdrawDescriptionError = null; notifyListeners(); } - bool validateWithdrawForm({required String iban, required String description}) { + bool validateWithdrawForm({required String iban, required String beneficiaryName, required String description}) { bool isValid = true; _ibanError = null; + _beneficiaryNameError = null; _withdrawDescriptionError = null; + if (beneficiaryName.trim().isEmpty) { + _beneficiaryNameError = LocaleKeys.beneficiaryNameIsRequired.tr(); + isValid = false; + } else if (!ValidationUtils.validateBeneficiaryName(beneficiaryName)) { + _beneficiaryNameError = LocaleKeys.invalidBeneficiaryNameFormat.tr(); + isValid = false; + } + if (iban.trim().isEmpty) { _ibanError = LocaleKeys.ibanIsRequired.tr(); isValid = false; @@ -354,6 +371,7 @@ class HabibWalletViewModel extends ChangeNotifier { Future submitWalletWithdrawalRequest({ required String iban, + required String beneficiaryName, required String remarks, required String patientName, required String patientId, @@ -368,6 +386,8 @@ class HabibWalletViewModel extends ChangeNotifier { isSubmittingWithdrawal = true; notifyListeners(); + final isBankTransfer = refundDestination == '2'; + // Only send the selected advance (not all advances) final advances = selectedAdvance != null ? [ @@ -401,6 +421,7 @@ class HabibWalletViewModel extends ChangeNotifier { refundDestination: refundDestination, // Use provided refund destination refundIban: iban, + refundBeneficiaryName: isBankTransfer ? beneficiaryName : '', refundRemarks: remarks, refundTotalAmount: selectedAdvanceBalance, refundProceduresList: const [], @@ -474,6 +495,7 @@ class HabibWalletViewModel extends ChangeNotifier { isAdvancesLoading = false; isSubmittingWithdrawal = false; _ibanError = null; + _beneficiaryNameError = null; _withdrawDescriptionError = null; _withdrawAttachmentError = null; withdrawAttachmentList.clear(); diff --git a/lib/features/my_invoices/models/get_invoices_list_response_model.dart b/lib/features/my_invoices/models/get_invoices_list_response_model.dart index 1aa580e7..a22e8ed9 100644 --- a/lib/features/my_invoices/models/get_invoices_list_response_model.dart +++ b/lib/features/my_invoices/models/get_invoices_list_response_model.dart @@ -19,6 +19,7 @@ class GetInvoicesListResponseModel { String? doctorImageUrl; int? doctorRate; bool? isAllowRefund; + String? invoiceCategory; int? invoiceType; String? invoiceTypeDescription; bool? isPackageInvoice; @@ -45,6 +46,7 @@ class GetInvoicesListResponseModel { this.doctorImageUrl, this.doctorRate, this.isAllowRefund, + this.invoiceCategory, this.invoiceType, this.invoiceTypeDescription, this.isPackageInvoice, @@ -76,6 +78,7 @@ class GetInvoicesListResponseModel { doctorImageUrl: json["DoctorImageURL"], doctorRate: json["DoctorRate"], isAllowRefund: json["IsAllowRefund"], + invoiceCategory: json["InvoiceCategory"], invoiceType: json["InvoiceType"], invoiceTypeDescription: json["InvoiceTypeDescription"], isPackageInvoice: json["IsPackageInvoice"], @@ -103,6 +106,7 @@ class GetInvoicesListResponseModel { "DoctorImageURL": doctorImageUrl, "DoctorRate": doctorRate, "IsAllowRefund": isAllowRefund, + "InvoiceCategory": invoiceCategory, "invoiceType": invoiceType, "invoiceDescription": invoiceTypeDescription, "isOutPatient": isOutPatient, diff --git a/lib/features/refund_request/models/req_models/submit_refund_request_model.dart b/lib/features/refund_request/models/req_models/submit_refund_request_model.dart index ceca1b87..eca85948 100644 --- a/lib/features/refund_request/models/req_models/submit_refund_request_model.dart +++ b/lib/features/refund_request/models/req_models/submit_refund_request_model.dart @@ -71,6 +71,7 @@ class SubmitRefundRequestModel { final String refundClinicName; final String refundDestination; final String refundIban; + final String refundBeneficiaryName; final String refundRemarks; final double refundTotalAmount; final bool isRefundFastTrack; @@ -79,6 +80,10 @@ class SubmitRefundRequestModel { /// Populated only for wallet withdrawal refunds (refundType == '2'). final List refundAdvancesList; + /// Populated only for invoice refunds (refundType == '1'). Comes from + /// GetInvoicesListResponseModel.invoiceCategory. + final String refundInvoiceCategory; + /// List of base64 encoded images for refund attachments (max 4 files, each max 2MB) final List refundAttachmentsBase64List; @@ -99,15 +104,17 @@ class SubmitRefundRequestModel { required this.refundClinicName, required this.refundDestination, required this.refundIban, + this.refundBeneficiaryName = '', required this.refundRemarks, required this.refundTotalAmount, required this.isRefundFastTrack, required this.refundProceduresList, this.refundAdvancesList = const [], this.refundAttachmentsBase64List = const [], + this.refundInvoiceCategory = '', }); - /// refundType '1' = invoice refund → sends Refund_ProceduresList + /// refundType '1' = invoice refund → sends Refund_ProceduresList + Refund_InvoiceCategory /// refundType '2' = wallet withdrawal → sends Refund_AdvancesList Map toJson() { final Map json = { @@ -123,6 +130,7 @@ class SubmitRefundRequestModel { "Refund_DoctorName": refundDoctorName ?? '', "Refund_RefundDestination": refundDestination ?? '', "Refund_IBAN": refundIban ?? '', + "Refund_BeneficiaryName": refundBeneficiaryName ?? '', "Refund_Remarks": refundRemarks ?? '', "Refund_TotalRefundAmount": refundTotalAmount, "Refund_IsFastTrack": isRefundFastTrack, @@ -139,6 +147,7 @@ class SubmitRefundRequestModel { json["Refund_ClinicId"] = refundClinicId; json["Refund_ClinicName"] = refundClinicName ?? ''; json["Refund_ProceduresList"] = refundProceduresList.map((p) => p.toJson()).toList(); + json["Refund_InvoiceCategory"] = refundInvoiceCategory ?? ''; } return json; diff --git a/lib/features/refund_request/refund_request_view_model.dart b/lib/features/refund_request/refund_request_view_model.dart index 7c139762..ddb8643e 100644 --- a/lib/features/refund_request/refund_request_view_model.dart +++ b/lib/features/refund_request/refund_request_view_model.dart @@ -119,6 +119,7 @@ class RefundRequestViewModel extends ChangeNotifier { isInvoiceDetailsLoading = false; invoiceDetails = null; _ibanError = null; + _beneficiaryNameError = null; _descriptionError = null; refundAttachmentList.clear(); _attachmentError = null; @@ -274,10 +275,13 @@ class RefundRequestViewModel extends ChangeNotifier { // ── Bank Transfer Error Tracking ────────────────────────────────────────── String? _ibanError; + String? _beneficiaryNameError; String? _descriptionError; String? get ibanError => _ibanError; + String? get beneficiaryNameError => _beneficiaryNameError; + String? get descriptionError => _descriptionError; void clearIbanError() { @@ -285,16 +289,30 @@ class RefundRequestViewModel extends ChangeNotifier { notifyListeners(); } + void clearBeneficiaryNameError() { + _beneficiaryNameError = null; + notifyListeners(); + } + void clearDescriptionError() { _descriptionError = null; notifyListeners(); } - bool validateBankTransferForm({required String iban, required String description}) { + bool validateBankTransferForm({required String iban, required String beneficiaryName, required String description}) { bool isValid = true; _ibanError = null; + _beneficiaryNameError = null; _descriptionError = null; + if (beneficiaryName.trim().isEmpty) { + _beneficiaryNameError = LocaleKeys.beneficiaryNameIsRequired.tr(); + isValid = false; + } else if (!ValidationUtils.validateBeneficiaryName(beneficiaryName)) { + _beneficiaryNameError = LocaleKeys.invalidBeneficiaryNameFormat.tr(); + isValid = false; + } + if (iban.trim().isEmpty) { _ibanError = LocaleKeys.ibanIsRequired.tr(); isValid = false; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 1ec1bbd9..45275556 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1902,6 +1902,9 @@ abstract class LocaleKeys { static const ibanIsRequired = 'ibanIsRequired'; static const descriptionIsRequired = 'descriptionIsRequired'; static const invalidIbanFormat = 'invalidIbanFormat'; + static const enterBeneficiaryName = 'enterBeneficiaryName'; + static const beneficiaryNameIsRequired = 'beneficiaryNameIsRequired'; + static const invalidBeneficiaryNameFormat = 'invalidBeneficiaryNameFormat'; static const refundHistory = 'refundHistory'; static const noRefundHistoryFound = 'noRefundHistoryFound'; static const selectProcedure = 'selectProcedure'; diff --git a/lib/presentation/habib_wallet/withdraw_request_create_page.dart b/lib/presentation/habib_wallet/withdraw_request_create_page.dart index 199f6af0..84bf287c 100644 --- a/lib/presentation/habib_wallet/withdraw_request_create_page.dart +++ b/lib/presentation/habib_wallet/withdraw_request_create_page.dart @@ -42,7 +42,9 @@ class _WithdrawRequestCreatePageState extends State { late AppState _appState; final TextEditingController _bankDetailsController = TextEditingController(); + final TextEditingController _beneficiaryNameController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); + final FocusNode _beneficiaryNameFocusNode = FocusNode(); final FocusNode _ibanFocusNode = FocusNode(); final FocusNode _descriptionFocusNode = FocusNode(); @@ -62,7 +64,9 @@ class _WithdrawRequestCreatePageState extends State { void dispose() { _habibWalletViewModel.resetWithdrawForm(silent: true); _bankDetailsController.dispose(); + _beneficiaryNameController.dispose(); _descriptionController.dispose(); + _beneficiaryNameFocusNode.dispose(); _ibanFocusNode.dispose(); _descriptionFocusNode.dispose(); super.dispose(); @@ -346,7 +350,9 @@ class _WithdrawRequestCreatePageState extends State { (isCash ? !vm.isCashWithdrawAmountExceeded // Cash: check amount limit : (isBankTransfer - ? (_bankDetailsController.text.trim().isNotEmpty && _descriptionController.text.trim().isNotEmpty) // Bank: check IBAN and description + ? (_bankDetailsController.text.trim().isNotEmpty && + _beneficiaryNameController.text.trim().isNotEmpty && + _descriptionController.text.trim().isNotEmpty) // Bank: check IBAN, beneficiary name and description : true)); // HMG Wallet: no additional validation return Container( @@ -395,6 +401,7 @@ class _WithdrawRequestCreatePageState extends State { if (isBankTransfer) { final isFormValid = vm.validateWithdrawForm( iban: _bankDetailsController.text, + beneficiaryName: _beneficiaryNameController.text, description: _descriptionController.text, ); if (!isFormValid) return; @@ -493,7 +500,7 @@ class _WithdrawRequestCreatePageState extends State { return Consumer( builder: (context, viewModel, _) { final isBankTransfer = viewModel.selectedWithdrawMethod == RefundTabType.invoices; - final hasError = viewModel.ibanError != null || viewModel.withdrawDescriptionError != null; + final hasError = viewModel.ibanError != null || viewModel.beneficiaryNameError != null || viewModel.withdrawDescriptionError != null; return Container( padding: EdgeInsets.all(16.h), decoration: BoxDecoration( @@ -507,6 +514,32 @@ class _WithdrawRequestCreatePageState extends State { child: Column( children: [ if (isBankTransfer) ...[ + TextInputWidget( + labelText: LocaleKeys.beneficiaryName.tr(), + hintText: LocaleKeys.enterBeneficiaryName.tr(), + controller: _beneficiaryNameController, + focusNode: _beneficiaryNameFocusNode, + autoFocus: false, + keyboardType: TextInputType.text, + isEnable: true, + isReadOnly: false, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 14.f, + isCountryDropDown: false, + leadingIcon: AppAssets.bankIcon, + fontFamily: "Poppins", + hasError: false, + errorMessage: viewModel.beneficiaryNameError, + onChange: (value) { + viewModel.clearBeneficiaryNameError(); + }, + onSubmitted: (value) { + _ibanFocusNode.requestFocus(); + }, + ), + Divider(color: AppColors.bottomNAVBorder, thickness: 1).withVerticalPadding(16.h), TextInputWidget( labelText: LocaleKeys.addIbanDetails.tr(), hintText: "SA0000000000000000000000", @@ -750,6 +783,7 @@ class _WithdrawRequestCreatePageState extends State { await vm.submitWalletWithdrawalRequest( iban: _bankDetailsController.text.trim(), + beneficiaryName: _beneficiaryNameController.text.trim(), remarks: _descriptionController.text.trim(), patientName: '${user?.firstName ?? ''} ${user?.lastName ?? ''}'.trim(), patientId: (user?.patientId ?? 0).toString(), diff --git a/lib/presentation/refund_request/refund_request_create_page.dart b/lib/presentation/refund_request/refund_request_create_page.dart index 6333f3b2..8d9493b0 100644 --- a/lib/presentation/refund_request/refund_request_create_page.dart +++ b/lib/presentation/refund_request/refund_request_create_page.dart @@ -52,7 +52,9 @@ class _RefundRequestCreatePageState extends State { late AppState _appState; final TextEditingController _ibanController = TextEditingController(); + final TextEditingController _beneficiaryNameController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); + final FocusNode _beneficiaryNameFocusNode = FocusNode(); final FocusNode _ibanFocusNode = FocusNode(); final FocusNode _descriptionFocusNode = FocusNode(); @@ -63,6 +65,7 @@ class _RefundRequestCreatePageState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { _refundRequestViewModel = context.read(); _ibanController.addListener(() => _refundRequestViewModel.triggerRebuild()); + _beneficiaryNameController.addListener(() => _refundRequestViewModel.triggerRebuild()); _descriptionController.addListener(() => _refundRequestViewModel.triggerRebuild()); _loadInvoiceDetails(); // _loadThresholdAmount(); @@ -72,7 +75,9 @@ class _RefundRequestCreatePageState extends State { @override void dispose() { _ibanController.dispose(); + _beneficiaryNameController.dispose(); _descriptionController.dispose(); + _beneficiaryNameFocusNode.dispose(); _ibanFocusNode.dispose(); _descriptionFocusNode.dispose(); _refundRequestViewModel.resetState(); @@ -557,7 +562,7 @@ class _RefundRequestCreatePageState extends State { Widget _buildBankInputDetails(RefundRequestViewModel vm) { final isBankTransfer = vm.selectedPaymentMethod == RefundTabType.invoices; - final hasError = vm.ibanError != null || vm.descriptionError != null; + final hasError = vm.ibanError != null || vm.beneficiaryNameError != null || vm.descriptionError != null; return Container( padding: EdgeInsets.all(16.h), decoration: BoxDecoration( @@ -571,6 +576,32 @@ class _RefundRequestCreatePageState extends State { child: Column( children: [ if (isBankTransfer) ...[ + TextInputWidget( + labelText: LocaleKeys.beneficiaryName.tr(), + hintText: LocaleKeys.enterBeneficiaryName.tr(), + controller: _beneficiaryNameController, + focusNode: _beneficiaryNameFocusNode, + autoFocus: false, + keyboardType: TextInputType.text, + isEnable: true, + isReadOnly: false, + prefix: null, + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 14.f, + isCountryDropDown: false, + leadingIcon: AppAssets.bankIcon, + fontFamily: "Poppins", + hasError: false, + errorMessage: vm.beneficiaryNameError, + onChange: (value) { + vm.clearBeneficiaryNameError(); + }, + onSubmitted: (value) { + _ibanFocusNode.requestFocus(); + }, + ), + Divider(color: AppColors.bottomNAVBorder, thickness: 1).withVerticalPadding(16.h), TextInputWidget( labelText: LocaleKeys.addIbanDetails.tr(), hintText: "SA0000000000000000000000", @@ -792,6 +823,7 @@ class _RefundRequestCreatePageState extends State { if (isBankTransfer) { final isValid = refundRequestViewModel.validateBankTransferForm( iban: _ibanController.text, + beneficiaryName: _beneficiaryNameController.text, description: _descriptionController.text, ); if (!isValid) return; @@ -801,7 +833,8 @@ class _RefundRequestCreatePageState extends State { }, isDisabled: isStep2 ? (refundRequestViewModel.selectedPaymentMethod == null || - (refundRequestViewModel.selectedPaymentMethod == RefundTabType.invoices && (_ibanController.text.trim().isEmpty || _descriptionController.text.trim().isEmpty)) || + (refundRequestViewModel.selectedPaymentMethod == RefundTabType.invoices && + (_ibanController.text.trim().isEmpty || _beneficiaryNameController.text.trim().isEmpty || _descriptionController.text.trim().isEmpty)) || (refundRequestViewModel.selectedPaymentMethod == RefundTabType.cash && refundRequestViewModel.isCashRefundAmountExceeded)) : (refundRequestViewModel.selectedProcedures.isEmpty || // For Outpatient/ER: require refund reasons @@ -873,9 +906,11 @@ class _RefundRequestCreatePageState extends State { refundClinicName: invoice.clinicName ?? '', refundDestination: refundDestination, refundIban: isBankTransfer ? _ibanController.text.trim() : '', + refundBeneficiaryName: isBankTransfer ? _beneficiaryNameController.text.trim() : '', refundRemarks: _descriptionController.text.trim(), refundTotalAmount: vm.totalRefundableAmount, refundAttachmentsBase64List: vm.refundAttachmentList, + refundInvoiceCategory: invoice.invoiceCategory ?? '', refundProceduresList: vm.selectedProcedures .map((p) => SubmitRefundProcedureItem( lineItemNo: p.apiLineItemNo ?? 0,