update api doc

This commit is contained in:
ismail 2026-07-19 12:27:55 +03:00
parent fe0a643607
commit 41bb9b5dd7
199 changed files with 15393 additions and 2576 deletions

1
.gitignore vendored
View File

@ -104,3 +104,4 @@ dump.rdb
node_modules node_modules
data/Documents data/Documents
deploy/target.dev-home.yaml

View File

@ -0,0 +1,39 @@
- generic [ref=e3]:
- generic [ref=e4]:
- img "HH Logo" [ref=e6]
- heading "Welcome to PX360" [level=1] [ref=e7]
- paragraph [ref=e8]: Patient Experience Management System
- generic [ref=e10]:
- generic [ref=e11]:
- generic [ref=e12]:
- img [ref=e13]
- text: Email Address
- generic [ref=e16]:
- img [ref=e17]
- textbox "Email Address" [active] [ref=e20]:
- /placeholder: Enter your email
- generic [ref=e21]:
- generic [ref=e22]:
- img [ref=e23]
- text: Password
- generic [ref=e26]:
- img [ref=e27]
- textbox "Password" [ref=e31]:
- /placeholder: Enter your password
- button [ref=e32] [cursor=pointer]:
- img [ref=e33]
- generic [ref=e36]:
- generic [ref=e37] [cursor=pointer]:
- checkbox "Remember me" [ref=e38]
- generic [ref=e39]: Remember me
- link "Forgot password?" [ref=e40] [cursor=pointer]:
- /url: /accounts/password/reset/
- button "Sign In" [ref=e41] [cursor=pointer]:
- img [ref=e42]
- text: Sign In
- generic [ref=e45]:
- paragraph [ref=e46]:
- text: Secure login powered by
- link "tenhal.sa" [ref=e47] [cursor=pointer]:
- /url: https://tenhal.sa
- paragraph [ref=e48]: © 2026 Al Hammadi Hospital

View File

@ -0,0 +1,41 @@
- generic [ref=e3]:
- generic [ref=e4]:
- img "HH Logo" [ref=e6]
- heading "Welcome to PX360" [level=1] [ref=e7]
- paragraph [ref=e8]: Patient Experience Management System
- generic [ref=e10]:
- generic [ref=e11]:
- generic [ref=e12]:
- img [ref=e49]
- text: Email Address
- generic [ref=e16]:
- img [ref=e52]
- textbox "Email Address" [ref=e20]:
- /placeholder: Enter your email
- text: e2e-px-employee@px360.test
- generic [ref=e21]:
- generic [ref=e22]:
- img [ref=e55]
- text: Password
- generic [ref=e26]:
- img [ref=e58]
- textbox "Password" [ref=e31]:
- /placeholder: Enter your password
- text: Dev@123456
- button [active] [ref=e32] [cursor=pointer]:
- img [ref=e62]
- generic [ref=e36]:
- generic [ref=e37] [cursor=pointer]:
- checkbox "Remember me" [ref=e38]
- generic [ref=e39]: Remember me
- link "Forgot password?" [ref=e40] [cursor=pointer]:
- /url: /accounts/password/reset/
- button "Sign In" [ref=e41] [cursor=pointer]:
- img [ref=e67]
- text: Sign In
- generic [ref=e45]:
- paragraph [ref=e46]:
- text: Secure login powered by
- link "tenhal.sa" [ref=e47] [cursor=pointer]:
- /url: https://tenhal.sa
- paragraph [ref=e48]: © 2026 Al Hammadi Hospital

View File

@ -0,0 +1,227 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Departments" [ref=e141] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e142]
- generic [ref=e144]: Contact Center Department
- generic [ref=e147]:
- generic [ref=e148]:
- img [ref=e150]
- generic [ref=e154]:
- heading "Contact Center Department" [level=1] [ref=e155]
- paragraph [ref=e156]: قسم مركز الاتصال
- generic [ref=e157]:
- generic [ref=e158]:
- img [ref=e159]
- text: hh_n_contact_center_department
- generic [ref=e162]: Administrative
- generic [ref=e163]:
- img [ref=e164]
- text: E2E Test Hospital
- generic [ref=e168]:
- generic [ref=e170]:
- img [ref=e172]
- generic [ref=e177]:
- paragraph [ref=e178]: Staff
- paragraph [ref=e179]: "4"
- link "Open Complaints 3 3 total" [ref=e180] [cursor=pointer]:
- /url: /organizations/departments/d810102c-d14a-4661-8672-6ef85a77fc8f/complaints/?status=open
- generic [ref=e181]:
- img [ref=e183]
- generic [ref=e186]:
- paragraph [ref=e187]: Open Complaints
- paragraph [ref=e188]: "3"
- paragraph [ref=e189]: 3 total
- link "Pending Inquiries 0 2 total" [ref=e190] [cursor=pointer]:
- /url: /organizations/departments/d810102c-d14a-4661-8672-6ef85a77fc8f/inquiries/?status=open
- generic [ref=e191]:
- img [ref=e193]
- generic [ref=e196]:
- paragraph [ref=e197]: Pending Inquiries
- paragraph [ref=e198]: "0"
- paragraph [ref=e199]: 2 total
- link "Open Observations 1 1 total" [ref=e200] [cursor=pointer]:
- /url: /organizations/departments/d810102c-d14a-4661-8672-6ef85a77fc8f/observations/?status=open
- generic [ref=e201]:
- img [ref=e203]
- generic [ref=e206]:
- paragraph [ref=e207]: Open Observations
- paragraph [ref=e208]: "1"
- paragraph [ref=e209]: 1 total
- generic [ref=e210]:
- generic [ref=e211]:
- img [ref=e213]
- generic [ref=e215]:
- heading "Pending Actions" [level=3] [ref=e216]
- paragraph [ref=e217]: 3 items awaiting response
- table [ref=e219]:
- rowgroup [ref=e220]:
- row "Type Reference Subject SLA Deadline Action" [ref=e221]:
- columnheader "Type" [ref=e222]
- columnheader "Reference" [ref=e223]
- columnheader "Subject" [ref=e224]
- columnheader "SLA Deadline" [ref=e225]
- columnheader "Action" [ref=e226]
- rowgroup [ref=e227]:
- row "Complaint Response CMP-202607-0006 Test Complaint - Analysis Required - Respond" [ref=e228]:
- cell "Complaint Response" [ref=e229]:
- generic [ref=e230]: Complaint Response
- cell "CMP-202607-0006" [ref=e231]
- cell "Test Complaint - Analysis Required" [ref=e232]
- cell "-" [ref=e233]
- cell "Respond" [ref=e234]:
- link "Respond" [ref=e236] [cursor=pointer]:
- /url: /complaints/f8aa6216-bb94-455e-ba18-d16a01024662/explain/dM0ilaUpzA1OuAceyjvwV9oxYA0geW_jsgipvrYUt_g/
- img [ref=e237]
- text: Respond
- row "Complaint Explanation CMP-202607-0005 Test Complaint - No Details - Respond" [ref=e239]:
- cell "Complaint Explanation" [ref=e240]:
- generic [ref=e241]: Complaint Explanation
- cell "CMP-202607-0005" [ref=e242]
- cell "Test Complaint - No Details" [ref=e243]
- cell "-" [ref=e244]
- cell "Respond" [ref=e245]:
- link "Respond" [ref=e247] [cursor=pointer]:
- /url: /complaints/9ca01d52-712d-4bca-b30d-6f1f818ff839/explain/iBIf0PrMq1RZYSEIDxSn4WZik4PFk6dunoNwarFTnXI/
- img [ref=e248]
- text: Respond
- row "Complaint Explanation CMP-202607-0005 Test Complaint - No Details - Respond" [ref=e252]:
- cell "Complaint Explanation" [ref=e253]:
- generic [ref=e254]: Complaint Explanation
- cell "CMP-202607-0005" [ref=e255]
- cell "Test Complaint - No Details" [ref=e256]
- cell "-" [ref=e257]
- cell "Respond" [ref=e258]:
- link "Respond" [ref=e260] [cursor=pointer]:
- /url: /complaints/9ca01d52-712d-4bca-b30d-6f1f818ff839/explain/X1Zd54Tb1NcExZeRr6ACN5RtPCwB_RTKeqNeFcVP24E/
- img [ref=e261]
- text: Respond
- generic [ref=e266]:
- button "Dashboard" [ref=e267] [cursor=pointer]:
- img [ref=e268]
- text: Dashboard
- button "Staff (4)" [ref=e270] [cursor=pointer]:
- img [ref=e271]
- text: Staff (4)
- button "Roles" [ref=e276] [cursor=pointer]:
- img [ref=e277]
- text: Roles
- button "Sections (2)" [ref=e289] [cursor=pointer]:
- img [ref=e290]
- text: Sections (2)
- button "Complaints (3)" [ref=e294] [cursor=pointer]:
- img [ref=e295]
- text: Complaints (3)
- button "Inquiries (2)" [ref=e298] [cursor=pointer]:
- img [ref=e299]
- text: Inquiries (2)
- button "Observations (1)" [ref=e302] [cursor=pointer]:
- img [ref=e303]
- text: Observations (1)
- button "Suggestions (1)" [ref=e306] [cursor=pointer]:
- img [ref=e307]
- text: Suggestions (1)
- button "Appreciations (2)" [ref=e309] [cursor=pointer]:
- img [ref=e310]
- text: Appreciations (2)
- button "Standards (0)" [ref=e312] [cursor=pointer]:
- img [ref=e313]
- text: Standards (0)
- button "Projects (8)" [ref=e316] [cursor=pointer]:
- img [ref=e317]
- text: Projects (8)
- paragraph [ref=e324]: Loading analytics...
- contentinfo [ref=e325]:
- paragraph [ref=e326]:
- text: Powered by
- link "tenhal.sa" [ref=e327] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,258 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Inquiries Console" [level=1] [ref=e142]
- paragraph [ref=e143]: Manage patient inquiries and requests
- link "New Inquiry" [ref=e144] [cursor=pointer]:
- /url: /inquiries/new/
- img [ref=e145]
- text: New Inquiry
- generic [ref=e146]:
- generic [ref=e147]:
- img [ref=e150]
- generic [ref=e153]:
- heading "Total Inquiries" [level=6] [ref=e154]
- heading "5" [level=2] [ref=e155]
- generic [ref=e156]:
- img [ref=e159]
- generic [ref=e162]:
- heading "Resolved" [level=6] [ref=e163]
- heading "2 (40.0%)" [level=2] [ref=e164]
- generic [ref=e165]:
- img [ref=e168]
- generic [ref=e171]:
- heading "In Progress" [level=6] [ref=e172]
- heading "1" [level=2] [ref=e173]
- generic [ref=e174]:
- img [ref=e177]
- generic [ref=e179]:
- heading "Overdue" [level=6] [ref=e180]
- heading "0" [level=2] [ref=e181]
- generic [ref=e182]:
- generic [ref=e183]:
- img [ref=e185]
- heading "Filters" [level=5] [ref=e186]
- generic [ref=e188]:
- generic [ref=e189]:
- generic [ref=e190]: Search
- textbox "Subject, contact name..." [ref=e191]
- generic [ref=e192]:
- generic [ref=e193]: Status
- combobox [ref=e194]:
- option "All Status" [selected]
- option "Open"
- option "In Progress"
- option "Resolved"
- option "Closed"
- generic [ref=e195]:
- generic [ref=e196]: Category
- combobox [ref=e197]:
- option "All Categories" [selected]
- option "General"
- option "Appointments"
- option "Billing"
- option "Medical Records"
- option "Pharmacy"
- option "Insurance"
- option "Feedback"
- option "Other"
- button "Filter" [ref=e198] [cursor=pointer]:
- img [ref=e199]
- text: Filter
- generic [ref=e202]:
- generic [ref=e203]:
- generic [ref=e204]:
- img [ref=e206]
- heading "All Inquiries" [level=5] [ref=e209]
- generic [ref=e210]: "Total: 5"
- paragraph [ref=e211]: "Showing: 1-5 of 5"
- generic [ref=e212]:
- table [ref=e213]:
- rowgroup [ref=e214]:
- row "Reference Subject Contact Category Status Priority Created Assigned" [ref=e215]:
- columnheader "Reference" [ref=e216]
- columnheader "Subject" [ref=e217]
- columnheader "Contact" [ref=e218]
- columnheader "Category" [ref=e219]
- columnheader "Status" [ref=e220]
- columnheader "Priority" [ref=e221]
- columnheader "Created" [ref=e222]
- columnheader "Assigned" [ref=e223]
- rowgroup [ref=e224]:
- row "INQ-202607-0007 test12121212 ismail mosa 0569874563 General Information Open Medium Jul 12, 2026 Unassigned" [ref=e225] [cursor=pointer]:
- cell "INQ-202607-0007" [ref=e226]:
- code [ref=e227]: INQ-202607-0007
- cell "test12121212" [ref=e228]:
- link "test12121212" [ref=e229]:
- /url: /inquiries/b4a65498-e8d8-403c-82bb-ae09adc7fc1c/
- cell "ismail mosa 0569874563" [ref=e230]:
- generic [ref=e231]: ismail mosa
- generic [ref=e232]: "0569874563"
- cell "General Information" [ref=e233]
- cell "Open" [ref=e234]
- cell "Medium" [ref=e235]
- cell "Jul 12, 2026" [ref=e236]
- cell "Unassigned" [ref=e237]
- row "INQ-202607-0006 test12121212 test 0569874563 General Information Open Medium Jul 12, 2026 Unassigned" [ref=e238] [cursor=pointer]:
- cell "INQ-202607-0006" [ref=e239]:
- code [ref=e240]: INQ-202607-0006
- cell "test12121212" [ref=e241]:
- link "test12121212" [ref=e242]:
- /url: /inquiries/1c3eda08-ff03-4521-bcb8-ec137c79b417/
- cell "test 0569874563" [ref=e243]:
- generic [ref=e244]: test
- generic [ref=e245]: "0569874563"
- cell "General Information" [ref=e246]
- cell "Open" [ref=e247]
- cell "Medium" [ref=e248]
- cell "Jul 12, 2026" [ref=e249]
- cell "Unassigned" [ref=e250]
- row "INQ-202607-0005 test test 0569874589 General Information In Progress Medium Jul 09, 2026 Unassigned" [ref=e251] [cursor=pointer]:
- cell "INQ-202607-0005" [ref=e252]:
- code [ref=e253]: INQ-202607-0005
- cell "test" [ref=e254]:
- link "test" [ref=e255]:
- /url: /inquiries/c05309c2-3518-46ae-9b1c-c144acdb5d58/
- cell "test 0569874589" [ref=e256]:
- generic [ref=e257]: test
- generic [ref=e258]: "0569874589"
- cell "General Information" [ref=e259]
- cell "In Progress" [ref=e260]
- cell "Medium" [ref=e261]
- cell "Jul 09, 2026" [ref=e262]
- cell "Unassigned" [ref=e263]
- row "INQ-202607-0004 hi ismail 0569874589 General Information Resolved Medium Jul 09, 2026 ismail mosa" [ref=e264] [cursor=pointer]:
- cell "INQ-202607-0004" [ref=e265]:
- code [ref=e266]: INQ-202607-0004
- cell "hi" [ref=e267]:
- link "hi" [ref=e268]:
- /url: /inquiries/d1df0b83-5c16-4f51-b735-e89f612deb50/
- cell "ismail 0569874589" [ref=e269]:
- generic [ref=e270]: ismail
- generic [ref=e271]: "0569874589"
- cell "General Information" [ref=e272]
- cell "Resolved" [ref=e273]
- cell "Medium" [ref=e274]
- cell "Jul 09, 2026" [ref=e275]
- cell "ismail mosa" [ref=e276]
- row "INQ-202607-0003 test test 0569874589 General Information Resolved Medium Jul 08, 2026 ismail mosa" [ref=e277] [cursor=pointer]:
- cell "INQ-202607-0003" [ref=e278]:
- code [ref=e279]: INQ-202607-0003
- cell "test" [ref=e280]:
- link "test" [ref=e281]:
- /url: /inquiries/031cbd06-e2dc-4cd9-9b81-b1218e090d6c/
- cell "test 0569874589" [ref=e282]:
- generic [ref=e283]: test
- generic [ref=e284]: "0569874589"
- cell "General Information" [ref=e285]
- cell "Resolved" [ref=e286]
- cell "Medium" [ref=e287]
- cell "Jul 08, 2026" [ref=e288]
- cell "ismail mosa" [ref=e289]
- generic [ref=e290]:
- generic [ref=e291]:
- generic [ref=e292]: Showing 1-5 of 5
- generic [ref=e293]:
- generic [ref=e294]: Show
- combobox [ref=e295]:
- option "10"
- option "25" [selected]
- option "50"
- option "100"
- generic [ref=e296]:
- img [ref=e298]
- generic [ref=e300]: "1"
- img [ref=e302]
- contentinfo [ref=e304]:
- paragraph [ref=e305]:
- text: Powered by
- link "tenhal.sa" [ref=e306] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,172 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Inquiries" [ref=e141] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e142]
- generic [ref=e144]: INQ-202607-0007
- generic [ref=e145]: Open
- generic [ref=e146]: Medium
- generic [ref=e147]:
- img [ref=e148]
- text: Not Sent to Dept
- generic [ref=e150]:
- heading "test12121212" [level=1] [ref=e151]
- generic [ref=e153]:
- img [ref=e154]
- text: 2026-07-12
- generic [ref=e157]:
- generic [ref=e158]:
- img [ref=e160]
- generic [ref=e162]: Created
- generic [ref=e164]:
- img [ref=e166]
- generic [ref=e169]: Assigned
- generic [ref=e171]:
- img [ref=e173]
- generic [ref=e176]: Sent to Dept
- generic [ref=e178]:
- img [ref=e180]
- generic [ref=e182]: Dept Response
- generic [ref=e184]:
- img [ref=e186]
- generic [ref=e189]: Resolved
- generic [ref=e190]:
- generic [ref=e191]:
- img [ref=e193]
- generic [ref=e195]:
- paragraph [ref=e196]: Activate this inquiry
- paragraph [ref=e197]: Activation moves it to In Progress so actions become available.
- generic [ref=e198]:
- button "Cancel" [ref=e200] [cursor=pointer]
- button "Activate" [ref=e202] [cursor=pointer]
- navigation [ref=e203]:
- button "Details" [ref=e204] [cursor=pointer]
- button "Timeline" [ref=e205] [cursor=pointer]
- button "Notes" [ref=e206] [cursor=pointer]
- main [ref=e207]:
- generic [ref=e209]:
- generic [ref=e210]:
- generic [ref=e211]:
- paragraph
- paragraph [ref=e212]: "231313131"
- paragraph
- generic [ref=e213]:
- generic [ref=e215]:
- img [ref=e216]
- generic [ref=e219]: AI Analysis
- generic [ref=e220]:
- paragraph [ref=e221]: "231313131"
- paragraph [ref=e222]: "231313131"
- generic [ref=e223]:
- heading "Patient" [level=3] [ref=e224]:
- img [ref=e225]
- text: Patient
- generic [ref=e228]:
- generic [ref=e229]: ismail mosa
- generic [ref=e230]: "|"
- generic [ref=e231]:
- img [ref=e232]
- text: "0569874563"
- generic [ref=e235]:
- heading "Quick Actions" [level=3] [ref=e236]
- generic [ref=e237]:
- button "Activate" [ref=e239] [cursor=pointer]:
- img [ref=e240]
- generic [ref=e243]: Activate
- button "Cancel Inquiry" [ref=e245] [cursor=pointer]:
- img [ref=e246]
- generic [ref=e250]: Cancel Inquiry
- contentinfo [ref=e251]:
- paragraph [ref=e252]:
- text: Powered by
- link "tenhal.sa" [ref=e253] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,344 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Observations" [level=1] [ref=e142]:
- img [ref=e143]
- text: Observations
- paragraph [ref=e146]: Manage and triage staff-reported observations
- generic [ref=e147]:
- link "New Observation" [ref=e148] [cursor=pointer]:
- /url: /observations/create/
- img [ref=e149]
- text: New Observation
- link "Public Form" [ref=e151] [cursor=pointer]:
- /url: /core/public/submit/
- img [ref=e152]
- text: Public Form
- generic [ref=e153]:
- generic [ref=e155]:
- generic [ref=e156]:
- paragraph [ref=e157]: Total
- paragraph [ref=e158]: "2"
- img [ref=e160]
- generic [ref=e162]:
- generic [ref=e163]:
- paragraph [ref=e164]: New
- paragraph
- img [ref=e166]
- generic [ref=e170]:
- generic [ref=e171]:
- paragraph [ref=e172]: In Progress
- paragraph [ref=e173]: "2"
- img [ref=e175]
- generic [ref=e178]:
- generic [ref=e179]:
- img [ref=e181]
- heading "Filters" [level=5] [ref=e183]
- button [ref=e184] [cursor=pointer]:
- img [ref=e185]
- generic [ref=e188]:
- generic [ref=e189]:
- generic [ref=e190]:
- generic [ref=e191]: Search
- textbox "Tracking code, description..." [ref=e192]
- generic [ref=e193]:
- generic [ref=e194]: Status
- combobox [ref=e195]:
- option "All Statuses" [selected]
- option "Open"
- option "In Progress"
- option "Resolved"
- option "Closed"
- generic [ref=e196]:
- generic [ref=e197]: Severity
- combobox [ref=e198]:
- option "All Severities" [selected]
- option "Low"
- option "Medium"
- option "High"
- option "Critical"
- generic [ref=e199]:
- generic [ref=e200]: Category
- combobox [ref=e201]:
- option "All Categories" [selected]
- option "Patient Safety"
- option "Clinical Quality"
- option "Infection Control"
- option "Medication Safety"
- option "Equipment & Devices"
- option "Facility & Environment"
- option "Staff Behavior"
- option "Communication"
- option "Documentation"
- option "Process & Workflow"
- option "Security"
- option "IT & Systems"
- option "Housekeeping"
- option "Food Services"
- option "Other"
- generic [ref=e202]:
- generic [ref=e203]: Department
- combobox [ref=e204]:
- option "All Departments" [selected]
- option "Contact Center Department"
- option "Emergency Administrative Department"
- option "Executive Administration"
- option "Financial Collection & Claims Department"
- option "Inpatient Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Security Department"
- option "Anesthesia Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "Emergency Medicine Department"
- option "IVF"
- option "Infection Control Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Surgery Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Nursing Department"
- option "Biomedical Department"
- option "Facility Management & Maintenance Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "Information Technology Department"
- option "Laundry Department"
- generic [ref=e206] [cursor=pointer]:
- generic [ref=e207]: All Departments
- combobox "All Departments" [ref=e208]
- generic [ref=e209]:
- generic [ref=e210]: Assigned To
- combobox [ref=e211]:
- option "All Users" [selected]
- option "E2E PX Employee"
- option "test test"
- option "test test"
- generic [ref=e212]:
- generic [ref=e213]: Date From
- textbox [ref=e214]
- generic [ref=e215]:
- generic [ref=e216]: Date To
- textbox [ref=e217]
- generic [ref=e218]:
- button "Apply Filters" [ref=e219] [cursor=pointer]:
- img [ref=e220]
- text: Apply Filters
- link "Clear" [ref=e223] [cursor=pointer]:
- /url: /observations/
- img [ref=e224]
- text: Clear
- generic [ref=e229]:
- img [ref=e231]
- heading "Observations List" [level=5] [ref=e233]
- generic [ref=e234]: Showing 1 to 4 of 4 observations
- table [ref=e237]:
- rowgroup [ref=e238]:
- row "Reference Date Category Description Severity Status Reporter Department Assigned Actions" [ref=e239]:
- columnheader "Reference" [ref=e240]
- columnheader "Date" [ref=e241]
- columnheader "Category" [ref=e242]
- columnheader "Description" [ref=e243]
- columnheader "Severity" [ref=e244]
- columnheader "Status" [ref=e245]
- columnheader "Reporter" [ref=e246]
- columnheader "Department" [ref=e247]
- columnheader "Assigned" [ref=e248]
- columnheader "Actions" [ref=e249]
- rowgroup [ref=e250]:
- row "OBS-202607-0001 Jul 08, 2026 16:33 - test observation Medium In Progress ismail mosa Contact Center Department ismail mosa" [ref=e251]:
- cell "OBS-202607-0001" [ref=e252]
- cell "Jul 08, 2026 16:33" [ref=e253]:
- text: Jul 08, 2026
- text: 16:33
- cell "-" [ref=e254]
- cell "test observation" [ref=e255]:
- generic [ref=e256]: test observation
- cell "Medium" [ref=e257]
- cell "In Progress" [ref=e258]
- cell "ismail mosa" [ref=e259]
- cell "Contact Center Department" [ref=e260]
- cell "ismail mosa" [ref=e261]
- cell [ref=e262]:
- link [ref=e263] [cursor=pointer]:
- /url: /observations/1d925706-110e-4bad-808f-26aae2029144/
- img [ref=e264]
- row "OBS-202606-E2EHOSP-0056 Jun 22, 2026 10:16 Other Observation i notice some trash in the hall Medium In Progress E2E PX Admin Contact Center Department E2E PX Admin" [ref=e267]:
- cell "OBS-202606-E2EHOSP-0056" [ref=e268]
- cell "Jun 22, 2026 10:16" [ref=e269]:
- text: Jun 22, 2026
- text: 10:16
- cell "Other" [ref=e270]:
- generic [ref=e271]: Other
- cell "Observation i notice some trash in the hall" [ref=e272]:
- generic [ref=e273]:
- strong [ref=e274]: Observation
- text: i notice some trash in the hall
- cell "Medium" [ref=e275]
- cell "In Progress" [ref=e276]
- cell "E2E PX Admin" [ref=e277]
- cell "Contact Center Department" [ref=e278]
- cell "E2E PX Admin" [ref=e279]
- cell [ref=e280]:
- link [ref=e281] [cursor=pointer]:
- /url: /observations/b9a80c99-31dc-4cda-a1e3-c20d94a28729/
- img [ref=e282]
- row "OBS-O8TCIC Jun 09, 2026 13:36 - Wet floor near the main entrance, no warning sign posted. Medium In Progress Anonymous Reporter - ismail mosa" [ref=e285]:
- cell "OBS-O8TCIC" [ref=e286]
- cell "Jun 09, 2026 13:36" [ref=e287]:
- text: Jun 09, 2026
- text: 13:36
- cell "-" [ref=e288]
- cell "Wet floor near the main entrance, no warning sign posted." [ref=e289]:
- generic [ref=e290]: Wet floor near the main entrance, no warning sign posted.
- cell "Medium" [ref=e291]
- cell "In Progress" [ref=e292]
- cell "Anonymous Reporter" [ref=e293]
- cell "-" [ref=e294]
- cell "ismail mosa" [ref=e295]
- cell [ref=e296]:
- link [ref=e297] [cursor=pointer]:
- /url: /observations/5f8fc6c6-3b38-4b88-8144-3f926c24e290/
- img [ref=e298]
- row "OBS-QPE02F Jun 07, 2026 21:08 Other Observation safsafsad Medium Open ismail Daif - Unassigned" [ref=e301]:
- cell "OBS-QPE02F" [ref=e302]
- cell "Jun 07, 2026 21:08" [ref=e303]:
- text: Jun 07, 2026
- text: 21:08
- cell "Other" [ref=e304]:
- generic [ref=e305]: Other
- cell "Observation safsafsad" [ref=e306]:
- generic [ref=e307]:
- strong [ref=e308]: Observation
- text: safsafsad
- cell "Medium" [ref=e309]
- cell "Open" [ref=e310]
- cell "ismail Daif" [ref=e311]
- cell "-" [ref=e312]
- cell "Unassigned" [ref=e313]
- cell [ref=e314]:
- link [ref=e315] [cursor=pointer]:
- /url: /observations/327d9b79-2126-4800-8cd7-647f006271ce/
- img [ref=e316]
- generic [ref=e319]:
- generic [ref=e320]:
- generic [ref=e321]: Showing 1-4 of 4
- generic [ref=e322]:
- generic [ref=e323]: Show
- combobox [ref=e324]:
- option "10"
- option "25" [selected]
- option "50"
- option "100"
- generic [ref=e325]:
- img [ref=e327]
- generic [ref=e329]: "1"
- img [ref=e331]
- contentinfo [ref=e333]:
- paragraph [ref=e334]:
- text: Powered by
- link "tenhal.sa" [ref=e335] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,259 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Observations" [ref=e141] [cursor=pointer]:
- /url: /observations/
- img [ref=e142]
- generic [ref=e144]: OBS-202607-0001
- generic [ref=e145]: In Progress
- generic [ref=e146]: Medium
- generic [ref=e147]:
- img [ref=e148]
- text: Not Sent to Dept
- heading "test observation" [level=1] [ref=e151]
- generic [ref=e153]:
- generic [ref=e154]:
- img [ref=e156]
- generic [ref=e158]: Created
- generic [ref=e160]:
- img [ref=e162]
- generic [ref=e164]: Assigned
- generic [ref=e166]:
- img [ref=e168]
- generic [ref=e171]: Sent to Dept
- generic [ref=e173]:
- img [ref=e175]
- generic [ref=e177]: Dept Response
- generic [ref=e179]:
- img [ref=e181]
- generic [ref=e184]: Resolved
- navigation [ref=e185]:
- button "Details" [ref=e186] [cursor=pointer]
- button "Timeline" [ref=e187] [cursor=pointer]
- button "RCA" [ref=e188] [cursor=pointer]
- button "Notes" [ref=e189] [cursor=pointer]
- main [ref=e190]:
- generic [ref=e192]:
- generic [ref=e193]:
- paragraph [ref=e195]: test observation
- generic [ref=e196]:
- generic [ref=e197]:
- paragraph [ref=e198]: Category
- paragraph [ref=e199]: "-"
- generic [ref=e200]:
- paragraph [ref=e201]: Location
- paragraph [ref=e202]: "-"
- generic [ref=e203]:
- paragraph [ref=e204]: Incident Date
- paragraph [ref=e205]: 08 Jul 2026
- generic [ref=e209]:
- img [ref=e210]
- paragraph [ref=e214]: No department assigned to this observation
- generic [ref=e215]:
- generic [ref=e216]:
- heading "Quick Actions" [level=3] [ref=e217]
- generic [ref=e218]:
- button "Respond to Patient" [ref=e219] [cursor=pointer]:
- img [ref=e220]
- generic [ref=e222]: Respond to Patient
- button "Reassign" [ref=e223] [cursor=pointer]:
- img [ref=e224]
- generic [ref=e227]: Reassign
- button "Send to Dept" [ref=e228] [cursor=pointer]:
- img [ref=e229]
- generic [ref=e232]: Send to Dept
- button "Resolve" [ref=e234] [cursor=pointer]:
- img [ref=e235]
- generic [ref=e238]: Resolve
- link "RCA" [ref=e239] [cursor=pointer]:
- /url: /rca/create/?related_model=observation&related_id=1d925706-110e-4bad-808f-26aae2029144
- img [ref=e240]
- generic [ref=e243]: RCA
- link "QI Project" [ref=e244] [cursor=pointer]:
- /url: /projects/create/?related_model=observation&related_id=1d925706-110e-4bad-808f-26aae2029144
- img [ref=e245]
- generic [ref=e247]: QI Project
- button "Delete" [ref=e249] [cursor=pointer]:
- img [ref=e250]
- generic [ref=e253]: Delete
- generic [ref=e254]:
- heading "Assignment" [level=3] [ref=e255]:
- img [ref=e256]
- text: Assignment
- list [ref=e261]:
- listitem [ref=e262]:
- generic [ref=e263]: Department
- generic [ref=e264]: Contact Center Department
- listitem [ref=e265]:
- generic [ref=e266]: Assigned To
- generic [ref=e267]: ismail mosa
- generic [ref=e268]:
- heading "Linked PX Action" [level=3] [ref=e269]:
- img [ref=e270]
- text: Linked PX Action
- paragraph [ref=e273]: Observation OBS-202607-0001
- link "View Action" [ref=e274] [cursor=pointer]:
- /url: /actions/9b185c2a-e3fe-4e13-a727-7aab21df8754/
- img [ref=e275]
- text: View Action
- generic [ref=e277]:
- heading "Triage" [level=3] [ref=e278]:
- img [ref=e279]
- text: Triage
- generic [ref=e280]:
- generic [ref=e281]:
- generic [ref=e282]: Department
- combobox [ref=e283]:
- option "Select department"
- option "Contact Center Department" [selected]
- option "Emergency Administrative Department"
- option "Executive Administration"
- option "Financial Collection & Claims Department"
- option "Inpatient Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Security Department"
- option "Anesthesia Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "Emergency Medicine Department"
- option "IVF"
- option "Infection Control Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Surgery Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Nursing Department"
- option "Biomedical Department"
- option "Facility Management & Maintenance Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "Information Technology Department"
- option "Laundry Department"
- generic [ref=e284]:
- generic [ref=e285]: Assign To
- combobox [ref=e286]:
- option "Select assignee" [selected]
- option "E2E PX Employee (e2e-px-employee@px360.test)"
- option "test test (9m5s6gzmft@ruutukf.com)"
- option "test test (esma3el555@gmail.com)"
- generic [ref=e287]:
- generic [ref=e288]: Status
- combobox [ref=e289]:
- option "Open"
- option "In Progress" [selected]
- option "Resolved"
- option "Closed"
- generic [ref=e290]:
- generic [ref=e291]: Note
- textbox "Add a note about this triage action (optional)..." [ref=e292]
- button "Update" [ref=e293] [cursor=pointer]:
- img [ref=e294]
- text: Update
- contentinfo [ref=e297]:
- paragraph [ref=e298]:
- text: Powered by
- link "tenhal.sa" [ref=e299] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,201 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Suggestions" [level=1] [ref=e142]
- paragraph [ref=e143]: Manage and review submitted suggestions
- link "New Suggestion" [ref=e145] [cursor=pointer]:
- /url: /suggestions/create/
- img [ref=e146]
- text: New Suggestion
- generic [ref=e147]:
- generic [ref=e148]:
- img [ref=e151]
- generic [ref=e153]:
- heading "Total Suggestions" [level=6] [ref=e154]
- heading "3" [level=2] [ref=e155]
- generic [ref=e156]:
- img [ref=e159]
- generic [ref=e161]:
- heading "Compliments" [level=6] [ref=e162]
- heading "0" [level=2] [ref=e163]
- generic [ref=e164]:
- img [ref=e167]
- generic [ref=e169]:
- heading "Avg Rating" [level=6] [ref=e170]
- heading "0.0 /5" [level=2] [ref=e171]
- generic [ref=e172]:
- img [ref=e175]
- generic [ref=e178]:
- heading "Pending Review" [level=6] [ref=e179]
- heading "2" [level=2] [ref=e180]
- generic [ref=e181]:
- generic [ref=e182]:
- generic [ref=e183]:
- img [ref=e185]
- heading "Suggestions" [level=5] [ref=e187]
- generic [ref=e188]:
- link "All" [ref=e189] [cursor=pointer]:
- /url: "?"
- link "Suggestions" [ref=e190] [cursor=pointer]:
- /url: "?feedback_type=suggestion"
- link "Compliments" [ref=e191] [cursor=pointer]:
- /url: "?feedback_type=compliment"
- button "Advanced" [ref=e193] [cursor=pointer]:
- img [ref=e194]
- text: Advanced
- paragraph [ref=e195]: "Showing: 1-3 of 3"
- table [ref=e197]:
- rowgroup [ref=e198]:
- row "ID Type Contact Title Category Rating Sentiment Status Created Actions" [ref=e199]:
- columnheader "ID" [ref=e200]
- columnheader "Type" [ref=e201]
- columnheader "Contact" [ref=e202]
- columnheader "Title" [ref=e203]
- columnheader "Category" [ref=e204]
- columnheader "Rating" [ref=e205]
- columnheader "Sentiment" [ref=e206]
- columnheader "Status" [ref=e207]
- columnheader "Created" [ref=e208]
- columnheader "Actions" [ref=e209]
- rowgroup [ref=e210]:
- row "SGT-202607-0003 Suggestion ismail Daif t Other - Neutral Submitted Jul 09, 2026" [ref=e211] [cursor=pointer]:
- cell "SGT-202607-0003" [ref=e212]
- cell "Suggestion" [ref=e213]
- cell "ismail Daif" [ref=e214]:
- generic [ref=e215]: ismail Daif
- cell "t" [ref=e216]:
- generic [ref=e217]: t
- cell "Other" [ref=e218]
- cell "-" [ref=e219]
- cell "Neutral" [ref=e220]
- cell "Submitted" [ref=e221]
- cell "Jul 09, 2026" [ref=e222]
- cell [ref=e223]:
- link "View" [ref=e225]:
- /url: /suggestions/78f015c5-fbed-4e10-987f-aa8ced75545a/
- img [ref=e226]
- row "SGT-202607-0002 Suggestion ismail test Other - Neutral Submitted Jul 09, 2026" [ref=e229] [cursor=pointer]:
- cell "SGT-202607-0002" [ref=e230]
- cell "Suggestion" [ref=e231]
- cell "ismail" [ref=e232]:
- generic [ref=e233]: ismail
- cell "test" [ref=e234]:
- generic [ref=e235]: test
- cell "Other" [ref=e236]
- cell "-" [ref=e237]
- cell "Neutral" [ref=e238]
- cell "Submitted" [ref=e239]
- cell "Jul 09, 2026" [ref=e240]
- cell [ref=e241]:
- link "View" [ref=e243]:
- /url: /suggestions/04b009f3-53aa-4eac-9baf-39b3633e56b0/
- img [ref=e244]
- row "SGT-202607-0001 Suggestion ismail The suggestion titled 'test' with content 'test' appears … Other - Neutral Closed Jul 09, 2026" [ref=e247] [cursor=pointer]:
- cell "SGT-202607-0001" [ref=e248]
- cell "Suggestion" [ref=e249]
- cell "ismail" [ref=e250]:
- generic [ref=e251]: ismail
- cell "The suggestion titled 'test' with content 'test' appears …" [ref=e252]:
- generic [ref=e253]: The suggestion titled 'test' with content 'test' appears …
- cell "Other" [ref=e254]
- cell "-" [ref=e255]
- cell "Neutral" [ref=e256]
- cell "Closed" [ref=e257]
- cell "Jul 09, 2026" [ref=e258]
- cell [ref=e259]:
- link "View" [ref=e261]:
- /url: /suggestions/ef1e633b-7fae-46e2-baab-0d5bdd71e7b7/
- img [ref=e262]
- contentinfo [ref=e265]:
- paragraph [ref=e266]:
- text: Powered by
- link "tenhal.sa" [ref=e267] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,188 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Suggestions" [ref=e141] [cursor=pointer]:
- /url: /suggestions/
- img [ref=e142]
- generic [ref=e144]: test
- generic [ref=e145]: Submitted
- generic [ref=e146]: Medium
- generic [ref=e147]:
- heading "Suggestion Detail" [level=1] [ref=e148]
- link "Edit" [ref=e150] [cursor=pointer]:
- /url: /suggestions/04b009f3-53aa-4eac-9baf-39b3633e56b0/update/
- img [ref=e151]
- text: Edit
- generic [ref=e155]:
- generic [ref=e156]:
- img [ref=e158]
- generic [ref=e160]: Submitted
- generic [ref=e162]:
- img [ref=e164]
- generic [ref=e167]: Assigned
- generic [ref=e169]:
- img [ref=e171]
- generic [ref=e174]: Reviewed
- generic [ref=e176]:
- img [ref=e178]
- generic [ref=e180]: Acknowledged
- generic [ref=e182]:
- img [ref=e184]
- generic [ref=e187]: Closed
- navigation [ref=e188]:
- button "Details" [ref=e189] [cursor=pointer]
- button "AI Analysis" [ref=e190] [cursor=pointer]
- button "Notes" [ref=e191] [cursor=pointer]
- main [ref=e192]:
- generic [ref=e195]:
- heading "Suggestion Details" [level=3] [ref=e196]:
- img [ref=e197]
- text: Suggestion Details
- paragraph [ref=e200]: test
- generic [ref=e201]:
- generic [ref=e202]:
- generic [ref=e203]: Hospital
- generic [ref=e204]: E2E Test Hospital
- generic [ref=e205]:
- generic [ref=e206]: Category
- generic [ref=e207]: Other
- generic [ref=e208]:
- generic [ref=e209]: Location
- generic [ref=e210]: Not specified
- generic [ref=e211]:
- generic [ref=e212]: Sentiment
- generic [ref=e214]: Neutral
- generic [ref=e215]:
- heading "Contact" [level=6] [ref=e216]
- generic [ref=e217]:
- generic [ref=e218]: ismail
- generic [ref=e219]: "0569874589"
- generic [ref=e221]:
- img [ref=e222]
- text: July 09, 2026 13:46
- generic [ref=e225]:
- heading "Quick Actions" [level=3] [ref=e226]
- generic [ref=e227]:
- generic [ref=e228]:
- text: Status
- combobox [ref=e229]:
- option "Submitted" [selected]
- option "Reviewed"
- option "Acknowledged"
- option "Closed"
- option "Reopened"
- textbox "Add a note..." [ref=e230]
- button "Update" [ref=e231] [cursor=pointer]:
- img [ref=e232]
- text: Update
- separator [ref=e237]
- generic [ref=e238]:
- text: Assign To
- combobox [ref=e239]:
- option "Select user..." [selected]
- option "E2E PX Employee"
- option "test test"
- option "test test"
- button "Assign" [ref=e240] [cursor=pointer]:
- img [ref=e241]
- text: Assign
- separator [ref=e245]
- generic [ref=e246]:
- text: Department
- button "Send to Department" [ref=e247] [cursor=pointer]:
- img [ref=e248]
- text: Send to Department
- separator [ref=e251]
- contentinfo [ref=e252]:
- paragraph [ref=e253]:
- text: Powered by
- link "tenhal.sa" [ref=e254] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,221 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Appreciations" [level=1] [ref=e142]:
- img [ref=e143]
- text: Appreciations
- paragraph [ref=e145]: Manage and review patient appreciation submissions
- generic [ref=e146]:
- link "Create Internal" [ref=e147] [cursor=pointer]:
- /url: /appreciation/new/
- img [ref=e148]
- text: Create Internal
- link "Public Form" [ref=e149] [cursor=pointer]:
- /url: /core/public/submit/
- img [ref=e150]
- text: Public Form
- generic [ref=e154]:
- paragraph [ref=e155]: "0"
- paragraph [ref=e156]: Pending
- generic [ref=e157]:
- generic [ref=e159]:
- img [ref=e161]
- generic [ref=e164]:
- paragraph [ref=e165]: Total
- paragraph [ref=e166]: "3"
- generic [ref=e168]:
- img [ref=e170]
- generic [ref=e173]:
- paragraph [ref=e174]: Draft
- paragraph [ref=e175]: "0"
- generic [ref=e177]:
- img [ref=e179]
- generic [ref=e181]:
- paragraph [ref=e182]: Activated
- paragraph [ref=e183]: "0"
- generic [ref=e185]:
- img [ref=e187]
- generic [ref=e190]:
- paragraph [ref=e191]: Sent
- paragraph [ref=e192]: "3"
- generic [ref=e193]:
- link "Leaderboard See top performers" [ref=e194] [cursor=pointer]:
- /url: /appreciation/leaderboard/
- generic [ref=e196]:
- img [ref=e198]
- generic [ref=e204]:
- heading "Leaderboard" [level=3] [ref=e205]
- paragraph [ref=e206]: See top performers
- link "My Badges View earned badges" [ref=e207] [cursor=pointer]:
- /url: /appreciation/badges/
- generic [ref=e209]:
- img [ref=e211]
- generic [ref=e214]:
- heading "My Badges" [level=3] [ref=e215]
- paragraph [ref=e216]: View earned badges
- generic [ref=e217]:
- generic [ref=e218]:
- img [ref=e220]
- heading "Filters" [level=3] [ref=e222]
- generic [ref=e224]:
- generic [ref=e225]:
- generic [ref=e226]: Search
- textbox "Search messages..." [ref=e227]
- generic [ref=e228]:
- generic [ref=e229]: Status
- combobox [ref=e230]:
- option "All Status" [selected]
- option "Draft"
- option "Activated"
- option "AI Analyzed"
- option "Sent"
- option "Acknowledged"
- button "Search" [ref=e231] [cursor=pointer]:
- img [ref=e232]
- text: Search
- link "Clear" [ref=e235] [cursor=pointer]:
- /url: /appreciation/
- table [ref=e237]:
- rowgroup [ref=e238]:
- row "From Message Staff Department Category Status Date Actions" [ref=e239]:
- columnheader "From" [ref=e240]
- columnheader "Message" [ref=e241]
- columnheader "Staff" [ref=e242]
- columnheader "Department" [ref=e243]
- columnheader "Category" [ref=e244]
- columnheader "Status" [ref=e245]
- columnheader "Date" [ref=e246]
- columnheader "Actions" [ref=e247]
- rowgroup [ref=e248]:
- row "ismail thank - Contact Center Department - Sent Jul 09, 2026" [ref=e249] [cursor=pointer]:
- cell "ismail" [ref=e250]
- cell "thank" [ref=e251]:
- generic [ref=e252]: thank
- cell "-" [ref=e253]
- cell "Contact Center Department" [ref=e254]
- cell "-" [ref=e255]
- cell "Sent" [ref=e256]
- cell "Jul 09, 2026" [ref=e257]
- cell [ref=e258]:
- link "View" [ref=e260]:
- /url: /appreciation/detail/5306821e-d596-498f-88c5-fa3addcdd491/
- img [ref=e261]
- row "ismail mosa test - Contact Center Department - Sent Jul 09, 2026" [ref=e264] [cursor=pointer]:
- cell "ismail mosa" [ref=e265]
- cell "test" [ref=e266]:
- generic [ref=e267]: test
- cell "-" [ref=e268]
- cell "Contact Center Department" [ref=e269]
- cell "-" [ref=e270]
- cell "Sent" [ref=e271]
- cell "Jul 09, 2026" [ref=e272]
- cell [ref=e273]:
- link "View" [ref=e275]:
- /url: /appreciation/detail/3697b78b-5ff3-4392-8982-af20f58daebf/
- img [ref=e276]
- row "ismail mosa thank you - Contact Center Department - Sent Jul 09, 2026" [ref=e279] [cursor=pointer]:
- cell "ismail mosa" [ref=e280]
- cell "thank you" [ref=e281]:
- generic [ref=e282]: thank you
- cell "-" [ref=e283]
- cell "Contact Center Department" [ref=e284]
- cell "-" [ref=e285]
- cell "Sent" [ref=e286]
- cell "Jul 09, 2026" [ref=e287]
- cell [ref=e288]:
- link "View" [ref=e290]:
- /url: /appreciation/detail/240ea5bd-a12d-4f76-8a79-a8dd9de5714a/
- img [ref=e291]
- contentinfo [ref=e294]:
- paragraph [ref=e295]:
- text: Powered by
- link "tenhal.sa" [ref=e296] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,137 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Appreciations" [ref=e141] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e142]
- generic [ref=e144]: Detail
- generic [ref=e145]: Sent
- generic [ref=e146]:
- heading "Appreciation Detail" [level=1] [ref=e147]:
- img [ref=e148]
- text: Appreciation Detail
- generic [ref=e151]: 2026-07-09 13:55 — E2E Test Hospital
- navigation [ref=e152]:
- button "Details" [ref=e153] [cursor=pointer]
- button "AI Analysis" [ref=e154] [cursor=pointer]
- button "Notes" [ref=e155] [cursor=pointer]
- main [ref=e156]:
- generic [ref=e158]:
- generic [ref=e159]:
- heading "Appreciation Message" [level=3] [ref=e160]:
- img [ref=e161]
- text: Appreciation Message
- paragraph
- paragraph [ref=e163]: thank
- paragraph
- generic [ref=e164]:
- heading "Submitted By" [level=3] [ref=e165]:
- img [ref=e166]
- text: Submitted By
- generic [ref=e169]:
- generic [ref=e170]:
- paragraph [ref=e171]: Name
- paragraph [ref=e172]: ismail
- generic [ref=e173]:
- paragraph [ref=e174]: Phone
- paragraph [ref=e175]: "0569874589"
- generic [ref=e176]:
- heading "Sent" [level=3] [ref=e177]:
- img [ref=e178]
- text: Sent
- generic [ref=e182]:
- paragraph [ref=e183]: Sent At
- paragraph [ref=e184]: 2026-07-09 13:57
- contentinfo [ref=e186]:
- paragraph [ref=e187]:
- text: Powered by
- link "tenhal.sa" [ref=e188] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,256 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e141]:
- generic [ref=e142]:
- link "Inquiries" [ref=e143] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e144]
- generic [ref=e146]: New Inquiry
- heading "Create New Inquiry" [level=1] [ref=e147]
- generic [ref=e149]:
- generic [ref=e150]:
- img [ref=e152]
- heading "Inquiry Information" [level=5] [ref=e155]
- generic [ref=e156]:
- generic [ref=e157]:
- heading "Organization" [level=6] [ref=e158]:
- img [ref=e159]
- text: Organization
- generic [ref=e162]:
- generic [ref=e163]:
- generic [ref=e164]: Location Type *
- combobox "Location Type *" [ref=e165]:
- option "Select Location Type" [selected]
- option "Outpatient"
- option "Inpatient"
- option "Emergency"
- option "General"
- generic [ref=e166]:
- generic [ref=e167]: Area
- combobox "Area" [ref=e168]:
- option "Select Area (optional)" [selected]
- option "Anesthesia (E2E-HOSP)"
- option "Dialysis (E2E-HOSP)"
- option "Emergency (E2E-HOSP)"
- option "Endoscopy (E2E-HOSP)"
- option "ICCU (E2E-HOSP)"
- option "ICU (E2E-HOSP)"
- option "ICU Stepdown (E2E-HOSP)"
- option "LTACU (E2E-HOSP)"
- option "Labor & Delivery (E2E-HOSP)"
- option "Main OR (E2E-HOSP)"
- option "Medical Ward (E2E-HOSP)"
- option "NICU - Neonatal Intensive Care Unit (E2E-HOSP)"
- option "Nursery (E2E-HOSP)"
- option "OB Wards (E2E-HOSP)"
- option "OPD1 (E2E-HOSP)"
- option "OPD2 (E2E-HOSP)"
- option "OPD3 (E2E-HOSP)"
- option "OPD4 (E2E-HOSP)"
- option "OPD5 (E2E-HOSP)"
- option "OPD6 (E2E-HOSP)"
- option "OPD7 (E2E-HOSP)"
- option "PICU (E2E-HOSP)"
- option "PICU Stepdown (E2E-HOSP)"
- option "Pediatric Ward (E2E-HOSP)"
- option "Recovery (E2E-HOSP)"
- option "Stepdown 3-4 (E2E-HOSP)"
- option "Surgical Wards (E2E-HOSP)"
- generic [ref=e169]:
- generic [ref=e170]: Department Category *
- combobox [ref=e171]:
- option "Select Category" [selected]
- option "Medical"
- option "Nursing"
- option "Administrative"
- option "Support Services"
- generic [ref=e172]:
- generic [ref=e173]: Department (Optional)
- combobox "Department (Optional)" [ref=e174]:
- option "Select Department" [selected]
- option "Anesthesia Department"
- option "Biomedical Department"
- option "Contact Center Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Emergency Administrative Department"
- option "Emergency Medicine Department"
- option "Executive Administration"
- option "Facility Management & Maintenance Department"
- option "Financial Collection & Claims Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "IVF"
- option "Infection Control Department"
- option "Information Technology Department"
- option "Inpatient Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Laundry Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Nursing Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Security Department"
- option "Surgery Department"
- generic [ref=e175]:
- generic [ref=e176]: Section
- combobox "Section" [ref=e177]:
- option "Select Section" [selected]
- generic [ref=e178]:
- heading "Contact Information" [level=6] [ref=e179]:
- img [ref=e180]
- text: Contact Information
- generic [ref=e183]:
- generic [ref=e184]:
- generic [ref=e185]: Contact Name *
- textbox "Contact Name *" [ref=e186]
- generic [ref=e187]:
- generic [ref=e188]: Contact Phone *
- textbox "Contact Phone *" [ref=e189]
- generic [ref=e190]:
- generic [ref=e191]: Contact Email
- textbox "Contact Email" [ref=e192]
- generic [ref=e193]:
- heading "Inquiry Details" [level=6] [ref=e194]:
- img [ref=e195]
- text: Inquiry Details
- generic [ref=e198]:
- generic [ref=e199]:
- generic [ref=e200]: Inquiry Type *
- combobox "Inquiry Type *" [ref=e201]:
- option "General Inquiry" [selected]
- option "Appointment Related"
- option "Billing & Insurance"
- option "Medical Records"
- option "Pharmacy"
- option "Insurance"
- option "Feedback"
- option "Other"
- generic [ref=e202]:
- generic [ref=e203]: Priority
- combobox "Priority" [ref=e204]:
- option "Low"
- option "Medium" [selected]
- option "High"
- option "Critical"
- generic [ref=e205]:
- generic [ref=e206]: Subject *
- textbox "Subject *" [ref=e207]:
- /placeholder: Brief subject
- generic [ref=e208]:
- generic [ref=e209]: Message *
- textbox "Message *" [ref=e210]:
- /placeholder: Describe your inquiry
- generic [ref=e211]:
- button "Create Inquiry" [ref=e212] [cursor=pointer]:
- img [ref=e213]
- text: Create Inquiry
- link "Cancel" [ref=e216] [cursor=pointer]:
- /url: /inquiries/
- contentinfo [ref=e217]:
- paragraph [ref=e218]:
- text: Powered by
- link "tenhal.sa" [ref=e219] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,258 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e141]:
- generic [ref=e142]:
- link "Inquiries" [ref=e143] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e144]
- generic [ref=e146]: New Inquiry
- heading "Create New Inquiry" [level=1] [ref=e147]
- generic [ref=e149]:
- generic [ref=e150]:
- img [ref=e152]
- heading "Inquiry Information" [level=5] [ref=e155]
- generic [ref=e156]:
- generic [ref=e157]:
- heading "Organization" [level=6] [ref=e158]:
- img [ref=e159]
- text: Organization
- generic [ref=e162]:
- generic [ref=e163]:
- generic [ref=e164]: Location Type *
- combobox "Location Type *" [active] [ref=e165]:
- option "Select Location Type" [selected]
- option "Outpatient"
- option "Inpatient"
- option "Emergency"
- option "General"
- generic [ref=e166]:
- generic [ref=e167]: Area
- combobox "Area" [ref=e168]:
- option "Select Area (optional)" [selected]
- option "Anesthesia (E2E-HOSP)"
- option "Dialysis (E2E-HOSP)"
- option "Emergency (E2E-HOSP)"
- option "Endoscopy (E2E-HOSP)"
- option "ICCU (E2E-HOSP)"
- option "ICU (E2E-HOSP)"
- option "ICU Stepdown (E2E-HOSP)"
- option "LTACU (E2E-HOSP)"
- option "Labor & Delivery (E2E-HOSP)"
- option "Main OR (E2E-HOSP)"
- option "Medical Ward (E2E-HOSP)"
- option "NICU - Neonatal Intensive Care Unit (E2E-HOSP)"
- option "Nursery (E2E-HOSP)"
- option "OB Wards (E2E-HOSP)"
- option "OPD1 (E2E-HOSP)"
- option "OPD2 (E2E-HOSP)"
- option "OPD3 (E2E-HOSP)"
- option "OPD4 (E2E-HOSP)"
- option "OPD5 (E2E-HOSP)"
- option "OPD6 (E2E-HOSP)"
- option "OPD7 (E2E-HOSP)"
- option "PICU (E2E-HOSP)"
- option "PICU Stepdown (E2E-HOSP)"
- option "Pediatric Ward (E2E-HOSP)"
- option "Recovery (E2E-HOSP)"
- option "Stepdown 3-4 (E2E-HOSP)"
- option "Surgical Wards (E2E-HOSP)"
- generic [ref=e169]:
- generic [ref=e170]: Department Category *
- combobox [ref=e171]:
- option "Select Category" [selected]
- option "Medical"
- option "Nursing"
- option "Administrative"
- option "Support Services"
- generic [ref=e172]:
- generic [ref=e173]: Department (Optional)
- combobox "Department (Optional)" [ref=e174]:
- option "Select Department" [selected]
- option "Anesthesia Department"
- option "Biomedical Department"
- option "Contact Center Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Emergency Administrative Department"
- option "Emergency Medicine Department"
- option "Executive Administration"
- option "Facility Management & Maintenance Department"
- option "Financial Collection & Claims Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "IVF"
- option "Infection Control Department"
- option "Information Technology Department"
- option "Inpatient Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Laundry Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Nursing Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Security Department"
- option "Surgery Department"
- generic [ref=e175]:
- generic [ref=e176]: Section
- combobox "Section" [ref=e177]:
- option "Select Section" [selected]
- generic [ref=e178]:
- heading "Contact Information" [level=6] [ref=e179]:
- img [ref=e180]
- text: Contact Information
- generic [ref=e183]:
- generic [ref=e184]:
- generic [ref=e185]: Contact Name *
- textbox "Contact Name *" [ref=e186]: Test User
- generic [ref=e187]:
- generic [ref=e188]: Contact Phone *
- textbox "Contact Phone *" [ref=e189]: "+966501234567"
- generic [ref=e190]:
- generic [ref=e191]: Contact Email
- textbox "Contact Email" [ref=e192]
- generic [ref=e193]:
- heading "Inquiry Details" [level=6] [ref=e194]:
- img [ref=e195]
- text: Inquiry Details
- generic [ref=e198]:
- generic [ref=e199]:
- generic [ref=e200]: Inquiry Type *
- combobox "Inquiry Type *" [ref=e201]:
- option "General Inquiry" [selected]
- option "Appointment Related"
- option "Billing & Insurance"
- option "Medical Records"
- option "Pharmacy"
- option "Insurance"
- option "Feedback"
- option "Other"
- generic [ref=e202]:
- generic [ref=e203]: Priority
- combobox "Priority" [ref=e204]:
- option "Low"
- option "Medium" [selected]
- option "High"
- option "Critical"
- generic [ref=e205]:
- generic [ref=e206]: Subject *
- textbox "Subject *" [ref=e207]:
- /placeholder: Brief subject
- text: Playwright Test Inquiry
- generic [ref=e208]:
- generic [ref=e209]: Message *
- textbox "Message *" [ref=e210]:
- /placeholder: Describe your inquiry
- text: This is a test inquiry created by Playwright to verify px-employee access.
- generic [ref=e211]:
- button "Create Inquiry" [ref=e212] [cursor=pointer]:
- img [ref=e213]
- text: Create Inquiry
- link "Cancel" [ref=e216] [cursor=pointer]:
- /url: /inquiries/
- contentinfo [ref=e217]:
- paragraph [ref=e218]:
- text: Powered by
- link "tenhal.sa" [ref=e219] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,258 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e141]:
- generic [ref=e142]:
- link "Inquiries" [ref=e143] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e144]
- generic [ref=e146]: New Inquiry
- heading "Create New Inquiry" [level=1] [ref=e147]
- generic [ref=e149]:
- generic [ref=e150]:
- img [ref=e152]
- heading "Inquiry Information" [level=5] [ref=e155]
- generic [ref=e156]:
- generic [ref=e157]:
- heading "Organization" [level=6] [ref=e158]:
- img [ref=e159]
- text: Organization
- generic [ref=e162]:
- generic [ref=e163]:
- generic [ref=e164]: Location Type *
- combobox "Location Type *" [active] [ref=e165]:
- option "Select Location Type"
- option "Outpatient"
- option "Inpatient"
- option "Emergency"
- option "General"
- generic [ref=e166]:
- generic [ref=e167]: Area
- combobox "Area" [ref=e168]:
- option "Select Area (optional)" [selected]
- option "Anesthesia"
- option "Dialysis"
- option "Emergency"
- option "Endoscopy"
- option "ICCU"
- option "ICU"
- option "ICU Stepdown"
- option "LTACU"
- option "Labor & Delivery"
- option "Main OR"
- option "Medical Ward"
- option "NICU - Neonatal Intensive Care Unit"
- option "Nursery"
- option "OB Wards"
- option "OPD1"
- option "OPD2"
- option "OPD3"
- option "OPD4"
- option "OPD5"
- option "OPD6"
- option "OPD7"
- option "PICU"
- option "PICU Stepdown"
- option "Pediatric Ward"
- option "Recovery"
- option "Stepdown 3-4"
- option "Surgical Wards"
- generic [ref=e169]:
- generic [ref=e170]: Department Category *
- combobox [ref=e171]:
- option "Select Category" [selected]
- option "Medical"
- option "Nursing"
- option "Administrative"
- option "Support Services"
- generic [ref=e172]:
- generic [ref=e173]: Department (Optional)
- combobox "Department (Optional)" [ref=e174]:
- option "Select Department" [selected]
- option "Anesthesia Department"
- option "Biomedical Department"
- option "Contact Center Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Emergency Administrative Department"
- option "Emergency Medicine Department"
- option "Executive Administration"
- option "Facility Management & Maintenance Department"
- option "Financial Collection & Claims Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "IVF"
- option "Infection Control Department"
- option "Information Technology Department"
- option "Inpatient Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Laundry Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Nursing Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Security Department"
- option "Surgery Department"
- generic [ref=e175]:
- generic [ref=e176]: Section
- combobox "Section" [ref=e177]:
- option "Select Section" [selected]
- generic [ref=e178]:
- heading "Contact Information" [level=6] [ref=e179]:
- img [ref=e180]
- text: Contact Information
- generic [ref=e183]:
- generic [ref=e184]:
- generic [ref=e185]: Contact Name *
- textbox "Contact Name *" [ref=e186]: Test User
- generic [ref=e187]:
- generic [ref=e188]: Contact Phone *
- textbox "Contact Phone *" [ref=e189]: "+966501234567"
- generic [ref=e190]:
- generic [ref=e191]: Contact Email
- textbox "Contact Email" [ref=e192]
- generic [ref=e193]:
- heading "Inquiry Details" [level=6] [ref=e194]:
- img [ref=e195]
- text: Inquiry Details
- generic [ref=e198]:
- generic [ref=e199]:
- generic [ref=e200]: Inquiry Type *
- combobox "Inquiry Type *" [ref=e201]:
- option "General Inquiry"
- option "Appointment Related" [selected]
- option "Billing & Insurance"
- option "Medical Records"
- option "Pharmacy"
- option "Insurance"
- option "Feedback"
- option "Other"
- generic [ref=e202]:
- generic [ref=e203]: Priority
- combobox "Priority" [ref=e204]:
- option "Low"
- option "Medium" [selected]
- option "High"
- option "Critical"
- generic [ref=e205]:
- generic [ref=e206]: Subject *
- textbox "Subject *" [ref=e207]:
- /placeholder: Brief subject
- text: Playwright Test Inquiry
- generic [ref=e208]:
- generic [ref=e209]: Message *
- textbox "Message *" [ref=e210]:
- /placeholder: Describe your inquiry
- text: This is a test inquiry created by Playwright to verify px-employee access.
- generic [ref=e211]:
- button "Create Inquiry" [ref=e212] [cursor=pointer]:
- img [ref=e213]
- text: Create Inquiry
- link "Cancel" [ref=e216] [cursor=pointer]:
- /url: /inquiries/
- contentinfo [ref=e217]:
- paragraph [ref=e218]:
- text: Powered by
- link "tenhal.sa" [ref=e219] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,39 @@
- generic [ref=e3]:
- generic [ref=e4]:
- img "HH Logo" [ref=e6]
- heading "Welcome to PX360" [level=1] [ref=e7]
- paragraph [ref=e8]: Patient Experience Management System
- generic [ref=e10]:
- generic [ref=e11]:
- generic [ref=e12]:
- img [ref=e13]
- text: Email Address
- generic [ref=e16]:
- img [ref=e17]
- textbox "Email Address" [active] [ref=e20]:
- /placeholder: Enter your email
- generic [ref=e21]:
- generic [ref=e22]:
- img [ref=e23]
- text: Password
- generic [ref=e26]:
- img [ref=e27]
- textbox "Password" [ref=e31]:
- /placeholder: Enter your password
- button [ref=e32] [cursor=pointer]:
- img [ref=e33]
- generic [ref=e36]:
- generic [ref=e37] [cursor=pointer]:
- checkbox "Remember me" [ref=e38]
- generic [ref=e39]: Remember me
- link "Forgot password?" [ref=e40] [cursor=pointer]:
- /url: /accounts/password/reset/
- button "Sign In" [ref=e41] [cursor=pointer]:
- img [ref=e42]
- text: Sign In
- generic [ref=e45]:
- paragraph [ref=e46]:
- text: Secure login powered by
- link "tenhal.sa" [ref=e47] [cursor=pointer]:
- /url: https://tenhal.sa
- paragraph [ref=e48]: © 2026 Al Hammadi Hospital

View File

@ -0,0 +1,350 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Departments" [ref=e141] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e142]
- generic [ref=e144]: Contact Center Department
- generic [ref=e147]:
- generic [ref=e148]:
- img [ref=e150]
- generic [ref=e154]:
- heading "Contact Center Department" [level=1] [ref=e155]
- paragraph [ref=e156]: قسم مركز الاتصال
- generic [ref=e157]:
- generic [ref=e158]:
- img [ref=e159]
- text: hh_n_contact_center_department
- generic [ref=e162]: Administrative
- generic [ref=e163]:
- img [ref=e164]
- text: E2E Test Hospital
- generic [ref=e168]:
- generic [ref=e170]:
- img [ref=e172]
- generic [ref=e177]:
- paragraph [ref=e178]: Staff
- paragraph [ref=e179]: "4"
- link "Open Complaints 3 3 total" [ref=e180] [cursor=pointer]:
- /url: /organizations/departments/d810102c-d14a-4661-8672-6ef85a77fc8f/complaints/?status=open
- generic [ref=e181]:
- img [ref=e183]
- generic [ref=e186]:
- paragraph [ref=e187]: Open Complaints
- paragraph [ref=e188]: "3"
- paragraph [ref=e189]: 3 total
- link "Pending Inquiries 0 2 total" [ref=e190] [cursor=pointer]:
- /url: /organizations/departments/d810102c-d14a-4661-8672-6ef85a77fc8f/inquiries/?status=open
- generic [ref=e191]:
- img [ref=e193]
- generic [ref=e196]:
- paragraph [ref=e197]: Pending Inquiries
- paragraph [ref=e198]: "0"
- paragraph [ref=e199]: 2 total
- link "Open Observations 1 1 total" [ref=e200] [cursor=pointer]:
- /url: /organizations/departments/d810102c-d14a-4661-8672-6ef85a77fc8f/observations/?status=open
- generic [ref=e201]:
- img [ref=e203]
- generic [ref=e206]:
- paragraph [ref=e207]: Open Observations
- paragraph [ref=e208]: "1"
- paragraph [ref=e209]: 1 total
- generic [ref=e210]:
- generic [ref=e211]:
- img [ref=e213]
- generic [ref=e215]:
- heading "Pending Actions" [level=3] [ref=e216]
- paragraph [ref=e217]: 3 items awaiting response
- table [ref=e219]:
- rowgroup [ref=e220]:
- row "Type Reference Subject SLA Deadline Action" [ref=e221]:
- columnheader "Type" [ref=e222]
- columnheader "Reference" [ref=e223]
- columnheader "Subject" [ref=e224]
- columnheader "SLA Deadline" [ref=e225]
- columnheader "Action" [ref=e226]
- rowgroup [ref=e227]:
- row "Complaint Response CMP-202607-0006 Test Complaint - Analysis Required - Respond" [ref=e228]:
- cell "Complaint Response" [ref=e229]:
- generic [ref=e230]: Complaint Response
- cell "CMP-202607-0006" [ref=e231]
- cell "Test Complaint - Analysis Required" [ref=e232]
- cell "-" [ref=e233]
- cell "Respond" [ref=e234]:
- link "Respond" [ref=e236] [cursor=pointer]:
- /url: /complaints/f8aa6216-bb94-455e-ba18-d16a01024662/explain/dM0ilaUpzA1OuAceyjvwV9oxYA0geW_jsgipvrYUt_g/
- img [ref=e237]
- text: Respond
- row "Complaint Explanation CMP-202607-0005 Test Complaint - No Details - Respond" [ref=e239]:
- cell "Complaint Explanation" [ref=e240]:
- generic [ref=e241]: Complaint Explanation
- cell "CMP-202607-0005" [ref=e242]
- cell "Test Complaint - No Details" [ref=e243]
- cell "-" [ref=e244]
- cell "Respond" [ref=e245]:
- link "Respond" [ref=e247] [cursor=pointer]:
- /url: /complaints/9ca01d52-712d-4bca-b30d-6f1f818ff839/explain/iBIf0PrMq1RZYSEIDxSn4WZik4PFk6dunoNwarFTnXI/
- img [ref=e248]
- text: Respond
- row "Complaint Explanation CMP-202607-0005 Test Complaint - No Details - Respond" [ref=e252]:
- cell "Complaint Explanation" [ref=e253]:
- generic [ref=e254]: Complaint Explanation
- cell "CMP-202607-0005" [ref=e255]
- cell "Test Complaint - No Details" [ref=e256]
- cell "-" [ref=e257]
- cell "Respond" [ref=e258]:
- link "Respond" [ref=e260] [cursor=pointer]:
- /url: /complaints/9ca01d52-712d-4bca-b30d-6f1f818ff839/explain/X1Zd54Tb1NcExZeRr6ACN5RtPCwB_RTKeqNeFcVP24E/
- img [ref=e261]
- text: Respond
- generic [ref=e266]:
- button "Dashboard" [ref=e267] [cursor=pointer]:
- img [ref=e268]
- text: Dashboard
- button "Staff (4)" [ref=e270] [cursor=pointer]:
- img [ref=e271]
- text: Staff (4)
- button "Roles" [ref=e276] [cursor=pointer]:
- img [ref=e277]
- text: Roles
- button "Sections (2)" [ref=e289] [cursor=pointer]:
- img [ref=e290]
- text: Sections (2)
- button "Complaints (3)" [ref=e294] [cursor=pointer]:
- img [ref=e295]
- text: Complaints (3)
- button "Inquiries (2)" [ref=e298] [cursor=pointer]:
- img [ref=e299]
- text: Inquiries (2)
- button "Observations (1)" [ref=e302] [cursor=pointer]:
- img [ref=e303]
- text: Observations (1)
- button "Suggestions (1)" [ref=e306] [cursor=pointer]:
- img [ref=e307]
- text: Suggestions (1)
- button "Appreciations (2)" [ref=e309] [cursor=pointer]:
- img [ref=e310]
- text: Appreciations (2)
- button "Standards (0)" [ref=e312] [cursor=pointer]:
- img [ref=e313]
- text: Standards (0)
- button "Projects (8)" [ref=e316] [cursor=pointer]:
- img [ref=e317]
- text: Projects (8)
- generic [ref=e321]:
- generic [ref=e322]:
- generic [ref=e323]:
- paragraph [ref=e324]: Avg Physician Rating
- paragraph [ref=e325]: "0.0"
- paragraph [ref=e326]: out of 5.0
- generic [ref=e327]:
- paragraph [ref=e328]: Resolution Rate
- paragraph [ref=e329]: 0%
- paragraph [ref=e330]: 3 total
- generic [ref=e331]:
- paragraph [ref=e332]: Open Actions
- paragraph [ref=e333]: "3"
- paragraph [ref=e334]: PX actions pending
- generic [ref=e335]:
- paragraph [ref=e336]: Satisfaction Rate
- paragraph [ref=e337]: 0%
- paragraph [ref=e338]: resolution satisfaction
- generic [ref=e339]:
- paragraph [ref=e340]: Reopened
- paragraph [ref=e341]: "0"
- paragraph [ref=e342]: reopened complaints
- generic [ref=e343]:
- paragraph [ref=e344]: Reassigned
- paragraph [ref=e345]: "5"
- paragraph [ref=e346]: reassigned complaints
- generic [ref=e347]:
- generic [ref=e348]:
- heading "Complaint Trend" [level=4] [ref=e349]
- generic [ref=e351]:
- application "area chart with 1 data series" [ref=e352]:
- generic [ref=e355]:
- generic "4" [ref=e356]
- generic "3.5" [ref=e357]
- generic "3" [ref=e358]
- generic "2.5" [ref=e359]
- generic "2" [ref=e360]
- generic [ref=e361]:
- generic [ref=e365]:
- generic:
- generic:
- generic: "3"
- generic "2026-07" [ref=e371]
- tooltip:
- generic [ref=e372]:
- heading "Physician Rating Trend" [level=4] [ref=e373]
- paragraph [ref=e375]: No physician rating data
- generic [ref=e376]:
- heading "Complaint Status" [level=4] [ref=e377]
- generic [ref=e379]:
- application "donut chart with 1 data series" [ref=e380]:
- generic [ref=e386]:
- generic:
- generic: 100.0%
- button "In Progress, visible. Press Enter or Space to toggle." [ref=e390] [cursor=pointer]:
- generic [ref=e391]:
- img
- generic [ref=e392]: In Progress
- text:
- generic [ref=e393]:
- heading "Actions Overview" [level=4] [ref=e394]
- generic [ref=e396]:
- application "donut chart with 2 data series" [ref=e397]:
- generic [ref=e403]:
- generic:
- generic: 66.7%
- generic:
- generic: 33.3%
- generic [ref=e406]:
- button "In Progress, visible. Press Enter or Space to toggle." [ref=e407] [cursor=pointer]:
- generic [ref=e408]:
- img
- generic [ref=e409]: In Progress
- button "Open, visible. Press Enter or Space to toggle." [ref=e410] [cursor=pointer]:
- generic [ref=e411]:
- img
- generic [ref=e412]: Open
- text: ● ●
- generic [ref=e413]:
- heading "Complaint Severity" [level=4] [ref=e414]
- generic [ref=e416]:
- application "bar chart with 1 data series" [ref=e417]:
- generic [ref=e420]:
- generic "2" [ref=e421]
- generic "1.5" [ref=e422]
- generic "1" [ref=e423]
- generic "0.5" [ref=e424]
- generic "0" [ref=e425]
- generic [ref=e426]:
- generic [ref=e430]:
- generic:
- generic:
- generic: "2"
- generic:
- generic: "1"
- generic [ref=e435]:
- generic "Low" [ref=e436]
- generic "Medium" [ref=e437]
- tooltip:
- generic [ref=e438]:
- heading "Satisfaction Distribution" [level=4] [ref=e439]
- paragraph [ref=e441]: No satisfaction data
- generic [ref=e442]:
- heading "Top Physicians" [level=4] [ref=e443]
- paragraph [ref=e445]: No physician data
- generic [ref=e446]:
- heading "Top Categories" [level=4] [ref=e447]
- generic [ref=e449]:
- application "bar chart with 1 data series" [ref=e450]:
- generic [ref=e452]:
- generic [ref=e455]:
- generic:
- generic:
- generic: "3"
- generic "Institutional Issues" [ref=e460]
- generic [ref=e462]:
- generic "3.0" [ref=e463]
- generic "2.5" [ref=e464]
- generic "2.0" [ref=e465]
- generic "1.5" [ref=e466]
- generic "1.0" [ref=e467]
- generic "0.5" [ref=e468]
- generic "0.0" [ref=e469]
- tooltip:
- contentinfo [ref=e470]:
- paragraph [ref=e471]:
- text: Powered by
- link "tenhal.sa" [ref=e472] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,258 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Inquiries Console" [level=1] [ref=e142]
- paragraph [ref=e143]: Manage patient inquiries and requests
- link "New Inquiry" [ref=e144] [cursor=pointer]:
- /url: /inquiries/new/
- img [ref=e145]
- text: New Inquiry
- generic [ref=e146]:
- generic [ref=e147]:
- img [ref=e150]
- generic [ref=e153]:
- heading "Total Inquiries" [level=6] [ref=e154]
- heading "5" [level=2] [ref=e155]
- generic [ref=e156]:
- img [ref=e159]
- generic [ref=e162]:
- heading "Resolved" [level=6] [ref=e163]
- heading "2 (40.0%)" [level=2] [ref=e164]
- generic [ref=e165]:
- img [ref=e168]
- generic [ref=e171]:
- heading "In Progress" [level=6] [ref=e172]
- heading "1" [level=2] [ref=e173]
- generic [ref=e174]:
- img [ref=e177]
- generic [ref=e179]:
- heading "Overdue" [level=6] [ref=e180]
- heading "0" [level=2] [ref=e181]
- generic [ref=e182]:
- generic [ref=e183]:
- img [ref=e185]
- heading "Filters" [level=5] [ref=e186]
- generic [ref=e188]:
- generic [ref=e189]:
- generic [ref=e190]: Search
- textbox "Subject, contact name..." [ref=e191]
- generic [ref=e192]:
- generic [ref=e193]: Status
- combobox [ref=e194]:
- option "All Status" [selected]
- option "Open"
- option "In Progress"
- option "Resolved"
- option "Closed"
- generic [ref=e195]:
- generic [ref=e196]: Category
- combobox [ref=e197]:
- option "All Categories" [selected]
- option "General"
- option "Appointments"
- option "Billing"
- option "Medical Records"
- option "Pharmacy"
- option "Insurance"
- option "Feedback"
- option "Other"
- button "Filter" [ref=e198] [cursor=pointer]:
- img [ref=e199]
- text: Filter
- generic [ref=e202]:
- generic [ref=e203]:
- generic [ref=e204]:
- img [ref=e206]
- heading "All Inquiries" [level=5] [ref=e209]
- generic [ref=e210]: "Total: 5"
- paragraph [ref=e211]: "Showing: 1-5 of 5"
- generic [ref=e212]:
- table [ref=e213]:
- rowgroup [ref=e214]:
- row "Reference Subject Contact Category Status Priority Created Assigned" [ref=e215]:
- columnheader "Reference" [ref=e216]
- columnheader "Subject" [ref=e217]
- columnheader "Contact" [ref=e218]
- columnheader "Category" [ref=e219]
- columnheader "Status" [ref=e220]
- columnheader "Priority" [ref=e221]
- columnheader "Created" [ref=e222]
- columnheader "Assigned" [ref=e223]
- rowgroup [ref=e224]:
- row "INQ-202607-0007 test12121212 ismail mosa 0569874563 General Information Open Medium Jul 12, 2026 Unassigned" [ref=e225] [cursor=pointer]:
- cell "INQ-202607-0007" [ref=e226]:
- code [ref=e227]: INQ-202607-0007
- cell "test12121212" [ref=e228]:
- link "test12121212" [ref=e229]:
- /url: /inquiries/b4a65498-e8d8-403c-82bb-ae09adc7fc1c/
- cell "ismail mosa 0569874563" [ref=e230]:
- generic [ref=e231]: ismail mosa
- generic [ref=e232]: "0569874563"
- cell "General Information" [ref=e233]
- cell "Open" [ref=e234]
- cell "Medium" [ref=e235]
- cell "Jul 12, 2026" [ref=e236]
- cell "Unassigned" [ref=e237]
- row "INQ-202607-0006 test12121212 test 0569874563 General Information Open Medium Jul 12, 2026 Unassigned" [ref=e238] [cursor=pointer]:
- cell "INQ-202607-0006" [ref=e239]:
- code [ref=e240]: INQ-202607-0006
- cell "test12121212" [ref=e241]:
- link "test12121212" [ref=e242]:
- /url: /inquiries/1c3eda08-ff03-4521-bcb8-ec137c79b417/
- cell "test 0569874563" [ref=e243]:
- generic [ref=e244]: test
- generic [ref=e245]: "0569874563"
- cell "General Information" [ref=e246]
- cell "Open" [ref=e247]
- cell "Medium" [ref=e248]
- cell "Jul 12, 2026" [ref=e249]
- cell "Unassigned" [ref=e250]
- row "INQ-202607-0005 test test 0569874589 General Information In Progress Medium Jul 09, 2026 Unassigned" [ref=e251] [cursor=pointer]:
- cell "INQ-202607-0005" [ref=e252]:
- code [ref=e253]: INQ-202607-0005
- cell "test" [ref=e254]:
- link "test" [ref=e255]:
- /url: /inquiries/c05309c2-3518-46ae-9b1c-c144acdb5d58/
- cell "test 0569874589" [ref=e256]:
- generic [ref=e257]: test
- generic [ref=e258]: "0569874589"
- cell "General Information" [ref=e259]
- cell "In Progress" [ref=e260]
- cell "Medium" [ref=e261]
- cell "Jul 09, 2026" [ref=e262]
- cell "Unassigned" [ref=e263]
- row "INQ-202607-0004 hi ismail 0569874589 General Information Resolved Medium Jul 09, 2026 ismail mosa" [ref=e264] [cursor=pointer]:
- cell "INQ-202607-0004" [ref=e265]:
- code [ref=e266]: INQ-202607-0004
- cell "hi" [ref=e267]:
- link "hi" [ref=e268]:
- /url: /inquiries/d1df0b83-5c16-4f51-b735-e89f612deb50/
- cell "ismail 0569874589" [ref=e269]:
- generic [ref=e270]: ismail
- generic [ref=e271]: "0569874589"
- cell "General Information" [ref=e272]
- cell "Resolved" [ref=e273]
- cell "Medium" [ref=e274]
- cell "Jul 09, 2026" [ref=e275]
- cell "ismail mosa" [ref=e276]
- row "INQ-202607-0003 test test 0569874589 General Information Resolved Medium Jul 08, 2026 ismail mosa" [ref=e277] [cursor=pointer]:
- cell "INQ-202607-0003" [ref=e278]:
- code [ref=e279]: INQ-202607-0003
- cell "test" [ref=e280]:
- link "test" [ref=e281]:
- /url: /inquiries/031cbd06-e2dc-4cd9-9b81-b1218e090d6c/
- cell "test 0569874589" [ref=e282]:
- generic [ref=e283]: test
- generic [ref=e284]: "0569874589"
- cell "General Information" [ref=e285]
- cell "Resolved" [ref=e286]
- cell "Medium" [ref=e287]
- cell "Jul 08, 2026" [ref=e288]
- cell "ismail mosa" [ref=e289]
- generic [ref=e290]:
- generic [ref=e291]:
- generic [ref=e292]: Showing 1-5 of 5
- generic [ref=e293]:
- generic [ref=e294]: Show
- combobox [ref=e295]:
- option "10"
- option "25" [selected]
- option "50"
- option "100"
- generic [ref=e296]:
- img [ref=e298]
- generic [ref=e300]: "1"
- img [ref=e302]
- contentinfo [ref=e304]:
- paragraph [ref=e305]:
- text: Powered by
- link "tenhal.sa" [ref=e306] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,243 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Complaints Registry" [level=1] [ref=e142]
- paragraph [ref=e143]: Manage and monitor patient feedback in real-time
- generic [ref=e144]:
- generic [ref=e145]:
- img [ref=e146]
- textbox "Search ID, Name or Dept..." [ref=e149]
- link "Public Form" [ref=e150] [cursor=pointer]:
- /url: /core/public/submit/
- img [ref=e151]
- text: Public Form
- button "Export" [ref=e155] [cursor=pointer]:
- img [ref=e156]
- text: Export
- link "New Case" [ref=e159] [cursor=pointer]:
- /url: /complaints/new/
- img [ref=e160]
- text: New Case
- generic [ref=e161]:
- generic [ref=e162]:
- img [ref=e165]
- generic [ref=e169]:
- heading "Total Received" [level=6] [ref=e170]
- heading "3" [level=2] [ref=e171]
- generic [ref=e172]:
- img [ref=e175]
- generic [ref=e178]:
- heading "Resolved" [level=6] [ref=e179]
- heading "0 (0.0%)" [level=2] [ref=e180]
- generic [ref=e181]:
- img [ref=e184]
- generic [ref=e187]:
- heading "Pending" [level=6] [ref=e188]
- heading "3 (100.0%)" [level=2] [ref=e189]
- generic [ref=e190]:
- img [ref=e193]
- generic [ref=e195]:
- heading "TAT Alert" [level=6] [ref=e196]
- heading "3 (100.0%)" [level=2] [ref=e197]
- generic [ref=e198]:
- generic [ref=e199]:
- generic [ref=e200]:
- img [ref=e202]
- heading "Complaints" [level=5] [ref=e204]
- generic [ref=e205]:
- link "All Cases" [ref=e206] [cursor=pointer]:
- /url: "?"
- link "Pending" [ref=e207] [cursor=pointer]:
- /url: "?status=open"
- link "In Progress" [ref=e208] [cursor=pointer]:
- /url: "?status=in_progress"
- link "Resolved" [ref=e209] [cursor=pointer]:
- /url: "?status=resolved"
- button "Advanced" [ref=e211] [cursor=pointer]:
- img [ref=e212]
- text: Advanced
- paragraph [ref=e213]: "Showing: 1-3 of 3"
- generic [ref=e214]:
- table [ref=e215]:
- rowgroup [ref=e216]:
- row "Complaint ID Patient Name Description Source Department Status SLA Severity Priority Assigned Actions" [ref=e217]:
- columnheader "Complaint ID" [ref=e218]
- columnheader "Patient Name" [ref=e219]
- columnheader "Description" [ref=e220]
- columnheader "Source" [ref=e221]
- columnheader "Department" [ref=e222]
- columnheader "Status" [ref=e223]
- columnheader "SLA" [ref=e224]
- columnheader "Severity" [ref=e225]
- columnheader "Priority" [ref=e226]
- columnheader "Assigned" [ref=e227]
- columnheader "Actions" [ref=e228]
- rowgroup [ref=e229]:
- row "CMP-202607-0006 test شكوى عامة Patient Patient Experience Department In Progress Overdue 21h 46m Medium Medium test test" [ref=e230] [cursor=pointer]:
- cell "CMP-202607-0006" [ref=e231]
- cell "test" [ref=e232]:
- generic [ref=e233]: test
- cell "شكوى عامة" [ref=e234]:
- generic [ref=e235]: شكوى عامة
- button "View description" [ref=e236]:
- img [ref=e237]
- cell "Patient" [ref=e246]
- cell "Patient Experience Department" [ref=e247]
- cell "In Progress" [ref=e248]
- cell "Overdue 21h 46m" [ref=e249]
- cell "Medium" [ref=e250]
- cell "Medium" [ref=e251]
- cell "test test" [ref=e252]
- cell [ref=e253]:
- generic [ref=e254]:
- link "View" [ref=e255]:
- /url: /complaints/f8aa6216-bb94-455e-ba18-d16a01024662/
- img [ref=e256]
- link "Assign" [ref=e259]:
- /url: /complaints/f8aa6216-bb94-455e-ba18-d16a01024662/assign/
- img [ref=e260]
- row "CMP-202607-0005 ismail شكوى اختبار Patient Contact Center Department In Progress Overdue 23h 22m Low Low test test" [ref=e263] [cursor=pointer]:
- cell "CMP-202607-0005" [ref=e264]
- cell "ismail" [ref=e265]:
- generic [ref=e266]: ismail
- cell "شكوى اختبار" [ref=e267]:
- generic [ref=e268]: شكوى اختبار
- button "View description" [ref=e269]:
- img [ref=e270]
- cell "Patient" [ref=e279]
- cell "Contact Center Department" [ref=e280]
- cell "In Progress" [ref=e281]
- cell "Overdue 23h 22m" [ref=e282]
- cell "Low" [ref=e283]
- cell "Low" [ref=e284]
- cell "test test" [ref=e285]
- cell [ref=e286]:
- link "View" [ref=e288]:
- /url: /complaints/9ca01d52-712d-4bca-b30d-6f1f818ff839/
- img [ref=e289]
- row "CMP-202607-0004 OVR ismail اختبار عام Internal Contact Center Department In Progress Overdue 4d 7h 22m Low Low ismail mosa" [ref=e292] [cursor=pointer]:
- cell "CMP-202607-0004 OVR" [ref=e293]
- cell "ismail" [ref=e294]:
- generic [ref=e295]: ismail
- cell "اختبار عام" [ref=e296]:
- generic [ref=e297]: اختبار عام
- button "View description" [ref=e298]:
- img [ref=e299]
- cell "Internal" [ref=e308]
- cell "Contact Center Department" [ref=e309]
- cell "In Progress" [ref=e310]
- cell "Overdue 4d 7h 22m" [ref=e311]
- cell "Low" [ref=e312]
- cell "Low" [ref=e313]
- cell "ismail mosa" [ref=e314]
- cell [ref=e315]:
- link "View" [ref=e317]:
- /url: /complaints/1c4e8dde-48d8-42ab-8c8d-4ada8314fc40/
- img [ref=e318]
- generic [ref=e321]:
- generic [ref=e322]:
- generic [ref=e323]: Showing 1-3 of 3
- generic [ref=e324]:
- generic [ref=e325]: Show
- combobox [ref=e326]:
- option "10"
- option "25" [selected]
- option "50"
- option "100"
- generic [ref=e327]:
- img [ref=e329]
- generic [ref=e331]: "1"
- img [ref=e333]
- contentinfo [ref=e335]:
- paragraph [ref=e336]:
- text: Powered by
- link "tenhal.sa" [ref=e337] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,344 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Observations" [level=1] [ref=e142]:
- img [ref=e143]
- text: Observations
- paragraph [ref=e146]: Manage and triage staff-reported observations
- generic [ref=e147]:
- link "New Observation" [ref=e148] [cursor=pointer]:
- /url: /observations/create/
- img [ref=e149]
- text: New Observation
- link "Public Form" [ref=e151] [cursor=pointer]:
- /url: /core/public/submit/
- img [ref=e152]
- text: Public Form
- generic [ref=e153]:
- generic [ref=e155]:
- generic [ref=e156]:
- paragraph [ref=e157]: Total
- paragraph [ref=e158]: "2"
- img [ref=e160]
- generic [ref=e162]:
- generic [ref=e163]:
- paragraph [ref=e164]: New
- paragraph
- img [ref=e166]
- generic [ref=e170]:
- generic [ref=e171]:
- paragraph [ref=e172]: In Progress
- paragraph [ref=e173]: "2"
- img [ref=e175]
- generic [ref=e178]:
- generic [ref=e179]:
- img [ref=e181]
- heading "Filters" [level=5] [ref=e183]
- button [ref=e184] [cursor=pointer]:
- img [ref=e185]
- generic [ref=e188]:
- generic [ref=e189]:
- generic [ref=e190]:
- generic [ref=e191]: Search
- textbox "Tracking code, description..." [ref=e192]
- generic [ref=e193]:
- generic [ref=e194]: Status
- combobox [ref=e195]:
- option "All Statuses" [selected]
- option "Open"
- option "In Progress"
- option "Resolved"
- option "Closed"
- generic [ref=e196]:
- generic [ref=e197]: Severity
- combobox [ref=e198]:
- option "All Severities" [selected]
- option "Low"
- option "Medium"
- option "High"
- option "Critical"
- generic [ref=e199]:
- generic [ref=e200]: Category
- combobox [ref=e201]:
- option "All Categories" [selected]
- option "Patient Safety"
- option "Clinical Quality"
- option "Infection Control"
- option "Medication Safety"
- option "Equipment & Devices"
- option "Facility & Environment"
- option "Staff Behavior"
- option "Communication"
- option "Documentation"
- option "Process & Workflow"
- option "Security"
- option "IT & Systems"
- option "Housekeeping"
- option "Food Services"
- option "Other"
- generic [ref=e202]:
- generic [ref=e203]: Department
- combobox [ref=e204]:
- option "All Departments" [selected]
- option "Contact Center Department"
- option "Emergency Administrative Department"
- option "Executive Administration"
- option "Financial Collection & Claims Department"
- option "Inpatient Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Security Department"
- option "Anesthesia Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "Emergency Medicine Department"
- option "IVF"
- option "Infection Control Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Surgery Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Nursing Department"
- option "Biomedical Department"
- option "Facility Management & Maintenance Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "Information Technology Department"
- option "Laundry Department"
- generic [ref=e206] [cursor=pointer]:
- generic [ref=e207]: All Departments
- combobox "All Departments" [ref=e208]
- generic [ref=e209]:
- generic [ref=e210]: Assigned To
- combobox [ref=e211]:
- option "All Users" [selected]
- option "E2E PX Employee"
- option "test test"
- option "test test"
- generic [ref=e212]:
- generic [ref=e213]: Date From
- textbox [ref=e214]
- generic [ref=e215]:
- generic [ref=e216]: Date To
- textbox [ref=e217]
- generic [ref=e218]:
- button "Apply Filters" [ref=e219] [cursor=pointer]:
- img [ref=e220]
- text: Apply Filters
- link "Clear" [ref=e223] [cursor=pointer]:
- /url: /observations/
- img [ref=e224]
- text: Clear
- generic [ref=e229]:
- img [ref=e231]
- heading "Observations List" [level=5] [ref=e233]
- generic [ref=e234]: Showing 1 to 4 of 4 observations
- table [ref=e237]:
- rowgroup [ref=e238]:
- row "Reference Date Category Description Severity Status Reporter Department Assigned Actions" [ref=e239]:
- columnheader "Reference" [ref=e240]
- columnheader "Date" [ref=e241]
- columnheader "Category" [ref=e242]
- columnheader "Description" [ref=e243]
- columnheader "Severity" [ref=e244]
- columnheader "Status" [ref=e245]
- columnheader "Reporter" [ref=e246]
- columnheader "Department" [ref=e247]
- columnheader "Assigned" [ref=e248]
- columnheader "Actions" [ref=e249]
- rowgroup [ref=e250]:
- row "OBS-202607-0001 Jul 08, 2026 16:33 - test observation Medium In Progress ismail mosa Contact Center Department ismail mosa" [ref=e251]:
- cell "OBS-202607-0001" [ref=e252]
- cell "Jul 08, 2026 16:33" [ref=e253]:
- text: Jul 08, 2026
- text: 16:33
- cell "-" [ref=e254]
- cell "test observation" [ref=e255]:
- generic [ref=e256]: test observation
- cell "Medium" [ref=e257]
- cell "In Progress" [ref=e258]
- cell "ismail mosa" [ref=e259]
- cell "Contact Center Department" [ref=e260]
- cell "ismail mosa" [ref=e261]
- cell [ref=e262]:
- link [ref=e263] [cursor=pointer]:
- /url: /observations/1d925706-110e-4bad-808f-26aae2029144/
- img [ref=e264]
- row "OBS-202606-E2EHOSP-0056 Jun 22, 2026 10:16 Other Observation i notice some trash in the hall Medium In Progress E2E PX Admin Contact Center Department E2E PX Admin" [ref=e267]:
- cell "OBS-202606-E2EHOSP-0056" [ref=e268]
- cell "Jun 22, 2026 10:16" [ref=e269]:
- text: Jun 22, 2026
- text: 10:16
- cell "Other" [ref=e270]:
- generic [ref=e271]: Other
- cell "Observation i notice some trash in the hall" [ref=e272]:
- generic [ref=e273]:
- strong [ref=e274]: Observation
- text: i notice some trash in the hall
- cell "Medium" [ref=e275]
- cell "In Progress" [ref=e276]
- cell "E2E PX Admin" [ref=e277]
- cell "Contact Center Department" [ref=e278]
- cell "E2E PX Admin" [ref=e279]
- cell [ref=e280]:
- link [ref=e281] [cursor=pointer]:
- /url: /observations/b9a80c99-31dc-4cda-a1e3-c20d94a28729/
- img [ref=e282]
- row "OBS-O8TCIC Jun 09, 2026 13:36 - Wet floor near the main entrance, no warning sign posted. Medium In Progress Anonymous Reporter - ismail mosa" [ref=e285]:
- cell "OBS-O8TCIC" [ref=e286]
- cell "Jun 09, 2026 13:36" [ref=e287]:
- text: Jun 09, 2026
- text: 13:36
- cell "-" [ref=e288]
- cell "Wet floor near the main entrance, no warning sign posted." [ref=e289]:
- generic [ref=e290]: Wet floor near the main entrance, no warning sign posted.
- cell "Medium" [ref=e291]
- cell "In Progress" [ref=e292]
- cell "Anonymous Reporter" [ref=e293]
- cell "-" [ref=e294]
- cell "ismail mosa" [ref=e295]
- cell [ref=e296]:
- link [ref=e297] [cursor=pointer]:
- /url: /observations/5f8fc6c6-3b38-4b88-8144-3f926c24e290/
- img [ref=e298]
- row "OBS-QPE02F Jun 07, 2026 21:08 Other Observation safsafsad Medium Open ismail Daif - Unassigned" [ref=e301]:
- cell "OBS-QPE02F" [ref=e302]
- cell "Jun 07, 2026 21:08" [ref=e303]:
- text: Jun 07, 2026
- text: 21:08
- cell "Other" [ref=e304]:
- generic [ref=e305]: Other
- cell "Observation safsafsad" [ref=e306]:
- generic [ref=e307]:
- strong [ref=e308]: Observation
- text: safsafsad
- cell "Medium" [ref=e309]
- cell "Open" [ref=e310]
- cell "ismail Daif" [ref=e311]
- cell "-" [ref=e312]
- cell "Unassigned" [ref=e313]
- cell [ref=e314]:
- link [ref=e315] [cursor=pointer]:
- /url: /observations/327d9b79-2126-4800-8cd7-647f006271ce/
- img [ref=e316]
- generic [ref=e319]:
- generic [ref=e320]:
- generic [ref=e321]: Showing 1-4 of 4
- generic [ref=e322]:
- generic [ref=e323]: Show
- combobox [ref=e324]:
- option "10"
- option "25" [selected]
- option "50"
- option "100"
- generic [ref=e325]:
- img [ref=e327]
- generic [ref=e329]: "1"
- img [ref=e331]
- contentinfo [ref=e333]:
- paragraph [ref=e334]:
- text: Powered by
- link "tenhal.sa" [ref=e335] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,201 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Suggestions" [level=1] [ref=e142]
- paragraph [ref=e143]: Manage and review submitted suggestions
- link "New Suggestion" [ref=e145] [cursor=pointer]:
- /url: /suggestions/create/
- img [ref=e146]
- text: New Suggestion
- generic [ref=e147]:
- generic [ref=e148]:
- img [ref=e151]
- generic [ref=e153]:
- heading "Total Suggestions" [level=6] [ref=e154]
- heading "3" [level=2] [ref=e155]
- generic [ref=e156]:
- img [ref=e159]
- generic [ref=e161]:
- heading "Compliments" [level=6] [ref=e162]
- heading "0" [level=2] [ref=e163]
- generic [ref=e164]:
- img [ref=e167]
- generic [ref=e169]:
- heading "Avg Rating" [level=6] [ref=e170]
- heading "0.0 /5" [level=2] [ref=e171]
- generic [ref=e172]:
- img [ref=e175]
- generic [ref=e178]:
- heading "Pending Review" [level=6] [ref=e179]
- heading "2" [level=2] [ref=e180]
- generic [ref=e181]:
- generic [ref=e182]:
- generic [ref=e183]:
- img [ref=e185]
- heading "Suggestions" [level=5] [ref=e187]
- generic [ref=e188]:
- link "All" [ref=e189] [cursor=pointer]:
- /url: "?"
- link "Suggestions" [ref=e190] [cursor=pointer]:
- /url: "?feedback_type=suggestion"
- link "Compliments" [ref=e191] [cursor=pointer]:
- /url: "?feedback_type=compliment"
- button "Advanced" [ref=e193] [cursor=pointer]:
- img [ref=e194]
- text: Advanced
- paragraph [ref=e195]: "Showing: 1-3 of 3"
- table [ref=e197]:
- rowgroup [ref=e198]:
- row "ID Type Contact Title Category Rating Sentiment Status Created Actions" [ref=e199]:
- columnheader "ID" [ref=e200]
- columnheader "Type" [ref=e201]
- columnheader "Contact" [ref=e202]
- columnheader "Title" [ref=e203]
- columnheader "Category" [ref=e204]
- columnheader "Rating" [ref=e205]
- columnheader "Sentiment" [ref=e206]
- columnheader "Status" [ref=e207]
- columnheader "Created" [ref=e208]
- columnheader "Actions" [ref=e209]
- rowgroup [ref=e210]:
- row "SGT-202607-0003 Suggestion ismail Daif t Other - Neutral Submitted Jul 09, 2026" [ref=e211] [cursor=pointer]:
- cell "SGT-202607-0003" [ref=e212]
- cell "Suggestion" [ref=e213]
- cell "ismail Daif" [ref=e214]:
- generic [ref=e215]: ismail Daif
- cell "t" [ref=e216]:
- generic [ref=e217]: t
- cell "Other" [ref=e218]
- cell "-" [ref=e219]
- cell "Neutral" [ref=e220]
- cell "Submitted" [ref=e221]
- cell "Jul 09, 2026" [ref=e222]
- cell [ref=e223]:
- link "View" [ref=e225]:
- /url: /suggestions/78f015c5-fbed-4e10-987f-aa8ced75545a/
- img [ref=e226]
- row "SGT-202607-0002 Suggestion ismail test Other - Neutral Submitted Jul 09, 2026" [ref=e229] [cursor=pointer]:
- cell "SGT-202607-0002" [ref=e230]
- cell "Suggestion" [ref=e231]
- cell "ismail" [ref=e232]:
- generic [ref=e233]: ismail
- cell "test" [ref=e234]:
- generic [ref=e235]: test
- cell "Other" [ref=e236]
- cell "-" [ref=e237]
- cell "Neutral" [ref=e238]
- cell "Submitted" [ref=e239]
- cell "Jul 09, 2026" [ref=e240]
- cell [ref=e241]:
- link "View" [ref=e243]:
- /url: /suggestions/04b009f3-53aa-4eac-9baf-39b3633e56b0/
- img [ref=e244]
- row "SGT-202607-0001 Suggestion ismail The suggestion titled 'test' with content 'test' appears … Other - Neutral Closed Jul 09, 2026" [ref=e247] [cursor=pointer]:
- cell "SGT-202607-0001" [ref=e248]
- cell "Suggestion" [ref=e249]
- cell "ismail" [ref=e250]:
- generic [ref=e251]: ismail
- cell "The suggestion titled 'test' with content 'test' appears …" [ref=e252]:
- generic [ref=e253]: The suggestion titled 'test' with content 'test' appears …
- cell "Other" [ref=e254]
- cell "-" [ref=e255]
- cell "Neutral" [ref=e256]
- cell "Closed" [ref=e257]
- cell "Jul 09, 2026" [ref=e258]
- cell [ref=e259]:
- link "View" [ref=e261]:
- /url: /suggestions/ef1e633b-7fae-46e2-baab-0d5bdd71e7b7/
- img [ref=e262]
- contentinfo [ref=e265]:
- paragraph [ref=e266]:
- text: Powered by
- link "tenhal.sa" [ref=e267] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,221 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Appreciations" [level=1] [ref=e142]:
- img [ref=e143]
- text: Appreciations
- paragraph [ref=e145]: Manage and review patient appreciation submissions
- generic [ref=e146]:
- link "Create Internal" [ref=e147] [cursor=pointer]:
- /url: /appreciation/new/
- img [ref=e148]
- text: Create Internal
- link "Public Form" [ref=e149] [cursor=pointer]:
- /url: /core/public/submit/
- img [ref=e150]
- text: Public Form
- generic [ref=e154]:
- paragraph [ref=e155]: "0"
- paragraph [ref=e156]: Pending
- generic [ref=e157]:
- generic [ref=e159]:
- img [ref=e161]
- generic [ref=e164]:
- paragraph [ref=e165]: Total
- paragraph [ref=e166]: "3"
- generic [ref=e168]:
- img [ref=e170]
- generic [ref=e173]:
- paragraph [ref=e174]: Draft
- paragraph [ref=e175]: "0"
- generic [ref=e177]:
- img [ref=e179]
- generic [ref=e181]:
- paragraph [ref=e182]: Activated
- paragraph [ref=e183]: "0"
- generic [ref=e185]:
- img [ref=e187]
- generic [ref=e190]:
- paragraph [ref=e191]: Sent
- paragraph [ref=e192]: "3"
- generic [ref=e193]:
- link "Leaderboard See top performers" [ref=e194] [cursor=pointer]:
- /url: /appreciation/leaderboard/
- generic [ref=e196]:
- img [ref=e198]
- generic [ref=e204]:
- heading "Leaderboard" [level=3] [ref=e205]
- paragraph [ref=e206]: See top performers
- link "My Badges View earned badges" [ref=e207] [cursor=pointer]:
- /url: /appreciation/badges/
- generic [ref=e209]:
- img [ref=e211]
- generic [ref=e214]:
- heading "My Badges" [level=3] [ref=e215]
- paragraph [ref=e216]: View earned badges
- generic [ref=e217]:
- generic [ref=e218]:
- img [ref=e220]
- heading "Filters" [level=3] [ref=e222]
- generic [ref=e224]:
- generic [ref=e225]:
- generic [ref=e226]: Search
- textbox "Search messages..." [ref=e227]
- generic [ref=e228]:
- generic [ref=e229]: Status
- combobox [ref=e230]:
- option "All Status" [selected]
- option "Draft"
- option "Activated"
- option "AI Analyzed"
- option "Sent"
- option "Acknowledged"
- button "Search" [ref=e231] [cursor=pointer]:
- img [ref=e232]
- text: Search
- link "Clear" [ref=e235] [cursor=pointer]:
- /url: /appreciation/
- table [ref=e237]:
- rowgroup [ref=e238]:
- row "From Message Staff Department Category Status Date Actions" [ref=e239]:
- columnheader "From" [ref=e240]
- columnheader "Message" [ref=e241]
- columnheader "Staff" [ref=e242]
- columnheader "Department" [ref=e243]
- columnheader "Category" [ref=e244]
- columnheader "Status" [ref=e245]
- columnheader "Date" [ref=e246]
- columnheader "Actions" [ref=e247]
- rowgroup [ref=e248]:
- row "ismail thank - Contact Center Department - Sent Jul 09, 2026" [ref=e249] [cursor=pointer]:
- cell "ismail" [ref=e250]
- cell "thank" [ref=e251]:
- generic [ref=e252]: thank
- cell "-" [ref=e253]
- cell "Contact Center Department" [ref=e254]
- cell "-" [ref=e255]
- cell "Sent" [ref=e256]
- cell "Jul 09, 2026" [ref=e257]
- cell [ref=e258]:
- link "View" [ref=e260]:
- /url: /appreciation/detail/5306821e-d596-498f-88c5-fa3addcdd491/
- img [ref=e261]
- row "ismail mosa test - Contact Center Department - Sent Jul 09, 2026" [ref=e264] [cursor=pointer]:
- cell "ismail mosa" [ref=e265]
- cell "test" [ref=e266]:
- generic [ref=e267]: test
- cell "-" [ref=e268]
- cell "Contact Center Department" [ref=e269]
- cell "-" [ref=e270]
- cell "Sent" [ref=e271]
- cell "Jul 09, 2026" [ref=e272]
- cell [ref=e273]:
- link "View" [ref=e275]:
- /url: /appreciation/detail/3697b78b-5ff3-4392-8982-af20f58daebf/
- img [ref=e276]
- row "ismail mosa thank you - Contact Center Department - Sent Jul 09, 2026" [ref=e279] [cursor=pointer]:
- cell "ismail mosa" [ref=e280]
- cell "thank you" [ref=e281]:
- generic [ref=e282]: thank you
- cell "-" [ref=e283]
- cell "Contact Center Department" [ref=e284]
- cell "-" [ref=e285]
- cell "Sent" [ref=e286]
- cell "Jul 09, 2026" [ref=e287]
- cell [ref=e288]:
- link "View" [ref=e290]:
- /url: /appreciation/detail/240ea5bd-a12d-4f76-8a79-a8dd9de5714a/
- img [ref=e291]
- contentinfo [ref=e294]:
- paragraph [ref=e295]:
- text: Powered by
- link "tenhal.sa" [ref=e296] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,256 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e141]:
- generic [ref=e142]:
- link "Inquiries" [ref=e143] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e144]
- generic [ref=e146]: New Inquiry
- heading "Create New Inquiry" [level=1] [ref=e147]
- generic [ref=e149]:
- generic [ref=e150]:
- img [ref=e152]
- heading "Inquiry Information" [level=5] [ref=e155]
- generic [ref=e156]:
- generic [ref=e157]:
- heading "Organization" [level=6] [ref=e158]:
- img [ref=e159]
- text: Organization
- generic [ref=e162]:
- generic [ref=e163]:
- generic [ref=e164]: Location Type *
- combobox "Location Type *" [ref=e165]:
- option "Select Location Type" [selected]
- option "Outpatient"
- option "Inpatient"
- option "Emergency"
- option "General"
- generic [ref=e166]:
- generic [ref=e167]: Area
- combobox "Area" [ref=e168]:
- option "Select Area (optional)" [selected]
- option "Anesthesia (E2E-HOSP)"
- option "Dialysis (E2E-HOSP)"
- option "Emergency (E2E-HOSP)"
- option "Endoscopy (E2E-HOSP)"
- option "ICCU (E2E-HOSP)"
- option "ICU (E2E-HOSP)"
- option "ICU Stepdown (E2E-HOSP)"
- option "LTACU (E2E-HOSP)"
- option "Labor & Delivery (E2E-HOSP)"
- option "Main OR (E2E-HOSP)"
- option "Medical Ward (E2E-HOSP)"
- option "NICU - Neonatal Intensive Care Unit (E2E-HOSP)"
- option "Nursery (E2E-HOSP)"
- option "OB Wards (E2E-HOSP)"
- option "OPD1 (E2E-HOSP)"
- option "OPD2 (E2E-HOSP)"
- option "OPD3 (E2E-HOSP)"
- option "OPD4 (E2E-HOSP)"
- option "OPD5 (E2E-HOSP)"
- option "OPD6 (E2E-HOSP)"
- option "OPD7 (E2E-HOSP)"
- option "PICU (E2E-HOSP)"
- option "PICU Stepdown (E2E-HOSP)"
- option "Pediatric Ward (E2E-HOSP)"
- option "Recovery (E2E-HOSP)"
- option "Stepdown 3-4 (E2E-HOSP)"
- option "Surgical Wards (E2E-HOSP)"
- generic [ref=e169]:
- generic [ref=e170]: Department Category *
- combobox [ref=e171]:
- option "Select Category" [selected]
- option "Medical"
- option "Nursing"
- option "Administrative"
- option "Support Services"
- generic [ref=e172]:
- generic [ref=e173]: Department (Optional)
- combobox "Department (Optional)" [ref=e174]:
- option "Select Department" [selected]
- option "Anesthesia Department"
- option "Biomedical Department"
- option "Contact Center Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Emergency Administrative Department"
- option "Emergency Medicine Department"
- option "Executive Administration"
- option "Facility Management & Maintenance Department"
- option "Financial Collection & Claims Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "IVF"
- option "Infection Control Department"
- option "Information Technology Department"
- option "Inpatient Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Laundry Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Nursing Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Security Department"
- option "Surgery Department"
- generic [ref=e175]:
- generic [ref=e176]: Section
- combobox "Section" [ref=e177]:
- option "Select Section" [selected]
- generic [ref=e178]:
- heading "Contact Information" [level=6] [ref=e179]:
- img [ref=e180]
- text: Contact Information
- generic [ref=e183]:
- generic [ref=e184]:
- generic [ref=e185]: Contact Name *
- textbox "Contact Name *" [ref=e186]
- generic [ref=e187]:
- generic [ref=e188]: Contact Phone *
- textbox "Contact Phone *" [ref=e189]
- generic [ref=e190]:
- generic [ref=e191]: Contact Email
- textbox "Contact Email" [ref=e192]
- generic [ref=e193]:
- heading "Inquiry Details" [level=6] [ref=e194]:
- img [ref=e195]
- text: Inquiry Details
- generic [ref=e198]:
- generic [ref=e199]:
- generic [ref=e200]: Inquiry Type *
- combobox "Inquiry Type *" [ref=e201]:
- option "General Inquiry" [selected]
- option "Appointment Related"
- option "Billing & Insurance"
- option "Medical Records"
- option "Pharmacy"
- option "Insurance"
- option "Feedback"
- option "Other"
- generic [ref=e202]:
- generic [ref=e203]: Priority
- combobox "Priority" [ref=e204]:
- option "Low"
- option "Medium" [selected]
- option "High"
- option "Critical"
- generic [ref=e205]:
- generic [ref=e206]: Subject *
- textbox "Subject *" [ref=e207]:
- /placeholder: Brief subject
- generic [ref=e208]:
- generic [ref=e209]: Message *
- textbox "Message *" [ref=e210]:
- /placeholder: Describe your inquiry
- generic [ref=e211]:
- button "Create Inquiry" [ref=e212] [cursor=pointer]:
- img [ref=e213]
- text: Create Inquiry
- link "Cancel" [ref=e216] [cursor=pointer]:
- /url: /inquiries/
- contentinfo [ref=e217]:
- paragraph [ref=e218]:
- text: Powered by
- link "tenhal.sa" [ref=e219] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,124 @@
- generic [active] [ref=e1]:
- banner [ref=e2]:
- generic [ref=e4]:
- generic [ref=e5]:
- img "HH Logo" [ref=e7]
- generic [ref=e8]:
- heading "PX360" [level=1] [ref=e9]
- paragraph [ref=e10]: Patient Experience Management
- generic [ref=e11]:
- link "🇬🇧 English" [ref=e12] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e13]: 🇬🇧
- generic [ref=e14]: English
- link "🇸🇦 Arabic" [ref=e15] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e16]: 🇸🇦
- generic [ref=e17]: Arabic
- main [ref=e18]:
- generic [ref=e21]:
- generic [ref=e22]:
- img [ref=e24]
- heading "Report an Observation" [level=1] [ref=e27]
- paragraph [ref=e28]: Help us improve by reporting issues you notice
- generic [ref=e30]:
- img [ref=e31]
- generic [ref=e34]:
- strong [ref=e35]: Anonymous Reporting
- paragraph [ref=e36]: You can submit this report anonymously. Providing your information is optional but may help us follow up if needed.
- generic [ref=e37]:
- generic [ref=e38]:
- generic [ref=e39]:
- img [ref=e40]
- text: Hospital
- generic [ref=e44]: (optional)
- combobox [ref=e45]:
- option "Select hospital" [selected]
- option "Al Nuzha"
- option "Al Olya"
- option "Al Suwaidi"
- option "Al-Hammadi Hospital (KPI Test)"
- option "E2E Test Hospital"
- generic [ref=e46]:
- generic [ref=e47]:
- img [ref=e48]
- text: Location Type
- generic [ref=e51]: "*"
- combobox [ref=e52]:
- option "Select Location Type" [selected]
- option "Outpatient (OP)"
- option "Inpatient (IP)"
- option "Emergency (ER)"
- option "General"
- generic [ref=e53]:
- generic [ref=e54]:
- img [ref=e55]
- text: Category
- generic [ref=e58]: (optional)
- combobox [ref=e59]:
- option "Select Category" [selected]
- option "Medical"
- option "Nursing"
- option "Administrative"
- option "Support Services"
- generic [ref=e60]:
- generic [ref=e61]:
- img [ref=e62]
- text: Description
- generic [ref=e65]: "*"
- textbox "Please describe what you observed in detail..." [ref=e66]
- paragraph [ref=e67]: Please describe what you observed in detail.
- generic [ref=e68]:
- generic [ref=e69]:
- img [ref=e70]
- text: Location Details
- generic [ref=e73]: (optional)
- textbox "e.g., Building A, Floor 2, Room 205" [ref=e74]
- generic [ref=e75]:
- generic [ref=e76]:
- img [ref=e77]
- text: When did this occur?
- textbox [ref=e79]: 2026-07-12T23:40:27
- generic [ref=e80]:
- generic [ref=e81]:
- img [ref=e82]
- text: Attachments
- generic [ref=e84]: (optional)
- generic [ref=e85] [cursor=pointer]:
- img [ref=e86]
- paragraph [ref=e89]: Click to upload files
- paragraph [ref=e90]: Images, PDF, Word, Excel (max 10MB each)
- generic [ref=e91]:
- generic [ref=e92]:
- img [ref=e93]
- text: Your Information
- generic [ref=e96]: (optional)
- paragraph [ref=e97]: Providing your information helps us follow up if needed. Leave blank to submit anonymously.
- generic [ref=e98]:
- generic [ref=e99]:
- generic [ref=e100]: Staff ID
- textbox "Your staff ID (optional)" [ref=e101]
- generic [ref=e102]:
- generic [ref=e103]: Name
- textbox "Your name (optional)" [ref=e104]
- generic [ref=e105]:
- generic [ref=e106]: Phone
- textbox "Your phone number (optional)" [ref=e107]
- generic [ref=e108]:
- generic [ref=e109]: Email
- textbox "Your email (optional)" [ref=e110]
- button "Submit Observation" [ref=e111] [cursor=pointer]:
- img [ref=e112]
- text: Submit Observation
- link "Track an existing observation" [ref=e116] [cursor=pointer]:
- /url: /observations/track/
- img [ref=e117]
- text: Track an existing observation
- contentinfo [ref=e120]:
- generic [ref=e121]:
- paragraph [ref=e122]: © 2026 Al Hammadi Hospital - PX360. All rights reserved.
- paragraph [ref=e123]: Your feedback helps us improve our services
- paragraph [ref=e124]:
- text: Powered by
- link "tenhal.sa" [ref=e125] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,133 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- generic [ref=e142]:
- link "Suggestions" [ref=e143] [cursor=pointer]:
- /url: /suggestions/
- img [ref=e144]
- generic [ref=e146]: New
- heading "New Suggestion" [level=1] [ref=e147]
- link "Back" [ref=e148] [cursor=pointer]:
- /url: /suggestions/
- img [ref=e149]
- text: Back
- generic [ref=e151]:
- generic [ref=e152]:
- img [ref=e154]
- heading "Suggestion Details" [level=5] [ref=e156]
- generic [ref=e157]:
- generic [ref=e158]:
- generic [ref=e159]:
- generic [ref=e160]: Your Name *
- textbox "Your full name" [ref=e161]
- generic [ref=e162]:
- generic [ref=e163]: Phone Number *
- textbox "Your phone number" [ref=e164]
- generic [ref=e165]:
- generic [ref=e166]: Hospital *
- combobox [ref=e167]:
- option "Select Hospital" [selected]
- option "Al Nuzha"
- option "Al Olya"
- option "Al Suwaidi"
- option "Al-Hammadi Hospital (KPI Test)"
- option "E2E Test Hospital"
- generic [ref=e168]:
- generic [ref=e169]: Message *
- textbox "Describe your suggestion in detail..." [ref=e170]
- button "Submit Suggestion" [ref=e172] [cursor=pointer]:
- img [ref=e173]
- text: Submit Suggestion
- contentinfo [ref=e176]:
- paragraph [ref=e177]:
- text: Powered by
- link "tenhal.sa" [ref=e178] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,260 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e141]:
- generic [ref=e142]:
- heading "New Appreciation" [level=1] [ref=e143]:
- img [ref=e144]
- text: New Appreciation
- paragraph [ref=e146]: Recognize a department for excellent service or care.
- link "Back" [ref=e147] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e148]
- text: Back
- generic [ref=e150]:
- generic [ref=e151]:
- generic [ref=e152]: Hospital *
- combobox "Hospital *" [ref=e153]:
- option "---------"
- option "Al Nuzha"
- option "Al Olya"
- option "Al Suwaidi"
- option "Al-Hammadi Hospital (KPI Test)"
- option "E2E Test Hospital" [selected]
- generic [ref=e154]:
- generic [ref=e155]: Department *
- combobox "Department *" [ref=e156]:
- option "---------" [selected]
- option "Anesthesia Department — Al Nuzha"
- option "Biomedical Department — Al Nuzha"
- option "Contact Center Department — Al Nuzha"
- option "Critical Care Department — Al Nuzha"
- option "Dental Department — Al Nuzha"
- option "Dermatology Department — Al Nuzha"
- option "Emergency Administrative Department — Al Nuzha"
- option "Emergency Medicine Department — Al Nuzha"
- option "Executive Administration — Al Nuzha"
- option "Facility Management & Maintenance Department — Al Nuzha"
- option "Financial Collection & Claims Department — Al Nuzha"
- option "Food Services Department — Al Nuzha"
- option "Housekeeping & Hospitality Department — Al Nuzha"
- option "IVF — Al Nuzha"
- option "Infection Control Department — Al Nuzha"
- option "Information Technology Department — Al Nuzha"
- option "Inpatient Department — Al Nuzha"
- option "Internal Medicine Department — Al Nuzha"
- option "Laboratory Department — Al Nuzha"
- option "Laundry Department — Al Nuzha"
- option "Medical Administration — Al Nuzha"
- option "Medical Ancillary Services Department — Al Nuzha"
- option "Medical Approvals Department — Al Nuzha"
- option "Medical Records Department — Al Nuzha"
- option "Nursing Department — Al Nuzha"
- option "ONCOLOGY Department — Al Nuzha"
- option "Obstetrics & Gynecology Department — Al Nuzha"
- option "Operating Rooms (OR) Department — Al Nuzha"
- option "Ophthalmology Department — Al Nuzha"
- option "Outpatient Department — Al Nuzha"
- option "Patient Affairs Department — Al Nuzha"
- option "Patient Experience Department — Al Nuzha"
- option "Pediatric Department — Al Nuzha"
- option "Pharmacy Department — Al Nuzha"
- option "Radiology Department — Al Nuzha"
- option "Security Department — Al Nuzha"
- option "Surgery Department — Al Nuzha"
- option "Facility Management & Maintenance Department — Al Olya"
- option "HR Department — Al Olya"
- option "Support Services Department — Al Olya"
- option "Anesthesia Department — Al Suwaidi"
- option "Biomedical Department — Al Suwaidi"
- option "Contact Center Department — Al Suwaidi"
- option "Critical Care Department — Al Suwaidi"
- option "Dental Department — Al Suwaidi"
- option "Dermatology Department — Al Suwaidi"
- option "Emergency Administrative Department — Al Suwaidi"
- option "Emergency Medicine Department — Al Suwaidi"
- option "Executive Administration — Al Suwaidi"
- option "Facility Management & Maintenance Department — Al Suwaidi"
- option "Financial Collection & Claims Department — Al Suwaidi"
- option "Food Services Department — Al Suwaidi"
- option "Housekeeping & Hospitality Department — Al Suwaidi"
- option "IVF — Al Suwaidi"
- option "Infection Control Department — Al Suwaidi"
- option "Information Technology Department — Al Suwaidi"
- option "Inpatient Department — Al Suwaidi"
- option "Internal Medicine Department — Al Suwaidi"
- option "Laboratory Department — Al Suwaidi"
- option "Laundry Department — Al Suwaidi"
- option "Medical Administration — Al Suwaidi"
- option "Medical Ancillary Services Department — Al Suwaidi"
- option "Medical Approvals Department — Al Suwaidi"
- option "Medical Records Department — Al Suwaidi"
- option "Nursing Department — Al Suwaidi"
- option "ONCOLOGY Department — Al Suwaidi"
- option "Obstetrics & Gynecology Department — Al Suwaidi"
- option "Operating Rooms (OR) Department — Al Suwaidi"
- option "Ophthalmology Department — Al Suwaidi"
- option "Outpatient Department — Al Suwaidi"
- option "Patient Affairs Department — Al Suwaidi"
- option "Patient Experience Department — Al Suwaidi"
- option "Pediatric Department — Al Suwaidi"
- option "Pharmacy Department — Al Suwaidi"
- option "Radiology Department — Al Suwaidi"
- option "Security Department — Al Suwaidi"
- option "Surgery Department — Al Suwaidi"
- option "Administration — Al-Hammadi Hospital (KPI Test)"
- option "Cardiology — Al-Hammadi Hospital (KPI Test)"
- option "Emergency — Al-Hammadi Hospital (KPI Test)"
- option "Housekeeping — Al-Hammadi Hospital (KPI Test)"
- option "ICU — Al-Hammadi Hospital (KPI Test)"
- option "Laboratory — Al-Hammadi Hospital (KPI Test)"
- option "Maintenance — Al-Hammadi Hospital (KPI Test)"
- option "Medical Reports — Al-Hammadi Hospital (KPI Test)"
- option "Nursing — Al-Hammadi Hospital (KPI Test)"
- option "Orthopedics — Al-Hammadi Hospital (KPI Test)"
- option "Pediatrics — Al-Hammadi Hospital (KPI Test)"
- option "Pharmacy — Al-Hammadi Hospital (KPI Test)"
- option "Reception — Al-Hammadi Hospital (KPI Test)"
- option "Security — Al-Hammadi Hospital (KPI Test)"
- option "Surgery — Al-Hammadi Hospital (KPI Test)"
- option "Anesthesia Department — E2E Test Hospital"
- option "Biomedical Department — E2E Test Hospital"
- option "Contact Center Department — E2E Test Hospital"
- option "Critical Care Department — E2E Test Hospital"
- option "Dental Department — E2E Test Hospital"
- option "Dermatology Department — E2E Test Hospital"
- option "E2E Dept 1781720032405 — E2E Test Hospital"
- option "E2E Dept 1781720188555 — E2E Test Hospital"
- option "Emergency Administrative Department — E2E Test Hospital"
- option "Emergency Medicine Department — E2E Test Hospital"
- option "Executive Administration — E2E Test Hospital"
- option "Facility Management & Maintenance Department — E2E Test Hospital"
- option "Financial Collection & Claims Department — E2E Test Hospital"
- option "Food Services Department — E2E Test Hospital"
- option "Housekeeping & Hospitality Department — E2E Test Hospital"
- option "IVF — E2E Test Hospital"
- option "Infection Control Department — E2E Test Hospital"
- option "Information Technology Department — E2E Test Hospital"
- option "Inpatient Department — E2E Test Hospital"
- option "Internal Medicine Department — E2E Test Hospital"
- option "Laboratory Department — E2E Test Hospital"
- option "Laundry Department — E2E Test Hospital"
- option "Medical Administration — E2E Test Hospital"
- option "Medical Ancillary Services Department — E2E Test Hospital"
- option "Medical Approvals Department — E2E Test Hospital"
- option "Medical Records Department — E2E Test Hospital"
- option "Nursing Department — E2E Test Hospital"
- option "ONCOLOGY Department — E2E Test Hospital"
- option "Obstetrics & Gynecology Department — E2E Test Hospital"
- option "Operating Rooms (OR) Department — E2E Test Hospital"
- option "Ophthalmology Department — E2E Test Hospital"
- option "Outpatient Department — E2E Test Hospital"
- option "Patient Affairs Department — E2E Test Hospital"
- option "Patient Experience Department — E2E Test Hospital"
- option "Pediatric Department — E2E Test Hospital"
- option "Pharmacy Department — E2E Test Hospital"
- option "Radiology Department — E2E Test Hospital"
- option "Security Department — E2E Test Hospital"
- option "Surgery Department — E2E Test Hospital"
- generic [ref=e157]:
- generic [ref=e158]: Message *
- textbox "Message *" [ref=e159]:
- /placeholder: Write your message of appreciation...
- generic [ref=e160]:
- button "Create Appreciation" [ref=e161] [cursor=pointer]:
- img [ref=e162]
- text: Create Appreciation
- link "Cancel" [ref=e164] [cursor=pointer]:
- /url: /appreciation/
- contentinfo [ref=e165]:
- paragraph [ref=e166]:
- text: Powered by
- link "tenhal.sa" [ref=e167] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,172 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Inquiries" [ref=e141] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e142]
- generic [ref=e144]: INQ-202607-0007
- generic [ref=e145]: Open
- generic [ref=e146]: Medium
- generic [ref=e147]:
- img [ref=e148]
- text: Not Sent to Dept
- generic [ref=e150]:
- heading "test12121212" [level=1] [ref=e151]
- generic [ref=e153]:
- img [ref=e154]
- text: 2026-07-12
- generic [ref=e157]:
- generic [ref=e158]:
- img [ref=e160]
- generic [ref=e162]: Created
- generic [ref=e164]:
- img [ref=e166]
- generic [ref=e169]: Assigned
- generic [ref=e171]:
- img [ref=e173]
- generic [ref=e176]: Sent to Dept
- generic [ref=e178]:
- img [ref=e180]
- generic [ref=e182]: Dept Response
- generic [ref=e184]:
- img [ref=e186]
- generic [ref=e189]: Resolved
- generic [ref=e190]:
- generic [ref=e191]:
- img [ref=e193]
- generic [ref=e195]:
- paragraph [ref=e196]: Activate this inquiry
- paragraph [ref=e197]: Activation moves it to In Progress so actions become available.
- generic [ref=e198]:
- button "Cancel" [ref=e200] [cursor=pointer]
- button "Activate" [ref=e202] [cursor=pointer]
- navigation [ref=e203]:
- button "Details" [ref=e204] [cursor=pointer]
- button "Timeline" [ref=e205] [cursor=pointer]
- button "Notes" [ref=e206] [cursor=pointer]
- main [ref=e207]:
- generic [ref=e209]:
- generic [ref=e210]:
- generic [ref=e211]:
- paragraph
- paragraph [ref=e212]: "231313131"
- paragraph
- generic [ref=e213]:
- generic [ref=e215]:
- img [ref=e216]
- generic [ref=e219]: AI Analysis
- generic [ref=e220]:
- paragraph [ref=e221]: "231313131"
- paragraph [ref=e222]: "231313131"
- generic [ref=e223]:
- heading "Patient" [level=3] [ref=e224]:
- img [ref=e225]
- text: Patient
- generic [ref=e228]:
- generic [ref=e229]: ismail mosa
- generic [ref=e230]: "|"
- generic [ref=e231]:
- img [ref=e232]
- text: "0569874563"
- generic [ref=e235]:
- heading "Quick Actions" [level=3] [ref=e236]
- generic [ref=e237]:
- button "Activate" [ref=e239] [cursor=pointer]:
- img [ref=e240]
- generic [ref=e243]: Activate
- button "Cancel Inquiry" [ref=e245] [cursor=pointer]:
- img [ref=e246]
- generic [ref=e250]: Cancel Inquiry
- contentinfo [ref=e251]:
- paragraph [ref=e252]:
- text: Powered by
- link "tenhal.sa" [ref=e253] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,243 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e140]:
- generic [ref=e141]:
- heading "Complaints Registry" [level=1] [ref=e142]
- paragraph [ref=e143]: Manage and monitor patient feedback in real-time
- generic [ref=e144]:
- generic [ref=e145]:
- img [ref=e146]
- textbox "Search ID, Name or Dept..." [ref=e149]
- link "Public Form" [ref=e150] [cursor=pointer]:
- /url: /core/public/submit/
- img [ref=e151]
- text: Public Form
- button "Export" [ref=e155] [cursor=pointer]:
- img [ref=e156]
- text: Export
- link "New Case" [ref=e159] [cursor=pointer]:
- /url: /complaints/new/
- img [ref=e160]
- text: New Case
- generic [ref=e161]:
- generic [ref=e162]:
- img [ref=e165]
- generic [ref=e169]:
- heading "Total Received" [level=6] [ref=e170]
- heading "3" [level=2] [ref=e171]
- generic [ref=e172]:
- img [ref=e175]
- generic [ref=e178]:
- heading "Resolved" [level=6] [ref=e179]
- heading "0 (0.0%)" [level=2] [ref=e180]
- generic [ref=e181]:
- img [ref=e184]
- generic [ref=e187]:
- heading "Pending" [level=6] [ref=e188]
- heading "3 (100.0%)" [level=2] [ref=e189]
- generic [ref=e190]:
- img [ref=e193]
- generic [ref=e195]:
- heading "TAT Alert" [level=6] [ref=e196]
- heading "3 (100.0%)" [level=2] [ref=e197]
- generic [ref=e198]:
- generic [ref=e199]:
- generic [ref=e200]:
- img [ref=e202]
- heading "Complaints" [level=5] [ref=e204]
- generic [ref=e205]:
- link "All Cases" [ref=e206] [cursor=pointer]:
- /url: "?"
- link "Pending" [ref=e207] [cursor=pointer]:
- /url: "?status=open"
- link "In Progress" [ref=e208] [cursor=pointer]:
- /url: "?status=in_progress"
- link "Resolved" [ref=e209] [cursor=pointer]:
- /url: "?status=resolved"
- button "Advanced" [ref=e211] [cursor=pointer]:
- img [ref=e212]
- text: Advanced
- paragraph [ref=e213]: "Showing: 1-3 of 3"
- generic [ref=e214]:
- table [ref=e215]:
- rowgroup [ref=e216]:
- row "Complaint ID Patient Name Description Source Department Status SLA Severity Priority Assigned Actions" [ref=e217]:
- columnheader "Complaint ID" [ref=e218]
- columnheader "Patient Name" [ref=e219]
- columnheader "Description" [ref=e220]
- columnheader "Source" [ref=e221]
- columnheader "Department" [ref=e222]
- columnheader "Status" [ref=e223]
- columnheader "SLA" [ref=e224]
- columnheader "Severity" [ref=e225]
- columnheader "Priority" [ref=e226]
- columnheader "Assigned" [ref=e227]
- columnheader "Actions" [ref=e228]
- rowgroup [ref=e229]:
- row "CMP-202607-0006 test شكوى عامة Patient Patient Experience Department In Progress Overdue 21h 47m Medium Medium test test" [ref=e230] [cursor=pointer]:
- cell "CMP-202607-0006" [ref=e231]
- cell "test" [ref=e232]:
- generic [ref=e233]: test
- cell "شكوى عامة" [ref=e234]:
- generic [ref=e235]: شكوى عامة
- button "View description" [ref=e236]:
- img [ref=e237]
- cell "Patient" [ref=e246]
- cell "Patient Experience Department" [ref=e247]
- cell "In Progress" [ref=e248]
- cell "Overdue 21h 47m" [ref=e249]
- cell "Medium" [ref=e250]
- cell "Medium" [ref=e251]
- cell "test test" [ref=e252]
- cell [ref=e253]:
- generic [ref=e254]:
- link "View" [ref=e255]:
- /url: /complaints/f8aa6216-bb94-455e-ba18-d16a01024662/
- img [ref=e256]
- link "Assign" [ref=e259]:
- /url: /complaints/f8aa6216-bb94-455e-ba18-d16a01024662/assign/
- img [ref=e260]
- row "CMP-202607-0005 ismail شكوى اختبار Patient Contact Center Department In Progress Overdue 23h 22m Low Low test test" [ref=e263] [cursor=pointer]:
- cell "CMP-202607-0005" [ref=e264]
- cell "ismail" [ref=e265]:
- generic [ref=e266]: ismail
- cell "شكوى اختبار" [ref=e267]:
- generic [ref=e268]: شكوى اختبار
- button "View description" [ref=e269]:
- img [ref=e270]
- cell "Patient" [ref=e279]
- cell "Contact Center Department" [ref=e280]
- cell "In Progress" [ref=e281]
- cell "Overdue 23h 22m" [ref=e282]
- cell "Low" [ref=e283]
- cell "Low" [ref=e284]
- cell "test test" [ref=e285]
- cell [ref=e286]:
- link "View" [ref=e288]:
- /url: /complaints/9ca01d52-712d-4bca-b30d-6f1f818ff839/
- img [ref=e289]
- row "CMP-202607-0004 OVR ismail اختبار عام Internal Contact Center Department In Progress Overdue 4d 7h 22m Low Low ismail mosa" [ref=e292] [cursor=pointer]:
- cell "CMP-202607-0004 OVR" [ref=e293]
- cell "ismail" [ref=e294]:
- generic [ref=e295]: ismail
- cell "اختبار عام" [ref=e296]:
- generic [ref=e297]: اختبار عام
- button "View description" [ref=e298]:
- img [ref=e299]
- cell "Internal" [ref=e308]
- cell "Contact Center Department" [ref=e309]
- cell "In Progress" [ref=e310]
- cell "Overdue 4d 7h 22m" [ref=e311]
- cell "Low" [ref=e312]
- cell "Low" [ref=e313]
- cell "ismail mosa" [ref=e314]
- cell [ref=e315]:
- link "View" [ref=e317]:
- /url: /complaints/1c4e8dde-48d8-42ab-8c8d-4ada8314fc40/
- img [ref=e318]
- generic [ref=e321]:
- generic [ref=e322]:
- generic [ref=e323]: Showing 1-3 of 3
- generic [ref=e324]:
- generic [ref=e325]: Show
- combobox [ref=e326]:
- option "10"
- option "25" [selected]
- option "50"
- option "100"
- generic [ref=e327]:
- img [ref=e329]
- generic [ref=e331]: "1"
- img [ref=e333]
- contentinfo [ref=e335]:
- paragraph [ref=e336]:
- text: Powered by
- link "tenhal.sa" [ref=e337] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,259 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Observations" [ref=e141] [cursor=pointer]:
- /url: /observations/
- img [ref=e142]
- generic [ref=e144]: OBS-202607-0001
- generic [ref=e145]: In Progress
- generic [ref=e146]: Medium
- generic [ref=e147]:
- img [ref=e148]
- text: Not Sent to Dept
- heading "test observation" [level=1] [ref=e151]
- generic [ref=e153]:
- generic [ref=e154]:
- img [ref=e156]
- generic [ref=e158]: Created
- generic [ref=e160]:
- img [ref=e162]
- generic [ref=e164]: Assigned
- generic [ref=e166]:
- img [ref=e168]
- generic [ref=e171]: Sent to Dept
- generic [ref=e173]:
- img [ref=e175]
- generic [ref=e177]: Dept Response
- generic [ref=e179]:
- img [ref=e181]
- generic [ref=e184]: Resolved
- navigation [ref=e185]:
- button "Details" [ref=e186] [cursor=pointer]
- button "Timeline" [ref=e187] [cursor=pointer]
- button "RCA" [ref=e188] [cursor=pointer]
- button "Notes" [ref=e189] [cursor=pointer]
- main [ref=e190]:
- generic [ref=e192]:
- generic [ref=e193]:
- paragraph [ref=e195]: test observation
- generic [ref=e196]:
- generic [ref=e197]:
- paragraph [ref=e198]: Category
- paragraph [ref=e199]: "-"
- generic [ref=e200]:
- paragraph [ref=e201]: Location
- paragraph [ref=e202]: "-"
- generic [ref=e203]:
- paragraph [ref=e204]: Incident Date
- paragraph [ref=e205]: 08 Jul 2026
- generic [ref=e209]:
- img [ref=e210]
- paragraph [ref=e214]: No department assigned to this observation
- generic [ref=e215]:
- generic [ref=e216]:
- heading "Quick Actions" [level=3] [ref=e217]
- generic [ref=e218]:
- button "Respond to Patient" [ref=e219] [cursor=pointer]:
- img [ref=e220]
- generic [ref=e222]: Respond to Patient
- button "Reassign" [ref=e223] [cursor=pointer]:
- img [ref=e224]
- generic [ref=e227]: Reassign
- button "Send to Dept" [ref=e228] [cursor=pointer]:
- img [ref=e229]
- generic [ref=e232]: Send to Dept
- button "Resolve" [ref=e234] [cursor=pointer]:
- img [ref=e235]
- generic [ref=e238]: Resolve
- link "RCA" [ref=e239] [cursor=pointer]:
- /url: /rca/create/?related_model=observation&related_id=1d925706-110e-4bad-808f-26aae2029144
- img [ref=e240]
- generic [ref=e243]: RCA
- link "QI Project" [ref=e244] [cursor=pointer]:
- /url: /projects/create/?related_model=observation&related_id=1d925706-110e-4bad-808f-26aae2029144
- img [ref=e245]
- generic [ref=e247]: QI Project
- button "Delete" [ref=e249] [cursor=pointer]:
- img [ref=e250]
- generic [ref=e253]: Delete
- generic [ref=e254]:
- heading "Assignment" [level=3] [ref=e255]:
- img [ref=e256]
- text: Assignment
- list [ref=e261]:
- listitem [ref=e262]:
- generic [ref=e263]: Department
- generic [ref=e264]: Contact Center Department
- listitem [ref=e265]:
- generic [ref=e266]: Assigned To
- generic [ref=e267]: ismail mosa
- generic [ref=e268]:
- heading "Linked PX Action" [level=3] [ref=e269]:
- img [ref=e270]
- text: Linked PX Action
- paragraph [ref=e273]: Observation OBS-202607-0001
- link "View Action" [ref=e274] [cursor=pointer]:
- /url: /actions/9b185c2a-e3fe-4e13-a727-7aab21df8754/
- img [ref=e275]
- text: View Action
- generic [ref=e277]:
- heading "Triage" [level=3] [ref=e278]:
- img [ref=e279]
- text: Triage
- generic [ref=e280]:
- generic [ref=e281]:
- generic [ref=e282]: Department
- combobox [ref=e283]:
- option "Select department"
- option "Contact Center Department" [selected]
- option "Emergency Administrative Department"
- option "Executive Administration"
- option "Financial Collection & Claims Department"
- option "Inpatient Department"
- option "Medical Approvals Department"
- option "Medical Records Department"
- option "Outpatient Department"
- option "Patient Affairs Department"
- option "Patient Experience Department"
- option "Security Department"
- option "Anesthesia Department"
- option "Critical Care Department"
- option "Dental Department"
- option "Dermatology Department"
- option "Emergency Medicine Department"
- option "IVF"
- option "Infection Control Department"
- option "Internal Medicine Department"
- option "Laboratory Department"
- option "Medical Administration"
- option "Medical Ancillary Services Department"
- option "ONCOLOGY Department"
- option "Obstetrics & Gynecology Department"
- option "Operating Rooms (OR) Department"
- option "Ophthalmology Department"
- option "Pediatric Department"
- option "Pharmacy Department"
- option "Radiology Department"
- option "Surgery Department"
- option "E2E Dept 1781720032405"
- option "E2E Dept 1781720188555"
- option "Nursing Department"
- option "Biomedical Department"
- option "Facility Management & Maintenance Department"
- option "Food Services Department"
- option "Housekeeping & Hospitality Department"
- option "Information Technology Department"
- option "Laundry Department"
- generic [ref=e284]:
- generic [ref=e285]: Assign To
- combobox [ref=e286]:
- option "Select assignee" [selected]
- option "E2E PX Employee (e2e-px-employee@px360.test)"
- option "test test (9m5s6gzmft@ruutukf.com)"
- option "test test (esma3el555@gmail.com)"
- generic [ref=e287]:
- generic [ref=e288]: Status
- combobox [ref=e289]:
- option "Open"
- option "In Progress" [selected]
- option "Resolved"
- option "Closed"
- generic [ref=e290]:
- generic [ref=e291]: Note
- textbox "Add a note about this triage action (optional)..." [ref=e292]
- button "Update" [ref=e293] [cursor=pointer]:
- img [ref=e294]
- text: Update
- contentinfo [ref=e297]:
- paragraph [ref=e298]:
- text: Powered by
- link "tenhal.sa" [ref=e299] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,188 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Suggestions" [ref=e141] [cursor=pointer]:
- /url: /suggestions/
- img [ref=e142]
- generic [ref=e144]: test
- generic [ref=e145]: Submitted
- generic [ref=e146]: Medium
- generic [ref=e147]:
- heading "Suggestion Detail" [level=1] [ref=e148]
- link "Edit" [ref=e150] [cursor=pointer]:
- /url: /suggestions/04b009f3-53aa-4eac-9baf-39b3633e56b0/update/
- img [ref=e151]
- text: Edit
- generic [ref=e155]:
- generic [ref=e156]:
- img [ref=e158]
- generic [ref=e160]: Submitted
- generic [ref=e162]:
- img [ref=e164]
- generic [ref=e167]: Assigned
- generic [ref=e169]:
- img [ref=e171]
- generic [ref=e174]: Reviewed
- generic [ref=e176]:
- img [ref=e178]
- generic [ref=e180]: Acknowledged
- generic [ref=e182]:
- img [ref=e184]
- generic [ref=e187]: Closed
- navigation [ref=e188]:
- button "Details" [ref=e189] [cursor=pointer]
- button "AI Analysis" [ref=e190] [cursor=pointer]
- button "Notes" [ref=e191] [cursor=pointer]
- main [ref=e192]:
- generic [ref=e195]:
- heading "Suggestion Details" [level=3] [ref=e196]:
- img [ref=e197]
- text: Suggestion Details
- paragraph [ref=e200]: test
- generic [ref=e201]:
- generic [ref=e202]:
- generic [ref=e203]: Hospital
- generic [ref=e204]: E2E Test Hospital
- generic [ref=e205]:
- generic [ref=e206]: Category
- generic [ref=e207]: Other
- generic [ref=e208]:
- generic [ref=e209]: Location
- generic [ref=e210]: Not specified
- generic [ref=e211]:
- generic [ref=e212]: Sentiment
- generic [ref=e214]: Neutral
- generic [ref=e215]:
- heading "Contact" [level=6] [ref=e216]
- generic [ref=e217]:
- generic [ref=e218]: ismail
- generic [ref=e219]: "0569874589"
- generic [ref=e221]:
- img [ref=e222]
- text: July 09, 2026 13:46
- generic [ref=e225]:
- heading "Quick Actions" [level=3] [ref=e226]
- generic [ref=e227]:
- generic [ref=e228]:
- text: Status
- combobox [ref=e229]:
- option "Submitted" [selected]
- option "Reviewed"
- option "Acknowledged"
- option "Closed"
- option "Reopened"
- textbox "Add a note..." [ref=e230]
- button "Update" [ref=e231] [cursor=pointer]:
- img [ref=e232]
- text: Update
- separator [ref=e237]
- generic [ref=e238]:
- text: Assign To
- combobox [ref=e239]:
- option "Select user..." [selected]
- option "E2E PX Employee"
- option "test test"
- option "test test"
- button "Assign" [ref=e240] [cursor=pointer]:
- img [ref=e241]
- text: Assign
- separator [ref=e245]
- generic [ref=e246]:
- text: Department
- button "Send to Department" [ref=e247] [cursor=pointer]:
- img [ref=e248]
- text: Send to Department
- separator [ref=e251]
- contentinfo [ref=e252]:
- paragraph [ref=e253]:
- text: Powered by
- link "tenhal.sa" [ref=e254] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -0,0 +1,137 @@
- generic [ref=e2]:
- complementary [ref=e3]:
- generic [ref=e4]:
- img "Al Hammadi" [ref=e6]
- img "Al Hammadi" [ref=e8]
- navigation [ref=e9]:
- link [ref=e10] [cursor=pointer]:
- /url: /analytics/dashboard/
- img [ref=e11]
- link [ref=e16] [cursor=pointer]:
- /url: /projects/my-tasks/
- img [ref=e17]
- link [ref=e20] [cursor=pointer]:
- /url: /
- img [ref=e21]
- link [ref=e23] [cursor=pointer]:
- /url: /complaints/
- img [ref=e24]
- link [ref=e27] [cursor=pointer]:
- /url: /inquiries/
- img [ref=e28]
- link [ref=e31] [cursor=pointer]:
- /url: /observations/
- img [ref=e32]
- link [ref=e35] [cursor=pointer]:
- /url: /suggestions/?feedback_type=suggestion
- img [ref=e36]
- link [ref=e38] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e39]
- link [ref=e41] [cursor=pointer]:
- /url: /organizations/patients/
- img [ref=e42]
- button [ref=e46] [cursor=pointer]:
- img [ref=e47]
- button [ref=e52] [cursor=pointer]:
- img [ref=e53]
- link [ref=e58] [cursor=pointer]:
- /url: /organizations/departments/
- img [ref=e59]
- link [ref=e63] [cursor=pointer]:
- /url: /projects/
- img [ref=e64]
- button [ref=e69] [cursor=pointer]:
- img [ref=e70]
- button [ref=e74] [cursor=pointer]:
- img [ref=e75]
- button [ref=e80] [cursor=pointer]:
- img [ref=e81]
- button [ref=e88] [cursor=pointer]:
- img [ref=e89]
- button [ref=e93] [cursor=pointer]:
- img [ref=e94]
- link [ref=e96] [cursor=pointer]:
- /url: /accounts/acknowledgements/my-acknowledgements/
- img [ref=e97]
- button [ref=e103] [cursor=pointer]:
- img [ref=e104]
- generic [ref=e107]:
- link "🇬🇧" [ref=e108] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e109]: 🇬🇧
- link "🇸🇦" [ref=e110] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e111]: 🇸🇦
- generic [ref=e112]:
- banner [ref=e113]:
- generic [ref=e114]:
- heading "Welcome, E2E" [level=2] [ref=e115]
- paragraph [ref=e116]: Welcome to PX360 Patient Experience Management
- generic [ref=e117]:
- button [ref=e119] [cursor=pointer]:
- img [ref=e120]
- generic [ref=e123]:
- link "🇬🇧 English" [ref=e124] [cursor=pointer]:
- /url: /core/set-language/?language=en
- generic [ref=e125]: 🇬🇧
- generic [ref=e126]: English
- link "🇸🇦 Arabic" [ref=e127] [cursor=pointer]:
- /url: /core/set-language/?language=ar
- generic [ref=e128]: 🇸🇦
- generic [ref=e129]: Arabic
- button "E2E PX Employee PX Employee E" [ref=e131] [cursor=pointer]:
- generic [ref=e132]:
- generic [ref=e133]: E2E PX Employee
- generic [ref=e134]: PX Employee
- generic [ref=e135]: E
- img [ref=e136]
- main [ref=e138]:
- generic [ref=e139]:
- generic [ref=e140]:
- link "Appreciations" [ref=e141] [cursor=pointer]:
- /url: /appreciation/
- img [ref=e142]
- generic [ref=e144]: Detail
- generic [ref=e145]: Sent
- generic [ref=e146]:
- heading "Appreciation Detail" [level=1] [ref=e147]:
- img [ref=e148]
- text: Appreciation Detail
- generic [ref=e151]: 2026-07-09 13:55 — E2E Test Hospital
- navigation [ref=e152]:
- button "Details" [ref=e153] [cursor=pointer]
- button "AI Analysis" [ref=e154] [cursor=pointer]
- button "Notes" [ref=e155] [cursor=pointer]
- main [ref=e156]:
- generic [ref=e158]:
- generic [ref=e159]:
- heading "Appreciation Message" [level=3] [ref=e160]:
- img [ref=e161]
- text: Appreciation Message
- paragraph
- paragraph [ref=e163]: thank
- paragraph
- generic [ref=e164]:
- heading "Submitted By" [level=3] [ref=e165]:
- img [ref=e166]
- text: Submitted By
- generic [ref=e169]:
- generic [ref=e170]:
- paragraph [ref=e171]: Name
- paragraph [ref=e172]: ismail
- generic [ref=e173]:
- paragraph [ref=e174]: Phone
- paragraph [ref=e175]: "0569874589"
- generic [ref=e176]:
- heading "Sent" [level=3] [ref=e177]:
- img [ref=e178]
- text: Sent
- generic [ref=e182]:
- paragraph [ref=e183]: Sent At
- paragraph [ref=e184]: 2026-07-09 13:57
- contentinfo [ref=e186]:
- paragraph [ref=e187]:
- text: Powered by
- link "tenhal.sa" [ref=e188] [cursor=pointer]:
- /url: https://tenhal.sa

View File

@ -1 +1 @@
hh-dev:hh-dev-build-1783592335|1783592388 hh-dev:hh-dev-build-1784025225|1784026493

View File

@ -207,4 +207,4 @@ META_APP_SECRET = "b80750bd12ab7f1c21d7d0ca891ba5ab"
META_REDIRECT_URI = "https://micha-nonparabolic-lovie.ngrok-free.dev/social/callback/META/" META_REDIRECT_URI = "https://micha-nonparabolic-lovie.ngrok-free.dev/social/callback/META/"
META_WEBHOOK_VERIFY_TOKEN = "random_secret_string_khanfaheed123456" META_WEBHOOK_VERIFY_TOKEN = "random_secret_string_khanfaheed123456"
EMAIL_LOGO_URL = "http://127.0.0.1:8000/static/img/HH_R_H_Logo.png" EMAIL_LOGO_URL = "http://127.0.0.1:8000/static/images/logos/HH_P_H_Logo_CMYK---LANDSCIP1.png"

View File

@ -1,5 +1,6 @@
"""Forms for the appreciation module.""" """Forms for the appreciation module."""
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _
from apps.appreciation.models import Appreciation from apps.appreciation.models import Appreciation
from apps.organizations.models import Department from apps.organizations.models import Department
@ -8,13 +9,25 @@ from apps.organizations.models import Department
class AppreciationForm(forms.ModelForm): class AppreciationForm(forms.ModelForm):
"""Internal form for creating an appreciation (department-targeted).""" """Internal form for creating an appreciation (department-targeted)."""
national_id = forms.CharField(
label=_("National ID/Iqama No."),
max_length=10,
required=True,
widget=forms.TextInput(attrs={
"class": "w-full px-4 py-3 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-navy/20 outline-none",
"placeholder": _("National ID or Iqama number"),
"inputmode": "numeric",
"pattern": "[0-9]{10}",
"maxlength": "10",
"minlength": "10",
}),
help_text=_("Exactly 10 digits, numbers only."),
)
class Meta: class Meta:
model = Appreciation model = Appreciation
fields = ["hospital", "department", "message_en"] fields = ["department", "message_en", "national_id"]
widgets = { widgets = {
"hospital": forms.Select(
attrs={"class": "w-full px-4 py-3 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-navy/20 outline-none"}
),
"message_en": forms.Textarea( "message_en": forms.Textarea(
attrs={ attrs={
"rows": 5, "rows": 5,
@ -27,14 +40,21 @@ class AppreciationForm(forms.ModelForm):
), ),
} }
def clean_national_id(self):
national_id = self.cleaned_data.get("national_id", "").replace(" ", "")
if not national_id.isdigit():
raise forms.ValidationError(_("National ID/Iqama must contain digits only."))
if len(national_id) != 10:
raise forms.ValidationError(_("National ID/Iqama must be exactly 10 digits (you entered %(n)d).") % {"n": len(national_id)})
return national_id
def __init__(self, *args, hospital=None, **kwargs): def __init__(self, *args, hospital=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if hospital: if hospital:
self.fields["hospital"].initial = hospital dept_qs = Department.objects.filter(hospital=hospital, status="active").order_by("name")
dept_qs = Department.objects.filter(status="active").select_related("hospital").order_by( else:
"hospital__name", "name" dept_qs = Department.objects.filter(status="active").select_related("hospital").order_by("name")
) self.fields["department"].label_from_instance = lambda obj: (
f"{obj.name}{obj.hospital.name}" if obj.hospital else obj.name
)
self.fields["department"].queryset = dept_qs self.fields["department"].queryset = dept_qs
self.fields["department"].label_from_instance = lambda obj: (
f"{obj.name}{obj.hospital.name}" if obj.hospital else obj.name
)

View File

@ -0,0 +1,23 @@
# Generated by Django 6.0.1 on 2026-07-13 16:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('appreciation', '0006_appreciation_reference_number_and_more'),
]
operations = [
migrations.AddField(
model_name='appreciation',
name='dept_response_reminder_sent_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='appreciation',
name='dept_response_second_reminder_sent_at',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -0,0 +1,21 @@
from django.db import migrations
def convert_ai_analyzed_to_activated(apps, schema_editor):
Appreciation = apps.get_model("appreciation", "Appreciation")
Appreciation.objects.filter(status="ai_analyzed").update(status="activated")
def reverse_conversion(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
("appreciation", "0007_add_reminder_fields"),
]
operations = [
migrations.RunPython(convert_ai_analyzed_to_activated, reverse_conversion),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 6.0.1 on 2026-07-18 15:10
import apps.core.encryption
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('appreciation', '0008_remove_ai_analyzed_status'),
]
operations = [
migrations.AddField(
model_name='appreciation',
name='national_id',
field=apps.core.encryption.EncryptedCharField(blank=True, help_text='National ID/Iqama of the submitter', max_length=512),
),
migrations.AddField(
model_name='appreciation',
name='national_id_hash',
field=models.CharField(blank=True, db_index=True, max_length=64),
),
]

View File

@ -12,6 +12,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelatio
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from apps.core.encryption import EncryptedCharField, compute_national_id_hash
from apps.core.models import SoftDeleteModel, TimeStampedModel, UUIDModel from apps.core.models import SoftDeleteModel, TimeStampedModel, UUIDModel
@ -220,6 +221,14 @@ class Appreciation(UUIDModel, TimeStampedModel, SoftDeleteModel):
notification_sent = models.BooleanField(default=False) notification_sent = models.BooleanField(default=False)
notification_sent_at = models.DateTimeField(null=True, blank=True) notification_sent_at = models.DateTimeField(null=True, blank=True)
# Reminder tracking
dept_response_reminder_sent_at = models.DateTimeField(null=True, blank=True)
dept_response_second_reminder_sent_at = models.DateTimeField(null=True, blank=True)
# Submitter identity
national_id = EncryptedCharField(max_length=512, blank=True, help_text="National ID/Iqama of the submitter")
national_id_hash = models.CharField(max_length=64, blank=True, db_index=True)
# Metadata # Metadata
metadata = models.JSONField(default=dict, blank=True) metadata = models.JSONField(default=dict, blank=True)
@ -245,6 +254,10 @@ class Appreciation(UUIDModel, TimeStampedModel, SoftDeleteModel):
from apps.core.reference import generate_reference from apps.core.reference import generate_reference
self.reference_number = generate_reference("APR", self.hospital) self.reference_number = generate_reference("APR", self.hospital)
if self.national_id:
self.national_id_hash = compute_national_id_hash(self.national_id)
else:
self.national_id_hash = ""
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_owner(self): def get_owner(self):
@ -320,20 +333,16 @@ class Appreciation(UUIDModel, TimeStampedModel, SoftDeleteModel):
def mark_ai_analyzed(self, analysis_data): def mark_ai_analyzed(self, analysis_data):
from django.utils import timezone from django.utils import timezone
if self.status != AppreciationStatus.ACTIVATED:
raise ValueError(f"Cannot mark as AI analyzed from '{self.status}' status. Must be in 'activated'.")
self.status = AppreciationStatus.AI_ANALYZED
self.ai_analyzed_at = timezone.now() self.ai_analyzed_at = timezone.now()
self.ai_analysis = analysis_data self.ai_analysis = analysis_data
self.save(update_fields=["status", "ai_analyzed_at", "ai_analysis"]) self.save(update_fields=["ai_analyzed_at", "ai_analysis"])
def send(self): def send(self):
"""Send appreciation and trigger notification""" """Send appreciation and trigger notification"""
from django.utils import timezone from django.utils import timezone
if self.status not in (AppreciationStatus.ACTIVATED, AppreciationStatus.AI_ANALYZED): if self.status not in (AppreciationStatus.ACTIVATED,):
raise ValueError(f"Cannot send appreciation in '{self.status}' status. Must be 'activated' or 'ai_analyzed'.") raise ValueError(f"Cannot send appreciation in '{self.status}' status. Must be 'activated'.")
self.status = AppreciationStatus.SENT self.status = AppreciationStatus.SENT
self.sent_at = timezone.now() self.sent_at = timezone.now()

View File

@ -15,10 +15,17 @@ def send_appreciation_notifications(
email_body="", email_body="",
host="", host="",
): ):
"""Send email + SMS to champion, manager, and appreciated staff in the background. """Send the appreciation email to the appreciated staff member with the
department champion and manager CC'd.
Replaces the synchronous notification loop that used to block the Behaviour:
appreciation_send_to response. - If ``staff_id`` is provided AND the staff member has an email: ONE email
is sent To: the staff member with champion + manager as CC: (deduplicated).
- If no staff member is selected (or the staff has no email): separate
emails are sent To: each of the champion and manager (current behaviour).
- In-app notifications are created for every recipient (To and CC) whose
email maps to a User account.
- SMS notifications are NOT sent (email-only flow).
""" """
from apps.appreciation.models import Appreciation from apps.appreciation.models import Appreciation
from apps.notifications.services import NotificationService, get_email_header_html from apps.notifications.services import NotificationService, get_email_header_html
@ -40,20 +47,95 @@ def send_appreciation_notifications(
link = f"https://{host}/appreciation/detail/{appreciation.pk}/" link = f"https://{host}/appreciation/detail/{appreciation.pk}/"
notified = [] notified = []
# Notify champion + manager # Generate the appreciation letter PDF once and reuse for all email notifications.
for target in targets: # Wrapped in try/except so email delivery still succeeds if PDF rendering fails.
display_name = ( pdf_attachment = None
target["staff"].get_full_name() if target.get("staff") try:
else (target["user"].get_full_name() if target.get("user") else target["label"]) from apps.appreciation.ui_views import _render_appreciation_pdf_bytes, _appreciation_pdf_context
ctx = _appreciation_pdf_context(appreciation)
pdf_bytes = _render_appreciation_pdf_bytes(
"appreciation/appreciation_letter.html", ctx, obj=appreciation,
) )
if target.get("email"): pdf_attachment = (
f"appreciation_letter_{appreciation.reference_number}.pdf",
pdf_bytes,
"application/pdf",
)
except Exception:
logger.exception(
f"Failed to generate appreciation letter PDF for {appreciation_id}; "
f"sending notifications without attachment"
)
# Build the champion + manager email list (in order: champion first, then manager)
target_emails_raw = [t["email"] for t in targets if t.get("email")]
# Resolve the appreciated staff member (if any) and their email
staff_email = None
appreciated_staff = None
if staff_id:
try:
appreciated_staff = Staff.objects.get(id=staff_id)
s_email, _ = _staff_contact(appreciated_staff)
staff_email = s_email or None
except Staff.DoesNotExist:
appreciated_staff = None
# Build the deduplicated CC list (exclude the staff member's own email)
seen_lower = {staff_email.lower()} if staff_email else set()
cc_emails = []
for email in target_emails_raw:
key = email.lower()
if key not in seen_lower:
seen_lower.add(key)
cc_emails.append(email)
attachments_arg = [pdf_attachment] if pdf_attachment else None
if staff_email:
# CASE 1 (primary): ONE email to the staff member, CC champion + manager.
send_subject = email_subject or f"An Appreciation Has Been Submitted About You - {appreciation.reference_number}"
send_body = email_body or (
f"An appreciation (#{appreciation.reference_number}) has been submitted about you."
)
try:
NotificationService.send_email(
email=staff_email,
subject=send_subject,
message=send_body + f"\n\n{link}",
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">You've Been Appreciated!</h2>
<p>An appreciation <strong>#{appreciation.reference_number}</strong> has been submitted about you.</p>
<p><strong>Message:</strong> {appreciation.message_en or 'N/A'}</p>
{f'<p><strong>Note:</strong> {note}</p>' if note else ''}
<p><a href="{link}">View Appreciation</a></p>
</div>
</div>
""",
related_object=appreciation,
attachments=attachments_arg,
cc=cc_emails,
)
notified.append(staff_email)
for cc_addr in cc_emails:
notified.append(f"CC: {cc_addr}")
except Exception:
logger.exception(f"Failed to send staff email to {staff_email} with CC {cc_emails}")
elif cc_emails:
# CASE 2 (fallback): no specific staff — separate emails to champion + manager.
send_subject = email_subject or f"Appreciation Sent to Department - {appreciation.reference_number}"
send_body = email_body or (
f"Appreciation #{appreciation.reference_number} has been sent to your department ({department.name})."
)
for email in cc_emails:
try: try:
send_subject = email_subject or f"Appreciation Sent to Department - {appreciation.reference_number}"
send_body = email_body or (
f"Appreciation #{appreciation.reference_number} has been sent to your department ({department.name})."
)
NotificationService.send_email( NotificationService.send_email(
email=target["email"], email=email,
subject=send_subject, subject=send_subject,
message=send_body + f"\n\n{link}", message=send_body + f"\n\n{link}",
html_message=f""" html_message=f"""
@ -64,67 +146,57 @@ def send_appreciation_notifications(
<p>Appreciation <strong>#{appreciation.reference_number}</strong> has been sent to your department <strong>({department.name})</strong>.</p> <p>Appreciation <strong>#{appreciation.reference_number}</strong> has been sent to your department <strong>({department.name})</strong>.</p>
<p><strong>Message:</strong> {appreciation.message_en or 'N/A'}</p> <p><strong>Message:</strong> {appreciation.message_en or 'N/A'}</p>
{f'<p><strong>Note:</strong> {note}</p>' if note else ''} {f'<p><strong>Note:</strong> {note}</p>' if note else ''}
<p><strong>Role:</strong> {target['label']}</p>
<p><a href="{link}">View Appreciation</a></p> <p><a href="{link}">View Appreciation</a></p>
</div> </div>
</div> </div>
""", """,
related_object=appreciation, related_object=appreciation,
attachments=attachments_arg,
) )
notified.append(email)
except Exception: except Exception:
logger.exception(f"Failed to send email to {target.get('email')}") logger.exception(f"Failed to send department email to {email}")
if target.get("phone"):
try:
NotificationService.send_sms(
target["phone"],
f"PX360: Appreciation #{appreciation.reference_number} sent to {department.name}. {link}",
related_object=appreciation,
)
except Exception:
pass
notified.append(f"{display_name} ({target['label']})")
# Notify the appreciated staff member else:
if staff_id: logger.warning(
try: f"Appreciation {appreciation_id}: no recipients to notify "
appreciated_staff = Staff.objects.get(id=staff_id) f"(no staff email and no champion/manager emails)"
except Staff.DoesNotExist: )
appreciated_staff = None
if appreciated_staff:
s_email, s_phone = _staff_contact(appreciated_staff)
s_name = appreciated_staff.get_full_name()
if s_email:
try:
NotificationService.send_email(
email=s_email,
subject=f"An Appreciation Has Been Submitted About You - {appreciation.reference_number}",
message=f"An appreciation (#{appreciation.reference_number}) has been submitted about you.\n\nMessage: {appreciation.message_en or 'N/A'}\n\nView: {link}",
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">You've Been Appreciated!</h2>
<p>An appreciation <strong>#{appreciation.reference_number}</strong> has been submitted about you.</p>
<p><strong>Message:</strong> {appreciation.message_en or 'N/A'}</p>
{f'<p><strong>Note:</strong> {note}</p>' if note else ''}
<p><a href="{link}">View Appreciation</a></p>
</div>
</div>
""",
related_object=appreciation,
)
notified.append(f"{s_name} (Staff)")
except Exception:
logger.exception(f"Failed to send email to staff {s_email}")
if s_phone:
try:
NotificationService.send_sms(
s_phone,
f"PX360: An appreciation has been submitted about you. {link}",
related_object=appreciation,
)
except Exception:
pass
logger.info(f"Appreciation {appreciation_id} notifications sent to: {', '.join(notified)}") logger.info(f"Appreciation {appreciation_id} notifications sent to: {', '.join(notified)}")
@shared_task
def send_appreciation_reminder_email(appreciation_id, reminder_type, recipients):
"""Send a reminder email to department champion and manager for an appreciation."""
import logging
logger = logging.getLogger(__name__)
try:
from apps.appreciation.models import Appreciation
from apps.notifications.services import NotificationService, get_email_header_html
appreciation = Appreciation.objects.get(pk=appreciation_id)
for recipient in recipients:
NotificationService.send_email(
email=recipient["email"],
subject=f"Reminder: Appreciation {appreciation.reference_number} - Acknowledgment Required",
message=f"This is a reminder that appreciation {appreciation.reference_number} is awaiting acknowledgment. Please acknowledge it as soon as possible.",
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Reminder: Acknowledgment Required</h2>
<p style="margin: 0 0 12px 0;">This is a reminder that appreciation <strong>{appreciation.reference_number}</strong> is awaiting acknowledgment from your department.</p>
<p style="margin: 0; color: #6b7280;">Please acknowledge it as soon as possible.</p>
</div>
</div>
""",
related_object=appreciation,
)
logger.info(f"Appreciation {appreciation_id} {reminder_type} reminder sent to {len(recipients)} recipients")
except Exception as e:
logger.error(f"Failed to send appreciation reminder email for {appreciation_id}: {e}")

View File

@ -41,8 +41,22 @@ def appreciation_list(request):
).order_by("-created_at") ).order_by("-created_at")
user = request.user user = request.user
selected_hospital = getattr(request, "tenant_hospital", None)
if user.is_px_admin(): if user.is_px_admin():
pass if selected_hospital:
queryset = queryset.filter(hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department:
queryset = queryset.filter(department=user.department)
elif user.is_champion() and user.department:
queryset = queryset.filter(department=user.department)
elif user.is_source_user():
queryset = queryset.filter(created_by=user)
elif user.hospital: elif user.hospital:
queryset = queryset.filter(hospital=user.hospital) queryset = queryset.filter(hospital=user.hospital)
else: else:
@ -61,16 +75,30 @@ def appreciation_list(request):
) )
base_qs = Appreciation.objects.all() base_qs = Appreciation.objects.all()
if not user.is_px_admin() and user.hospital: if user.is_px_admin():
if selected_hospital:
base_qs = base_qs.filter(hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital:
base_qs = base_qs.filter(hospital=user.hospital) base_qs = base_qs.filter(hospital=user.hospital)
elif not user.is_px_admin(): elif user.is_px_management() or user.is_px_employee():
if user.hospital:
base_qs = base_qs.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department:
base_qs = base_qs.filter(department=user.department)
elif user.is_champion() and user.department:
base_qs = base_qs.filter(department=user.department)
elif user.is_source_user():
base_qs = base_qs.filter(created_by=user)
elif user.hospital:
base_qs = base_qs.filter(hospital=user.hospital)
else:
base_qs = base_qs.none() base_qs = base_qs.none()
stats = { stats = {
"total": base_qs.count(), "total": base_qs.count(),
"draft": base_qs.filter(status=AppreciationStatus.DRAFT).count(), "draft": base_qs.filter(status=AppreciationStatus.DRAFT).count(),
"activated": base_qs.filter( "activated": base_qs.filter(
status__in=[AppreciationStatus.ACTIVATED, AppreciationStatus.AI_ANALYZED] status=AppreciationStatus.ACTIVATED
).count(), ).count(),
"sent": base_qs.filter(status__in=[AppreciationStatus.SENT, AppreciationStatus.ACKNOWLEDGED]).count(), "sent": base_qs.filter(status__in=[AppreciationStatus.SENT, AppreciationStatus.ACKNOWLEDGED]).count(),
} }
@ -132,6 +160,9 @@ def appreciation_detail(request, pk):
appreciation_ct = ContentType.objects.get_for_model(appreciation) appreciation_ct = ContentType.objects.get_for_model(appreciation)
generic_notes = appreciation.notes.select_related("created_by").all() generic_notes = appreciation.notes.select_related("created_by").all()
has_recipient = appreciation.recipient is not None
is_activated = appreciation.status == AppreciationStatus.ACTIVATED
context = { context = {
"appreciation": appreciation, "appreciation": appreciation,
"metadata": metadata, "metadata": metadata,
@ -140,13 +171,23 @@ def appreciation_detail(request, pk):
"categories": categories, "categories": categories,
"hospital_departments": hospital_departments, "hospital_departments": hospital_departments,
"can_activate": appreciation.status == AppreciationStatus.DRAFT, "can_activate": appreciation.status == AppreciationStatus.DRAFT,
"can_send": appreciation.status in (AppreciationStatus.ACTIVATED, AppreciationStatus.AI_ANALYZED), "can_send": is_activated and appreciation.department_id is not None,
"has_recipient": has_recipient,
"is_recipient": False, "is_recipient": False,
"content_type_id": appreciation_ct.pk, "content_type_id": appreciation_ct.pk,
"object_id": appreciation.pk, "object_id": appreciation.pk,
"notes": generic_notes, "notes": generic_notes,
"notes_count": generic_notes.count(), "notes_count": generic_notes.count(),
} }
_apr_steps = [
{"label": _("Activate"), "icon": "zap", "done": appreciation.activated_at is not None},
{"label": _("Select Staff"), "icon": "user-check", "done": has_recipient or appreciation.sent_at is not None},
{"label": _("Send"), "icon": "send", "done": appreciation.sent_at is not None},
]
_apr_next = next((i for i, s in enumerate(_apr_steps) if not s["done"]), len(_apr_steps))
if _apr_next < len(_apr_steps):
_apr_steps[_apr_next]["current"] = True
context["workflow_steps"] = _apr_steps
return render(request, "appreciation/appreciation_detail.html", context) return render(request, "appreciation/appreciation_detail.html", context)
@ -165,17 +206,30 @@ def appreciation_create(request):
from .forms import AppreciationForm from .forms import AppreciationForm
hospital = user.hospital hospital = getattr(request, "tenant_hospital", None) or user.hospital
if request.method == "POST": if request.method == "POST":
form = AppreciationForm(request.POST, hospital=hospital) form = AppreciationForm(request.POST, hospital=hospital)
if form.is_valid(): if form.is_valid():
appreciation = form.save(commit=False) appreciation = form.save(commit=False)
appreciation.sender = user appreciation.sender = user
appreciation.hospital = form.cleaned_data["hospital"] appreciation.hospital = hospital
appreciation.status = AppreciationStatus.DRAFT appreciation.status = AppreciationStatus.DRAFT
appreciation.save() appreciation.save()
form.save_m2m() form.save_m2m()
staff_id = request.POST.get("staff_id")
if staff_id:
try:
from django.contrib.contenttypes.models import ContentType
staff_ct = ContentType.objects.get_for_model(Staff)
staff = Staff.objects.get(pk=staff_id, status="active")
appreciation.recipient_content_type = staff_ct
appreciation.recipient_object_id = staff.id
appreciation.save(update_fields=["recipient_content_type", "recipient_object_id"])
except Staff.DoesNotExist:
pass
messages.success(request, _("Appreciation created successfully.")) messages.success(request, _("Appreciation created successfully."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk) return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
else: else:
@ -261,12 +315,71 @@ def appreciation_activate(request, pk):
return redirect("appreciation:appreciation_detail", pk=appreciation.pk) return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
@login_required
@require_http_methods(["POST"])
def appreciation_select_recipient(request, pk):
"""Select the department and optional staff recipient for an appreciation (without sending)."""
appreciation = get_object_or_404(Appreciation, pk=pk)
if appreciation.status != AppreciationStatus.ACTIVATED:
messages.error(request, _("Appreciation must be activated before selecting a recipient."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
user = request.user
if not (
user.is_px_admin() or user.is_hospital_admin()
or user.is_px_management() or user.is_px_employee()
):
messages.error(request, _("Permission denied."))
return redirect("appreciation:appreciation_list")
department_id = request.POST.get("department_id")
staff_id = request.POST.get("staff_id")
if request.POST.get("clear_recipient"):
appreciation.department = None
appreciation.recipient_content_type = None
appreciation.recipient_object_id = None
appreciation.save(update_fields=["department", "recipient_content_type", "recipient_object_id"])
messages.info(request, _("Recipient cleared. Please select a new recipient."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
if not department_id:
messages.error(request, _("Please select a department."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
try:
department = Department.objects.get(id=department_id, status="active")
except Department.DoesNotExist:
messages.error(request, _("Invalid department selected."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
appreciation.department = department
if staff_id:
try:
from django.contrib.contenttypes.models import ContentType
staff_ct = ContentType.objects.get_for_model(Staff)
staff = Staff.objects.get(pk=staff_id, status="active")
appreciation.recipient_content_type = staff_ct
appreciation.recipient_object_id = staff.id
except Staff.DoesNotExist:
pass
else:
appreciation.recipient_content_type = None
appreciation.recipient_object_id = None
appreciation.save()
messages.success(request, _("Recipient selected: {}").format(department.name))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
@login_required @login_required
@require_http_methods(["POST"]) @require_http_methods(["POST"])
def appreciation_send(request, pk): def appreciation_send(request, pk):
appreciation = get_object_or_404(Appreciation, pk=pk) appreciation = get_object_or_404(Appreciation, pk=pk)
if appreciation.status not in (AppreciationStatus.ACTIVATED, AppreciationStatus.AI_ANALYZED): if appreciation.status != AppreciationStatus.ACTIVATED:
messages.error(request, _("This appreciation must be activated before sending.")) messages.error(request, _("This appreciation must be activated before sending."))
return redirect("appreciation:appreciation_detail", pk=pk) return redirect("appreciation:appreciation_detail", pk=pk)
@ -341,7 +454,7 @@ def appreciation_send_to(request, pk):
) )
# Must be activated before it can be sent # Must be activated before it can be sent
if appreciation.status not in (AppreciationStatus.ACTIVATED, AppreciationStatus.AI_ANALYZED): if appreciation.status != AppreciationStatus.ACTIVATED:
return JsonResponse( return JsonResponse(
{"success": False, "error": str(_("Activate this appreciation before sending it to a department."))}, {"success": False, "error": str(_("Activate this appreciation before sending it to a department."))},
status=400, status=400,
@ -435,7 +548,7 @@ def appreciation_send_to(request, pk):
except Staff.DoesNotExist: except Staff.DoesNotExist:
pass pass
# Advance to SENT (validates status is ACTIVATED/AI_ANALYZED) # Advance to SENT (validates status is ACTIVATED)
appreciation.send() appreciation.send()
# Send notifications (email + SMS to champion, manager, staff) in the background # Send notifications (email + SMS to champion, manager, staff) in the background
@ -508,6 +621,79 @@ def appreciation_acknowledge(request, pk):
return redirect('appreciation:appreciation_detail', pk=pk) return redirect('appreciation:appreciation_detail', pk=pk)
@login_required
@require_http_methods(["POST"])
def appreciation_send_dept_reminder(request, pk):
"""Send a reminder to the department that hasn't acknowledged an appreciation."""
appreciation = get_object_or_404(Appreciation, pk=pk)
user = request.user
if not (
user.is_px_admin() or user.is_hospital_admin()
or user.is_px_management() or user.is_px_employee()
):
messages.error(request, _("You don't have permission to send reminders."))
return redirect("appreciation:appreciation_detail", pk=pk)
if appreciation.status not in (AppreciationStatus.SENT, AppreciationStatus.ACKNOWLEDGED):
messages.warning(request, _("Appreciation must be sent before sending reminders."))
return redirect("appreciation:appreciation_detail", pk=pk)
dept = appreciation.department
if not dept:
messages.error(request, _("No department assigned."))
return redirect("appreciation:appreciation_detail", pk=pk)
reminder_type = request.POST.get("reminder_type", "first")
if reminder_type == "first" and appreciation.dept_response_reminder_sent_at:
messages.warning(request, _("First reminder already sent."))
return redirect("appreciation:appreciation_detail", pk=pk)
if reminder_type == "second" and appreciation.dept_response_second_reminder_sent_at:
messages.warning(request, _("Second reminder already sent."))
return redirect("appreciation:appreciation_detail", pk=pk)
if reminder_type == "second" and not appreciation.dept_response_reminder_sent_at:
messages.warning(request, _("Please send the first reminder before the second."))
return redirect("appreciation:appreciation_detail", pk=pk)
try:
from apps.notifications.services import NotificationService, get_email_header_html
from apps.organizations.department_contacts import get_champion_and_manager
targets = get_champion_and_manager(dept)
if not targets:
messages.error(request, _(f"No champion or manager assigned to {dept.name}."))
return redirect("appreciation:appreciation_detail", pk=pk)
recipients = []
for target in targets:
email = target.get("email") or ""
if email:
recipients.append({"email": email, "name": target.get("display_name", "")})
if reminder_type == "first":
appreciation.dept_response_reminder_sent_at = timezone.now()
else:
appreciation.dept_response_second_reminder_sent_at = timezone.now()
appreciation.save(update_fields=["dept_response_reminder_sent_at", "dept_response_second_reminder_sent_at"])
from apps.appreciation.tasks import send_appreciation_reminder_email
send_appreciation_reminder_email.delay(str(appreciation.id), reminder_type, recipients)
AuditService.log_event(
event_type="appreciation_reminder_sent",
description=f"{reminder_type.capitalize()} reminder sent for appreciation {appreciation.reference_number} by {user.get_full_name()}",
content_object=appreciation,
)
messages.success(request, _(f"{reminder_type.capitalize()} reminder sent to {len(recipients)} recipient(s)."))
except Exception as e:
logger.error(f"Failed to send appreciation reminder: {e}")
messages.error(request, _("Failed to send reminder."))
return redirect("appreciation:appreciation_detail", pk=pk)
# ============================================================================ # ============================================================================
# LEADERBOARD VIEWS # LEADERBOARD VIEWS
# ============================================================================ # ============================================================================
@ -533,8 +719,23 @@ def leaderboard_view(request):
queryset = AppreciationStats.objects.filter(year=year, month=month) queryset = AppreciationStats.objects.filter(year=year, month=month)
# Apply RBAC # Apply RBAC
if not user.is_px_admin() and user.hospital: selected_hospital = getattr(request, "tenant_hospital", None)
if user.is_px_admin():
if selected_hospital:
queryset = queryset.filter(hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(hospital=user.hospital) queryset = queryset.filter(hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department:
queryset = queryset.filter(department=user.department)
elif user.is_champion() and user.department:
queryset = queryset.filter(department=user.department)
elif user.hospital:
queryset = queryset.filter(hospital=user.hospital)
else:
queryset = queryset.none()
# Apply filters # Apply filters
hospital_filter = request.GET.get('hospital') hospital_filter = request.GET.get('hospital')
@ -669,7 +870,7 @@ def category_list(request):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to manage categories.") messages.error(request, "You don't have permission to manage categories.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -715,7 +916,7 @@ def category_create(request):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to create categories.") messages.error(request, "You don't have permission to create categories.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -773,7 +974,7 @@ def category_edit(request, pk):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to edit categories.") messages.error(request, "You don't have permission to edit categories.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -818,7 +1019,7 @@ def category_delete(request, pk):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to delete categories.") messages.error(request, "You don't have permission to delete categories.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -853,7 +1054,7 @@ def badge_list(request):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to manage badges.") messages.error(request, "You don't have permission to manage badges.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -899,7 +1100,7 @@ def badge_create(request):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to create badges.") messages.error(request, "You don't have permission to create badges.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -961,7 +1162,7 @@ def badge_edit(request, pk):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to edit badges.") messages.error(request, "You don't have permission to edit badges.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -1008,7 +1209,7 @@ def badge_delete(request, pk):
user = request.user user = request.user
# Check permission # Check permission
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to delete badges.") messages.error(request, "You don't have permission to delete badges.")
return redirect('appreciation:appreciation_list') return redirect('appreciation:appreciation_list')
@ -1039,7 +1240,7 @@ def appreciation_restore(request, pk):
"""Restore deleted appreciation""" """Restore deleted appreciation"""
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to restore appreciation.") messages.error(request, "You don't have permission to restore appreciation.")
return redirect('config:deleted_items') return redirect('config:deleted_items')
@ -1224,6 +1425,16 @@ def public_appreciation_submit(request):
if not contact_name or not contact_phone or not message: if not contact_name or not contact_phone or not message:
return JsonResponse({"success": False, "message": "Name, phone, and message are required."}, status=400) return JsonResponse({"success": False, "message": "Name, phone, and message are required."}, status=400)
from apps.core.validators import validate_saudi_phone
from django.core.exceptions import ValidationError
try:
validate_saudi_phone(contact_phone)
except ValidationError:
return JsonResponse(
{"success": False, "message": "Please enter a valid Saudi mobile number (e.g. 05XXXXXXXX or +9665XXXXXXXX)."},
status=400,
)
if not hospital_id: if not hospital_id:
return JsonResponse({"success": False, "message": "Please select a hospital."}, status=400) return JsonResponse({"success": False, "message": "Please select a hospital."}, status=400)
@ -1273,13 +1484,143 @@ def public_appreciation_submit(request):
logger.exception("ERROR in public_appreciation_submit") logger.exception("ERROR in public_appreciation_submit")
return JsonResponse({"success": False, "message": str(e)}, status=500) return JsonResponse({"success": False, "message": str(e)}, status=500)
def _appreciation_pdf_context(appreciation):
"""Build the common context for the appreciation letter & certificate PDFs."""
hospital = appreciation.hospital
hospital_name = hospital.name if hospital else ""
hospital_name_ar = getattr(hospital, "name_ar", "") if hospital else ""
recipient = appreciation.recipient
recipient_name = appreciation.get_recipient_name() or ""
department_name = appreciation.department.get_localized_name() if appreciation.department else ""
recipient_title = ""
if recipient is not None:
getter = getattr(recipient, "get_localized_job_title", None)
if callable(getter):
recipient_title = getter() or ""
recipient_title = recipient_title or getattr(recipient, "job_title", "") or ""
meta = appreciation.metadata or {}
is_patient = meta.get("source") == "public_form" or bool(meta.get("submitted_by_name"))
patient_name = (meta.get("submitted_by_name") or "").strip()
# Signer: sender (unless anonymous), else the activating officer, else generic PX team.
signer = appreciation.sender if (appreciation.sender and not appreciation.is_anonymous) else appreciation.activated_by
signer_name = _("Al Hammadi Patient Experience")
signer_role = _("Patient Experience Team")
if signer:
signer_name = signer.get_full_name() or signer_name
role_names = signer.get_role_names() if hasattr(signer, "get_role_names") else []
if role_names:
signer_role = ", ".join(role_names)
date_dt = appreciation.sent_at or appreciation.activated_at or appreciation.created_at
return {
"hospital_name": hospital_name,
"hospital_name_ar": hospital_name_ar,
"recipient_name": recipient_name,
"recipient_title": recipient_title,
"department_name": department_name,
"is_patient": is_patient,
"patient_name": patient_name,
"message_en": appreciation.message_en or "",
"message_ar": appreciation.message_ar or "",
"has_ar": bool((appreciation.message_ar or "").strip()),
"signer_name": signer_name,
"signer_role": signer_role,
"date_dt": date_dt,
"reference": appreciation.reference_number or "",
}
def _render_appreciation_pdf_bytes(template_name, context, obj=None):
"""Render a PDF template via WeasyPrint and return the raw PDF bytes.
When ``obj`` is provided, its hospital letterhead is injected into the context
as ``letterhead_path`` (data URI) via the shared ``pdf_utils`` helper.
Used by both the PDF download views (wraps bytes in HttpResponse) and the
notification task (attaches bytes to email).
"""
from django.conf import settings
from django.template.loader import render_to_string
from weasyprint import HTML
full_context = dict(context)
if obj is not None:
from apps.core.pdf_utils import _get_letterhead_data_uri
full_context.setdefault("letterhead_path", _get_letterhead_data_uri(obj))
html_string = render_to_string(template_name, full_context)
return HTML(string=html_string, base_url=str(settings.BASE_DIR / "static")).write_pdf()
def _appreciation_pdf_response(template_name, context, filename, obj=None):
"""Render a PDF template via WeasyPrint and return it as an attachment response."""
from django.http import HttpResponse
pdf_file = _render_appreciation_pdf_bytes(template_name, context, obj=obj)
response = HttpResponse(pdf_file, content_type="application/pdf")
response["Content-Disposition"] = f'attachment; filename="{filename}"'
return response
def _check_appreciation_pdf_access(request, appreciation):
"""Shared permission + activation gate for the appreciation PDF views."""
user = request.user
allowed = (
user.is_px_admin()
or user.is_hospital_admin()
or user.is_px_management()
or user.is_px_employee()
or (user.hospital_id and appreciation.hospital_id == user.hospital_id)
)
if not allowed:
messages.error(request, _("You don't have permission to view this appreciation."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
if not appreciation.activated_at:
messages.error(request, _("This appreciation must be activated before generating a document."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
if not appreciation.recipient:
messages.error(request, _("Select a recipient before generating a document."))
return redirect("appreciation:appreciation_detail", pk=appreciation.pk)
return None
@login_required @login_required
def appreciation_pdf(request, pk): def appreciation_letter(request, pk):
from apps.core.pdf_utils import generate_letterhead_pdf """Everyday Letter of Appreciation (portrait, message-forward, sender-signed)."""
obj = get_object_or_404(Appreciation, id=pk) obj = get_object_or_404(Appreciation, id=pk)
return generate_letterhead_pdf( denied = _check_appreciation_pdf_access(request, obj)
'appreciation/appreciation_pdf.html', if denied:
{'object': obj}, return denied
f'appreciation_{obj.reference_number}.pdf', ctx = _appreciation_pdf_context(obj)
return _appreciation_pdf_response(
"appreciation/appreciation_letter.html",
ctx,
f"appreciation_letter_{obj.reference_number}.pdf",
obj=obj, obj=obj,
) )
@login_required
def appreciation_certificate(request, pk):
"""Formal Certificate of Appreciation (landscape, leadership-signed)."""
obj = get_object_or_404(Appreciation, id=pk)
denied = _check_appreciation_pdf_access(request, obj)
if denied:
return denied
ctx = _appreciation_pdf_context(obj)
hospital = obj.hospital
ctx["ceo_name"] = hospital.ceo.get_full_name() if hospital and hospital.ceo else ""
ctx["medical_director_name"] = (
hospital.medical_director.get_full_name() if hospital and hospital.medical_director else ""
)
ctx["hospital_initials"] = (
"".join(w[0] for w in (hospital.name or "").split()[:2]).upper() if hospital and hospital.name else "HH"
)
return _appreciation_pdf_response(
"appreciation/appreciation_certificate.html",
ctx,
f"appreciation_certificate_{obj.reference_number}.pdf",
)

View File

@ -34,8 +34,10 @@ urlpatterns = [
path('new/', ui_views.appreciation_create, name='appreciation_create'), path('new/', ui_views.appreciation_create, name='appreciation_create'),
path('detail/<uuid:pk>/', ui_views.appreciation_detail, name='appreciation_detail'), path('detail/<uuid:pk>/', ui_views.appreciation_detail, name='appreciation_detail'),
path('detail/<uuid:pk>/activate/', ui_views.appreciation_activate, name='appreciation_activate'), path('detail/<uuid:pk>/activate/', ui_views.appreciation_activate, name='appreciation_activate'),
path('detail/<uuid:pk>/select-recipient/', ui_views.appreciation_select_recipient, name='appreciation_select_recipient'),
path('detail/<uuid:pk>/send/', ui_views.appreciation_send, name='appreciation_send'), path('detail/<uuid:pk>/send/', ui_views.appreciation_send, name='appreciation_send'),
path('detail/<uuid:pk>/send-to/', ui_views.appreciation_send_to, name='appreciation_send_to'), path('detail/<uuid:pk>/send-to/', ui_views.appreciation_send_to, name='appreciation_send_to'),
path('detail/<uuid:pk>/send-reminder/', ui_views.appreciation_send_dept_reminder, name='appreciation_send_dept_reminder'),
path('acknowledge/<uuid:pk>/', ui_views.appreciation_acknowledge, name='appreciation_acknowledge'), path('acknowledge/<uuid:pk>/', ui_views.appreciation_acknowledge, name='appreciation_acknowledge'),
path('leaderboard/', ui_views.leaderboard_view, name='leaderboard_view'), path('leaderboard/', ui_views.leaderboard_view, name='leaderboard_view'),
path('badges/', ui_views.my_badges_view, name='my_badges_view'), path('badges/', ui_views.my_badges_view, name='my_badges_view'),
@ -64,5 +66,7 @@ urlpatterns = [
# Public submission (no auth required) # Public submission (no auth required)
path('public/submit/', ui_views.public_appreciation_submit, name='public_appreciation_submit'), path('public/submit/', ui_views.public_appreciation_submit, name='public_appreciation_submit'),
path('detail/<uuid:pk>/pdf/', ui_views.appreciation_pdf, name='appreciation_pdf'), # PDF downloads (require activation)
path('detail/<uuid:pk>/letter/', ui_views.appreciation_letter, name='appreciation_letter'),
path('detail/<uuid:pk>/certificate/', ui_views.appreciation_certificate, name='appreciation_certificate'),
] ]

View File

@ -398,7 +398,7 @@ class AppreciationBadgeViewSet(viewsets.ModelViewSet):
queryset = AppreciationBadge.objects.all() queryset = AppreciationBadge.objects.all()
serializer_class = AppreciationBadgeSerializer serializer_class = AppreciationBadgeSerializer
permission_classes = [IsAuthenticated, IsPXAdminOrHospitalAdmin] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
"""Filter badges by hospital""" """Filter badges by hospital"""

View File

@ -26,6 +26,7 @@ from apps.complaints.models import (
) )
from apps.core.models import PriorityChoices, SeverityChoices from apps.core.models import PriorityChoices, SeverityChoices
from apps.core.form_mixins import HospitalFieldMixin, DepartmentFieldMixin from apps.core.form_mixins import HospitalFieldMixin, DepartmentFieldMixin
from apps.core.validators import SAUDI_PHONE_HTML_PATTERN, validate_saudi_phone
from apps.organizations.models import Area, Department, Hospital, LocationType, Patient, Staff, Section from apps.organizations.models import Area, Department, Hospital, LocationType, Patient, Staff, Section
@ -93,7 +94,12 @@ class PublicComplaintForm(forms.ModelForm):
label=_("Mobile Number"), label=_("Mobile Number"),
max_length=20, max_length=20,
required=True, required=True,
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your mobile number")}), widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": _("05XXXXXXXX or +9665XXXXXXXX"),
"pattern": SAUDI_PHONE_HTML_PATTERN,
"inputmode": "tel",
}),
) )
# Patient Information # Patient Information
@ -280,16 +286,10 @@ class PublicComplaintForm(forms.ModelForm):
).order_by("name_en") ).order_by("name_en")
def clean_mobile_number(self): def clean_mobile_number(self):
"""Validate mobile number format""" """Validate Saudi mobile number format (05xxxxxxxx / 5xxxxxxxx / +9665xxxxxxxx / 9665xxxxxxxx)."""
mobile_number = self.cleaned_data.get("mobile_number") mobile_number = self.cleaned_data.get("mobile_number")
if mobile_number:
# Remove spaces and dashes validate_saudi_phone(mobile_number)
mobile_number = mobile_number.replace(" ", "").replace("-", "")
# Validate Saudi mobile format (05xxxxxxxx)
if not mobile_number.startswith("05") or len(mobile_number) != 10:
raise ValidationError(_("Please enter a valid Saudi mobile number (10 digits starting with 05)"))
return mobile_number return mobile_number
def clean_national_id(self): def clean_national_id(self):
@ -711,6 +711,7 @@ class InquiryForm(HospitalFieldMixin, forms.ModelForm):
"contact_name", "contact_name",
"contact_phone", "contact_phone",
"contact_email", "contact_email",
"national_id",
"section", "section",
"priority", "priority",
"source", "source",
@ -763,6 +764,24 @@ class InquiryForm(HospitalFieldMixin, forms.ModelForm):
department_id=department_id department_id=department_id
).order_by("name_en") ).order_by("name_en")
def clean_area(self):
area = self.cleaned_data.get("area")
if not area:
return None
return area
def clean_department(self):
dept = self.cleaned_data.get("department")
if not dept:
return None
return dept
def clean_section(self):
section = self.cleaned_data.get("section")
if not section:
return None
return section
class SLAConfigForm(HospitalFieldMixin, forms.ModelForm): class SLAConfigForm(HospitalFieldMixin, forms.ModelForm):
"""Form for creating and editing SLA configurations""" """Form for creating and editing SLA configurations"""
@ -869,7 +888,13 @@ class PublicInquiryForm(forms.Form):
label=_("Phone Number"), label=_("Phone Number"),
max_length=20, max_length=20,
required=True, required=True,
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your phone number")}), validators=[validate_saudi_phone],
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": _("05XXXXXXXX or +9665XXXXXXXX"),
"pattern": SAUDI_PHONE_HTML_PATTERN,
"inputmode": "tel",
}),
) )
email = forms.EmailField( email = forms.EmailField(
@ -878,6 +903,13 @@ class PublicInquiryForm(forms.Form):
widget=forms.EmailInput(attrs={"class": "form-control", "placeholder": _("your@email.com")}), widget=forms.EmailInput(attrs={"class": "form-control", "placeholder": _("your@email.com")}),
) )
national_id = forms.CharField(
label=_("National ID/Iqama"),
max_length=20,
required=False,
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("National ID or Iqama (optional)")}),
)
# Inquiry Details # Inquiry Details
hospital = forms.ModelChoiceField( hospital = forms.ModelChoiceField(
label=_("Hospital"), label=_("Hospital"),

View File

@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-07-15 15:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('complaints', '0042_inquiry_closed_by_inquiry_resolved_by'),
]
operations = [
migrations.AlterField(
model_name='inquiry',
name='status',
field=models.CharField(choices=[('open', 'Open'), ('in_progress', 'In Progress'), ('resolved', 'Resolved'), ('closed', 'Closed'), ('contact_no_response', 'Contacted - No Response')], db_index=True, default='open', max_length=25),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 6.0.1 on 2026-07-18 14:46
import apps.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('complaints', '0043_alter_inquiry_status'),
]
operations = [
migrations.AlterField(
model_name='complaint',
name='contact_phone',
field=models.CharField(blank=True, max_length=20, validators=[apps.core.validators.validate_saudi_phone]),
),
migrations.AlterField(
model_name='inquiry',
name='contact_phone',
field=models.CharField(blank=True, max_length=20, validators=[apps.core.validators.validate_saudi_phone]),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 6.0.1 on 2026-07-19 06:23
import apps.core.encryption
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('complaints', '0044_alter_complaint_contact_phone_and_more'),
]
operations = [
migrations.AddField(
model_name='inquiry',
name='national_id',
field=apps.core.encryption.EncryptedCharField(blank=True, help_text='National ID/Iqama of the contact', max_length=512),
),
migrations.AddField(
model_name='inquiry',
name='national_id_hash',
field=models.CharField(blank=True, db_index=True, max_length=64),
),
]

View File

@ -16,9 +16,10 @@ from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.core.encryption import EncryptedCharField, compute_national_id_hash, mask_national_id from apps.core.encryption import EncryptedCharField, compute_national_id_hash, mask_national_id
from apps.core.models import PriorityChoices, SeverityChoices, SoftDeleteModel, TenantModel, TimeStampedModel, UUIDModel from apps.core.models import PriorityChoices, SeverityChoices, SoftDeleteModel, TenantModel, TimeStampedModel, UUIDModel
from apps.core.validators import validate_saudi_phone
from apps.organizations.models import LocationType from apps.organizations.models import LocationType
@ -213,7 +214,7 @@ class Complaint(UUIDModel, TimeStampedModel, SoftDeleteModel):
# Contact information for anonymous/unregistered submissions # Contact information for anonymous/unregistered submissions
contact_name = models.CharField(max_length=200, blank=True) contact_name = models.CharField(max_length=200, blank=True)
contact_phone = models.CharField(max_length=20, blank=True) contact_phone = models.CharField(max_length=20, blank=True, validators=[validate_saudi_phone])
contact_email = models.EmailField(blank=True) contact_email = models.EmailField(blank=True)
# Public complaint form fields # Public complaint form fields
@ -1539,8 +1540,10 @@ class Inquiry(UUIDModel, TimeStampedModel, SoftDeleteModel):
# Contact information (if patient not in system) # Contact information (if patient not in system)
contact_name = models.CharField(max_length=200, blank=True) contact_name = models.CharField(max_length=200, blank=True)
contact_phone = models.CharField(max_length=20, blank=True) contact_phone = models.CharField(max_length=20, blank=True, validators=[validate_saudi_phone])
contact_email = models.EmailField(blank=True) contact_email = models.EmailField(blank=True)
national_id = EncryptedCharField(max_length=512, blank=True, help_text="National ID/Iqama of the contact")
national_id_hash = models.CharField(max_length=64, blank=True, db_index=True)
# Organization # Organization
hospital = models.ForeignKey("organizations.Hospital", on_delete=models.CASCADE, related_name="inquiries") hospital = models.ForeignKey("organizations.Hospital", on_delete=models.CASCADE, related_name="inquiries")
@ -1699,6 +1702,7 @@ class Inquiry(UUIDModel, TimeStampedModel, SoftDeleteModel):
("in_progress", _("In Progress")), ("in_progress", _("In Progress")),
("resolved", _("Resolved")), ("resolved", _("Resolved")),
("closed", _("Closed")), ("closed", _("Closed")),
("contact_no_response", _("Contacted - No Response")),
], ],
default="open", default="open",
db_index=True, db_index=True,
@ -2024,11 +2028,27 @@ class Inquiry(UUIDModel, TimeStampedModel, SoftDeleteModel):
else: else:
self.timeline_sla = "more_than_72_hours" self.timeline_sla = "more_than_72_hours"
if self.national_id:
self.national_id_hash = compute_national_id_hash(self.national_id)
else:
self.national_id_hash = ""
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self): def __str__(self):
return f"{self.subject} ({self.status})" return f"{self.subject} ({self.status})"
@property
def handling_department_id(self):
"""The department currently responsible for handling this inquiry.
When the inquiry has been forwarded, this is the receiving department
(``transferred_to_department``); otherwise it's the primary ``department``.
Used to gate department actions so a department that has forwarded an
inquiry away can no longer act on it only the current handler can.
"""
return self.transferred_to_department_id or self.department_id
def get_absolute_url(self): def get_absolute_url(self):
from django.urls import reverse from django.urls import reverse

View File

@ -3069,26 +3069,36 @@ def _apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis):
inquiry.priority = analysis.get("priority", inquiry.priority) inquiry.priority = analysis.get("priority", inquiry.priority)
department_name = analysis.get("department", "") # Department auto-detection — skip when the user already provided one
if department_name: # (quick-create modal sets metadata.ai_skip_department). For those, AI only
if department := Department.objects.filter( # fills subject + priority, never overriding the user's department.
hospital_id=inquiry.hospital.id, name__iexact=department_name, status="active" if not (inquiry.metadata or {}).get("ai_skip_department"):
).first(): department_name = analysis.get("department", "")
inquiry.department = department if department_name:
logger.info(f"Matched department exactly: {department.name}") if department := Department.objects.filter(
elif department := Department.objects.filter( hospital_id=inquiry.hospital.id, name__iexact=department_name, status="active"
hospital_id=inquiry.hospital.id, name__icontains=department_name, status="active" ).first():
).first(): inquiry.department = department
inquiry.department = department logger.info(f"Matched department exactly: {department.name}")
logger.info(f"Matched department partially: {department.name} from '{department_name}'") elif department := Department.objects.filter(
hospital_id=inquiry.hospital.id, name__icontains=department_name, status="active"
).first():
inquiry.department = department
logger.info(f"Matched department partially: {department.name} from '{department_name}'")
if analysis.get("brief_summary_en"): if analysis.get("brief_summary_en"):
inquiry.ai_brief_en = analysis["brief_summary_en"] inquiry.ai_brief_en = analysis["brief_summary_en"]
if analysis.get("brief_summary_ar"): if analysis.get("brief_summary_ar"):
inquiry.ai_brief_ar = analysis["brief_summary_ar"] inquiry.ai_brief_ar = analysis["brief_summary_ar"]
# For quick-created inquiries (no user-authored subject), let the AI title
# become the subject. Guarded by a metadata flag so user subjects are never
# overwritten for inquiries created via the full form.
if not inquiry.metadata: if not inquiry.metadata:
inquiry.metadata = {} inquiry.metadata = {}
if inquiry.metadata.get("subject_pending_ai") and analysis.get("title_en"):
inquiry.subject = analysis["title_en"][:500]
inquiry.metadata["subject_pending_ai"] = False
inquiry.metadata["ai_analysis"] = { inquiry.metadata["ai_analysis"] = {
"title_en": analysis.get("title_en", ""), "title_en": analysis.get("title_en", ""),
@ -3123,6 +3133,7 @@ def _apply_inquiry_ai_analysis(inquiry, analysis, emotion_analysis):
inquiry.save( inquiry.save(
update_fields=[ update_fields=[
"subject",
"priority", "priority",
"department", "department",
"ai_brief_en", "ai_brief_en",
@ -3696,3 +3707,39 @@ def send_department_notification_task(payload):
f"{emailed} emailed, {smsed} smsed" f"{emailed} emailed, {smsed} smsed"
) )
return {"emailed": emailed, "smsed": smsed} return {"emailed": emailed, "smsed": smsed}
@shared_task
def send_inquiry_reminder_email(inquiry_id, reminder_type, recipients):
"""Send a reminder email to department champion and manager for an inquiry."""
import logging
logger = logging.getLogger(__name__)
try:
from apps.complaints.models import Inquiry
from apps.notifications.services import NotificationService, get_email_header_html
inquiry = Inquiry.objects.get(pk=inquiry_id)
for recipient in recipients:
NotificationService.send_email(
email=recipient["email"],
subject=f"Reminder: Inquiry #{inquiry.reference_number} - Response Required",
message=f"This is a reminder that inquiry #{inquiry.reference_number} is awaiting your department's response. Please submit your response as soon as possible.",
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Reminder: Response Required</h2>
<p style="margin: 0 0 12px 0;">This is a reminder that inquiry <strong>#{inquiry.reference_number}</strong> is awaiting your department's response.</p>
<p style="margin: 0; color: #6b7280;">Please submit your response as soon as possible.</p>
</div>
</div>
""",
related_object=inquiry,
)
logger.info(f"Inquiry {inquiry_id} {reminder_type} reminder sent to {len(recipients)} recipients")
except Exception as e:
logger.error(f"Failed to send inquiry reminder email for {inquiry_id}: {e}")

View File

@ -25,6 +25,25 @@ from apps.organizations.models import Department, Hospital, Staff
from apps.observations.models import Observation from apps.observations.models import Observation
from apps.px_sources.models import SourceUser, PXSource from apps.px_sources.models import SourceUser, PXSource
def _safe_next_redirect(request, fallback):
"""Redirect to request.POST['next'] if it's a safe relative URL.
Blocks open-redirect attacks. A URL is considered safe only if it starts
with a single leading slash and is not a protocol-relative form (``//``
or ``/\\``) which browsers may resolve against a different host.
Falls back to ``fallback`` (a redirect()/reverse() result) otherwise.
"""
next_url = request.POST.get("next") if hasattr(request, "POST") else None
if (
next_url
and next_url.startswith("/")
and not next_url.startswith("//")
and not next_url.startswith("/\\")
):
return redirect(next_url)
return fallback
from .models import ( from .models import (
Complaint, Complaint,
ComplaintAttachment, ComplaintAttachment,
@ -330,6 +349,9 @@ def complaint_list(request):
queryset = queryset.filter(hospital=selected_hospital) queryset = queryset.filter(hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital: elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(hospital=user.hospital) queryset = queryset.filter(hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department: elif user.is_department_manager() and user.department:
queryset = queryset.filter(department=user.department) queryset = queryset.filter(department=user.department)
elif user.is_champion() and user.department: elif user.is_champion() and user.department:
@ -877,9 +899,7 @@ def complaint_detail(request, pk):
context["notes"] = complaint.notes.select_related("created_by").all() context["notes"] = complaint.notes.select_related("created_by").all()
context["notes_count"] = context["notes"].count() context["notes_count"] = context["notes"].count()
# Datastar variant for testing: /complaints/<pk>/?ds=1 renders complaint_detail_ds.html return render(request, "complaints/complaint_detail.html", context)
template = "complaints/complaint_detail_ds.html" if request.GET.get("ds") else "complaints/complaint_detail.html"
return render(request, template, context)
@login_required @login_required
@ -1462,6 +1482,18 @@ def complaint_send_dept_reminder(request, pk):
) )
department = involved_dept.department department = involved_dept.department
reminder_type = request.POST.get("reminder_type", "first")
if reminder_type == "first" and involved_dept.first_reminder_sent_at:
messages.warning(request, _("First reminder already sent to this department."))
return redirect("complaints:complaint_detail", pk=pk)
if reminder_type == "second" and involved_dept.second_reminder_sent_at:
messages.warning(request, _("Second reminder already sent to this department."))
return redirect("complaints:complaint_detail", pk=pk)
if reminder_type == "second" and not involved_dept.first_reminder_sent_at:
messages.warning(request, _("Please send the first reminder before the second."))
return redirect("complaints:complaint_detail", pk=pk)
targets = get_champion_and_manager(department) targets = get_champion_and_manager(department)
if not targets: if not targets:
messages.error( messages.error(
@ -1522,10 +1554,16 @@ def complaint_send_dept_reminder(request, pk):
notified.append(f"{display_name} ({label})") notified.append(f"{display_name} ({label})")
if sent_any: if sent_any:
if reminder_type == "first":
involved_dept.first_reminder_sent_at = timezone.now()
else:
involved_dept.second_reminder_sent_at = timezone.now()
involved_dept.save(update_fields=["first_reminder_sent_at", "second_reminder_sent_at"])
ComplaintUpdate.objects.create( ComplaintUpdate.objects.create(
complaint=complaint, complaint=complaint,
update_type="communication", update_type="communication",
message=f"Manual reminder sent to {department.get_localized_name()}{', '.join(notified)}", message=f"Manual {reminder_type} reminder sent to {department.get_localized_name()}{', '.join(notified)}",
created_by=user, created_by=user,
metadata={ metadata={
"event_type": "manual_reminder", "event_type": "manual_reminder",
@ -2762,7 +2800,7 @@ def inquiry_list(request):
from .models import Inquiry from .models import Inquiry
# Base queryset with optimizations # Base queryset with optimizations
queryset = Inquiry.objects.select_related("patient", "hospital", "department", "assigned_to", "responded_by") queryset = Inquiry.objects.select_related("patient", "hospital", "department", "outgoing_department", "assigned_to", "responded_by")
# Apply RBAC filters # Apply RBAC filters
user = request.user user = request.user
@ -2774,6 +2812,9 @@ def inquiry_list(request):
queryset = queryset.filter(hospital=selected_hospital) queryset = queryset.filter(hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital: elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(hospital=user.hospital) queryset = queryset.filter(hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department: elif user.is_department_manager() and user.department:
queryset = queryset.filter( queryset = queryset.filter(
Q(department=user.department) | Q(outgoing_department=user.department) Q(department=user.department) | Q(outgoing_department=user.department)
@ -2789,33 +2830,55 @@ def inquiry_list(request):
else: else:
queryset = queryset.none() queryset = queryset.none()
# Apply filters # Snapshot the RBAC base before direction/filters so we can compute
# incoming/outgoing counts for the tabs.
base_qs = queryset
# Apply non-direction filters (status, category, hospital, department, search)
status_filter = request.GET.get("status") status_filter = request.GET.get("status")
if status_filter: if status_filter:
queryset = queryset.filter(status=status_filter) base_qs = base_qs.filter(status=status_filter)
category_filter = request.GET.get("category") category_filter = request.GET.get("category")
if category_filter: if category_filter:
queryset = queryset.filter(category=category_filter) base_qs = base_qs.filter(category=category_filter)
hospital_filter = request.GET.get("hospital") hospital_filter = request.GET.get("hospital")
if hospital_filter: if hospital_filter:
queryset = queryset.filter(hospital_id=hospital_filter) base_qs = base_qs.filter(hospital_id=hospital_filter)
department_filter = request.GET.get("department") department_filter = request.GET.get("department")
if department_filter: if department_filter:
queryset = queryset.filter(department_id=department_filter) base_qs = base_qs.filter(department_id=department_filter)
# Search
search_query = request.GET.get("search") search_query = request.GET.get("search")
if search_query: if search_query:
queryset = queryset.filter( base_qs = base_qs.filter(
Q(subject__icontains=search_query) Q(subject__icontains=search_query)
| Q(message__icontains=search_query) | Q(message__icontains=search_query)
| Q(contact_name__icontains=search_query) | Q(contact_name__icontains=search_query)
| Q(contact_email__icontains=search_query) | Q(contact_email__icontains=search_query)
) )
# Direction split: incoming vs outgoing
direction = request.GET.get("direction", "incoming")
dept = getattr(user, "department", None)
is_dept_scoped = user.is_department_manager() or user.is_champion()
if is_dept_scoped and dept:
sent_away = Q(department=dept) & Q(transferred_to_department__isnull=False) & ~Q(transferred_to_department=dept)
incoming_qs = base_qs.filter(
Q(department=dept) | Q(transferred_to_department=dept) | Q(outgoing_department=dept)
).exclude(sent_away)
outgoing_qs = base_qs.filter(sent_away)
else:
# PX roles: incoming = not forwarded; outgoing = forwarded to a department
incoming_qs = base_qs.exclude(transferred_to_department__isnull=False)
outgoing_qs = base_qs.filter(transferred_to_department__isnull=False)
incoming_count = incoming_qs.count()
outgoing_count = outgoing_qs.count()
queryset = outgoing_qs if direction == "outgoing" else incoming_qs
# Ordering # Ordering
order_by = request.GET.get("order_by", "-created_at") order_by = request.GET.get("order_by", "-created_at")
queryset = queryset.order_by(order_by) queryset = queryset.order_by(order_by)
@ -2850,6 +2913,9 @@ def inquiry_list(request):
"stats": stats, "stats": stats,
"departments": departments, "departments": departments,
"filters": request.GET, "filters": request.GET,
"direction": direction,
"incoming_count": incoming_count,
"outgoing_count": outgoing_count,
} }
return render(request, "complaints/inquiry_list.html", context) return render(request, "complaints/inquiry_list.html", context)
@ -2884,7 +2950,11 @@ def inquiry_detail(request, pk):
# Check access # Check access
user = request.user user = request.user
if not user.is_px_admin(): if not user.is_px_admin():
if user.is_hospital_admin() and inquiry.hospital != user.hospital: if user.is_px_management() or user.is_px_employee():
if user.hospital and inquiry.hospital != user.hospital:
messages.error(request, "You don't have permission to view this inquiry.")
return redirect("inquiries:inquiry_list")
elif user.is_hospital_admin() and inquiry.hospital != user.hospital:
messages.error(request, "You don't have permission to view this inquiry.") messages.error(request, "You don't have permission to view this inquiry.")
return redirect("inquiries:inquiry_list") return redirect("inquiries:inquiry_list")
elif ( elif (
@ -3146,6 +3216,151 @@ def inquiry_send_to_staff(request, pk):
return redirect("inquiries:inquiry_detail", pk=pk) return redirect("inquiries:inquiry_detail", pk=pk)
@login_required
@require_http_methods(["POST"])
def inquiry_quick_create(request):
"""Quick-create an inquiry from the simple modal (PX staff only).
Fields: message, contact name, phone (required), email (optional),
location type, category, department, section. The inquiry is auto-activated
and assigned to the current user. AI fills subject (from its title) and
priority in the background; the user's department/section/category are kept.
"""
from .models import Inquiry, InquiryUpdate
user = request.user
if not (
user.is_px_admin() or user.is_hospital_admin()
or user.is_px_management() or user.is_px_employee()
):
return JsonResponse(
{"success": False, "error": str(_("You don't have permission to create inquiries."))},
status=403,
)
message = (request.POST.get("message") or "").strip()
contact_name = (request.POST.get("contact_name") or "").strip()
contact_phone = (request.POST.get("contact_phone") or "").strip()
contact_email = (request.POST.get("contact_email") or "").strip()
location_type = (request.POST.get("location_type") or "").strip()
department_id = (request.POST.get("department") or "").strip()
section_id = (request.POST.get("section") or "").strip()
# Required-field validation (the inquiry-type `category` is intentionally NOT
# collected here — the model field is required at DB level so it is defaulted
# to "general" below. The department-category in the modal is a UI filter only.)
missing = {
"message": message,
"contact_name": contact_name,
"contact_phone": contact_phone,
"location_type": location_type,
"department": department_id,
}
for field, value in missing.items():
if not value:
return JsonResponse(
{"success": False, "error": str(_("This field is required: {}").format(field))},
status=400,
)
hospital = getattr(request, "tenant_hospital", None) or user.hospital
if not hospital:
return JsonResponse({"success": False, "error": str(_("No hospital assigned to your account."))}, status=400)
# Validate location_type
from apps.complaints.models import Inquiry as _Inquiry
valid_location_types = {c for c, _ in _Inquiry._meta.get_field("location_type").choices or []}
if valid_location_types and location_type not in valid_location_types:
return JsonResponse({"success": False, "error": str(_("Invalid location type."))}, status=400)
# Validate department belongs to the hospital
from apps.organizations.models import Department, Section
try:
department = Department.objects.get(id=department_id, hospital=hospital, status="active")
except Department.DoesNotExist:
return JsonResponse({"success": False, "error": str(_("Invalid department for this hospital."))}, status=400)
# Section is optional — only validate if provided
section = None
if section_id:
try:
section = Section.objects.get(id=section_id, department=department)
except Section.DoesNotExist:
return JsonResponse(
{"success": False, "error": str(_("Selected section does not belong to this department."))},
status=400,
)
try:
inquiry = Inquiry.objects.create(
hospital=hospital,
created_by=user,
message=message,
contact_name=contact_name,
contact_phone=contact_phone,
contact_email=contact_email,
location_type=location_type,
category="general",
department=department,
section=section,
# The inquiry is routed to this department for handling — mark it as
# sent so it appears in the department's incoming queue (not outgoing).
sent_to_department=True,
sent_to_department_at=timezone.now(),
# Defaults — AI refines subject/priority in the background (NOT department)
subject=message[:80],
priority="medium",
# Auto-activate + auto-assign to the creator
status="in_progress",
assigned_to=user,
assigned_at=timezone.now(),
activated_at=timezone.now(),
metadata={
"subject_pending_ai": True, # AI fills the subject from its generated title
"ai_skip_department": True, # user provided department — AI must not override it
},
)
# Background AI analysis (fills subject/priority/department) + notifications
from apps.complaints.tasks import analyze_inquiry_with_ai, notify_staff_new_item
analyze_inquiry_with_ai.delay(str(inquiry.id))
notify_staff_new_item.delay("inquiry", str(inquiry.id))
InquiryUpdate.objects.create(
inquiry=inquiry,
update_type="note",
message="Inquiry created.",
created_by=user,
)
roles_display = ", ".join(user.get_role_names()) if hasattr(user, "get_role_names") else ""
InquiryUpdate.objects.create(
inquiry=inquiry,
update_type="assignment",
message=f"Inquiry auto-activated and assigned to {user.get_full_name()} ({roles_display})",
created_by=user,
)
AuditService.log_event(
event_type="inquiry_created",
description=f"Quick inquiry created: {inquiry.subject}",
user=user,
content_object=inquiry,
metadata={"category": inquiry.category, "quick_create": True},
)
return JsonResponse({
"success": True,
"inquiry_id": str(inquiry.id),
"reference_number": inquiry.reference_number,
})
except Exception as e:
logger.error(f"Quick inquiry create failed: {e}")
return JsonResponse(
{"success": False, "error": str(_("An error occurred while creating the inquiry."))},
status=500,
)
@login_required @login_required
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
def inquiry_create(request): def inquiry_create(request):
@ -3431,6 +3646,8 @@ def inquiry_assign(request, pk):
user.is_px_admin() user.is_px_admin()
or user.is_hospital_admin() or user.is_hospital_admin()
or (user.is_department_manager() and user.department and user.department == inquiry.department) or (user.is_department_manager() and user.department and user.department == inquiry.department)
or user.is_px_management()
or user.is_px_employee()
) )
if not can_assign: if not can_assign:
messages.error(request, "You don't have permission to assign inquiries.") messages.error(request, "You don't have permission to assign inquiries.")
@ -3509,24 +3726,37 @@ def inquiry_change_status(request, pk):
if not ( if not (
user.is_px_admin() or user.is_hospital_admin() user.is_px_admin() or user.is_hospital_admin()
or user.is_px_management() or user.is_px_employee() or user.is_px_management() or user.is_px_employee()
or (user.is_department_manager() and user.department_id == inquiry.handling_department_id)
or (user.is_champion() and user.department_id == inquiry.handling_department_id)
): ):
messages.error(request, "You don't have permission to change inquiry status.") messages.error(request, "You don't have permission to change inquiry status.")
return redirect("inquiries:inquiry_detail", pk=pk) return _safe_next_redirect(request, redirect("inquiries:inquiry_detail", pk=pk))
new_status = request.POST.get("status") new_status = request.POST.get("status")
note = request.POST.get("note", "") note = request.POST.get("note", "")
if not new_status: if not new_status:
messages.error(request, "Please select a status.") messages.error(request, "Please select a status.")
return redirect("inquiries:inquiry_detail", pk=pk) return _safe_next_redirect(request, redirect("inquiries:inquiry_detail", pk=pk))
old_status = inquiry.status old_status = inquiry.status
inquiry.status = new_status inquiry.status = new_status
# Handle status-specific logic # Handle status-specific logic
if new_status == "resolved" and not inquiry.response: # Department users (champion / dept manager) must record a patient contact
messages.error(request, "Please add a response before resolving.") # outcome before closing/resolving an inquiry. PX roles follow their own flow.
return redirect("inquiries:inquiry_detail", pk=pk) if new_status in ("resolved", "closed") and (user.is_champion() or user.is_department_manager()):
if inquiry.contact_status not in ("contacted", "contacted_no_response"):
messages.error(request, _("Please record a patient contact outcome before closing."))
return _safe_next_redirect(request, redirect("inquiries:inquiry_detail", pk=pk))
# "Contacted - No Response" is a terminal status set when the department could
# not reach the patient. Auto-sync the contact_status tracking so outreach data
# stays consistent without double data entry.
if new_status == "contact_no_response":
inquiry.contact_status = "contacted_no_response"
inquiry.contact_status_at = timezone.now()
inquiry.contact_status_by = user
inquiry._acting_user = request.user inquiry._acting_user = request.user
inquiry.save() inquiry.save()
@ -3551,7 +3781,67 @@ def inquiry_change_status(request, pk):
) )
messages.success(request, f"Inquiry status changed to {new_status}.") messages.success(request, f"Inquiry status changed to {new_status}.")
return redirect("inquiries:inquiry_detail", pk=pk) return _safe_next_redirect(request, redirect("inquiries:inquiry_detail", pk=pk))
@login_required
@require_http_methods(["POST"])
def inquiry_set_contact_status(request, pk):
"""Record the outcome of the department's patient-contact attempt.
Sets the existing ``contact_status`` field (not_contacted / contacted /
contacted_no_response). Used by the department (champion/manager) that owns
the inquiry to log communication outcomes with the patient.
"""
from .models import Inquiry, InquiryUpdate
inquiry = get_object_or_404(Inquiry, pk=pk)
user = request.user
if not (
user.is_px_admin() or user.is_hospital_admin()
or user.is_px_management() or user.is_px_employee()
or (user.is_department_manager() and user.department_id == inquiry.handling_department_id)
or (user.is_champion() and user.department_id == inquiry.handling_department_id)
):
messages.error(request, _("You don't have permission to set the contact status."))
return redirect("inquiries:inquiry_detail", pk=pk)
new_contact_status = request.POST.get("contact_status")
note = request.POST.get("note", "").strip()
valid = {c for c, _ in Inquiry._meta.get_field("contact_status").choices}
if new_contact_status not in valid:
messages.error(request, _("Please select a valid contact status."))
return redirect("inquiries:inquiry_detail", pk=pk)
old_contact_status = inquiry.contact_status
inquiry.contact_status = new_contact_status
inquiry.contact_status_at = timezone.now()
inquiry.contact_status_by = user
inquiry.save(update_fields=["contact_status", "contact_status_at", "contact_status_by"])
display = dict(Inquiry._meta.get_field("contact_status").choices).get(new_contact_status, new_contact_status)
InquiryUpdate.objects.create(
inquiry=inquiry,
update_type="communication",
message=note or _("Contact status set to: {status}").format(status=display),
created_by=user,
)
AuditService.log_event(
event_type="inquiry_contact_status",
description=_("Contact status changed from {old} to {new}").format(
old=old_contact_status or "", new=new_contact_status
),
user=user,
content_object=inquiry,
metadata={"old_contact_status": old_contact_status, "new_contact_status": new_contact_status},
)
messages.success(request, _("Contact status updated to: {status}").format(status=display))
return _safe_next_redirect(request, redirect("inquiries:inquiry_detail", pk=pk))
@login_required @login_required
@ -3566,6 +3856,8 @@ def inquiry_reopen(request, pk):
user.is_px_admin() user.is_px_admin()
or user.is_hospital_admin() or user.is_hospital_admin()
or (user.is_department_manager() and user.department == inquiry.department) or (user.is_department_manager() and user.department == inquiry.department)
or user.is_px_management()
or user.is_px_employee()
): ):
messages.error(request, "You don't have permission to reopen inquiries.") messages.error(request, "You don't have permission to reopen inquiries.")
return redirect("inquiries:inquiry_detail", pk=pk) return redirect("inquiries:inquiry_detail", pk=pk)
@ -3643,6 +3935,8 @@ def inquiry_respond(request, pk):
and user.department and user.department
and user.department in [inquiry.department, inquiry.outgoing_department] and user.department in [inquiry.department, inquiry.outgoing_department]
) )
or user.is_px_management()
or user.is_px_employee()
) )
if not can_respond: if not can_respond:
messages.error(request, "You don't have permission to respond to inquiries.") messages.error(request, "You don't have permission to respond to inquiries.")
@ -3755,8 +4049,9 @@ def inquiry_transfer_to_department(request, pk):
if not ( if not (
user.is_px_admin() user.is_px_admin()
or user.is_hospital_admin() or user.is_hospital_admin()
or user.is_department_manager() or (user.is_department_manager() and user.department_id == inquiry.handling_department_id)
or user.is_px_management() or user.is_px_employee() or user.is_px_management() or user.is_px_employee()
or (user.is_champion() and user.department_id == inquiry.handling_department_id)
): ):
messages.error(request, _("You don't have permission to transfer inquiries to departments.")) messages.error(request, _("You don't have permission to transfer inquiries to departments."))
return redirect("inquiries:inquiry_detail", pk=pk) return redirect("inquiries:inquiry_detail", pk=pk)
@ -4000,8 +4295,9 @@ def inquiry_send_to(request, pk):
if not ( if not (
user.is_px_admin() user.is_px_admin()
or user.is_hospital_admin() or user.is_hospital_admin()
or user.is_department_manager() or (user.is_department_manager() and user.department_id == inquiry.handling_department_id)
or user.is_px_management() or user.is_px_employee() or user.is_px_management() or user.is_px_employee()
or (user.is_champion() and user.department_id == inquiry.handling_department_id)
): ):
return JsonResponse({ return JsonResponse({
"success": False, "success": False,
@ -4097,9 +4393,8 @@ def inquiry_send_to(request, pk):
inquiry.transferred_to_department = department inquiry.transferred_to_department = department
inquiry.transfer_count = (inquiry.transfer_count or 0) + 1 inquiry.transfer_count = (inquiry.transfer_count or 0) + 1
if inquiry.department_id == department.pk: inquiry.sent_to_department = True
inquiry.sent_to_department = True inquiry.sent_to_department_at = timezone.now()
inquiry.sent_to_department_at = timezone.now()
sla_config = inquiry.get_sla_config() sla_config = inquiry.get_sla_config()
if sla_config and sla_config.dept_response_hours: if sla_config and sla_config.dept_response_hours:
@ -4184,6 +4479,8 @@ def inquiry_department_response(request, pk):
inquiry.department == user.department inquiry.department == user.department
or inquiry.outgoing_department == user.department or inquiry.outgoing_department == user.department
)) ))
or user.is_px_management()
or user.is_px_employee()
): ):
messages.error(request, "You don't have permission to respond to this inquiry.") messages.error(request, "You don't have permission to respond to this inquiry.")
return redirect("inquiries:inquiry_detail", pk=pk) return redirect("inquiries:inquiry_detail", pk=pk)
@ -4243,28 +4540,6 @@ Generate a JSON response with:
content_object=inquiry, content_object=inquiry,
) )
# Notify inquirer that department has responded
try:
from apps.notifications.services import NotificationService
from apps.core.utils import build_public_track_url
track_url = build_public_track_url("inquiry", inquiry.reference_number)
if inquiry.contact_phone:
NotificationService.send_sms(
phone=inquiry.contact_phone,
message=f"PX360: Your inquiry {inquiry.reference_number} has been responded to. View: {track_url}",
related_object=inquiry,
)
if inquiry.contact_email:
NotificationService.send_email(
email=inquiry.contact_email,
subject=f"PX360: Response to Your Inquiry {inquiry.reference_number}",
message=f"Your inquiry has been responded to.\n\nView: {track_url}",
related_object=inquiry,
)
except Exception as e:
logger.warning(f"Failed to send inquirer notification: {e}")
messages.success(request, "Department response submitted successfully.") messages.success(request, "Department response submitted successfully.")
if request.headers.get('X-Requested-With') == 'XMLHttpRequest': if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
from django.http import JsonResponse from django.http import JsonResponse
@ -4307,30 +4582,29 @@ def inquiry_send_dept_response_reminder(request, pk):
reminder_type = request.POST.get("reminder_type", "first") reminder_type = request.POST.get("reminder_type", "first")
if reminder_type == "first" and inquiry.dept_response_reminder_sent_at:
messages.warning(request, "First reminder already sent.")
return redirect("inquiries:inquiry_detail", pk=pk)
if reminder_type == "second" and inquiry.dept_response_second_reminder_sent_at:
messages.warning(request, "Second reminder already sent.")
return redirect("inquiries:inquiry_detail", pk=pk)
if reminder_type == "second" and not inquiry.dept_response_reminder_sent_at:
messages.warning(request, "Please send the first reminder before the second.")
return redirect("inquiries:inquiry_detail", pk=pk)
try: try:
from apps.notifications.services import NotificationService, get_email_header_html from apps.organizations.department_contacts import get_champion_and_manager
targets = get_champion_and_manager(dept)
if not targets:
messages.error(request, f"No champion or manager assigned to {dept.name}.")
return redirect("inquiries:inquiry_detail", pk=pk)
recipients = [] recipients = []
if dept.champion and dept.champion.user and dept.champion.user.email: for target in targets:
recipients.append(dept.champion.user) email = target.get("email") or ""
if email:
for recipient in recipients: recipients.append({"email": email, "name": target.get("display_name", "")})
NotificationService.send_email(
email=recipient.email,
subject=f"Reminder: Inquiry #{inquiry.reference_number} - Response Required",
message=f"This is a reminder that inquiry #{inquiry.reference_number} is awaiting your department's response. Please submit your response as soon as possible.",
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Reminder: Response Required</h2>
<p style="margin: 0 0 12px 0;">This is a reminder that inquiry <strong>#{inquiry.reference_number}</strong> is awaiting your department's response.</p>
<p style="margin: 0; color: #6b7280;">Please submit your response as soon as possible.</p>
</div>
</div>
""",
related_object=inquiry,
)
if reminder_type == "first": if reminder_type == "first":
inquiry.dept_response_reminder_sent_at = timezone.now() inquiry.dept_response_reminder_sent_at = timezone.now()
@ -4338,6 +4612,9 @@ def inquiry_send_dept_response_reminder(request, pk):
inquiry.dept_response_second_reminder_sent_at = timezone.now() inquiry.dept_response_second_reminder_sent_at = timezone.now()
inquiry.save() inquiry.save()
from .tasks import send_inquiry_reminder_email
send_inquiry_reminder_email.delay(str(inquiry.id), reminder_type, recipients)
InquiryUpdate.objects.create( InquiryUpdate.objects.create(
inquiry=inquiry, inquiry=inquiry,
update_type="note", update_type="note",
@ -4400,7 +4677,7 @@ def inquiry_update_contact_stage(request, pk):
inquiry = get_object_or_404(Inquiry, pk=pk) inquiry = get_object_or_404(Inquiry, pk=pk)
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin() or inquiry.assigned_to == user): if not (user.is_px_admin() or user.is_hospital_admin() or inquiry.assigned_to == user or user.is_px_management() or user.is_px_employee()):
messages.error(request, _("You don't have permission to update contact tracking.")) messages.error(request, _("You don't have permission to update contact tracking."))
return redirect("inquiries:inquiry_detail", pk=pk) return redirect("inquiries:inquiry_detail", pk=pk)
@ -4885,6 +5162,7 @@ def public_inquiry_submit(request):
name = request.POST.get("name") name = request.POST.get("name")
email = request.POST.get("email") email = request.POST.get("email")
phone = request.POST.get("phone") phone = request.POST.get("phone")
national_id = request.POST.get("national_id", "")
hospital_id = request.POST.get("hospital") hospital_id = request.POST.get("hospital")
location_type = request.POST.get("location_type", "") location_type = request.POST.get("location_type", "")
department_id = request.POST.get("department", "") department_id = request.POST.get("department", "")
@ -4949,6 +5227,7 @@ def public_inquiry_submit(request):
contact_name=name, contact_name=name,
contact_phone=phone, contact_phone=phone,
contact_email=email, contact_email=email,
national_id=national_id,
status="open", status="open",
is_outgoing=False, is_outgoing=False,
) )
@ -6508,7 +6787,7 @@ def adverse_action_add(request, complaint_pk):
"complaint": complaint, "complaint": complaint,
"action_type_choices": ComplaintAdverseAction.ActionType.choices, "action_type_choices": ComplaintAdverseAction.ActionType.choices,
"severity_choices": ComplaintAdverseAction.SeverityLevel.choices, "severity_choices": ComplaintAdverseAction.SeverityLevel.choices,
"staff_list": Staff.objects.filter(hospital=complaint.hospital, is_active=True), "staff_list": Staff.objects.filter(hospital=complaint.hospital, status="active"),
}, },
) )
@ -6561,11 +6840,10 @@ def adverse_action_add(request, complaint_pk):
"complaint": complaint, "complaint": complaint,
"action_type_choices": ComplaintAdverseAction.ActionType.choices, "action_type_choices": ComplaintAdverseAction.ActionType.choices,
"severity_choices": ComplaintAdverseAction.SeverityLevel.choices, "severity_choices": ComplaintAdverseAction.SeverityLevel.choices,
"staff_list": Staff.objects.filter(hospital=complaint.hospital, is_active=True) "staff_list": Staff.objects.filter(hospital=complaint.hospital, status="active")
if complaint.hospital if complaint.hospital
else Staff.objects.filter(is_active=True), else Staff.objects.filter(status="active"),
} }
return render(request, "complaints/adverse_action_form.html", context) return render(request, "complaints/adverse_action_form.html", context)
@ -6623,9 +6901,9 @@ def adverse_action_edit(request, pk):
"complaint": complaint, "complaint": complaint,
"action_type_choices": ComplaintAdverseAction.ActionType.choices, "action_type_choices": ComplaintAdverseAction.ActionType.choices,
"severity_choices": ComplaintAdverseAction.SeverityLevel.choices, "severity_choices": ComplaintAdverseAction.SeverityLevel.choices,
"staff_list": Staff.objects.filter(hospital=complaint.hospital, is_active=True) "staff_list": Staff.objects.filter(hospital=complaint.hospital, status="active")
if complaint.hospital if complaint.hospital
else Staff.objects.filter(is_active=True), else Staff.objects.filter(status="active"),
"selected_staff": list(adverse_action.involved_staff.values_list("id", flat=True)), "selected_staff": list(adverse_action.involved_staff.values_list("id", flat=True)),
} }

View File

@ -9,11 +9,13 @@ urlpatterns = [
# Inquiries UI Views # Inquiries UI Views
path("", ui_views.inquiry_list, name="inquiry_list"), path("", ui_views.inquiry_list, name="inquiry_list"),
path("new/", ui_views.inquiry_create, name="inquiry_create"), path("new/", ui_views.inquiry_create, name="inquiry_create"),
path("new/quick/", ui_views.inquiry_quick_create, name="inquiry_quick_create"),
path("<uuid:pk>/", ui_views.inquiry_detail, name="inquiry_detail"), path("<uuid:pk>/", ui_views.inquiry_detail, name="inquiry_detail"),
path("<uuid:pk>/edit/", ui_views.inquiry_edit, name="inquiry_edit"), path("<uuid:pk>/edit/", ui_views.inquiry_edit, name="inquiry_edit"),
path("<uuid:pk>/activate/", ui_views.inquiry_activate, name="inquiry_activate"), path("<uuid:pk>/activate/", ui_views.inquiry_activate, name="inquiry_activate"),
path("<uuid:pk>/assign/", ui_views.inquiry_assign, name="inquiry_assign"), path("<uuid:pk>/assign/", ui_views.inquiry_assign, name="inquiry_assign"),
path("<uuid:pk>/change-status/", ui_views.inquiry_change_status, name="inquiry_change_status"), path("<uuid:pk>/change-status/", ui_views.inquiry_change_status, name="inquiry_change_status"),
path("<uuid:pk>/contact-status/", ui_views.inquiry_set_contact_status, name="inquiry_set_contact_status"),
path("<uuid:pk>/reopen/", ui_views.inquiry_reopen, name="inquiry_reopen"), path("<uuid:pk>/reopen/", ui_views.inquiry_reopen, name="inquiry_reopen"),
path("<uuid:pk>/add-note/", ui_views.inquiry_add_note, name="inquiry_add_note"), path("<uuid:pk>/add-note/", ui_views.inquiry_add_note, name="inquiry_add_note"),
path("<uuid:pk>/respond/", ui_views.inquiry_respond, name="inquiry_respond"), path("<uuid:pk>/respond/", ui_views.inquiry_respond, name="inquiry_respond"),

View File

@ -1721,7 +1721,7 @@ Provide 3-5 suggestions. Keep them concise and focused on how to respond to the
user.is_px_admin() user.is_px_admin()
or (user.is_hospital_admin() and user.hospital == complaint.hospital) or (user.is_hospital_admin() and user.hospital == complaint.hospital)
or (user.is_department_manager() and user.department == complaint.department) or (user.is_department_manager() and user.department == complaint.department)
or (user.is_px_employee and complaint.hospital == user.hospital) or (user.is_px_employee() and complaint.hospital == user.hospital)
) )
if not can_generate: if not can_generate:
return Response( return Response(
@ -1804,7 +1804,7 @@ Provide 3-5 suggestions. Keep them concise and focused on how to respond to the
user.is_px_admin() user.is_px_admin()
or (user.is_hospital_admin() and user.hospital == complaint.hospital) or (user.is_hospital_admin() and user.hospital == complaint.hospital)
or (user.is_department_manager() and user.department == complaint.department) or (user.is_department_manager() and user.department == complaint.department)
or (user.is_px_employee and complaint.hospital == user.hospital) or (user.is_px_employee() and complaint.hospital == user.hospital)
) )
if not can_generate: if not can_generate:
return Response( return Response(
@ -2992,6 +2992,8 @@ class InquiryViewSet(viewsets.ModelViewSet):
user = request.user user = request.user
can_generate = ( can_generate = (
user.is_px_admin() user.is_px_admin()
or user.is_px_management()
or user.is_px_employee()
or (user.is_hospital_admin() and user.hospital == inquiry.hospital) or (user.is_hospital_admin() and user.hospital == inquiry.hospital)
or inquiry.assigned_to == user or inquiry.assigned_to == user
) )
@ -5210,28 +5212,6 @@ Generate JSON with "summary_en" and "summary_ar"."""
except Exception: except Exception:
pass pass
# Notify inquirer
track_url = build_public_track_url("inquiry", inquiry.reference_number)
if inquiry.contact_phone:
try:
NotificationService.send_sms(
phone=inquiry.contact_phone,
message=f"PX360: Your inquiry {inquiry.reference_number} has been responded to. View: {track_url}",
related_object=inquiry,
)
except Exception:
pass
if inquiry.contact_email:
try:
NotificationService.send_email(
email=inquiry.contact_email,
subject=f"PX360: Response to Your Inquiry {inquiry.reference_number}",
message=f"Your inquiry has been responded to.\n\nView: {track_url}",
related_object=inquiry,
)
except Exception:
pass
return render(request, "complaints/inquiry_response_success_token.html", {"inquiry": inquiry}) return render(request, "complaints/inquiry_response_success_token.html", {"inquiry": inquiry})
return render(request, "complaints/inquiry_response_form_token.html", {"inquiry": inquiry}) return render(request, "complaints/inquiry_response_form_token.html", {"inquiry": inquiry})

View File

@ -1010,8 +1010,8 @@ class AIService:
"title": title or "Complaint", "title": title or "Complaint",
"title_en": title or "Complaint", "title_en": title or "Complaint",
"title_ar": title or "شكوى", "title_ar": title or "شكوى",
"short_description_en": description[:200] if description else "", "short_description_en": "",
"short_description_ar": description[:200] if description else "", "short_description_ar": "",
"severity": "medium", "severity": "medium",
"priority": "medium", "priority": "medium",
"category": "other", "category": "other",
@ -1032,8 +1032,8 @@ class AIService:
"title": title or "Complaint", "title": title or "Complaint",
"title_en": title or "Complaint", "title_en": title or "Complaint",
"title_ar": title or "شكوى", "title_ar": title or "شكوى",
"short_description_en": description[:200] if description else "", "short_description_en": "",
"short_description_ar": description[:200] if description else "", "short_description_ar": "",
"severity": "medium", "severity": "medium",
"priority": "medium", "priority": "medium",
"category": "other", "category": "other",
@ -1182,8 +1182,8 @@ Always use EXACT names and codes from the provided taxonomy - do not invent new
"title_ar": subject, "title_ar": subject,
"brief_summary_en": "Inquiry", "brief_summary_en": "Inquiry",
"brief_summary_ar": "استفسار", "brief_summary_ar": "استفسار",
"short_description_en": message[:200] if message else "", "short_description_en": "",
"short_description_ar": message[:200] if message else "", "short_description_ar": "",
"priority": "medium", "priority": "medium",
"department": "", "department": "",
"reasoning_en": "AI analysis failed, using default values", "reasoning_en": "AI analysis failed, using default values",
@ -1198,8 +1198,8 @@ Always use EXACT names and codes from the provided taxonomy - do not invent new
"title_ar": subject, "title_ar": subject,
"brief_summary_en": "Inquiry", "brief_summary_en": "Inquiry",
"brief_summary_ar": "استفسار", "brief_summary_ar": "استفسار",
"short_description_en": message[:200] if message else "", "short_description_en": "",
"short_description_ar": message[:200] if message else "", "short_description_ar": "",
"priority": "medium", "priority": "medium",
"department": "", "department": "",
"reasoning_en": f"AI service unavailable: {str(e)}", "reasoning_en": f"AI service unavailable: {str(e)}",
@ -1418,8 +1418,8 @@ Always use EXACT names and codes from the provided taxonomy - do not invent new
"title_ar": title or "ملاحظة", "title_ar": title or "ملاحظة",
"brief_summary_en": "Observation", "brief_summary_en": "Observation",
"brief_summary_ar": "ملاحظة", "brief_summary_ar": "ملاحظة",
"short_description_en": description[:200] if description else "", "short_description_en": "",
"short_description_ar": description[:200] if description else "", "short_description_ar": "",
"severity": "medium", "severity": "medium",
"category": "Other", "category": "Other",
"department": "", "department": "",
@ -1439,8 +1439,8 @@ Always use EXACT names and codes from the provided taxonomy - do not invent new
"title_ar": title or "ملاحظة", "title_ar": title or "ملاحظة",
"brief_summary_en": "Observation", "brief_summary_en": "Observation",
"brief_summary_ar": "ملاحظة", "brief_summary_ar": "ملاحظة",
"short_description_en": description[:200] if description else "", "short_description_en": "",
"short_description_ar": description[:200] if description else "", "short_description_ar": "",
"severity": "medium", "severity": "medium",
"category": "Other", "category": "Other",
"department": "", "department": "",
@ -2218,10 +2218,10 @@ Respond in JSON format:
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error(f"Failed to parse suggestion AI response: {e}") logger.error(f"Failed to parse suggestion AI response: {e}")
return { return {
"short_description_en": message[:200] if message else "", "short_description_en": "",
"short_description_ar": message[:200] if message else "", "short_description_ar": "",
"description_en": message[:500] if message else "", "description_en": "",
"description_ar": message[:500] if message else "", "description_ar": "",
"category": "other", "category": "other",
"priority": "medium", "priority": "medium",
"suggested_actions": [], "suggested_actions": [],
@ -2233,10 +2233,10 @@ Respond in JSON format:
except AIServiceError as e: except AIServiceError as e:
logger.error(f"AI service error for suggestion: {e}") logger.error(f"AI service error for suggestion: {e}")
return { return {
"short_description_en": message[:200] if message else "", "short_description_en": "",
"short_description_ar": message[:200] if message else "", "short_description_ar": "",
"description_en": message[:500] if message else "", "description_en": "",
"description_ar": message[:500] if message else "", "description_ar": "",
"category": "other", "category": "other",
"priority": "medium", "priority": "medium",
"suggested_actions": [], "suggested_actions": [],

View File

@ -118,7 +118,9 @@ class DepartmentRespondentMiddleware(MiddlewareMixin):
if not request.user.is_champion(): if not request.user.is_champion():
return None return None
if request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_department_manager() or request.user.is_director(): if (request.user.is_px_admin() or request.user.is_hospital_admin()
or request.user.is_department_manager() or request.user.is_director()
or request.user.is_px_employee() or request.user.is_px_management()):
return None return None
if request.headers.get("x-requested-with") == "XMLHttpRequest": if request.headers.get("x-requested-with") == "XMLHttpRequest":

45
apps/core/validators.py Normal file
View File

@ -0,0 +1,45 @@
"""
Shared validators used across multiple apps.
"""
import re
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
# Regex for a Saudi mobile number after stripping spaces/dashes/parens/plus.
# Accepts any of:
# 05XXXXXXXX (10 digits, local with trunk 0)
# 5XXXXXXXX (9 digits, no trunk)
# 9665XXXXXXXX (12 digits, country code without +)
# +9665XXXXXXXX (13 digits, country code with +)
SAUDI_MOBILE_REGEX = re.compile(r"^(?:\+?966|0)?5\d{8}$")
# HTML5 form pattern equivalent (HTML anchors ^...$ implicitly).
# Exposed for reuse in widget attrs and template <input pattern="...">.
SAUDI_PHONE_HTML_PATTERN = r"(\+?966|0)?5[0-9]{8}"
def validate_saudi_phone(value):
"""
Validate that ``value`` is a Saudi mobile number.
Accepts the following formats (spaces, dashes and parentheses are ignored):
05XXXXXXXX
5XXXXXXXX
9665XXXXXXXX
+9665XXXXXXXX
Stored value is returned unchanged (no normalization).
Raises ``ValidationError`` if the number is not a valid Saudi mobile.
"""
if value is None or value == "":
return value
cleaned = re.sub(r"[\s\-()]", "", str(value))
if not SAUDI_MOBILE_REGEX.match(cleaned):
raise ValidationError(
_("Please enter a valid Saudi mobile number "
"(e.g. 05XXXXXXXX or +9665XXXXXXXX)."),
code="invalid_saudi_phone",
)
return value

View File

@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _
from apps.organizations.models import Department, Hospital, Patient, Staff from apps.organizations.models import Department, Hospital, Patient, Staff
from apps.core.form_mixins import HospitalFieldMixin from apps.core.form_mixins import HospitalFieldMixin
from apps.core.validators import SAUDI_PHONE_HTML_PATTERN, validate_saudi_phone
from .models import Feedback, FeedbackResponse, FeedbackStatus, FeedbackType, FeedbackCategory from .models import Feedback, FeedbackResponse, FeedbackStatus, FeedbackType, FeedbackCategory
@ -259,7 +260,13 @@ class PublicSuggestionForm(forms.Form):
label=_("Mobile Number"), label=_("Mobile Number"),
max_length=20, max_length=20,
required=True, required=True,
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your mobile number")}), validators=[validate_saudi_phone],
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": _("05XXXXXXXX or +9665XXXXXXXX"),
"pattern": SAUDI_PHONE_HTML_PATTERN,
"inputmode": "tel",
}),
) )
contact_email = forms.EmailField( contact_email = forms.EmailField(
label=_("Email Address"), label=_("Email Address"),

View File

@ -0,0 +1,26 @@
# Generated by Django 6.0.1 on 2026-07-14 12:00
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feedback', '0008_feedback_reference_number_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='feedback',
name='activated_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='feedback',
name='activated_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='activated_feedbacks', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 6.0.1 on 2026-07-18 14:46
import apps.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('feedback', '0009_add_activation_fields'),
]
operations = [
migrations.AlterField(
model_name='feedback',
name='contact_phone',
field=models.CharField(blank=True, max_length=20, validators=[apps.core.validators.validate_saudi_phone]),
),
]

View File

@ -15,6 +15,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.core.models import PriorityChoices, SoftDeleteModel, TimeStampedModel, UUIDModel from apps.core.models import PriorityChoices, SoftDeleteModel, TimeStampedModel, UUIDModel
from apps.core.validators import validate_saudi_phone
class FeedbackType(models.TextChoices): class FeedbackType(models.TextChoices):
@ -94,7 +95,7 @@ class Feedback(UUIDModel, TimeStampedModel, SoftDeleteModel):
is_anonymous = models.BooleanField(default=False) is_anonymous = models.BooleanField(default=False)
contact_name = models.CharField(max_length=200, blank=True) contact_name = models.CharField(max_length=200, blank=True)
contact_email = models.EmailField(blank=True) contact_email = models.EmailField(blank=True)
contact_phone = models.CharField(max_length=20, blank=True) contact_phone = models.CharField(max_length=20, blank=True, validators=[validate_saudi_phone])
encounter_id = models.CharField( encounter_id = models.CharField(
max_length=100, blank=True, db_index=True, help_text="Related encounter ID if applicable" max_length=100, blank=True, db_index=True, help_text="Related encounter ID if applicable"
@ -196,6 +197,10 @@ class Feedback(UUIDModel, TimeStampedModel, SoftDeleteModel):
status = models.CharField( status = models.CharField(
max_length=20, choices=FeedbackStatus.choices, default=FeedbackStatus.SUBMITTED, db_index=True max_length=20, choices=FeedbackStatus.choices, default=FeedbackStatus.SUBMITTED, db_index=True
) )
activated_at = models.DateTimeField(null=True, blank=True)
activated_by = models.ForeignKey(
"accounts.User", on_delete=models.SET_NULL, null=True, blank=True, related_name="activated_feedbacks"
)
# Assignment # Assignment
assigned_to = models.ForeignKey( assigned_to = models.ForeignKey(

View File

@ -17,6 +17,7 @@ urlpatterns = [
path("<uuid:pk>/update/", views.feedback_update, name="feedback_update"), path("<uuid:pk>/update/", views.feedback_update, name="feedback_update"),
path("<uuid:pk>/delete/", views.feedback_delete, name="feedback_delete"), path("<uuid:pk>/delete/", views.feedback_delete, name="feedback_delete"),
# Workflow actions # Workflow actions
path("<uuid:pk>/activate/", views.feedback_activate, name="feedback_activate"),
path("<uuid:pk>/assign/", views.feedback_assign, name="feedback_assign"), path("<uuid:pk>/assign/", views.feedback_assign, name="feedback_assign"),
path("<uuid:pk>/change-status/", views.feedback_change_status, name="feedback_change_status"), path("<uuid:pk>/change-status/", views.feedback_change_status, name="feedback_change_status"),
path("<uuid:pk>/send-to-department/", views.feedback_send_to_department, name="feedback_send_to_department"), path("<uuid:pk>/send-to-department/", views.feedback_send_to_department, name="feedback_send_to_department"),

View File

@ -259,11 +259,14 @@ def feedback_detail(request, pk):
hospital=feedback.hospital, status="active" hospital=feedback.hospital, status="active"
).order_by("name"), ).order_by("name"),
"status_choices": FeedbackStatus.choices, "status_choices": FeedbackStatus.choices,
"can_activate": feedback.activated_at is None,
"is_active": feedback.activated_at is not None,
"can_edit": ( "can_edit": (
user.is_px_admin() (user.is_px_admin()
or user.is_hospital_admin() or user.is_hospital_admin()
or user.is_px_management() or user.is_px_management()
or user.is_px_employee() or user.is_px_employee())
and feedback.activated_at is not None
), ),
"can_admin": user.is_px_admin() or user.is_hospital_admin(), "can_admin": user.is_px_admin() or user.is_hospital_admin(),
"can_send_to_department": ( "can_send_to_department": (
@ -282,8 +285,8 @@ def feedback_detail(request, pk):
# Workflow stepper # Workflow stepper
_fb_steps = [ _fb_steps = [
{"label": "Submitted", "icon": "plus-circle", "done": True}, {"label": "Submitted", "icon": "plus-circle", "done": True},
{"label": "Assigned", "icon": "user-plus", "done": feedback.assigned_to is not None}, {"label": "Activated", "icon": "zap", "done": feedback.activated_at is not None},
{"label": "Reviewed", "icon": "eye", "done": feedback.status in ("reviewed", "acknowledged", "closed")}, {"label": "Sent to Dept", "icon": "send", "done": feedback.department_id is not None or feedback.status in ("acknowledged", "closed")},
{"label": "Acknowledged", "icon": "thumbs-up", "done": feedback.status in ("acknowledged", "closed")}, {"label": "Acknowledged", "icon": "thumbs-up", "done": feedback.status in ("acknowledged", "closed")},
{"label": "Closed", "icon": "check-circle-2", "done": feedback.status == "closed"}, {"label": "Closed", "icon": "check-circle-2", "done": feedback.status == "closed"},
] ]
@ -298,7 +301,6 @@ def feedback_detail(request, pk):
@login_required @login_required
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
def feedback_create(request): def feedback_create(request):
from apps.organizations.models import LegacyLocation, LegacyMainSection, LegacySubSection, Hospital
from apps.feedback.models import FeedbackType, FeedbackCategory, FeedbackStatus from apps.feedback.models import FeedbackType, FeedbackCategory, FeedbackStatus
communication_request = None communication_request = None
@ -313,11 +315,12 @@ def feedback_create(request):
except Exception: except Exception:
pass pass
hospital = getattr(request, "tenant_hospital", None) or request.user.hospital
if request.method == "POST": if request.method == "POST":
contact_name = request.POST.get("contact_name", "").strip() contact_name = request.POST.get("contact_name", "").strip()
contact_phone = request.POST.get("contact_phone", "").strip() contact_phone = request.POST.get("contact_phone", "").strip()
message = request.POST.get("message", "").strip() message = request.POST.get("message", "").strip()
hospital_id = request.POST.get("hospital", "")
title = request.POST.get("title", message[:100]).strip() title = request.POST.get("title", message[:100]).strip()
errors = [] errors = []
@ -327,23 +330,15 @@ def feedback_create(request):
errors.append("Phone is required") errors.append("Phone is required")
if not message: if not message:
errors.append("Message is required") errors.append("Message is required")
if not hospital_id: if hospital is None:
errors.append("Hospital is required") errors.append("No hospital context. Contact your administrator.")
if errors: if errors:
for e in errors: for e in errors:
messages.error(request, e) messages.error(request, e)
else: else:
try: try:
hospital = Hospital.objects.get(id=hospital_id)
# Auto-link patient by phone (local first, then HIS). Never blocks creation.
from apps.organizations.patient_lookup import find_or_link_patient
linked_patient = find_or_link_patient(phone=contact_phone, hospital=hospital)
feedback = Feedback( feedback = Feedback(
patient=linked_patient,
hospital=hospital, hospital=hospital,
feedback_type=FeedbackType.SUGGESTION, feedback_type=FeedbackType.SUGGESTION,
title=title, title=title,
@ -365,10 +360,11 @@ def feedback_create(request):
except Exception: except Exception:
pass pass
from apps.feedback.tasks import analyze_suggestion_with_ai
from apps.complaints.tasks import notify_staff_new_item from apps.complaints.tasks import notify_staff_new_item
from apps.feedback.tasks import analyze_suggestion_with_ai, link_feedback_patient
analyze_suggestion_with_ai.delay(str(feedback.id)) analyze_suggestion_with_ai.delay(str(feedback.id))
notify_staff_new_item.delay("suggestion", str(feedback.id)) notify_staff_new_item.delay("suggestion", str(feedback.id))
link_feedback_patient.delay(str(feedback.id), phone=contact_phone)
FeedbackResponse.objects.create( FeedbackResponse.objects.create(
feedback=feedback, feedback=feedback,
@ -400,9 +396,8 @@ def feedback_create(request):
except Exception as e: except Exception as e:
messages.error(request, f"Error creating suggestion: {str(e)}") messages.error(request, f"Error creating suggestion: {str(e)}")
hospitals = Hospital.objects.filter(status="active").order_by("name")
context = { context = {
"hospitals": hospitals, "selected_hospital_id": str(hospital.id) if hospital else "",
"is_create": True, "is_create": True,
"communication_request": communication_request, "communication_request": communication_request,
"prefill": prefill, "prefill": prefill,
@ -418,7 +413,7 @@ def feedback_update(request, pk):
# Check permission # Check permission
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee()):
messages.error(request, "You don't have permission to edit this suggestion.") messages.error(request, "You don't have permission to edit this suggestion.")
return redirect("feedback:feedback_detail", pk=pk) return redirect("feedback:feedback_detail", pk=pk)
@ -473,6 +468,14 @@ def feedback_update(request, pk):
"feedback": feedback, "feedback": feedback,
"patients": patients, "patients": patients,
"is_create": False, "is_create": False,
"prefill": {
"contact_name": feedback.contact_name or "",
"contact_phone": feedback.contact_phone or "",
"contact_email": feedback.contact_email or "",
"message": feedback.message or "",
"title": feedback.title or "",
},
"selected_hospital_id": feedback.hospital_id or "",
} }
return render(request, "feedback/feedback_form.html", context) return render(request, "feedback/feedback_form.html", context)
@ -486,7 +489,7 @@ def feedback_delete(request, pk):
# Check permission # Check permission
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee()):
messages.error(request, "You don't have permission to delete this suggestion.") messages.error(request, "You don't have permission to delete this suggestion.")
return redirect("feedback:feedback_detail", pk=pk) return redirect("feedback:feedback_detail", pk=pk)
@ -525,7 +528,7 @@ def comment_import_list(request):
""" """
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
if not (request.user.is_px_admin() or request.user.is_hospital_admin()): if not (request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_px_employee()):
raise PermissionDenied raise PermissionDenied
from .models import CommentImport from .models import CommentImport
@ -547,7 +550,7 @@ def comment_list(request):
""" """
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
if not (request.user.is_px_admin() or request.user.is_hospital_admin()): if not (request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_px_employee()):
raise PermissionDenied raise PermissionDenied
from .models import PatientComment from .models import PatientComment
@ -616,7 +619,7 @@ def action_plan_list(request):
""" """
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
if not (request.user.is_px_admin() or request.user.is_hospital_admin()): if not (request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_px_employee()):
raise PermissionDenied raise PermissionDenied
from .models import CommentActionPlan from .models import CommentActionPlan
@ -653,7 +656,7 @@ def export_comments_step1(request):
""" """
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
if not (request.user.is_px_admin() or request.user.is_hospital_admin()): if not (request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_px_employee()):
raise PermissionDenied raise PermissionDenied
from .models import PatientComment from .models import PatientComment
@ -682,7 +685,7 @@ def export_comments_step2(request):
""" """
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
if not (request.user.is_px_admin() or request.user.is_hospital_admin()): if not (request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_px_employee()):
raise PermissionDenied raise PermissionDenied
from .models import PatientComment from .models import PatientComment
@ -708,7 +711,7 @@ def export_action_plans(request):
""" """
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
if not (request.user.is_px_admin() or request.user.is_hospital_admin()): if not (request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_px_employee()):
raise PermissionDenied raise PermissionDenied
from .models import CommentActionPlan from .models import CommentActionPlan
@ -723,6 +726,53 @@ def export_action_plans(request):
return export_action_plans(qs) return export_action_plans(qs)
@login_required
@require_http_methods(["POST"])
def feedback_activate(request, pk):
"""Activate a suggestion and auto-assign to the activating user."""
feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False)
user = request.user
if not (
user.is_px_admin()
or user.is_hospital_admin()
or user.is_px_management()
or user.is_px_employee()
):
messages.error(request, _("You don't have permission to activate this suggestion."))
return redirect("feedback:feedback_detail", pk=pk)
if feedback.activated_at:
messages.warning(request, _("This suggestion has already been activated."))
return redirect("feedback:feedback_detail", pk=pk)
feedback.activated_at = timezone.now()
feedback.activated_by = user
feedback.assigned_to = user
feedback.assigned_at = timezone.now()
if feedback.status == FeedbackStatus.SUBMITTED:
feedback.status = FeedbackStatus.REVIEWED
feedback.save()
FeedbackResponse.objects.create(
feedback=feedback,
response_type="note",
message=f"Suggestion activated and assigned to {user.get_full_name()}",
created_by=user,
is_internal=True,
)
AuditService.log_event(
event_type="suggestion_activated",
description=f"Suggestion '{feedback.title}' activated by {user.get_full_name()}",
user=user,
content_object=feedback,
)
messages.success(request, _("Suggestion activated successfully."))
return redirect("feedback:feedback_detail", pk=pk)
@login_required @login_required
@require_http_methods(["POST"]) @require_http_methods(["POST"])
def feedback_assign(request, pk): def feedback_assign(request, pk):
@ -887,7 +937,7 @@ def feedback_add_response(request, pk):
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_management()
or user.is_department_manager()): or user.is_department_manager() or user.is_px_employee()):
messages.error(request, "You don't have permission to add responses to this suggestion.") messages.error(request, "You don't have permission to add responses to this suggestion.")
return redirect("feedback:feedback_detail", pk=pk) return redirect("feedback:feedback_detail", pk=pk)
@ -920,7 +970,7 @@ def feedback_toggle_featured(request, pk):
# Check permission # Check permission
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to feature this suggestion.") messages.error(request, "You don't have permission to feature this suggestion.")
return redirect("feedback:feedback_detail", pk=pk) return redirect("feedback:feedback_detail", pk=pk)
@ -941,7 +991,7 @@ def feedback_toggle_follow_up(request, pk):
# Check permission # Check permission
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to modify this suggestion.") messages.error(request, "You don't have permission to modify this suggestion.")
return redirect("feedback:feedback_detail", pk=pk) return redirect("feedback:feedback_detail", pk=pk)
@ -1070,7 +1120,7 @@ def feedback_create_action(request, pk):
feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False) feedback = get_object_or_404(Feedback, pk=pk, is_deleted=False)
user = request.user user = request.user
if not (user.is_px_admin() or user.is_hospital_admin()): if not (user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to create actions from suggestions.") messages.error(request, "You don't have permission to create actions from suggestions.")
return redirect("feedback:feedback_detail", pk=pk) return redirect("feedback:feedback_detail", pk=pk)

View File

@ -8,6 +8,7 @@ from apps.complaints.models import Complaint, Inquiry
from apps.observations.models import Observation from apps.observations.models import Observation
from apps.appreciation.models import Appreciation, AppreciationStatus, AppreciationVisibility from apps.appreciation.models import Appreciation, AppreciationStatus, AppreciationVisibility
from apps.feedback.models import Feedback, FeedbackType, FeedbackStatus, FeedbackCategory from apps.feedback.models import Feedback, FeedbackType, FeedbackStatus, FeedbackCategory
from apps.physicians.models import PhysicianIndividualRating
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -36,7 +37,7 @@ class ExternalComplaintCreateSerializer(serializers.Serializer):
# Hospital context # Hospital context
hospital = serializers.CharField(max_length=200, required=True) hospital = serializers.CharField(max_length=200, required=True)
# Location hierarchy (all optional) # Location hierarchy
location_type = serializers.ChoiceField( location_type = serializers.ChoiceField(
choices=[("OP", "Outpatient"), ("IP", "Inpatient"), ("ER", "Emergency"), ("GENERAL", "General")], choices=[("OP", "Outpatient"), ("IP", "Inpatient"), ("ER", "Emergency"), ("GENERAL", "General")],
required=False, required=False,
@ -44,7 +45,7 @@ class ExternalComplaintCreateSerializer(serializers.Serializer):
default="", default="",
) )
area = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") area = serializers.CharField(max_length=200, required=False, allow_blank=True, default="")
department = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") department_code = serializers.CharField(max_length=200, required=True)
section = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") section = serializers.CharField(max_length=200, required=False, allow_blank=True, default="")
# Complaint content # Complaint content
@ -72,17 +73,19 @@ class ExternalComplaintCreateSerializer(serializers.Serializer):
except Area.DoesNotExist: except Area.DoesNotExist:
raise serializers.ValidationError({"area": f"Area '{area_name}' not found for this hospital."}) raise serializers.ValidationError({"area": f"Area '{area_name}' not found for this hospital."})
dept_name = data.get("department") department_code = data.get("department_code")
if dept_name: if department_code:
from apps.organizations.models import Department from apps.organizations.models import Department
try: try:
data["department"] = Department.objects.get( data["department"] = Department.objects.get(
name__iexact=dept_name.strip(), hospital=hospital, status="active" code__iexact=department_code.strip(), hospital=hospital, status="active"
) )
except Department.DoesNotExist: except Department.DoesNotExist:
raise serializers.ValidationError( raise serializers.ValidationError(
{"department": f"Department '{dept_name}' not found for this hospital."} {"department_code": f"Department with code '{department_code}' not found for this hospital."}
) )
else:
data["department"] = None
section_name = data.get("section") section_name = data.get("section")
if section_name: if section_name:
@ -156,7 +159,7 @@ class ExternalInquiryCreateSerializer(serializers.Serializer):
default="", default="",
) )
area = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") area = serializers.CharField(max_length=200, required=False, allow_blank=True, default="")
department = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") department_code = serializers.CharField(max_length=200, required=False, allow_blank=True, default="")
section = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") section = serializers.CharField(max_length=200, required=False, allow_blank=True, default="")
# Inquiry content # Inquiry content
@ -194,17 +197,19 @@ class ExternalInquiryCreateSerializer(serializers.Serializer):
except Area.DoesNotExist: except Area.DoesNotExist:
raise serializers.ValidationError({"area": f"Area '{area_name}' not found for this hospital."}) raise serializers.ValidationError({"area": f"Area '{area_name}' not found for this hospital."})
dept_name = data.get("department") department_code = data.get("department_code")
if dept_name: if department_code:
from apps.organizations.models import Department from apps.organizations.models import Department
try: try:
data["department"] = Department.objects.get( data["department"] = Department.objects.get(
name__iexact=dept_name.strip(), hospital=hospital, status="active" code__iexact=department_code.strip(), hospital=hospital, status="active"
) )
except Department.DoesNotExist: except Department.DoesNotExist:
raise serializers.ValidationError( raise serializers.ValidationError(
{"department": f"Department '{dept_name}' not found for this hospital."} {"department_code": f"Department with code '{department_code}' not found for this hospital."}
) )
else:
data["department"] = None
section_name = data.get("section") section_name = data.get("section")
if section_name: if section_name:
@ -467,7 +472,7 @@ class ExternalComplaintSatisfactionSerializer(serializers.Serializer):
class ExternalDoctorRatingCreateSerializer(serializers.Serializer): class ExternalDoctorRatingCreateSerializer(serializers.Serializer):
hospital_id = serializers.IntegerField(required=True) hospital = serializers.CharField(max_length=200, required=True)
doctor_id = serializers.CharField(max_length=50, required=True) doctor_id = serializers.CharField(max_length=50, required=True)
doctor_name = serializers.CharField(max_length=300, required=False, allow_blank=True, default="") doctor_name = serializers.CharField(max_length=300, required=False, allow_blank=True, default="")
rating = serializers.IntegerField(min_value=1, max_value=5, required=True) rating = serializers.IntegerField(min_value=1, max_value=5, required=True)
@ -481,15 +486,15 @@ class ExternalDoctorRatingCreateSerializer(serializers.Serializer):
allow_blank=True, allow_blank=True,
default="", default="",
) )
department_name = serializers.CharField(max_length=200, required=False, allow_blank=True, default="") department_code = serializers.CharField(max_length=200, required=False, allow_blank=True, default="")
admit_date = serializers.DateField(required=False, allow_null=True, default=None) admit_date = serializers.DateField(required=False, allow_null=True, default=None)
discharge_date = serializers.DateField(required=False, allow_null=True, default=None) discharge_date = serializers.DateField(required=False, allow_null=True, default=None)
def validate_hospital_id(self, value): def validate_hospital(self, value):
from apps.organizations.models import Hospital from apps.organizations.models import Hospital
try: try:
return Hospital.objects.get(id=value) return Hospital.objects.get(name__iexact=value.strip())
except Hospital.DoesNotExist: except Hospital.DoesNotExist:
raise serializers.ValidationError("Hospital not found.") raise serializers.ValidationError("Hospital not found.")
@ -497,21 +502,105 @@ class ExternalDoctorRatingCreateSerializer(serializers.Serializer):
return value.strip() return value.strip()
def validate(self, data): def validate(self, data):
from apps.organizations.models import Staff from apps.organizations.models import Department, Staff
from apps.physicians.adapter import DoctorRatingAdapter
hospital = data.get("hospital_id") hospital = data.get("hospital")
doctor_id = data.get("doctor_id", "").strip() doctor_id = data.get("doctor_id", "").strip()
if hospital and doctor_id: # Resolve department code → human-readable name (the rating model stores
staff = Staff.objects.filter( # a text department_name; HIS sends a stable department_code instead).
hospital=hospital, employee_id=doctor_id department_code = data.get("department_code", "").strip()
data["department_name"] = ""
dept = None
if department_code and hospital:
dept = Department.objects.filter(
code__iexact=department_code, hospital=hospital, status="active"
).first() ).first()
if not staff: if not dept:
raise serializers.ValidationError( raise serializers.ValidationError(
{ {"department_code": f"Department with code '{department_code}' not found for this hospital."}
"doctor_id": f"Doctor with employee ID '{doctor_id}' not found at this hospital."
}
) )
data["department_name"] = dept.name_en or dept.name
if hospital and doctor_id:
doctor_name = (data.get("doctor_name") or "").strip()
# 1. Canonical multi-strategy lookup (employee_id → license_number → name).
staff = DoctorRatingAdapter.find_staff_by_doctor_id(
doctor_id=doctor_id, hospital=hospital, doctor_name=doctor_name
)
# 2. Auto-create the Staff if not found and we have a name to populate
# the required first_name/last_name fields.
if not staff:
if not doctor_name:
raise serializers.ValidationError(
{
"doctor_name": (
"Staff not found for this doctor_id; provide doctor_name "
"to auto-create the staff record."
)
}
)
name_parts = doctor_name.split()
first_name = name_parts[0]
last_name = name_parts[-1] if len(name_parts) > 1 else name_parts[0]
staff, created = Staff.objects.get_or_create(
employee_id=doctor_id,
defaults={
"hospital": hospital,
"first_name": first_name,
"last_name": last_name,
"name": doctor_name,
"staff_type": Staff.StaffType.PHYSICIAN,
"job_title": "Physician",
"physician": True,
"status": "active",
"department": dept,
},
)
data["_staff_created"] = created
data["_staff"] = staff data["_staff"] = staff
return data return data
class ExternalDoctorRatingRetrieveSerializer(serializers.ModelSerializer):
"""Read-only serializer for retrieving a doctor rating."""
hospital_name = serializers.CharField(source="hospital.name", read_only=True)
staff_id = serializers.CharField(read_only=True, default=None)
doctor_name_display = serializers.SerializerMethodField()
class Meta:
model = PhysicianIndividualRating
fields = [
"id",
"staff_id",
"hospital_name",
"source",
"source_reference",
"doctor_id",
"doctor_name",
"doctor_name_raw",
"doctor_name_display",
"department_name",
"patient_uhid",
"patient_name",
"patient_type",
"admit_date",
"discharge_date",
"rating",
"feedback",
"rating_date",
"is_aggregated",
"aggregated_at",
"created_at",
"updated_at",
]
read_only_fields = fields
def get_doctor_name_display(self, obj):
return obj.doctor_name or obj.doctor_name_raw or ""

View File

@ -1,5 +1,5 @@
""" """
External API views for complaints, inquiries, observations, appreciations, and suggestions. External API views for complaints, inquiries, observations, appreciations, suggestions, and doctor ratings.
All endpoints require X-API-Key header authentication. All endpoints require X-API-Key header authentication.
""" """
@ -24,6 +24,7 @@ from .api_serializers import (
ExternalComplaintRetrieveSerializer, ExternalComplaintRetrieveSerializer,
ExternalComplaintSatisfactionSerializer, ExternalComplaintSatisfactionSerializer,
ExternalDoctorRatingCreateSerializer, ExternalDoctorRatingCreateSerializer,
ExternalDoctorRatingRetrieveSerializer,
ExternalInquiryCreateSerializer, ExternalInquiryCreateSerializer,
ExternalInquiryRetrieveSerializer, ExternalInquiryRetrieveSerializer,
ExternalObservationCreateSerializer, ExternalObservationCreateSerializer,
@ -161,8 +162,8 @@ class ExternalComplaintCreateView(ExternalAPIBase, APIView):
patient=None, patient=None,
hospital=data["hospital"], hospital=data["hospital"],
department=data.get("department"), department=data.get("department"),
section=data.get("section"), section=data.get("section") or None,
area=data.get("area"), area=data.get("area") or None,
location_type=data.get("location_type", ""), location_type=data.get("location_type", ""),
title=data["title"], title=data["title"],
description=data["description"], description=data["description"],
@ -355,9 +356,9 @@ class ExternalInquiryCreateView(ExternalAPIBase, APIView):
inquiry = Inquiry.objects.create( inquiry = Inquiry.objects.create(
patient=None, patient=None,
hospital=data["hospital"], hospital=data["hospital"],
department=data.get("department"), department=data.get("department") or None,
section=data.get("section"), section=data.get("section") or None,
area=data.get("area"), area=data.get("area") or None,
location_type=data.get("location_type", ""), location_type=data.get("location_type", ""),
subject=data["subject"], subject=data["subject"],
message=data["message"], message=data["message"],
@ -955,9 +956,9 @@ class ExternalDepartmentsListView(ExternalAPIBase, APIView):
class ExternalSectionsListView(ExternalAPIBase, APIView): class ExternalSectionsListView(ExternalAPIBase, APIView):
""" """
GET /api/v1/external/sections/?hospital=<name>&department=<name> GET /api/v1/external/sections/?hospital=<name>&department_code=<code>
List sections for a department. List sections for a department (identified by its stable HIS department code).
""" """
entity_name = "lookup" entity_name = "lookup"
@ -966,11 +967,11 @@ class ExternalSectionsListView(ExternalAPIBase, APIView):
from apps.organizations.models import Department, Hospital, Section from apps.organizations.models import Department, Hospital, Section
hospital_name = request.query_params.get("hospital", "").strip() hospital_name = request.query_params.get("hospital", "").strip()
department_name = request.query_params.get("department", "").strip() department_code = request.query_params.get("department_code", "").strip()
if not hospital_name or not department_name: if not hospital_name or not department_code:
return Response( return Response(
{"detail": "hospital and department query parameters are required."}, {"detail": "hospital and department_code query parameters are required."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
@ -981,10 +982,13 @@ class ExternalSectionsListView(ExternalAPIBase, APIView):
try: try:
department = Department.objects.get( department = Department.objects.get(
name__iexact=department_name, hospital=hospital, status="active" code__iexact=department_code, hospital=hospital, status="active"
) )
except Department.DoesNotExist: except Department.DoesNotExist:
return Response({"detail": "Department not found."}, status=status.HTTP_404_NOT_FOUND) return Response(
{"detail": f"Department with code '{department_code}' not found for this hospital."},
status=status.HTTP_404_NOT_FOUND,
)
qs = Section.objects.filter(department=department, status="active").order_by("name_en") qs = Section.objects.filter(department=department, status="active").order_by("name_en")
@ -1023,8 +1027,9 @@ class ExternalDoctorRatingCreateView(ExternalAPIBase, APIView):
ser.is_valid(raise_exception=True) ser.is_valid(raise_exception=True)
data = ser.validated_data data = ser.validated_data
hospital = data["hospital_id"] hospital = data["hospital"]
staff = data.pop("_staff") staff = data.pop("_staff")
staff_created = data.pop("_staff_created", False)
api_key = getattr(request, "api_key", None) api_key = getattr(request, "api_key", None)
if api_key and api_key.hospital and hospital != api_key.hospital: if api_key and api_key.hospital and hospital != api_key.hospital:
@ -1068,6 +1073,23 @@ class ExternalDoctorRatingCreateView(ExternalAPIBase, APIView):
staff.physician = True staff.physician = True
staff.save(update_fields=["physician"]) staff.save(update_fields=["physician"])
if staff_created:
AuditService.log_event(
event_type="external_staff_auto_created",
description=(
f"Staff auto-created via external API: {staff.get_full_name()} "
f"(employee_id={staff.employee_id})"
),
content_object=staff,
metadata={
"staff_id": str(staff.id),
"employee_id": staff.employee_id,
"doctor_id": data["doctor_id"],
"hospital_id": str(hospital.id),
"source": "external_api",
},
)
AuditService.log_event( AuditService.log_event(
event_type="external_doctor_rating_created", event_type="external_doctor_rating_created",
description=f"Doctor rating created via external API: {staff.get_full_name()} - {data['rating']}/5", description=f"Doctor rating created via external API: {staff.get_full_name()} - {data['rating']}/5",
@ -1085,9 +1107,98 @@ class ExternalDoctorRatingCreateView(ExternalAPIBase, APIView):
{ {
"success": True, "success": True,
"rating_id": str(rating.id), "rating_id": str(rating.id),
"staff_id": str(staff.id),
"doctor_name": staff.get_full_name(),
"rating": data["rating"], "rating": data["rating"],
}, },
status=status.HTTP_201_CREATED, status=status.HTTP_201_CREATED,
) )
class ExternalDoctorRatingRetrieveView(ExternalAPIBase, APIView):
"""
GET /api/v1/external/doctor-ratings/list/
GET /api/v1/external/doctor-ratings/<rating_id>/
List doctor ratings (scoped to the API key's hospital) or retrieve a
single rating by its UUID.
List filters (all optional, in addition to hospital scoping):
- doctor_id HIS employee ID (exact match)
- rating Integer 1-5 (exact match)
- created_from/created_to Created-at date range
- updated_from/updated_to Updated-at date range
- rating_from/rating_to Rating-date range (`rating_date` column)
- page, page_size Pagination (default 20, max 100)
"""
entity_name = "doctor_ratings"
def get(self, request, rating_id=None):
access_err = self.check_entity_access()
if access_err:
return access_err
if rating_id:
return self._retrieve(request, rating_id)
return self._list(request)
def _retrieve(self, request, rating_id):
from apps.physicians.models import PhysicianIndividualRating
qs = PhysicianIndividualRating.objects.filter(id=rating_id)
qs = self.filter_queryset_by_hospital(qs)
rating = qs.first()
if not rating:
return Response({"detail": "Doctor rating not found."}, status=status.HTTP_404_NOT_FOUND)
ser = ExternalDoctorRatingRetrieveSerializer(rating)
return Response(ser.data)
def _list(self, request):
from apps.physicians.models import PhysicianIndividualRating
qs = PhysicianIndividualRating.objects.all()
qs = self.filter_queryset_by_hospital(qs)
qs = self.apply_list_filters(qs, request)
# Doctor-rating-specific filters
doctor_id = request.query_params.get("doctor_id", "").strip()
if doctor_id:
qs = qs.filter(doctor_id=doctor_id)
rating_value = request.query_params.get("rating", "").strip()
if rating_value:
try:
qs = qs.filter(rating=int(rating_value))
except ValueError:
pass
rating_from = request.query_params.get("rating_from", "").strip()
if rating_from:
try:
qs = qs.filter(rating_date__gte=self._parse_date(rating_from, start=True))
except (ValueError, TypeError):
pass
rating_to = request.query_params.get("rating_to", "").strip()
if rating_to:
try:
qs = qs.filter(rating_date__lte=self._parse_date(rating_to, start=False))
except (ValueError, TypeError):
pass
page_size = min(int(request.query_params.get("page_size", 20)), 100)
page = int(request.query_params.get("page", 1))
offset = (page - 1) * page_size
total = qs.count()
items = qs[offset : offset + page_size]
ser = ExternalDoctorRatingRetrieveSerializer(items, many=True)
return Response(
{
"count": total,
"page": page,
"page_size": page_size,
"results": ser.data,
}
)

View File

@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-07-19 08:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('integrations', '0003_external_api_key'),
]
operations = [
migrations.AlterField(
model_name='externalapikey',
name='allowed_entities',
field=models.JSONField(blank=True, default=list, help_text='List of entities this key can access: ["complaints","inquiries","observations","appreciations","suggestions","doctor_ratings"]'),
),
]

View File

@ -59,7 +59,7 @@ class ExternalAPIKey(UUIDModel, TimeStampedModel):
allowed_entities = models.JSONField( allowed_entities = models.JSONField(
default=list, default=list,
blank=True, blank=True,
help_text='List of entities this key can access: ["complaints","inquiries","observations","appreciations","suggestions"]', help_text='List of entities this key can access: ["complaints","inquiries","observations","appreciations","suggestions","doctor_ratings"]',
) )
# Rate limiting # Rate limiting

View File

@ -14,6 +14,7 @@ from .api_views import (
ExternalComplaintSatisfactionView, ExternalComplaintSatisfactionView,
ExternalDepartmentsListView, ExternalDepartmentsListView,
ExternalDoctorRatingCreateView, ExternalDoctorRatingCreateView,
ExternalDoctorRatingRetrieveView,
ExternalHospitalsListView, ExternalHospitalsListView,
ExternalInquiryCreateView, ExternalInquiryCreateView,
ExternalInquiryRetrieveView, ExternalInquiryRetrieveView,
@ -145,4 +146,14 @@ urlpatterns = [
ExternalDoctorRatingCreateView.as_view(), ExternalDoctorRatingCreateView.as_view(),
name="external-doctor-rating-create", name="external-doctor-rating-create",
), ),
path(
"doctor-ratings/list/",
ExternalDoctorRatingRetrieveView.as_view(),
name="external-doctor-rating-list",
),
path(
"doctor-ratings/<uuid:rating_id>/",
ExternalDoctorRatingRetrieveView.as_view(),
name="external-doctor-rating-detail",
),
] ]

View File

@ -48,12 +48,22 @@ def journey_instance_list(request):
# Apply RBAC filters # Apply RBAC filters
user = request.user user = request.user
selected_hospital = getattr(request, "tenant_hospital", None)
if user.is_px_admin(): if user.is_px_admin():
pass # See all if selected_hospital:
queryset = queryset.filter(hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital: elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(hospital=user.hospital) queryset = queryset.filter(hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department: elif user.is_department_manager() and user.department:
queryset = queryset.filter(department=user.department) queryset = queryset.filter(department=user.department)
elif user.is_champion() and user.department:
queryset = queryset.filter(department=user.department)
elif user.is_source_user():
queryset = queryset.filter(created_by=user)
elif user.hospital: elif user.hospital:
queryset = queryset.filter(hospital=user.hospital) queryset = queryset.filter(hospital=user.hospital)
else: else:
@ -185,8 +195,22 @@ def journey_template_list(request):
# Apply RBAC filters # Apply RBAC filters
user = request.user user = request.user
selected_hospital = getattr(request, "tenant_hospital", None)
if user.is_px_admin(): if user.is_px_admin():
pass # See all if selected_hospital:
queryset = queryset.filter(hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
queryset = queryset.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department:
queryset = queryset.filter(department=user.department)
elif user.is_champion() and user.department:
queryset = queryset.filter(department=user.department)
elif user.is_source_user():
queryset = queryset.filter(created_by=user)
elif user.hospital: elif user.hospital:
queryset = queryset.filter(hospital=user.hospital) queryset = queryset.filter(hospital=user.hospital)
else: else:

View File

@ -23,17 +23,8 @@ def get_email_header_html():
logo_url = getattr(settings, "EMAIL_LOGO_URL", f"{settings.STATIC_URL}img/HH_P_V_Logo(hospital)_.png") logo_url = getattr(settings, "EMAIL_LOGO_URL", f"{settings.STATIC_URL}img/HH_P_V_Logo(hospital)_.png")
if not logo_url.startswith("http"): if not logo_url.startswith("http"):
logo_url = f"https://{settings.ALLOWED_HOSTS[0]}{logo_url}" if getattr(settings, "ALLOWED_HOSTS", []) else logo_url logo_url = f"https://{settings.ALLOWED_HOSTS[0]}{logo_url}" if getattr(settings, "ALLOWED_HOSTS", []) else logo_url
return f"""<div style="padding: 20px 30px; background-color: #ffffff;"> return f"""<div style="padding: 20px 30px; background-color: #ffffff;" align="center">
<table width="100%" cellpadding="0" cellspacing="0" border="0"> <img src="{logo_url}" alt="Al Hammadi" width="240" style="display: block; margin: 0 auto; max-width: 240px; height: auto;">
<tr>
<td style="text-align: left; width: 90px; vertical-align: middle;">
<img src="{logo_url}" alt="Al Hammadi" width="75" style="display: block; max-width: 75px; height: auto;">
</td>
<td style="text-align: right; direction: rtl; font-size: 15px; color: #005696; font-weight: 700; vertical-align: middle; font-family: 'Segoe UI', Tahoma, sans-serif;">
مستشفى الحمادي
</td>
</tr>
</table>
</div> </div>
<div style="height: 2px; background-color: #005696; font-size: 1px; line-height: 1px;">&nbsp;</div>""" <div style="height: 2px; background-color: #005696; font-size: 1px; line-height: 1px;">&nbsp;</div>"""
@ -295,6 +286,8 @@ class NotificationService:
metadata=None, metadata=None,
notification_type="system", notification_type="system",
user=None, user=None,
attachments=None,
cc=None,
): ):
""" """
Send Email notification and create corresponding in-app notification. Send Email notification and create corresponding in-app notification.
@ -308,6 +301,14 @@ class NotificationService:
metadata: Additional metadata dict (optional) metadata: Additional metadata dict (optional)
notification_type: Type of notification (default: 'system') notification_type: Type of notification (default: 'system')
user: User instance to create in-app notification for (optional, will try to lookup by email) user: User instance to create in-app notification for (optional, will try to lookup by email)
attachments: Optional list of (filename, content_bytes, mimetype) tuples.
Only applied on the standard Django email backend path; silently
skipped on the external Email API path (which would need its own
upload mechanism).
cc: Optional list of email addresses to CC. Only applied on the
standard Django email backend path; silently skipped on the
external Email API path. In-app notifications are created for
each CC'd user whose email maps to a User account.
Returns: Returns:
NotificationLog instance NotificationLog instance
@ -326,16 +327,34 @@ class NotificationService:
from apps.accounts.models import User from apps.accounts.models import User
# Normalise CC list to a clean list of strings (drop None/empty)
cc_list = [addr for addr in (cc or []) if addr]
# Enrich metadata with CC trace
log_metadata = dict(metadata or {})
if cc_list:
log_metadata = {**log_metadata, "cc": cc_list}
# Check if Email API is enabled and use it (simulator or external API) # Check if Email API is enabled and use it (simulator or external API)
email_api_config = settings.EXTERNAL_NOTIFICATION_API.get("email", {}) email_api_config = settings.EXTERNAL_NOTIFICATION_API.get("email", {})
if email_api_config.get("enabled", False): if email_api_config.get("enabled", False):
skipped = []
if attachments:
skipped.append(f"{len(attachments)} attachment(s)")
if cc_list:
skipped.append(f"{len(cc_list)} CC recipient(s)")
if skipped:
logger.warning(
f"Email API path does not support {' or '.join(skipped)}; "
f"sending email to {email} without them."
)
log = NotificationService.send_email_via_api( log = NotificationService.send_email_via_api(
message=message, message=message,
email=email, email=email,
subject=subject, subject=subject,
html_message=html_message, html_message=html_message,
related_object=related_object, related_object=related_object,
metadata=metadata, metadata=log_metadata,
) )
# Create in-app notification after sending via API # Create in-app notification after sending via API
NotificationService._create_in_app_notification_from_email( NotificationService._create_in_app_notification_from_email(
@ -351,31 +370,61 @@ class NotificationService:
message=message, message=message,
content_object=related_object, content_object=related_object,
provider="console", # TODO: Replace with actual provider provider="console", # TODO: Replace with actual provider
metadata=metadata or {}, metadata=log_metadata,
) )
# Create in-app notification # Create in-app notification for the primary (To:) recipient
NotificationService._create_in_app_notification_from_email( NotificationService._create_in_app_notification_from_email(
log, email, subject, message, notification_type, related_object, user log, email, subject, message, notification_type, related_object, user
) )
# Create in-app notifications for each CC'd user (silently skips emails
# that don't map to a User account).
for cc_addr in cc_list:
if cc_addr.lower() == email.lower():
continue # Already covered by the To: notification
NotificationService._create_in_app_notification_from_email(
log, cc_addr, subject, message, notification_type, related_object, None
)
# Check if Email is enabled # Check if Email is enabled
email_config = settings.NOTIFICATION_CHANNELS.get("email", {}) email_config = settings.NOTIFICATION_CHANNELS.get("email", {})
if not email_config.get("enabled", True): if not email_config.get("enabled", True):
logger.info(f"[Email Console] To: {email} | Subject: {subject} | Message: {message}") cc_summary = f" | CC: {', '.join(cc_list)}" if cc_list else ""
logger.info(f"[Email Console] To: {email}{cc_summary} | Subject: {subject} | Message: {message}")
log.mark_sent() log.mark_sent()
return log return log
# Send email using Django's email backend # Send email using Django's email backend
try: try:
send_mail( if attachments or cc_list:
subject=subject, from django.core.mail import EmailMultiAlternatives
message=message,
from_email=settings.DEFAULT_FROM_EMAIL, msg = EmailMultiAlternatives(
recipient_list=[email], subject=subject,
html_message=html_message, body=message,
fail_silently=False, from_email=settings.DEFAULT_FROM_EMAIL,
) to=[email],
cc=cc_list if cc_list else None,
)
if html_message:
msg.attach_alternative(html_message, "text/html")
for att in (attachments or []):
# att is (filename, content_bytes, mimetype)
try:
msg.attach(*att)
except Exception:
logger.exception(f"Failed to attach {att[0] if att else '?'} to email for {email}")
msg.send(fail_silently=False)
else:
send_mail(
subject=subject,
message=message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[email],
html_message=html_message,
fail_silently=False,
)
log.mark_sent() log.mark_sent()
logger.info(f"Email sent to {email}: {subject}") logger.info(f"Email sent to {email}: {subject}")
except Exception as e: except Exception as e:

View File

@ -8,6 +8,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.accounts.models import User from apps.accounts.models import User
from apps.core.validators import SAUDI_PHONE_HTML_PATTERN, validate_saudi_phone
from apps.organizations.models import Area, Department, Hospital, LocationType from apps.organizations.models import Area, Department, Hospital, LocationType
from apps.organizations.models import Section as OrgSection from apps.organizations.models import Section as OrgSection
@ -77,6 +78,7 @@ class ObservationPublicForm(forms.ModelForm):
"reporter_name", "reporter_name",
"reporter_phone", "reporter_phone",
"reporter_email", "reporter_email",
"national_id",
] ]
widgets = { widgets = {
"location_type": forms.Select( "location_type": forms.Select(
@ -139,6 +141,12 @@ class ObservationPublicForm(forms.ModelForm):
"placeholder": "Your email (optional)", "placeholder": "Your email (optional)",
} }
), ),
"national_id": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "National ID/Iqama (optional)",
}
),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -198,11 +206,15 @@ class ObservationInternalForm(forms.ModelForm):
"location_type", "location_type",
"area", "area",
"description", "description",
"title",
"location_text",
"section", "section",
"incident_datetime", "incident_datetime",
"patient_file_number", "patient_name",
"patient_national_id",
"patient_phone",
"patient_email",
"assigned_department", "assigned_department",
"assigned_to",
"px_source", "px_source",
] ]
widgets = { widgets = {
@ -225,16 +237,46 @@ class ObservationInternalForm(forms.ModelForm):
"placeholder": "Please describe what you observed in detail...", "placeholder": "Please describe what you observed in detail...",
} }
), ),
"title": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "Brief title (optional)",
}
),
"location_text": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "Specific location details (e.g. room number, floor)",
}
),
"incident_datetime": forms.DateTimeInput( "incident_datetime": forms.DateTimeInput(
attrs={ attrs={
"class": "form-control", "class": "form-control",
"type": "datetime-local", "type": "datetime-local",
} }
), ),
"patient_file_number": forms.TextInput( "patient_name": forms.TextInput(
attrs={ attrs={
"class": "form-control", "class": "form-control",
"placeholder": "Patient MRN / file number", "placeholder": "Patient name (optional)",
}
),
"patient_national_id": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "National ID/Iqama (optional)",
}
),
"patient_phone": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "Patient phone (optional)",
}
),
"patient_email": forms.EmailInput(
attrs={
"class": "form-control",
"placeholder": "Patient email (optional)",
} }
), ),
"assigned_department": forms.Select( "assigned_department": forms.Select(
@ -242,11 +284,6 @@ class ObservationInternalForm(forms.ModelForm):
"class": "form-select", "class": "form-select",
} }
), ),
"assigned_to": forms.Select(
attrs={
"class": "form-select",
}
),
} }
section = forms.ModelChoiceField( section = forms.ModelChoiceField(
@ -311,15 +348,6 @@ class ObservationInternalForm(forms.ModelForm):
self.fields["assigned_department"].queryset = Department.objects.filter(status="active").order_by("name_en") self.fields["assigned_department"].queryset = Department.objects.filter(status="active").order_by("name_en")
self.fields["assigned_department"].empty_label = "Select department (optional)" self.fields["assigned_department"].empty_label = "Select department (optional)"
if hospital:
from apps.core.utils import get_assignable_users
self.fields["assigned_to"].queryset = get_assignable_users(hospital)
else:
self.fields["assigned_to"].queryset = User.objects.filter(is_active=True).order_by(
"first_name", "last_name"
)
self.fields["assigned_to"].empty_label = "Select assignee (optional)"
def clean_description(self): def clean_description(self):
description = self.cleaned_data.get("description", "") description = self.cleaned_data.get("description", "")
if len(description.strip()) < 10: if len(description.strip()) < 10:
@ -725,7 +753,13 @@ class PublicObservationForm(forms.ModelForm):
label=_("Mobile Number"), label=_("Mobile Number"),
max_length=20, max_length=20,
required=True, required=True,
widget=forms.TextInput(attrs={"class": "form-control", "placeholder": _("Your mobile number")}), validators=[validate_saudi_phone],
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": _("05XXXXXXXX or +9665XXXXXXXX"),
"pattern": SAUDI_PHONE_HTML_PATTERN,
"inputmode": "tel",
}),
) )
reporter_email = forms.EmailField( reporter_email = forms.EmailField(
label=_("Email Address"), label=_("Email Address"),

View File

@ -0,0 +1,19 @@
# Generated by Django 6.0.1 on 2026-07-18 14:46
import apps.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('observations', '0020_remove_observation_dept_response_acceptance_notes_and_more'),
]
operations = [
migrations.AlterField(
model_name='observation',
name='reporter_phone',
field=models.CharField(blank=True, help_text='Optional phone number for follow-up', max_length=20, validators=[apps.core.validators.validate_saudi_phone]),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-07-19 06:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('observations', '0021_alter_observation_reporter_phone'),
]
operations = [
migrations.AddField(
model_name='observation',
name='national_id',
field=models.CharField(blank=True, help_text='Optional National ID/Iqama of the reporter', max_length=20),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 6.0.1 on 2026-07-19 08:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('observations', '0022_observation_add_national_id'),
]
operations = [
migrations.AddField(
model_name='observation',
name='patient_email',
field=models.EmailField(blank=True, help_text='Email of the patient', max_length=254),
),
migrations.AddField(
model_name='observation',
name='patient_name',
field=models.CharField(blank=True, help_text='Name of the patient involved', max_length=200),
),
migrations.AddField(
model_name='observation',
name='patient_national_id',
field=models.CharField(blank=True, help_text='National ID/Iqama of the patient', max_length=20),
),
migrations.AddField(
model_name='observation',
name='patient_phone',
field=models.CharField(blank=True, help_text='Phone number of the patient', max_length=20),
),
]

View File

@ -20,6 +20,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from apps.core.models import SoftDeleteModel, TimeStampedModel, UUIDModel from apps.core.models import SoftDeleteModel, TimeStampedModel, UUIDModel
from apps.core.validators import validate_saudi_phone
from apps.organizations.models import LocationType from apps.organizations.models import LocationType
@ -363,13 +364,18 @@ class Observation(UUIDModel, TimeStampedModel, SoftDeleteModel):
# Optional reporter information (anonymous supported) # Optional reporter information (anonymous supported)
reporter_staff_id = models.CharField(max_length=50, blank=True, help_text="Optional staff ID of the reporter") reporter_staff_id = models.CharField(max_length=50, blank=True, help_text="Optional staff ID of the reporter")
reporter_name = models.CharField(max_length=200, blank=True, help_text="Optional name of the reporter") reporter_name = models.CharField(max_length=200, blank=True, help_text="Optional name of the reporter")
reporter_phone = models.CharField(max_length=20, blank=True, help_text="Optional phone number for follow-up") reporter_phone = models.CharField(max_length=20, blank=True, validators=[validate_saudi_phone], help_text="Optional phone number for follow-up")
reporter_email = models.EmailField(blank=True, help_text="Optional email for follow-up") reporter_email = models.EmailField(blank=True, help_text="Optional email for follow-up")
national_id = models.CharField(max_length=20, blank=True, help_text="Optional National ID/Iqama of the reporter")
# Patient / subject information # Patient / subject information
patient_file_number = models.CharField( patient_file_number = models.CharField(
max_length=100, blank=True, help_text="Medical record number / file number of the patient" max_length=100, blank=True, help_text="Medical record number / file number of the patient"
) )
patient_name = models.CharField(max_length=200, blank=True, help_text="Name of the patient involved")
patient_national_id = models.CharField(max_length=20, blank=True, help_text="National ID/Iqama of the patient")
patient_phone = models.CharField(max_length=20, blank=True, help_text="Phone number of the patient")
patient_email = models.EmailField(blank=True, help_text="Email of the patient")
# Status and workflow # Status and workflow
status = models.CharField( status = models.CharField(

View File

@ -55,6 +55,7 @@ class ObservationService:
reporter_name: str = "", reporter_name: str = "",
reporter_phone: str = "", reporter_phone: str = "",
reporter_email: str = "", reporter_email: str = "",
national_id: str = "",
client_ip: str = None, client_ip: str = None,
user_agent: str = "", user_agent: str = "",
attachments: list = None, attachments: list = None,
@ -99,6 +100,7 @@ class ObservationService:
reporter_name=reporter_name, reporter_name=reporter_name,
reporter_phone=reporter_phone, reporter_phone=reporter_phone,
reporter_email=reporter_email, reporter_email=reporter_email,
national_id=national_id,
client_ip=client_ip, client_ip=client_ip,
user_agent=user_agent, user_agent=user_agent,
hospital=hospital, hospital=hospital,

View File

@ -627,3 +627,39 @@ def send_observation_dept_response_reminders():
"first_reminder_count": first_reminder_count, "first_reminder_count": first_reminder_count,
"second_reminder_count": second_reminder_count, "second_reminder_count": second_reminder_count,
} }
@shared_task
def send_observation_reminder_email(observation_id, reminder_type, recipients):
"""Send a reminder email to department champion and manager for an observation."""
import logging
logger = logging.getLogger(__name__)
try:
from apps.observations.models import Observation
from apps.notifications.services import NotificationService, get_email_header_html
observation = Observation.objects.get(pk=observation_id)
for recipient in recipients:
NotificationService.send_email(
email=recipient["email"],
subject=f"Reminder: Observation {observation.tracking_code} - Response Required",
message=f"This is a reminder that observation {observation.tracking_code} is awaiting your department's response. Please submit your response as soon as possible.",
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Reminder: Response Required</h2>
<p style="margin: 0 0 12px 0;">This is a reminder that observation <strong>{observation.tracking_code}</strong> is awaiting your department's response.</p>
<p style="margin: 0; color: #6b7280;">Please submit your response as soon as possible.</p>
</div>
</div>
""",
related_object=observation,
)
logger.info(f"Observation {observation_id} {reminder_type} reminder sent to {len(recipients)} recipients")
except Exception as e:
logger.error(f"Failed to send observation reminder email for {observation_id}: {e}")

View File

@ -195,6 +195,7 @@ def observation_create_public(request):
reporter_name=form.cleaned_data.get("reporter_name", ""), reporter_name=form.cleaned_data.get("reporter_name", ""),
reporter_phone=form.cleaned_data.get("reporter_phone", ""), reporter_phone=form.cleaned_data.get("reporter_phone", ""),
reporter_email=form.cleaned_data.get("reporter_email", ""), reporter_email=form.cleaned_data.get("reporter_email", ""),
national_id=form.cleaned_data.get("national_id", ""),
client_ip=client_ip, client_ip=client_ip,
user_agent=user_agent, user_agent=user_agent,
attachments=attachments, attachments=attachments,
@ -316,8 +317,8 @@ def observation_create(request):
description=form.cleaned_data["description"], description=form.cleaned_data["description"],
severity=form.cleaned_data.get("severity") or request.POST.get("severity", "medium"), severity=form.cleaned_data.get("severity") or request.POST.get("severity", "medium"),
category=None, category=None,
title="", title=form.cleaned_data.get("title", "") or "",
location_text="", location_text=form.cleaned_data.get("location_text", "") or "",
incident_datetime=form.cleaned_data.get("incident_datetime"), incident_datetime=form.cleaned_data.get("incident_datetime"),
reporter_staff_id=user.employee_id or "", reporter_staff_id=user.employee_id or "",
reporter_name=user.get_full_name(), reporter_name=user.get_full_name(),
@ -332,23 +333,28 @@ def observation_create(request):
) )
assigned_dept = form.cleaned_data.get("assigned_department") assigned_dept = form.cleaned_data.get("assigned_department")
assigned_user = form.cleaned_data.get("assigned_to")
section = form.cleaned_data.get("section") section = form.cleaned_data.get("section")
if assigned_dept: if assigned_dept:
observation.assigned_department = assigned_dept observation.assigned_department = assigned_dept
if assigned_user:
observation.assigned_to = assigned_user
observation.status = ObservationStatus.IN_PROGRESS
ObservationStatusLog.objects.create(
observation=observation,
from_status=ObservationStatus.OPEN,
to_status=ObservationStatus.IN_PROGRESS,
comment="Auto-assigned during creation",
)
if section: if section:
observation.section = section observation.section = section
observation.patient_name = form.cleaned_data.get("patient_name", "") or ""
observation.patient_national_id = form.cleaned_data.get("patient_national_id", "") or ""
observation.patient_phone = form.cleaned_data.get("patient_phone", "") or ""
observation.patient_email = form.cleaned_data.get("patient_email", "") or ""
observation.assigned_to = user
observation.status = ObservationStatus.IN_PROGRESS
observation.save() observation.save()
ObservationStatusLog.objects.create(
observation=observation,
from_status=ObservationStatus.OPEN,
to_status=ObservationStatus.IN_PROGRESS,
comment="Auto-assigned during creation",
)
comm_req_id = request.POST.get("comm_req") comm_req_id = request.POST.get("comm_req")
if comm_req_id: if comm_req_id:
try: try:
@ -644,13 +650,13 @@ def observation_detail(request, pk):
"status_form": status_form, "status_form": status_form,
"note_form": note_form, "note_form": note_form,
"status_choices": ObservationStatus.choices, "status_choices": ObservationStatus.choices,
"can_triage": user.has_perm("observations.triage_observation") or user.is_px_admin(), "can_triage": user.has_perm("observations.triage_observation") or user.is_px_admin() or user.is_px_employee(),
"can_convert": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee(), "can_convert": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee(),
"can_send_to_department": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee() or user.is_department_manager() or user.is_px_management(), "can_send_to_department": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee() or user.is_department_manager() or user.is_px_management(),
"can_respond_to_department": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee() or (user.is_champion() and observation.assigned_department == user.department), "can_respond_to_department": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee() or (user.is_champion() and observation.assigned_department == user.department),
"can_send_reminder": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee(), "can_send_reminder": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee(),
"can_delete": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee(), "can_delete": user.is_px_admin() or user.is_hospital_admin() or user.is_px_management() or user.is_px_employee(),
"can_admin": user.is_px_admin() or user.is_hospital_admin(), "can_admin": user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee(),
"linked_rcas": linked_rcas, "linked_rcas": linked_rcas,
} }
@ -734,7 +740,7 @@ def observation_triage(request, pk):
# Check permission # Check permission
user = request.user user = request.user
if not (user.has_perm("observations.triage_observation") or user.is_px_admin()): if not (user.has_perm("observations.triage_observation") or user.is_px_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to triage observations.") messages.error(request, "You don't have permission to triage observations.")
return redirect("observations:observation_detail", pk=pk) return redirect("observations:observation_detail", pk=pk)
@ -770,7 +776,7 @@ def observation_change_status(request, pk):
# Check permission # Check permission
user = request.user user = request.user
if not (user.has_perm("observations.triage_observation") or user.is_px_admin() or user.is_hospital_admin()): if not (user.has_perm("observations.triage_observation") or user.is_px_admin() or user.is_hospital_admin() or user.is_px_employee()):
messages.error(request, "You don't have permission to change observation status.") messages.error(request, "You don't have permission to change observation status.")
return redirect("observations:observation_detail", pk=pk) return redirect("observations:observation_detail", pk=pk)
@ -1619,6 +1625,8 @@ def observation_department_response(request, pk):
if not ( if not (
user.is_px_admin() user.is_px_admin()
or user.is_hospital_admin() or user.is_hospital_admin()
or user.is_px_management()
or user.is_px_employee()
or (user.is_champion() and observation.assigned_department == user.department) or (user.is_champion() and observation.assigned_department == user.department)
): ):
messages.error(request, "You don't have permission to respond to this observation.") messages.error(request, "You don't have permission to respond to this observation.")
@ -1681,61 +1689,31 @@ Generate a JSON response with:
is_internal=True, is_internal=True,
) )
try:
from apps.notifications.services import NotificationService, get_email_header_html
from apps.core.utils import build_public_track_url
track_url = build_public_track_url("observation", observation.tracking_code)
if observation.reporter_phone:
NotificationService.send_sms(
phone=observation.reporter_phone,
message=f"PX360: Your observation {observation.tracking_code} has been responded to. View details: {track_url}",
related_object=observation,
metadata={"notification_type": "observation_department_response"},
)
if observation.reporter_email:
email_subject = f"PX360: Response to Your Observation {observation.tracking_code}"
email_body = (
f"Dear Valued Reporter,\n\n"
f"Your observation {observation.tracking_code} has been responded to.\n\n"
f"To view the full response, please visit:\n{track_url}\n\n"
f"Thank you for your contribution.\n\n"
f"Tracking Code: {observation.tracking_code}\n"
f"This is an automated message from PX 360."
)
NotificationService.send_email(
email=observation.reporter_email,
subject=email_subject,
message=email_body,
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Response to Your Observation</h2>
<p style="margin: 0 0 12px 0;">Dear Valued Reporter,</p>
<p style="margin: 0 0 12px 0;">Your observation <strong>{observation.tracking_code}</strong> has been responded to.</p>
<div style="text-align: center; margin: 20px 0;">
<a href="{track_url}" style="background: #005696; color: white; padding: 10px 24px; border-radius: 8px; text-decoration: none; font-weight: 600;">View Response</a>
</div>
<p style="margin: 0 0 6px 0; color: #6b7280; font-size: 13px;">Tracking Code: {observation.tracking_code}</p>
<p style="margin: 0 0 6px 0; color: #6b7280; font-size: 13px;">Thank you for your contribution.</p>
</div>
</div>
""",
related_object=observation,
metadata={"notification_type": "observation_department_response_email"},
)
except Exception as e:
logger.warning(f"Failed to send observation response notification: {e}")
messages.success(request, "Department response submitted successfully.") messages.success(request, "Department response submitted successfully.")
# Prefer a user-supplied relative next URL (e.g. back to the department
# observation detail page) over the default department landing page.
# Block protocol-relative URLs (``//host/`` or ``/\\host/``) to prevent
# open-redirect abuse.
next_url = request.POST.get("next")
if not (
next_url
and next_url.startswith("/")
and not next_url.startswith("//")
and not next_url.startswith("/\\")
):
next_url = None
if request.headers.get('X-Requested-With') == 'XMLHttpRequest': if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
from django.http import JsonResponse from django.http import JsonResponse
from django.urls import reverse from django.urls import reverse
redirect_url = reverse("organizations:department_detail", kwargs={"pk": user.department.pk}) if user.department else reverse("observations:observation_detail", kwargs={"pk": pk}) if next_url:
redirect_url = next_url
elif user.department:
redirect_url = reverse("organizations:department_detail", kwargs={"pk": user.department.pk})
else:
redirect_url = reverse("observations:observation_detail", kwargs={"pk": pk})
return JsonResponse({"success": True, "redirect_url": redirect_url}) return JsonResponse({"success": True, "redirect_url": redirect_url})
if next_url:
return redirect(next_url)
if user.department: if user.department:
return redirect("organizations:department_detail", pk=user.department.pk) return redirect("organizations:department_detail", pk=user.department.pk)
return redirect("observations:observation_detail", pk=pk) return redirect("observations:observation_detail", pk=pk)
@ -1771,30 +1749,30 @@ def observation_send_dept_response_reminder(request, pk):
reminder_type = request.POST.get("reminder_type", "first") reminder_type = request.POST.get("reminder_type", "first")
if reminder_type == "first" and observation.dept_response_reminder_sent_at:
messages.warning(request, "First reminder already sent.")
return redirect("observations:observation_detail", pk=pk)
if reminder_type == "second" and observation.dept_response_second_reminder_sent_at:
messages.warning(request, "Second reminder already sent.")
return redirect("observations:observation_detail", pk=pk)
if reminder_type == "second" and not observation.dept_response_reminder_sent_at:
messages.warning(request, "Please send the first reminder before the second.")
return redirect("observations:observation_detail", pk=pk)
try: try:
from apps.notifications.services import NotificationService, get_email_header_html from apps.notifications.services import NotificationService, get_email_header_html
from apps.organizations.department_contacts import get_champion_and_manager
targets = get_champion_and_manager(dept)
if not targets:
messages.error(request, f"No champion or manager assigned to {dept.name}.")
return redirect("observations:observation_detail", pk=pk)
recipients = [] recipients = []
if dept.champion and dept.champion.user and dept.champion.user.email: for target in targets:
recipients.append(dept.champion.user) email = target.get("email") or ""
if email:
for recipient in recipients: recipients.append({"email": email, "name": target.get("display_name", "")})
NotificationService.send_email(
email=recipient.email,
subject=f"Reminder: Observation {observation.tracking_code} - Response Required",
message=f"This is a reminder that observation {observation.tracking_code} is awaiting your department's response. Please submit your response as soon as possible.",
html_message=f"""
<div style="font-family: 'Segoe UI', Tahoma, sans-serif; max-width: 600px; margin: 0 auto; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
{get_email_header_html()}
<div style="padding: 20px;">
<h2 style="color: #005696; font-size: 18px; margin: 0 0 12px 0;">Reminder: Response Required</h2>
<p style="margin: 0 0 12px 0;">This is a reminder that observation <strong>{observation.tracking_code}</strong> is awaiting your department's response.</p>
<p style="margin: 0; color: #6b7280;">Please submit your response as soon as possible.</p>
</div>
</div>
""",
related_object=observation,
)
if reminder_type == "first": if reminder_type == "first":
observation.dept_response_reminder_sent_at = timezone.now() observation.dept_response_reminder_sent_at = timezone.now()
@ -1802,6 +1780,9 @@ def observation_send_dept_response_reminder(request, pk):
observation.dept_response_second_reminder_sent_at = timezone.now() observation.dept_response_second_reminder_sent_at = timezone.now()
observation.save() observation.save()
from apps.observations.tasks import send_observation_reminder_email
send_observation_reminder_email.delay(str(observation.id), reminder_type, recipients)
ObservationNote.objects.create( ObservationNote.objects.create(
observation=observation, observation=observation,
note=f"Manual {reminder_type} reminder sent by {user.get_full_name()}", note=f"Manual {reminder_type} reminder sent by {user.get_full_name()}",
@ -1823,11 +1804,13 @@ def observation_send_dept_response_reminder(request, pk):
@login_required @login_required
@permission_required("observations.manage_categories", raise_exception=True)
def category_list(request): def category_list(request):
""" """
List observation categories. List observation categories.
""" """
if not (request.user.has_perm("observations.manage_categories") or request.user.is_px_admin() or request.user.is_px_employee()):
from django.core.exceptions import PermissionDenied
raise PermissionDenied
categories = ObservationCategory.objects.all().order_by("sort_order", "name_en") categories = ObservationCategory.objects.all().order_by("sort_order", "name_en")
context = { context = {
@ -1838,12 +1821,14 @@ def category_list(request):
@login_required @login_required
@permission_required("observations.manage_categories", raise_exception=True)
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
def category_create(request): def category_create(request):
""" """
Create new observation category. Create new observation category.
""" """
if not (request.user.has_perm("observations.manage_categories") or request.user.is_px_admin() or request.user.is_px_employee()):
from django.core.exceptions import PermissionDenied
raise PermissionDenied
if request.method == "POST": if request.method == "POST":
form = ObservationCategoryForm(request.POST) form = ObservationCategoryForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -1862,12 +1847,14 @@ def category_create(request):
@login_required @login_required
@permission_required("observations.manage_categories", raise_exception=True)
@require_http_methods(["GET", "POST"]) @require_http_methods(["GET", "POST"])
def category_edit(request, pk): def category_edit(request, pk):
""" """
Edit observation category. Edit observation category.
""" """
if not (request.user.has_perm("observations.manage_categories") or request.user.is_px_admin() or request.user.is_px_employee()):
from django.core.exceptions import PermissionDenied
raise PermissionDenied
category = get_object_or_404(ObservationCategory, pk=pk) category = get_object_or_404(ObservationCategory, pk=pk)
if request.method == "POST": if request.method == "POST":
@ -1889,12 +1876,14 @@ def category_edit(request, pk):
@login_required @login_required
@permission_required("observations.manage_categories", raise_exception=True)
@require_http_methods(["POST"]) @require_http_methods(["POST"])
def category_delete(request, pk): def category_delete(request, pk):
""" """
Delete observation category. Delete observation category.
""" """
if not (request.user.has_perm("observations.manage_categories") or request.user.is_px_admin() or request.user.is_px_employee()):
from django.core.exceptions import PermissionDenied
raise PermissionDenied
category = get_object_or_404(ObservationCategory, pk=pk) category = get_object_or_404(ObservationCategory, pk=pk)
# Check if category is in use # Check if category is in use

View File

@ -0,0 +1,59 @@
"""
Management command to export sections (English name, Arabic name, department) to a CSV file.
Usage:
python manage.py export_sections --output-dir ./exports
"""
import csv
import os
from django.core.management.base import BaseCommand
from apps.organizations.models import Section
class Command(BaseCommand):
help = 'Export sections (English name, Arabic name, department) to a CSV file'
def add_arguments(self, parser):
parser.add_argument(
'--output-dir',
type=str,
default='./exports',
help='Directory to export the sections CSV file to',
)
def handle(self, *args, **options):
output_dir = options['output_dir']
os.makedirs(output_dir, exist_ok=True)
filepath = os.path.join(output_dir, 'sections.csv')
qs = (
Section.objects.all()
.select_related('department')
.order_by('department__name_en', 'name_en')
)
seen = set()
rows = []
for section in qs:
dept = section.department
row = (
section.name_en or '',
section.name_ar or '',
dept.name_en if dept else '',
dept.name_ar if dept else '',
)
if row in seen:
continue
seen.add(row)
rows.append(row)
with open(filepath, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(['Name (EN)', 'Name (AR)', 'Department (EN)', 'Department (AR)'])
writer.writerows(rows)
self.stdout.write(self.style.SUCCESS(
f'Exported {len(rows)} unique sections to {filepath}'
))

View File

@ -1939,9 +1939,28 @@ def department_detail(request, pk):
"involved_staff__staff" "involved_staff__staff"
).distinct().order_by("-created_at") ).distinct().order_by("-created_at")
inquiries = Inquiry.objects.filter( inquiry_direction = request.GET.get("inquiry_direction", "incoming")
Q(department=department, sent_to_department=True) | Q(outgoing_department=department) sent_away = (
).select_related("assigned_to", "patient").order_by("-created_at") Q(department=department)
& Q(transferred_to_department__isnull=False)
& ~Q(transferred_to_department=department)
)
incoming_inquiries_qs = Inquiry.objects.filter(
Q(department=department, sent_to_department=True)
| Q(outgoing_department=department)
| Q(transferred_to_department=department)
).exclude(sent_away)
outgoing_inquiries_qs = Inquiry.objects.filter(sent_away)
if inquiry_direction == "outgoing":
inquiries = outgoing_inquiries_qs
else:
inquiries = incoming_inquiries_qs
inquiries = inquiries.select_related("assigned_to", "patient", "department", "outgoing_department", "transferred_to_department").order_by("-created_at")
incoming_count = incoming_inquiries_qs.count()
outgoing_count = outgoing_inquiries_qs.count()
all_inquiries_count = incoming_count + outgoing_count
observations = Observation.objects.filter( observations = Observation.objects.filter(
assigned_department=department, sent_to_department=True assigned_department=department, sent_to_department=True
@ -2166,18 +2185,17 @@ def department_detail(request, pk):
pending_inquiries = Inquiry.objects.filter( pending_inquiries = Inquiry.objects.filter(
Q(outgoing_department=department) | Q(department=department, sent_to_department=True), Q(outgoing_department=department) | Q(department=department, sent_to_department=True),
transferred_at__isnull=False, transferred_at__isnull=False,
department_responded_at__isnull=True,
status__in=["open", "in_progress"], status__in=["open", "in_progress"],
).order_by("dept_response_sla_due_at") ).order_by("dept_response_sla_due_at")
for inq in pending_inquiries: for inq in pending_inquiries:
pending_actions.append({ pending_actions.append({
"type": "inquiry_response", "type": "inquiry_response",
"type_label": _("Inquiry Response"), "type_label": _("Inquiry to Handle"),
"reference": inq.reference_number or str(inq.id), "reference": inq.reference_number or str(inq.id),
"subject": inq.subject or inq.message[:80] if inq.message else "", "subject": inq.subject or inq.message[:80] if inq.message else "",
"sla_due_at": inq.dept_response_sla_due_at, "sla_due_at": inq.dept_response_sla_due_at,
"is_overdue": inq.dept_response_sla_due_at and inq.dept_response_sla_due_at < dj_tz.now(), "is_overdue": inq.dept_response_sla_due_at and inq.dept_response_sla_due_at < dj_tz.now(),
"url": "/inquiries/{}/department-response/".format(inq.pk), "url": "/organizations/departments/{}/inquiries/{}/".format(department.pk, inq.pk),
"badge_color": "cyan", "badge_color": "cyan",
"item_id": str(inq.pk), "item_id": str(inq.pk),
"existing_en": inq.department_response_en or "", "existing_en": inq.department_response_en or "",
@ -2241,6 +2259,10 @@ def department_detail(request, pk):
"staff_complaint_counts": staff_complaint_counts, "staff_complaint_counts": staff_complaint_counts,
"complaints": complaints[:5], "complaints": complaints[:5],
"inquiries": inquiries[:5], "inquiries": inquiries[:5],
"inquiry_direction": inquiry_direction,
"incoming_count": incoming_count,
"outgoing_count": outgoing_count,
"all_inquiries_count": all_inquiries_count,
"observations": observations[:5], "observations": observations[:5],
"appreciations": appreciations[:50], "appreciations": appreciations[:50],
"suggestions": suggestions[:50], "suggestions": suggestions[:50],
@ -2423,7 +2445,7 @@ def department_complaints_list(request, pk):
"complaint_dept_map": complaint_dept_map, "complaint_dept_map": complaint_dept_map,
"status_choices": Complaint.Status.choices if hasattr(Complaint, 'Status') else [], "status_choices": Complaint.Status.choices if hasattr(Complaint, 'Status') else [],
"list_stats": status_counts, "list_stats": status_counts,
"status_labels": {"open": _("Open"), "in_progress": _("In Progress"), "resolved": _("Resolved"), "closed": _("Closed")}, "status_labels": {"open": _("Open"), "in_progress": _("In Progress"), "resolved": _("Resolved"), "closed": _("Closed"), "contact_no_response": _("Contacted - No Response")},
} }
return render(request, "organizations/department_complaints.html", context) return render(request, "organizations/department_complaints.html", context)
@ -2442,9 +2464,26 @@ def department_inquiries_list(request, pk):
messages.error(request, _("You don't have permission to view this department.")) messages.error(request, _("You don't have permission to view this department."))
return redirect("organizations:department_list") return redirect("organizations:department_list")
queryset = Inquiry.objects.filter( direction = request.GET.get("direction", "incoming")
Q(department=department, sent_to_department=True) | Q(outgoing_department=department)
).select_related("assigned_to", "patient").order_by("-created_at") # Direction split (department POV):
# outgoing = this department transferred the inquiry away to another department
# incoming = inquiries this department currently handles (owned & not sent away, or transferred TO it)
sent_away = (
Q(department=department)
& Q(transferred_to_department__isnull=False)
& ~Q(transferred_to_department=department)
)
if direction == "outgoing":
queryset = Inquiry.objects.filter(sent_away)
else:
queryset = Inquiry.objects.filter(
Q(department=department, sent_to_department=True)
| Q(outgoing_department=department)
| Q(transferred_to_department=department)
).exclude(sent_away)
queryset = queryset.select_related("assigned_to", "patient").order_by("-created_at")
from django.db.models import Count from django.db.models import Count
list_stats = queryset.values("status").annotate(count=Count("pk")) list_stats = queryset.values("status").annotate(count=Count("pk"))
@ -2495,7 +2534,8 @@ def department_inquiries_list(request, pk):
"date_to": date_to or "", "date_to": date_to or "",
"can_respond": can_respond, "can_respond": can_respond,
"list_stats": status_counts, "list_stats": status_counts,
"status_labels": {"open": _("Open"), "in_progress": _("In Progress"), "resolved": _("Resolved"), "closed": _("Closed")}, "direction": direction,
"status_labels": {"open": _("Open"), "in_progress": _("In Progress"), "resolved": _("Resolved"), "closed": _("Closed"), "contact_no_response": _("Contacted - No Response")},
} }
return render(request, "organizations/department_inquiries.html", context) return render(request, "organizations/department_inquiries.html", context)
@ -2731,12 +2771,8 @@ def department_inquiry_detail(request, pk, ipk):
can_respond = ( can_respond = (
user.is_px_admin() user.is_px_admin()
or user.is_hospital_admin() or user.is_hospital_admin()
or (user.is_department_manager() and user.department == department) or (user.is_department_manager() and user.department_id == inquiry.handling_department_id)
or ( or (user.is_champion() and user.department_id == inquiry.handling_department_id)
user.is_champion()
and user.department
and user.department in [inquiry.department, inquiry.outgoing_department]
)
) )
location_parts = [] location_parts = []
@ -2750,12 +2786,21 @@ def department_inquiry_detail(request, pk, ipk):
inquiry_ct = ContentType.objects.get_for_model(Inquiry) inquiry_ct = ContentType.objects.get_for_model(Inquiry)
notes = inquiry.notes.select_related("created_by").order_by("-created_at") notes = inquiry.notes.select_related("created_by").order_by("-created_at")
departments = (
Department.objects.filter(hospital=department.hospital, status="active")
.exclude(pk=department.pk)
.order_by("name_en")
)
users = User.objects.filter(hospital=department.hospital, is_active=True)
context = { context = {
"department": department, "department": department,
"inquiry": inquiry, "inquiry": inquiry,
"location_str": " > ".join(location_parts) if location_parts else "", "location_str": " > ".join(location_parts) if location_parts else "",
"can_respond": can_respond, "can_respond": can_respond,
"notes": notes, "notes": notes,
"departments": departments,
"users": users,
"content_type_id": inquiry_ct.pk, "content_type_id": inquiry_ct.pk,
"object_id": inquiry.pk, "object_id": inquiry.pk,
} }
@ -3084,6 +3129,17 @@ def department_record_inquiry(request, pk):
"created_at": inquiry.created_at.strftime("%Y-%m-%d %H:%M"), "created_at": inquiry.created_at.strftime("%Y-%m-%d %H:%M"),
"due_at": inquiry.due_at.strftime("%Y-%m-%d %H:%M") if hasattr(inquiry, 'due_at') and inquiry.due_at else None, "due_at": inquiry.due_at.strftime("%Y-%m-%d %H:%M") if hasattr(inquiry, 'due_at') and inquiry.due_at else None,
"ai_summary": inquiry.short_description_en if hasattr(inquiry, 'short_description_en') else "", "ai_summary": inquiry.short_description_en if hasattr(inquiry, 'short_description_en') else "",
"department_response_en": inquiry.department_response_en or "",
"department_response_ar": inquiry.department_response_ar or "",
"department_responded_at": inquiry.department_responded_at.strftime("%Y-%m-%d %H:%M") if inquiry.department_responded_at else None,
"priority_display": inquiry.get_priority_display() if hasattr(inquiry, 'priority') and inquiry.priority else "",
"contact_status_display": inquiry.get_contact_status_display() if inquiry.contact_status else "",
"response": inquiry.response or inquiry.response_en or "",
"responded_at": inquiry.responded_at.strftime("%Y-%m-%d %H:%M") if inquiry.responded_at else None,
"responded_by": inquiry.responded_by.get_full_name() if inquiry.responded_by else None,
"resolved_at": inquiry.resolved_at.strftime("%Y-%m-%d %H:%M") if inquiry.resolved_at else None,
"resolved_by": inquiry.resolved_by.get_full_name() if inquiry.resolved_by else None,
"outgoing_department": inquiry.outgoing_department.get_localized_name() if inquiry.outgoing_department else None,
} }
return JsonResponse(data) return JsonResponse(data)

View File

@ -928,7 +928,7 @@ def api_department_staff(request, department_id):
staff_qs = ( staff_qs = (
Staff.objects.select_related("user") Staff.objects.select_related("user")
.filter(department=dept, hospital_id=dept.hospital_id, user__is_active=True) .filter(department=dept, hospital_id=dept.hospital_id, status="active")
.order_by("first_name", "last_name") .order_by("first_name", "last_name")
) )

View File

@ -357,7 +357,33 @@ def presentation_generate(request):
@login_required @login_required
def template_list(request): def template_list(request):
user = request.user
selected_hospital = getattr(request, "tenant_hospital", None)
templates = ReportTemplate.objects.select_related('created_by', 'hospital').prefetch_related('template_slides') templates = ReportTemplate.objects.select_related('created_by', 'hospital').prefetch_related('template_slides')
# Apply RBAC filters (previously missing entirely — every user saw all hospitals' templates)
if user.is_px_admin():
if selected_hospital:
templates = templates.filter(Q(hospital=selected_hospital) | Q(hospital__isnull=True))
elif user.is_hospital_admin() and user.hospital:
templates = templates.filter(Q(hospital=user.hospital) | Q(hospital__isnull=True))
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
templates = templates.filter(Q(hospital=user.hospital) | Q(hospital__isnull=True))
else:
templates = templates.filter(hospital__isnull=True)
elif user.is_department_manager() and user.department:
templates = templates.filter(Q(hospital=user.hospital) | Q(hospital__isnull=True))
elif user.is_champion() and user.department:
templates = templates.filter(Q(hospital=user.hospital) | Q(hospital__isnull=True))
elif user.is_source_user():
templates = templates.filter(created_by=user)
elif user.hospital:
templates = templates.filter(Q(hospital=user.hospital) | Q(hospital__isnull=True))
else:
templates = templates.filter(hospital__isnull=True)
context = { context = {
'templates': templates, 'templates': templates,
'data_sources': REPORT_DATA_SOURCES, 'data_sources': REPORT_DATA_SOURCES,

View File

@ -52,7 +52,7 @@ def _notify_project_team(project):
if not recipient_ids: if not recipient_ids:
return return
recipients = Staff.objects.filter(id__in=recipient_ids, user__isnull=False).select_related("user") recipients = Staff.objects.filter(id__in=recipient_ids).select_related("user")
subject = f"New QI Project: {project.name}" subject = f"New QI Project: {project.name}"
plain = ( plain = (
@ -71,7 +71,7 @@ def _notify_project_team(project):
</div> </div>
""" """
for sp in recipients: for sp in recipients:
email = getattr(sp.user, "email", None) email = sp.email or getattr(sp.user, "email", None)
if not email: if not email:
continue continue
try: try:
@ -1165,7 +1165,12 @@ def template_list(request):
queryset = QIProject.objects.filter(is_template=True).select_related("hospital").prefetch_related("departments") queryset = QIProject.objects.filter(is_template=True).select_related("hospital").prefetch_related("departments")
# Apply RBAC filters # Apply RBAC filters
if not user.is_px_admin(): selected_hospital = getattr(request, "tenant_hospital", None)
if user.is_px_admin():
if selected_hospital:
queryset = queryset.filter(Q(hospital=selected_hospital) | Q(hospital__isnull=True))
else:
# hospital_admin path (only px_admin/hospital_admin reach this point — gated above)
queryset = queryset.filter(Q(hospital=user.hospital) | Q(hospital__isnull=True)) queryset = queryset.filter(Q(hospital=user.hospital) | Q(hospital__isnull=True))
# Search # Search

View File

@ -513,10 +513,21 @@ def bulk_job_list(request):
List all bulk survey jobs for the user. List all bulk survey jobs for the user.
""" """
user = request.user user = request.user
selected_hospital = getattr(request, "tenant_hospital", None)
# Filter jobs # Filter jobs by RBAC
if user.is_px_admin(): if user.is_px_admin():
jobs = BulkSurveyJob.objects.all() if selected_hospital:
jobs = BulkSurveyJob.objects.filter(hospital=selected_hospital)
else:
jobs = BulkSurveyJob.objects.none()
elif user.is_hospital_admin() and user.hospital:
jobs = BulkSurveyJob.objects.filter(hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
jobs = BulkSurveyJob.objects.filter(hospital=user.hospital)
else:
jobs = BulkSurveyJob.objects.none()
elif user.hospital: elif user.hospital:
jobs = BulkSurveyJob.objects.filter(hospital=user.hospital) jobs = BulkSurveyJob.objects.filter(hospital=user.hospital)
else: else:

View File

@ -599,10 +599,22 @@ def survey_comments_list(request):
# Apply RBAC filters # Apply RBAC filters
user = request.user user = request.user
selected_hospital = getattr(request, "tenant_hospital", None)
if user.is_px_admin(): if user.is_px_admin():
pass # See all if selected_hospital:
queryset = queryset.filter(survey_template__hospital=selected_hospital)
elif user.is_hospital_admin() and user.hospital: elif user.is_hospital_admin() and user.hospital:
queryset = queryset.filter(survey_template__hospital=user.hospital) queryset = queryset.filter(survey_template__hospital=user.hospital)
elif user.is_px_management() or user.is_px_employee():
if user.hospital:
queryset = queryset.filter(survey_template__hospital=user.hospital)
elif user.is_department_manager() and user.department:
queryset = queryset.filter(survey_template__department=user.department)
elif user.is_champion() and user.department:
queryset = queryset.filter(survey_template__department=user.department)
elif user.is_source_user():
queryset = queryset.filter(created_by=user)
elif user.hospital: elif user.hospital:
queryset = queryset.filter(survey_template__hospital=user.hospital) queryset = queryset.filter(survey_template__hospital=user.hospital)
else: else:
@ -1817,8 +1829,19 @@ def survey_analytics_dashboard(request):
messages.error(request, "You don't have permission to view analytics.") messages.error(request, "You don't have permission to view analytics.")
return redirect("surveys:instance_list") return redirect("surveys:instance_list")
# Get available templates # Get available templates (scoped to user's hospital; system-wide templates included)
templates = SurveyTemplate.objects.filter(is_active=True).order_by("name") selected_hospital = getattr(request, "tenant_hospital", None)
if user.is_px_admin():
scope_hospital = selected_hospital
else:
scope_hospital = user.hospital
templates = SurveyTemplate.objects.filter(is_active=True)
if scope_hospital:
templates = templates.filter(Q(hospital=scope_hospital) | Q(hospital__isnull=True))
else:
templates = templates.filter(hospital__isnull=True)
templates = templates.order_by("name")
# Get selected template from query param # Get selected template from query param
selected_template_id = request.GET.get("template") selected_template_id = request.GET.get("template")

View File

@ -441,7 +441,7 @@ DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL", default="noreply@px360.sa")
SITE_URL = env("SITE_URL", default="http://localhost:8000") SITE_URL = env("SITE_URL", default="http://localhost:8000")
BASE_URL = SITE_URL BASE_URL = SITE_URL
SURVEY_BASE_URL = SITE_URL SURVEY_BASE_URL = SITE_URL
EMAIL_LOGO_URL = env("EMAIL_LOGO_URL", default=f"{SITE_URL}{STATIC_URL}img/HH_P_H_Logo.png") EMAIL_LOGO_URL = env("EMAIL_LOGO_URL", default=f"{SITE_URL}{STATIC_URL}images/logos/HH_P_H_Logo_CMYK---LANDSCIP1.png")
# HIS Integration Settings # HIS Integration Settings
HIS_API_URL = env("HIS_API_URL", default="https://his.alhammadi.med.sa/SSRCE/API/FetchPatientVisitTimeStamps") HIS_API_URL = env("HIS_API_URL", default="https://his.alhammadi.med.sa/SSRCE/API/FetchPatientVisitTimeStamps")

View File

@ -6,7 +6,7 @@ from .base import * # noqa
DEBUG = True DEBUG = True
ALLOWED_HOSTS = ["192.168.8.13","localhost", "127.0.0.1", "0.0.0.0", ".ngrok-free.app", "micha-nonparabolic-lovie.ngrok-free.dev"] ALLOWED_HOSTS = ["192.168.8.13","localhost", "127.0.0.1", "0.0.0.0"]
# Database - Use PostgreSQL even in dev for consistency # Database - Use PostgreSQL even in dev for consistency
# Override with SQLite if needed for quick local testing # Override with SQLite if needed for quick local testing
@ -17,11 +17,7 @@ ALLOWED_HOSTS = ["192.168.8.13","localhost", "127.0.0.1", "0.0.0.0", ".ngrok-fre
# ) # )
# } # }
CSRF_TRUSTED_ORIGINS = [ CSRF_TRUSTED_ORIGINS = []
"https://*.ngrok-free.app",
"https://*.ngrok.app",
"https://micha-nonparabolic-lovie.ngrok-free.dev",
]
# Database inherits from base.py (PostgreSQL) # Database inherits from base.py (PostgreSQL)
# To temporarily switch back to SQLite for testing, uncomment: # To temporarily switch back to SQLite for testing, uncomment:

View File

@ -0,0 +1,78 @@
# PX360 — DEV compose for `target deploy` (bundled postgres + redis)
#
# target injects into the `web_app` service on deploy:
# - container_name: <app>-web_app (so the shared Caddy can route to it)
# - networks: [target-proxy, default]
# - removes any `ports:` (the shared Caddy handles ingress)
#
# The web service MUST be named `web_app` — target's Caddy injection looks for it.
# `{{ .ImageTag }}` is replaced by target with the freshly built image tag.
#
# env file (in the deploy dir ~/.target/hh-dev/):
# .env — ALL config + secrets, shipped by `target deploy` (deploy/env.dev)
services:
web_app:
image: {{ .ImageTag }}
restart: unless-stopped
env_file:
- .env
volumes:
- media_volume:/app/media
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8888:8000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
celery:
image: {{ .ImageTag }}
restart: unless-stopped
command: celery -A config worker -l info --concurrency=2
env_file:
- .env
celery-beat:
image: {{ .ImageTag }}
restart: unless-stopped
command: celery -A config beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
env_file:
- .env
db:
image: postgres:15-alpine
restart: unless-stopped
ports:
- "5432:5432"
env_file:
- .env
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-px360}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pg_data:
redis_data:
media_volume:

View File

@ -24,7 +24,7 @@ services:
redis: redis:
condition: service_healthy condition: service_healthy
ports: ports:
- "8888:8000" - "8000:8000"
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health/"] test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]
interval: 30s interval: 30s

View File

@ -2,12 +2,12 @@
# Usage: `make deploy-dev` (runs: target deploy --config deploy/target.dev.yaml) # Usage: `make deploy-dev` (runs: target deploy --config deploy/target.dev.yaml)
app: app:
name: "hh-dev" # isolated project dir: ~/.target/hh-dev/ name: "hh-dev" # isolated project dir: ~/.target/hh-dev/
domain: "px360.ismailmosaibrahim.com" # shared Caddy routes this domain → web_app domain: "px360.hh.med.sa" # shared Caddy routes this domain → web_app
port: 8888 port: 8000
server: server:
ip: "192.168.8.73" ip: "10.132.16.5"
user: "ismail" user: "ahh-ops"
ssh_key: "~/.ssh/id_rsa" ssh_key: "~/.ssh/id_rsa"
build: build:
@ -15,7 +15,7 @@ build:
context: "." # repo root (where the Dockerfile lives) context: "." # repo root (where the Dockerfile lives)
deploy: deploy:
use_caddy: false # shared target-caddy handles HTTPS use_caddy: false # shared target-caddy handles HTTPS
caddy_email: "ismail.mosa.ibrahim@gmail.com" caddy_email: "ismail.mosa.ibrahim@gmail.com"
compose_file: "deploy/compose.dev.yml" # user-provided compose (uses {{ .ImageTag }}) compose_file: "deploy/compose.dev.yml" # user-provided compose (uses {{ .ImageTag }})
env_file: "deploy/env.dev" # shipped to ~/.target/hh-dev/.env each deploy env_file: "deploy/env.dev" # shipped to ~/.target/hh-dev/.env each deploy

View File

@ -705,3 +705,4 @@ For interactive API documentation with try-it-out functionality:
--- ---
**Last Updated:** December 14, 2025 **Last Updated:** December 14, 2025

View File

@ -744,7 +744,7 @@
], ],
"body": { "body": {
"mode": "raw", "mode": "raw",
"raw": "{\n \"hospital_id\": 1,\n \"doctor_id\": \"10738\",\n \"doctor_name\": \"Dr. Omaymah Yaqoub\",\n \"rating\": 4,\n \"feedback\": \"Great doctor, very caring and attentive.\",\n \"rating_date\": \"2026-06-10\",\n \"patient_uhid\": \"UHID12345\",\n \"patient_name\": \"Ahmed Ali\",\n \"patient_type\": \"OP\",\n \"department_name\": \"Internal Medicine\"\n}" "raw": "{\n \"hospital\": \"Al Nuzha\",\n \"doctor_id\": \"10738\",\n \"doctor_name\": \"Dr. Omaymah Yaqoub\",\n \"rating\": 4,\n \"feedback\": \"Great doctor, very caring and attentive.\",\n \"rating_date\": \"2026-06-10\",\n \"patient_uhid\": \"UHID12345\",\n \"patient_name\": \"Ahmed Ali\",\n \"patient_type\": \"OP\",\n \"department_name\": \"Internal Medicine\"\n}"
}, },
"url": { "url": {
"raw": "{{base_url}}/api/v1/external/doctor-ratings/", "raw": "{{base_url}}/api/v1/external/doctor-ratings/",
@ -765,7 +765,7 @@
}, },
"status": "Created", "status": "Created",
"code": 201, "code": 201,
"body": "{\n \"success\": true,\n \"rating_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"staff_id\": \"f1e2d3c4-b5a6-7890-abcd-ef1234567890\",\n \"doctor_name\": \"OMAYMAH YAQOUB ELAMEIAN\",\n \"rating\": 4\n}" "body": "{\n \"success\": true,\n \"rating_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n \"rating\": 4\n}"
}, },
{ {
"name": "Doctor Not Found", "name": "Doctor Not Found",

View File

@ -545,7 +545,7 @@ GET /api/v1/external/suggestions/<reference_number>/
### Submit a Doctor Rating ### Submit a Doctor Rating
Submit a patient rating for a doctor. The `doctor_id` must be an existing employee ID in the Staff table for the given hospital. Submit a patient rating for a doctor. The `doctor_id` should identify the doctor (existing staff matched by employee ID, license number, or name; otherwise a new Staff record is auto-created — see below).
``` ```
POST /api/v1/external/doctor-ratings/ POST /api/v1/external/doctor-ratings/
@ -555,10 +555,10 @@ POST /api/v1/external/doctor-ratings/
| Field | Required | Type | Description | | Field | Required | Type | Description |
|---|---|---|---| |---|---|---|---|
| `hospital_id` | Yes | integer | Hospital ID | | `hospital` | Yes | string | Hospital name (case-insensitive) |
| `doctor_id` | Yes | string | Doctor's employee ID (must exist in the hospital's Staff records) | | `doctor_id` | Yes | string | Doctor's employee ID |
| `rating` | Yes | integer | Rating from 1 to 5 | | `rating` | Yes | integer | Rating from 1 to 5 |
| `doctor_name` | No | string | Doctor name (stored for reference) | | `doctor_name` | Conditional | string | Doctor name. Required when `doctor_id` does not match an existing staff record (see auto-creation below). Otherwise stored for reference. |
| `feedback` | No | string | Patient feedback text | | `feedback` | No | string | Patient feedback text |
| `rating_date` | No | date | Date of rating (`YYYY-MM-DD`, defaults to current date) | | `rating_date` | No | date | Date of rating (`YYYY-MM-DD`, defaults to current date) |
| `patient_uhid` | No | string | Patient unique health ID | | `patient_uhid` | No | string | Patient unique health ID |
@ -568,11 +568,13 @@ POST /api/v1/external/doctor-ratings/
| `admit_date` | No | date | Admission date (`YYYY-MM-DD`) | | `admit_date` | No | date | Admission date (`YYYY-MM-DD`) |
| `discharge_date` | No | date | Discharge date (`YYYY-MM-DD`) | | `discharge_date` | No | date | Discharge date (`YYYY-MM-DD`) |
**Staff lookup & auto-creation:** When a rating is submitted, the server looks up the doctor by (in order) `employee_id`, `license_number`, then `doctor_name` (exact / contains / first+last name split). If no existing staff is matched, a new Staff record is **auto-created** with `staff_type=physician`, `job_title="Physician"`, `employee_id=doctor_id`, and the provided name. In that case `doctor_name` is required; omitting it returns `400`. The auto-creation is logged as an `external_staff_auto_created` audit event.
**Example:** **Example:**
```json ```json
{ {
"hospital_id": 1, "hospital": "Al Nuzha",
"doctor_id": "10738", "doctor_id": "10738",
"doctor_name": "Dr. Omaymah Yaqoub", "doctor_name": "Dr. Omaymah Yaqoub",
"rating": 4, "rating": 4,
@ -591,8 +593,6 @@ POST /api/v1/external/doctor-ratings/
{ {
"success": true, "success": true,
"rating_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "rating_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"staff_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"doctor_name": "OMAYMAH YAQOUB ELAMEIAN",
"rating": 4 "rating": 4
} }
``` ```
@ -601,21 +601,95 @@ POST /api/v1/external/doctor-ratings/
| HTTP Status | Condition | | HTTP Status | Condition |
|---|---| |---|---|
| `400` | `doctor_id` not found in the hospital's Staff records | | `400` | `doctor_id` matches no existing staff AND `doctor_name` is missing (cannot auto-create) |
| `400` | `rating` outside 1-5 range | | `400` | `rating` outside 1-5 range |
| `400` | `hospital_id` not found | | `400` | `hospital` not found |
| `403` | Hospital does not match API key scope | | `403` | Hospital does not match API key scope |
**Error Example (doctor not found):** **Error Example (cannot auto-create without a name):**
```json ```json
{ {
"doctor_id": [ "doctor_name": [
"Doctor with employee ID '99999' not found at this hospital." "Staff not found for this doctor_id; provide doctor_name to auto-create the staff record."
] ]
} }
``` ```
### List Doctor Ratings
Returns a paginated list of doctor ratings, scoped to the API key's hospital.
```
GET /api/v1/external/doctor-ratings/list/
```
**Query Parameters (all optional):**
| Parameter | Description |
|---|---|
| `doctor_id` | Filter by HIS employee ID (exact match) |
| `rating` | Filter by rating value 1-5 (exact match) |
| `rating_from` | Rating-date lower bound (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`) |
| `rating_to` | Rating-date upper bound (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SS`) |
| `created_from` / `created_to` | Created-at date range |
| `updated_from` / `updated_to` | Updated-at date range |
| `page` | Page number (default `1`) |
| `page_size` | Page size (default `20`, max `100`) |
**Response (200):**
```json
{
"count": 42,
"page": 1,
"page_size": 20,
"results": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"staff_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"hospital_name": "Al Nuzha",
"source": "his_api",
"source_reference": "",
"doctor_id": "10738",
"doctor_name": "OMAYMAH YAQOUB ELAMEIAN",
"doctor_name_raw": "10738 - OMAYMAH YAQOUB ELAMEIAN",
"doctor_name_display": "OMAYMAH YAQOUB ELAMEIAN",
"department_name": "Internal Medicine",
"patient_uhid": "UHID12345",
"patient_name": "Ahmed Ali",
"patient_type": "OP",
"admit_date": null,
"discharge_date": null,
"rating": 4,
"feedback": "Great doctor, very caring and attentive.",
"rating_date": "2026-06-10T00:00:00+03:00",
"is_aggregated": false,
"aggregated_at": null,
"created_at": "2026-06-10T14:22:11+03:00",
"updated_at": "2026-06-10T14:22:11+03:00"
}
]
}
```
### Retrieve a Doctor Rating
Retrieve a single rating by its UUID (returned in the `rating_id` field on creation).
```
GET /api/v1/external/doctor-ratings/<rating_id>/
```
**Response (200):** Same object shape as a single item from the list endpoint.
**Error Responses:**
| HTTP Status | Condition |
|---|---|
| `404` | Rating does not exist, or is outside the API key's hospital scope |
| `403` | API key does not have access to `doctor_ratings` |
--- ---
## Common Patterns ## Common Patterns

View File

@ -0,0 +1,506 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Admin Notification Templates</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f4f6; padding: 40px 10px;">
<tr>
<td align="center">
<!-- ========================================================================== -->
<!-- CARD 1: NEW COMPLAINT ADMIN NOTIFICATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ New Complaint (Admin Notification)
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى إدارة تجربة المريض</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم باستلام شكوى جديدة بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">20/يوليو/2026 10:45 ص</a> برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0067</a> الأولوية: <strong>عالية</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع عليها وتفعيلها، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(تفعيل الشكوى)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو مراجعتها وتفعيلها في أقرب وقت ممكن، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الإفادة)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Patient Experience Management</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that a new complaint has been received on: <a href="#" style="color: #0056b3; text-decoration: underline;">20/July/2026 10:45 AM</a> with reference: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0067</a> Priority: <strong>High</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view and activate it, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Activate Complaint)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please review and activate it at your earliest convenience, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Feedback Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 2: COMMUNICATION REQUEST -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Communication Request
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم علاقات المرضى</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بطلب تواصل جديد بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">20/يوليو/2026 01:15 م</a> من قبل المستخدم: <a href="#" style="color: #0056b3; text-decoration: underline;">نورة القحطاني</a> رقم الطلب: <a href="#" style="color: #0056b3; text-decoration: underline;">CR-2026-0158</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع على الطلب والرد عليه، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الطلب)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو الرد على الطلب في أقرب وقت، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Patient Relations Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that a new communication request has been submitted on: <a href="#" style="color: #0056b3; text-decoration: underline;">20/July/2026 01:15 PM</a> by user: <a href="#" style="color: #0056b3; text-decoration: underline;">Noura Al-Qahtani</a> Request ID: <a href="#" style="color: #0056b3; text-decoration: underline;">CR-2026-0158</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view and respond to the request, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Request Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please respond to the request at your earliest convenience, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Reply Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 3: INVITATION EXPIRED -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Invitation Expired
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى السيد / محمد العمري</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بأن <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">دعوتكم للانضمام إلى نظام PX360 قد انتهت صلاحيتها</span> بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">19/يوليو/2026 11:59 م</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
لطلب دعوة جديدة، يرجى التواصل مع المسؤول، وللإطلاع إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(طلب دعوة جديدة)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
سيتم إنشاء رابط دعوة جديد لكم لإكمال إعداد الحساب، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الإفادة)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Mr. Mohammed Al-Omari</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">your invitation to join PX360 has expired</span> on: <a href="#" style="color: #0056b3; text-decoration: underline;">19/July/2026 11:59 PM</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To request a new invitation, please contact your administrator, and to view, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Request New Invitation)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
A new invitation link will be generated for you to complete your account setup, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Feedback Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 4: PX360 WEEKLY DIGEST -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ PX360 Weekly Digest
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">ملخص الأسبوع - مستشفى الحمادي</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بالملخص الأسبوعي لأداء تجربة المريض للأسبوع: <a href="#" style="color: #0056b3; text-decoration: underline;">14-20 يوليو 2026</a> إجمالي الشكاوى: <strong>24</strong> | معدل الالتزام بـ SLA: <strong>87%</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع على التفاصيل الكاملة، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(لوحة المعلومات)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو مراجعة المؤشرات والتنبيهات والرد على المخاطر العالية، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الإفادة)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">Weekly Digest - Al Hammadi Hospital</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to share the weekly patient experience performance digest for the week: <a href="#" style="color: #0056b3; text-decoration: underline;">14-20 July 2026</a> Total Complaints: <strong>24</strong> | SLA Compliance: <strong>87%</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view the full dashboard, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Dashboard)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please review the metrics, early warnings, and respond to high-risk items, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Feedback Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,140 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Appointment Confirmation Template</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f4f6; padding: 40px 10px;">
<tr>
<td align="center">
<!-- ========================================================================== -->
<!-- CARD 1: APPOINTMENT CONFIRMATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Appointment Confirmation
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى المريض / ماجد الحربي</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بتأكيد موعدكم بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">25/يوليو/2026 10:30 ص</a> رقم الموعد: <a href="#" style="color: #0056b3; text-decoration: underline;">APT-202607-0214</a> القسم: <strong>العيادات الخارجية - الباطنة</strong> الطبيب: <strong>د. سامي إبراهيم</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع على تفاصيل الموعد، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(تفاصيل الموعد)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو الحضور قبل الموعد بـ 15 دقيقة لإتمام التسجيل. لإعادة الجدولة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(إعادة الجدولة)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Patient / Majed Al-Harbi</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to confirm your appointment on: <a href="#" style="color: #0056b3; text-decoration: underline;">25/July/2026 10:30 AM</a> Appointment ID: <a href="#" style="color: #0056b3; text-decoration: underline;">APT-202607-0214</a> Department: <strong>Outpatient - Internal Medicine</strong> Doctor: <strong>Dr. Sami Ibrahim</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view the appointment details, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Appointment Details)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please arrive 15 minutes before the appointment for registration. To reschedule, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Reschedule)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,311 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Complaint Escalation - Artboard Variant</title>
<style>
/* ===================================================================
ARTBOARD VARIANT — Fixed A4 page per stage
Each card is a 210mm × 297mm page with the hospital letterhead
as its background. The artboard's header/footer artwork shows
correctly because the page dimensions match its aspect ratio.
=================================================================== */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background-color: #e8eaed;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 40px 10px;
}
.page-wrapper {
text-align: center;
}
.stage-label {
max-width: 210mm;
margin: 0 auto 15px auto;
font-size: 18px;
font-weight: bold;
color: #4b5563;
text-transform: uppercase;
letter-spacing: 1px;
}
.artboard-page {
position: relative;
width: 210mm;
height: 297mm;
margin: 0 auto 30mm auto;
background-image: url("../static/images/artboard/Artboard 1@3x.png");
background-size: 210mm 297mm;
background-repeat: no-repeat;
background-position: top left;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
border: 1px solid #d1d5db;
overflow: hidden;
}
/* Content zone — flows from below the header artwork (38mm) to
above the footer artwork (32mm). No absolute positioning needed;
content flows naturally top-to-bottom within the padded area.
Matches the proven pattern from templates/appreciation/appreciation_letter.html. */
.content {
padding: 38mm 20mm 32mm 20mm;
height: 297mm;
box-sizing: border-box;
text-align: center;
}
.ar-block { direction: rtl; text-align: center; margin-bottom: 4mm; }
.en-block { direction: ltr; text-align: center; }
.ar-block h1 {
font-size: 22pt;
font-weight: bold;
color: #000000;
margin-bottom: 8mm;
}
.en-block h1 {
font-size: 22pt;
font-weight: bold;
color: #000000;
margin-bottom: 8mm;
}
.ar-block p, .en-block p {
font-size: 13pt;
line-height: 1.8;
color: #111827;
font-weight: 600;
margin-bottom: 5mm;
}
.link-red {
color: #dc2626;
font-weight: bold;
font-size: 14pt;
text-decoration: underline;
}
.link-blue {
color: #0056b3;
text-decoration: underline;
}
.highlight {
background-color: #ffff00;
padding: 0 4px;
color: #000000;
}
.divider {
border: 0;
border-top: 1px dashed #d1d5db;
margin: 6mm 0;
}
.btn-teal {
display: inline-block;
padding: 12px 30px;
font-size: 16px;
font-weight: bold;
color: #004b69;
text-decoration: none;
background-color: #008cc1;
border-radius: 2px;
margin-top: 5mm;
}
.contact-info {
font-size: 12pt;
line-height: 1.6;
color: #111827;
font-weight: 600;
margin-top: 5mm;
}
.signoff {
font-size: 13pt;
color: #1f2937;
font-weight: 600;
margin-top: 6mm;
}
</style>
</head>
<body>
<div class="page-wrapper">
<!-- ================================================================ -->
<!-- STAGE 1: NEW COMPLAINT NOTIFICATION -->
<!-- ================================================================ -->
<div class="stage-label">▼ Stage 1: New Complaint Notification</div>
<div class="artboard-page">
<div class="content">
<div class="ar-block">
<h1>إلى قسم المختبر</h1>
<p>تحية طيبة</p>
<p>
نود إبلاغكم باستلام شكوى بتاريخ:
<a href="#" class="link-blue">23/يونيو/2026 12:15 م</a>
من قبل وزارة الصحة برقم الهوية:
<a href="#" class="link-blue">1113543183</a>
</p>
<p>للإطلاع عليها، يمكنكم النقر على</p>
<p><a href="#" class="link-red">(نموذج الشكوى)</a></p>
<p>نرجو النظر في الأمر والتحقق خلال 24 ساعة، وللإفادة إضغط على</p>
<p><a href="#" class="link-red">(نموذج الرد على الشكوى)</a></p>
<div class="contact-info">
<p>للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p>رقم التواصل: <a href="tel:0537695000" class="link-blue">0537695000</a></p>
</div>
<a href="#" class="btn-teal">رابط</a>
<p class="signoff">نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض</p>
</div>
<hr class="divider" />
<div class="en-block">
<h1>To: Laboratory Department</h1>
<p>Greetings,</p>
<p>
We would like to inform you that a complaint has been received on:
<a href="#" class="link-blue">23/June/2026 12:15 PM</a>
by the Ministry of Health with ID number:
<a href="#" class="link-blue">1113543183</a>
</p>
<p>To view it, you can click on:</p>
<p><a href="#" class="link-red">(Complaint Form)</a></p>
<p>Please look into the matter and verify within 24 hours, and to provide feedback, click on:</p>
<p><a href="#" class="link-red">(Complaint Response Form)</a></p>
<div class="contact-info">
<p>For inquiries or more information, via Extension: 1379</p>
<p>Contact Number: <a href="tel:0537695000" class="link-blue">0537695000</a></p>
</div>
<a href="#" class="btn-teal">Link</a>
<p class="signoff">We appreciate your commitment.<br />Regards, Patient Relations and Experience Management</p>
</div>
</div>
</div>
<!-- ================================================================ -->
<!-- STAGE 2: FIRST REMINDER NOTIFICATION -->
<!-- ================================================================ -->
<div class="stage-label">▼ Stage 2: First Reminder Notification</div>
<div class="artboard-page">
<div class="content">
<div class="ar-block">
<h1>إلى قسم الأمن</h1>
<p>
نقدر لكم انشغالكم وحرصكم، نود
<span class="highlight">تذكيركم</span>
بالشكوى المقدمة
بتاريخ: <a href="#" class="link-blue">21/يونيو/2026 02:23 م</a>
من قبل المراجع برقم الهوية:
<a href="#" class="link-blue">2343965287</a>
</p>
<p><a href="#" class="link-red">(نموذج الشكوى)</a></p>
<p>نرجو منكم التكرم بالرد على هذه الشكوى خلال الـ 24 ساعة القادمة، حرصاً على تحسين تجربة المريض وضمان معالجة الشكوى في الوقت المناسب، للرد نأمل النقر على</p>
<p><a href="#" class="link-red">(نموذج الرد على الشكوى)</a></p>
<div class="contact-info">
<p>للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل على:</p>
<p>التحويلة: 1379 — رقم التواصل: <a href="tel:0537695000" class="link-blue">0537695000</a></p>
</div>
<a href="#" class="btn-teal">رابط</a>
<p class="signoff">نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض</p>
</div>
<hr class="divider" />
<div class="en-block">
<h1>To: Security Department</h1>
<p>
We appreciate your dedication and diligence. We would like to
<span class="highlight">remind you</span>
regarding the submitted complaint
dated: <a href="#" class="link-blue">21/June/2026 02:23 PM</a>
by the visitor with National ID:
<a href="#" class="link-blue">2343965287</a>
</p>
<p><a href="#" class="link-red">(Complaint Form)</a></p>
<p>Please kindly respond to this complaint within the next 24 hours, in our commitment to enhancing patient experience and ensuring timely resolution. To reply, please click on:</p>
<p><a href="#" class="link-red">(Complaint Response Form)</a></p>
<div class="contact-info">
<p>For inquiries or more information, please contact us via:</p>
<p>Extension: 1379 — Contact Number: <a href="tel:0537695000" class="link-blue">0537695000</a></p>
</div>
<a href="#" class="btn-teal">Link</a>
<p class="signoff">We appreciate your commitment.<br />Regards, Patient Relations and Experience Management</p>
</div>
</div>
</div>
<!-- ================================================================ -->
<!-- STAGE 3: SECOND REMINDER NOTIFICATION -->
<!-- ================================================================ -->
<div class="stage-label">▼ Stage 3: Second Reminder Notification</div>
<div class="artboard-page">
<div class="content">
<div class="ar-block">
<h1>إلى قسم المالية</h1>
<p>تحية طيبة</p>
<p>
نشكر لكم تعاونكم ونقدّر انشغالكم، ولكن نرجو
<span class="highlight">ملاحظة الوقت المتبقي للرد هو 6 ساعات</span>
على الشكوى المقدمة بتاريخ:
<a href="#" class="link-blue">22/يونيو/2026 04:20 م</a>
من قبل مجلس الضمان الصحي برقم الهوية:
<a href="#" class="link-blue">1050208576</a>
</p>
<p><a href="#" class="link-red">(نموذج الشكوى)</a></p>
<p>لذا نأمل منكم التكرم بالرد والإفادة في أقرب وقت ممكن لتجنب أي تأخير إضافي في معالجة الشكوى. وللرد، يرجى النقر على</p>
<p><a href="#" class="link-red">(نموذج الرد على الشكوى)</a></p>
<div class="contact-info">
<p>للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل معنا:</p>
<p>التحويلة: 1379 — رقم التواصل: <a href="tel:0537695000" class="link-blue">0537695000</a></p>
</div>
<a href="#" class="btn-teal">رابط</a>
<p class="signoff">نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض</p>
</div>
<hr class="divider" />
<div class="en-block">
<h1>To: Financial Department</h1>
<p>Greetings,</p>
<p>
We appreciate your cooperation and value your time, but please
<span class="highlight">note that the remaining time to respond is 6 hours</span>
regarding the complaint submitted on:
<a href="#" class="link-blue">22/June/2026 04:20 PM</a>
by the Council of Health Insurance with ID number:
<a href="#" class="link-blue">1050208576</a>
</p>
<p><a href="#" class="link-red">(Complaint Form)</a></p>
<p>Therefore, we kindly request your response and feedback as soon as possible to avoid any further delay in processing the complaint. To reply, please click on:</p>
<p><a href="#" class="link-red">(Complaint Response Form)</a></p>
<div class="contact-info">
<p>For inquiries or more information, please contact us via:</p>
<p>Extension: 1379 — Contact Number: <a href="tel:0537695000" class="link-blue">0537695000</a></p>
</div>
<a href="#" class="btn-teal">Link</a>
<p class="signoff">We appreciate your commitment.<br />Regards, Patient Relations and Experience Management</p>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,383 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Complaint System Escalation Templates</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f4f6; padding: 40px 10px;">
<tr>
<td align="center">
<!-- ========================================================================== -->
<!-- STAGE 1: NEW COMPLAINT NOTIFICATION -->
<!-- ========================================================================== -->
<!-- Template Title Divider -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 1: New Complaint Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم المختبر</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم باستلام شكوى بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">23/يونيو/2026 12:15 م</a> من قبل وزارة الصحة برقم الهوية: <a href="#" style="color: #0056b3; text-decoration: underline;">1113543183</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للإطلاع عليها، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الشكوى)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو النظر في الأمر والتحقق خلال 24 ساعة، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الشكوى)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Laboratory Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that a complaint has been received on: <a href="#" style="color: #0056b3; text-decoration: underline;">23/June/2026 12:15 PM</a> by the Ministry of Health with ID number: <a href="#" style="color: #0056b3; text-decoration: underline;">1113543183</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view it, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please look into the matter and verify within 24 hours, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- STAGE 2: FIRST REMINDER NOTIFICATION -->
<!-- ========================================================================== -->
<!-- Template Title Divider -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 2: First Reminder Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم الأمن</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نقدر لكم انشغالكم وحرصكم، نود
<span style="background-color: #ffff00; padding: 0 4px; color: #000000;">تذكيركم</span>
بالشكوى المقدمة
<br />
بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">21/يونيو/2026 02:23 م</a>
<br />
من قبل المراجع برقم الهوية: <a href="#" style="color: #0056b3; text-decoration: underline;">2343965287</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الشكوى)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو منكم التكرم بالرد على هذه الشكوى خلال الـ 24 ساعة القادمة، حرصاً على تحسين تجربة المريض وضمان معالجة الشكوى في الوقت المناسب , للرد نأمل النقر على
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الشكوى)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل على:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin:;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 40px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 40px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Security Department</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your dedication and diligence. We would like to <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">remind you</span> regarding the submitted complaint
<br />
Dated: <a href="#" style="color: #0056b3; text-decoration: underline;">21/June/2026 02:23 PM</a>
<br />
By the visitor with National ID: <a href="#" style="color: #0056b3; text-decoration: underline;">2343965287</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Form)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please kindly respond to this complaint within the next 24 hours, in our commitment to enhancing patient experience and ensuring timely resolution. To reply, please click on:
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Response Form)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin:;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 20px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- STAGE 3: SECOND REMINDER NOTIFICATION -->
<!-- ========================================================================== -->
<!-- Template Title Divider -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 3: Second Reminder Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم المالية</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نشكر لكم تعاونكم ونقدّر انشغالكم، ولكن نرجو <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">ملاحظة الوقت المتبقي للرد هو 6 ساعات</span> على الشكوى المقدمة بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">22/يونيو/2026 04:20 م</a> من قبل مجلس الضمان الصحي برقم الهوية: <a href="#" style="color: #0056b3; text-decoration: underline;">1050208576</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الشكوى)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
لذا نأمل منكم التكرم بالرد والإفادة في أقرب وقت ممكن لتجنب أي تأخير إضافي في معالجة الشكوى. وللرد، يرجى النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الشكوى)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل معنا:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Financial Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your cooperation and value your time, but please <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">note that the remaining time to respond is 6 hours</span> regarding the complaint submitted on: <a href="#" style="color: #0056b3; text-decoration: underline;">22/June/2026 04:20 PM</a> by the Council of Health Insurance with ID number: <a href="#" style="color: #0056b3; text-decoration: underline;">1050208576</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Therefore, we kindly request your response and feedback as soon as possible to avoid any further delay in processing the complaint. To reply, please click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,502 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Explanation Request Templates</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f4f6; padding: 40px 10px;">
<tr>
<td align="center">
<!-- ========================================================================== -->
<!-- CARD 1: EXPLANATION REQUEST (COMPLAINT) -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Explanation Request (Complaint)
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم الطوارئ</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بإسناد شكوى لقسمكم للرد عليها، بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">20/يوليو/2026 09:00 ص</a> برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0072</a> الموعد النهائي: <a href="#" style="color: #0056b3; text-decoration: underline;">22/يوليو/2026 05:00 م</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع على تفاصيل الشكوى، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الشكوى)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو تقديم رد القسم قبل الموعد النهائي، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Emergency Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Your department has been assigned to respond to a complaint, dated: <a href="#" style="color: #0056b3; text-decoration: underline;">20/July/2026 09:00 AM</a> with reference: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0072</a> Deadline: <a href="#" style="color: #0056b3; text-decoration: underline;">22/July/2026 05:00 PM</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view the complaint details, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please submit your department's response before the deadline, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 2: EXPLANATION REMINDER -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Explanation Reminder (First)
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى السيد / سعد الدوسري</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نقدر لكم انشغالكم وحرصكم، نود
<span style="background-color: #ffff00; padding: 0 4px; color: #000000;">تذكيركم</span>
بتقديم رد القسم على الشكوى
<br />
بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">19/يوليو/2026 11:00 ص</a>
<br />
برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0069</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الشكوى)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو منكم التكرم بتقديم الشرح خلال الـ 24 ساعة القادمة لتجنب التصعيد إلى مديركم المباشر , للرد نأمل النقر على
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل على:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin:;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 40px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 40px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Mr. Saad Al-Dosari</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your dedication and diligence. We would like to <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">remind you</span> to submit your department's response regarding the complaint
<br />
Dated: <a href="#" style="color: #0056b3; text-decoration: underline;">19/July/2026 11:00 AM</a>
<br />
Reference: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0069</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Form)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please kindly submit your explanation within the next 24 hours to avoid escalation to your direct manager. To reply, please click on:
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Response Form)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin:;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 20px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 3: EXPLANATION SECOND REMINDER (FINAL) -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Explanation Second Reminder (Final)
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى السيد / فهد الشمري</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نشكر لكم تعاونكم، ولكن نرجو <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">الملاحظة بأن هذا تذكيرك النهائي والعاجل</span> لتقديم الشرح بخصوص الشكوى بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">18/يوليو/2026 02:30 م</a> برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0065</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الشكوى)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
إذا لم يتم تقديم الشرح قبل الموعد النهائي، سيتم تصعيد الأمر إلى مديركم المباشر. يرجى التقديم فوراً
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل معنا:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Mr. Fahad Al-Shammari</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your cooperation, but please <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">note that this is your FINAL and URGENT reminder</span> to submit your explanation regarding the complaint dated: <a href="#" style="color: #0056b3; text-decoration: underline;">18/July/2026 02:30 PM</a> with reference: <a href="#" style="color: #0056b3; text-decoration: underline;">CMP-202607-0065</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Complaint Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
If the explanation is not submitted before the deadline, this matter will be escalated to your direct manager. Please submit immediately:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 4: INQUIRY EXPLANATION REQUEST -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Inquiry Explanation Request
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى السيد / عبدالله الغامدي</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بطلب رد على استفسار بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">20/يوليو/2026 08:00 ص</a> برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0021</a> الموضوع: <strong>تأخر موعد موعد العيادة</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع على تفاصيل الاستفسار، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الاستفسار)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو تقديم ردكم في أقرب وقت ممكن، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Mr. Abdullah Al-Ghamdi</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
You have been requested to provide a response regarding an inquiry, dated: <a href="#" style="color: #0056b3; text-decoration: underline;">20/July/2026 08:00 AM</a> with reference: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0021</a> Subject: <strong>Clinic appointment delay</strong>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view the inquiry details, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Inquiry Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please submit your response as soon as possible, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,380 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Inquiry System Escalation Templates</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f4f6; padding: 40px 10px;">
<tr>
<td align="center">
<!-- ========================================================================== -->
<!-- STAGE 1: NEW INQUIRY NOTIFICATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 1: New Inquiry Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم الطوارئ</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم باستلام استفسار بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">15/يوليو/2026 09:30 ص</a> من قبل المراجع برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0012</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للإطلاع عليها، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الاستفسار)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو النظر في الأمر والرد خلال 24 ساعة، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الاستفسار)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Emergency Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that an inquiry has been received on: <a href="#" style="color: #0056b3; text-decoration: underline;">15/July/2026 09:30 AM</a> by the visitor with reference number: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0012</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view it, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Inquiry Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please look into the matter and reply within 24 hours, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Inquiry Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- STAGE 2: FIRST REMINDER NOTIFICATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 2: First Reminder Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم الأشعة</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نقدر لكم انشغالكم وحرصكم، نود
<span style="background-color: #ffff00; padding: 0 4px; color: #000000;">تذكيركم</span>
بالاستفسار المقدم
<br />
بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">10/يوليو/2026 02:15 م</a>
<br />
برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0008</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الاستفسار)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو منكم التكرم بالرد على هذا الاستفسار خلال الـ 24 ساعة القادمة، حرصاً على تحسين تجربة المريض وضمان معالجة الاستفسار في الوقت المناسب , للرد نأمل النقر على
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الاستفسار)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل على:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin:;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 40px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 40px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Radiology Department</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your dedication and diligence. We would like to <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">remind you</span> regarding the submitted inquiry
<br />
Dated: <a href="#" style="color: #0056b3; text-decoration: underline;">10/July/2026 02:15 PM</a>
<br />
Reference Number: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0008</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Inquiry Form)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please kindly respond to this inquiry within the next 24 hours, in our commitment to enhancing patient experience and ensuring timely resolution. To reply, please click on:
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Inquiry Response Form)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin:;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 20px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- STAGE 3: SECOND REMINDER NOTIFICATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 3: Second Reminder Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم الصيدلية</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نشكر لكم تعاونكم ونقدّر انشغالكم، ولكن نرجو <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">ملاحظة الوقت المتبقي للرد هو 6 ساعات</span> على الاستفسار المقدم بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">08/يوليو/2026 11:45 ص</a> برقم المرجع: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0003</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الاستفسار)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
لذا نأمل منكم التكرم بالرد والإفادة في أقرب وقت ممكن لتجنب أي تأخير إضافي في معالجة الاستفسار. وللرد، يرجى النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الاستفسار)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل معنا:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Pharmacy Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your cooperation and value your time, but please <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">note that the remaining time to respond is 6 hours</span> regarding the inquiry submitted on: <a href="#" style="color: #0056b3; text-decoration: underline;">08/July/2026 11:45 AM</a> with reference number: <a href="#" style="color: #0056b3; text-decoration: underline;">INQ-202607-0003</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Inquiry Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Therefore, we kindly request your response and feedback as soon as possible to avoid any further delay in processing the inquiry. To reply, please click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Inquiry Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,380 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Observation System Escalation Templates</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f4f6; padding: 40px 10px;">
<tr>
<td align="center">
<!-- ========================================================================== -->
<!-- STAGE 1: NEW OBSERVATION NOTIFICATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 1: New Observation Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم التمريض</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بصدور ملاحظة بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">18/يوليو/2026 08:45 ص</a> من قبل الموظف: <a href="#" style="color: #0056b3; text-decoration: underline;">أحمد محمد</a> برمز التتبع: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0034</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للإطلاع عليها، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الملاحظة)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو النظر في الأمر والتحقق خلال 24 ساعة، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الملاحظة)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Nursing Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that an observation has been reported on: <a href="#" style="color: #0056b3; text-decoration: underline;">18/July/2026 08:45 AM</a> by staff member: <a href="#" style="color: #0056b3; text-decoration: underline;">Ahmed Mohammed</a> with tracking code: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0034</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view it, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please look into the matter and verify within 24 hours, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- STAGE 2: FIRST REMINDER NOTIFICATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 2: First Reminder Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم الجراحة</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نقدر لكم انشغالكم وحرصكم، نود
<span style="background-color: #ffff00; padding: 0 4px; color: #000000;">تذكيركم</span>
بالملاحظة المقدمة
<br />
بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">14/يوليو/2026 11:20 م</a>
<br />
برمز التتبع: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0028</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الملاحظة)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو منكم التكرم بالرد على هذه الملاحظة خلال الـ 24 ساعة القادمة، حرصاً على تحسين تجربة المريض وضمان معالجة الملاحظة في الوقت المناسب , للرد نأمل النقر على
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الملاحظة)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل على:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin:;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 40px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 40px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 30px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Surgery Department</h1>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your dedication and diligence. We would like to <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">remind you</span> regarding the reported observation
<br />
Dated: <a href="#" style="color: #0056b3; text-decoration: underline;">14/July/2026 11:20 PM</a>
<br />
Tracking Code: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0028</a>
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Form)</a>
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please kindly respond to this observation within the next 24 hours, in our commitment to enhancing patient experience and ensuring timely resolution. To reply, please click on:
</p>
<p style="margin: 0 0 30px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Response Form)</a>
</p>
<div style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin:;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 40px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 20px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- STAGE 3: SECOND REMINDER NOTIFICATION -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Stage 3: Second Reminder Notification
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم العناية المركزة</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نشكر لكم تعاونكم ونقدّر انشغالكم، ولكن نرجو <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">ملاحظة الوقت المتبقي للرد هو 4 ساعات</span> على الملاحظة المقدمة بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">11/يوليو/2026 03:10 م</a> برمز التتبع: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0021</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الملاحظة)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
لذا نأمل منكم التكرم بالرد والإفادة في أقرب وقت ممكن لتجنب أي تأخير إضافي في معالجة الملاحظة. وللرد، يرجى النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد على الملاحظة)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، يُرجى التواصل معنا:</p>
<p style="margin: 0 0 4px 0;">التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Critical Care Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We appreciate your cooperation and value your time, but please <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">note that the remaining time to respond is 4 hours</span> regarding the observation reported on: <a href="#" style="color: #0056b3; text-decoration: underline;">11/July/2026 03:10 PM</a> with tracking code: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0021</a>
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Therefore, we kindly request your response and feedback as soon as possible to avoid any further delay in processing the observation. To reply, please click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Response Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, please contact us via:</p>
<p style="margin: 0 0 4px 0;">Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

172
exports/sections.csv Normal file
View File

@ -0,0 +1,172 @@
Name (EN),Name (AR),Department (EN),Department (AR)
Appointment Office,,Contact Center Department,قسم مركز الاتصال
Operator Office,,Contact Center Department,قسم مركز الاتصال
CCU,,Critical Care Department,قسم العناية المركزة
ICU,,Critical Care Department,قسم العناية المركزة
ICU Stepdown,,Critical Care Department,قسم العناية المركزة
Dermatology,,Dermatology Department,قسم الجلدية
Admission Office,,Emergency Administrative Department,إدارة الطوارئ
Emergency Coordination,,Emergency Administrative Department,إدارة الطوارئ
Emergency Management,,Emergency Administrative Department,إدارة الطوارئ
Emergency Reception,,Emergency Administrative Department,إدارة الطوارئ
Administrative Assistant,,Executive Administration,الإدارة التنفيذية
Chief Administrative Officer,,Executive Administration,الإدارة التنفيذية
Secretary,,Executive Administration,الإدارة التنفيذية
Software Programmer,,Executive Administration,الإدارة التنفيذية
Electrical Maintenance,,Facility Management & Maintenance Department,قسم إدارة المرافق والصيانة
General Maintenance,,Facility Management & Maintenance Department,قسم إدارة المرافق والصيانة
Mechanical Maintenance,,Facility Management & Maintenance Department,قسم إدارة المرافق والصيانة
Safety,,Facility Management & Maintenance Department,قسم إدارة المرافق والصيانة
Water Treatment,,Facility Management & Maintenance Department,قسم إدارة المرافق والصيانة
Financial Collection & Claims,,Financial Collection & Claims Department,قسم المالية والتحصيل والمطالبات
General,,Information Technology Department,قسم تقنية المعلومات
Service Desk,,Information Technology Department,قسم تقنية المعلومات
Software and Applications,,Information Technology Department,قسم تقنية المعلومات
Social Worker,,Inpatient Department,قسم التنويم
AKU,,Internal Medicine Department,قسم الباطنية
Cardiology,,Internal Medicine Department,قسم الباطنية
Endocrinology,,Internal Medicine Department,قسم الباطنية
Endoscopy,,Internal Medicine Department,قسم الباطنية
Gastroenterology,,Internal Medicine Department,قسم الباطنية
Infectious Disease,,Internal Medicine Department,قسم الباطنية
Internal Medicine,,Internal Medicine Department,قسم الباطنية
Long-Term Acute Care Unit (LTACU),,Internal Medicine Department,قسم الباطنية
Medical Ward,,Internal Medicine Department,قسم الباطنية
Nephrology,,Internal Medicine Department,قسم الباطنية
Neurology,,Internal Medicine Department,قسم الباطنية
Psychiatry,,Internal Medicine Department,قسم الباطنية
Pulmonology,,Internal Medicine Department,قسم الباطنية
Rheumatology,,Internal Medicine Department,قسم الباطنية
Blood Donation & Blood Bank,,Laboratory Department,قسم المختبر
Clinical Chemistry,,Laboratory Department,قسم المختبر
General,,Laboratory Department,قسم المختبر
Hematology,,Laboratory Department,قسم المختبر
Histopathology,,Laboratory Department,قسم المختبر
Microbiology,,Laboratory Department,قسم المختبر
Parasitology,,Laboratory Department,قسم المختبر
Phlepotomy,,Laboratory Department,قسم المختبر
Receiving ِArea,,Laboratory Department,قسم المختبر
Send Out,,Laboratory Department,قسم المختبر
Serology & Molecular,,Laboratory Department,قسم المختبر
Special Chemistry,,Laboratory Department,قسم المختبر
General,,Medical Administration,الإدارة الطبية
Medical Director,,Medical Administration,الإدارة الطبية
Secretary,,Medical Administration,الإدارة الطبية
Anesthesia Technology,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
CSSD,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Cardiac Physiology Services,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Clinical Nutrition,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Dental Services,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Director of Ancillary Medical Services,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Emergency Medical Services,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
General,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Health Education,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Neurophysiology Services,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Optometrist Services,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Physical Therapy,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Respiratory Therapy,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Secretary,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Swallowing Speech and Hearing Unit,,Medical Ancillary Services Department,قسم الخدمات الطبية المساندة
Emergency,,Medical Approvals Department,قسم الموافقات الطبية
General,,Medical Approvals Department,قسم الموافقات الطبية
Inpatient,,Medical Approvals Department,قسم الموافقات الطبية
Outpatient,,Medical Approvals Department,قسم الموافقات الطبية
AKU,,Nursing Department,قسم التمريض
Anesthesia,,Nursing Department,قسم التمريض
Cathlab,,Nursing Department,قسم التمريض
Emergency,,Nursing Department,قسم التمريض
Endoscopy,,Nursing Department,قسم التمريض
ICU/CCU,,Nursing Department,قسم التمريض
ICU/CCU Stepdown,,Nursing Department,قسم التمريض
L&D,,Nursing Department,قسم التمريض
LTACU,,Nursing Department,قسم التمريض
Main OR,,Nursing Department,قسم التمريض
Management,,Nursing Department,قسم التمريض
Medical Ward,,Nursing Department,قسم التمريض
NICU,,Nursing Department,قسم التمريض
NICU ISOLATOIN,,Nursing Department,قسم التمريض
Nursery,,Nursing Department,قسم التمريض
OB/OR,,Nursing Department,قسم التمريض
OPD 5,,Nursing Department,قسم التمريض
OPD 6,,Nursing Department,قسم التمريض
OPD 7,,Nursing Department,قسم التمريض
OPD1,,Nursing Department,قسم التمريض
OPD2,,Nursing Department,قسم التمريض
OPD3,,Nursing Department,قسم التمريض
OPD4 Dental,,Nursing Department,قسم التمريض
OPD4 Ophtha,,Nursing Department,قسم التمريض
Obgyne Ward,,Nursing Department,قسم التمريض
Oncology,,Nursing Department,قسم التمريض
Orthopedic Cast Technician,,Nursing Department,قسم التمريض
Outpatient,,Nursing Department,قسم التمريض
PICU,,Nursing Department,قسم التمريض
PICU Stepdown,,Nursing Department,قسم التمريض
Pediatric Ward,,Nursing Department,قسم التمريض
Recovery,,Nursing Department,قسم التمريض
Surgical Ward,,Nursing Department,قسم التمريض
Hematology,,ONCOLOGY Department,قسم الأورام
Clinics,,Obstetrics & Gynecology Department,قسم النساء والولادة
Labor & Delivery (L&D) / Obstetrics Operating Rooms,,Obstetrics & Gynecology Department,قسم النساء والولادة
Wards,,Obstetrics & Gynecology Department,قسم النساء والولادة
Coordination,,Outpatient Department,قسم العيادات الخارجية
Management,,Outpatient Department,قسم العيادات الخارجية
Medical Reports,,Outpatient Department,قسم العيادات الخارجية
Medical Secretarial,,Outpatient Department,قسم العيادات الخارجية
Outpatient Reception,,Outpatient Department,قسم العيادات الخارجية
Patient Affairs Director,,Patient Affairs Department,قسم علاقات المرضى
Secretary,,Patient Affairs Department,قسم علاقات المرضى
Patient Experience Director,,Patient Experience Department,قسم تجربة المريض
Patient Experience Staff,,Patient Experience Department,قسم تجربة المريض
Circumcision,,Pediatric Department,قسم الأطفال
General Pediatrics,,Pediatric Department,قسم الأطفال
NICU,,Pediatric Department,قسم الأطفال
NICU ISOLATOIN,,Pediatric Department,قسم الأطفال
NURSERY,,Pediatric Department,قسم الأطفال
PICU,,Pediatric Department,قسم الأطفال
PICU STEPDOWN,,Pediatric Department,قسم الأطفال
Pediatric Cardiology,,Pediatric Department,قسم الأطفال
Pediatric Endocrinology & Diabetes,,Pediatric Department,قسم الأطفال
Pediatric Gastroenterology,,Pediatric Department,قسم الأطفال
Pediatric General Surgery,,Pediatric Department,قسم الأطفال
Pediatric Hematology,,Pediatric Department,قسم الأطفال
Pediatric Nephrology,,Pediatric Department,قسم الأطفال
Pediatric Neurology,,Pediatric Department,قسم الأطفال
Pediatric Neurosurgery,,Pediatric Department,قسم الأطفال
Pediatric Orthopedic Surgery,,Pediatric Department,قسم الأطفال
Pediatric Otolaryngology (ENT),,Pediatric Department,قسم الأطفال
Pediatric Pulmonology,,Pediatric Department,قسم الأطفال
Pediatric Urology,,Pediatric Department,قسم الأطفال
Pediatric Ward,,Pediatric Department,قسم الأطفال
6th Floor Pharmacy,,Pharmacy Department,قسم الصيدلية
Central Outpatient Pharmacy,,Pharmacy Department,قسم الصيدلية
ER Pharmacy,,Pharmacy Department,قسم الصيدلية
General,,Pharmacy Department,قسم الصيدلية
IV Admixture Unit,,Pharmacy Department,قسم الصيدلية
Inpatient Oncology Pharmacy,,Pharmacy Department,قسم الصيدلية
Inpatient Pharmacy,,Pharmacy Department,قسم الصيدلية
Narcotic Pharmacy,,Pharmacy Department,قسم الصيدلية
Outpatient Oncology Pharmacy,,Pharmacy Department,قسم الصيدلية
Pediatric Pharmacy,,Pharmacy Department,قسم الصيدلية
Pharmacy Warehouse,,Pharmacy Department,قسم الصيدلية
Bone Mineral Density (BMD),,Radiology Department,قسم الأشعة
CT,,Radiology Department,قسم الأشعة
Fluoroscopy,,Radiology Department,قسم الأشعة
General,,Radiology Department,قسم الأشعة
Interventional Radiology (IR),,Radiology Department,قسم الأشعة
MRI,,Radiology Department,قسم الأشعة
Mamography,,Radiology Department,قسم الأشعة
Ultrasound,,Radiology Department,قسم الأشعة
X-Ray,,Radiology Department,قسم الأشعة
Camera Room,,Security Department,قسم الأمن
Security Guards,,Security Department,قسم الأمن
Bariatric,,Surgery Department,قسم الجراحة
ENT,,Surgery Department,قسم الجراحة
General Surgery,,Surgery Department,قسم الجراحة
Neurosurgery,,Surgery Department,قسم الجراحة
Orthopedic Surgery,,Surgery Department,قسم الجراحة
Plastic Surgery,,Surgery Department,قسم الجراحة
Spine Surgery,,Surgery Department,قسم الجراحة
Surgical Ward,,Surgery Department,قسم الجراحة
Thoracic Surgery,,Surgery Department,قسم الجراحة
Urology,,Surgery Department,قسم الجراحة
Vscular Surgery,,Surgery Department,قسم الجراحة
1 Name (EN) Name (AR) Department (EN) Department (AR)
2 Appointment Office Contact Center Department قسم مركز الاتصال
3 Operator Office Contact Center Department قسم مركز الاتصال
4 CCU Critical Care Department قسم العناية المركزة
5 ICU Critical Care Department قسم العناية المركزة
6 ICU Stepdown Critical Care Department قسم العناية المركزة
7 Dermatology Dermatology Department قسم الجلدية
8 Admission Office Emergency Administrative Department إدارة الطوارئ
9 Emergency Coordination Emergency Administrative Department إدارة الطوارئ
10 Emergency Management Emergency Administrative Department إدارة الطوارئ
11 Emergency Reception Emergency Administrative Department إدارة الطوارئ
12 Administrative Assistant Executive Administration الإدارة التنفيذية
13 Chief Administrative Officer Executive Administration الإدارة التنفيذية
14 Secretary Executive Administration الإدارة التنفيذية
15 Software Programmer Executive Administration الإدارة التنفيذية
16 Electrical Maintenance Facility Management & Maintenance Department قسم إدارة المرافق والصيانة
17 General Maintenance Facility Management & Maintenance Department قسم إدارة المرافق والصيانة
18 Mechanical Maintenance Facility Management & Maintenance Department قسم إدارة المرافق والصيانة
19 Safety Facility Management & Maintenance Department قسم إدارة المرافق والصيانة
20 Water Treatment Facility Management & Maintenance Department قسم إدارة المرافق والصيانة
21 Financial Collection & Claims Financial Collection & Claims Department قسم المالية والتحصيل والمطالبات
22 General Information Technology Department قسم تقنية المعلومات
23 Service Desk Information Technology Department قسم تقنية المعلومات
24 Software and Applications Information Technology Department قسم تقنية المعلومات
25 Social Worker Inpatient Department قسم التنويم
26 AKU Internal Medicine Department قسم الباطنية
27 Cardiology Internal Medicine Department قسم الباطنية
28 Endocrinology Internal Medicine Department قسم الباطنية
29 Endoscopy Internal Medicine Department قسم الباطنية
30 Gastroenterology Internal Medicine Department قسم الباطنية
31 Infectious Disease Internal Medicine Department قسم الباطنية
32 Internal Medicine Internal Medicine Department قسم الباطنية
33 Long-Term Acute Care Unit (LTACU) Internal Medicine Department قسم الباطنية
34 Medical Ward Internal Medicine Department قسم الباطنية
35 Nephrology Internal Medicine Department قسم الباطنية
36 Neurology Internal Medicine Department قسم الباطنية
37 Psychiatry Internal Medicine Department قسم الباطنية
38 Pulmonology Internal Medicine Department قسم الباطنية
39 Rheumatology Internal Medicine Department قسم الباطنية
40 Blood Donation & Blood Bank Laboratory Department قسم المختبر
41 Clinical Chemistry Laboratory Department قسم المختبر
42 General Laboratory Department قسم المختبر
43 Hematology Laboratory Department قسم المختبر
44 Histopathology Laboratory Department قسم المختبر
45 Microbiology Laboratory Department قسم المختبر
46 Parasitology Laboratory Department قسم المختبر
47 Phlepotomy Laboratory Department قسم المختبر
48 Receiving ِArea Laboratory Department قسم المختبر
49 Send Out Laboratory Department قسم المختبر
50 Serology & Molecular Laboratory Department قسم المختبر
51 Special Chemistry Laboratory Department قسم المختبر
52 General Medical Administration الإدارة الطبية
53 Medical Director Medical Administration الإدارة الطبية
54 Secretary Medical Administration الإدارة الطبية
55 Anesthesia Technology Medical Ancillary Services Department قسم الخدمات الطبية المساندة
56 CSSD Medical Ancillary Services Department قسم الخدمات الطبية المساندة
57 Cardiac Physiology Services Medical Ancillary Services Department قسم الخدمات الطبية المساندة
58 Clinical Nutrition Medical Ancillary Services Department قسم الخدمات الطبية المساندة
59 Dental Services Medical Ancillary Services Department قسم الخدمات الطبية المساندة
60 Director of Ancillary Medical Services Medical Ancillary Services Department قسم الخدمات الطبية المساندة
61 Emergency Medical Services Medical Ancillary Services Department قسم الخدمات الطبية المساندة
62 General Medical Ancillary Services Department قسم الخدمات الطبية المساندة
63 Health Education Medical Ancillary Services Department قسم الخدمات الطبية المساندة
64 Neurophysiology Services Medical Ancillary Services Department قسم الخدمات الطبية المساندة
65 Optometrist Services Medical Ancillary Services Department قسم الخدمات الطبية المساندة
66 Physical Therapy Medical Ancillary Services Department قسم الخدمات الطبية المساندة
67 Respiratory Therapy Medical Ancillary Services Department قسم الخدمات الطبية المساندة
68 Secretary Medical Ancillary Services Department قسم الخدمات الطبية المساندة
69 Swallowing Speech and Hearing Unit Medical Ancillary Services Department قسم الخدمات الطبية المساندة
70 Emergency Medical Approvals Department قسم الموافقات الطبية
71 General Medical Approvals Department قسم الموافقات الطبية
72 Inpatient Medical Approvals Department قسم الموافقات الطبية
73 Outpatient Medical Approvals Department قسم الموافقات الطبية
74 AKU Nursing Department قسم التمريض
75 Anesthesia Nursing Department قسم التمريض
76 Cathlab Nursing Department قسم التمريض
77 Emergency Nursing Department قسم التمريض
78 Endoscopy Nursing Department قسم التمريض
79 ICU/CCU Nursing Department قسم التمريض
80 ICU/CCU Stepdown Nursing Department قسم التمريض
81 L&D Nursing Department قسم التمريض
82 LTACU Nursing Department قسم التمريض
83 Main OR Nursing Department قسم التمريض
84 Management Nursing Department قسم التمريض
85 Medical Ward Nursing Department قسم التمريض
86 NICU Nursing Department قسم التمريض
87 NICU ISOLATOIN Nursing Department قسم التمريض
88 Nursery Nursing Department قسم التمريض
89 OB/OR Nursing Department قسم التمريض
90 OPD 5 Nursing Department قسم التمريض
91 OPD 6 Nursing Department قسم التمريض
92 OPD 7 Nursing Department قسم التمريض
93 OPD1 Nursing Department قسم التمريض
94 OPD2 Nursing Department قسم التمريض
95 OPD3 Nursing Department قسم التمريض
96 OPD4 Dental Nursing Department قسم التمريض
97 OPD4 Ophtha Nursing Department قسم التمريض
98 Obgyne Ward Nursing Department قسم التمريض
99 Oncology Nursing Department قسم التمريض
100 Orthopedic Cast Technician Nursing Department قسم التمريض
101 Outpatient Nursing Department قسم التمريض
102 PICU Nursing Department قسم التمريض
103 PICU Stepdown Nursing Department قسم التمريض
104 Pediatric Ward Nursing Department قسم التمريض
105 Recovery Nursing Department قسم التمريض
106 Surgical Ward Nursing Department قسم التمريض
107 Hematology ONCOLOGY Department قسم الأورام
108 Clinics Obstetrics & Gynecology Department قسم النساء والولادة
109 Labor & Delivery (L&D) / Obstetrics Operating Rooms Obstetrics & Gynecology Department قسم النساء والولادة
110 Wards Obstetrics & Gynecology Department قسم النساء والولادة
111 Coordination Outpatient Department قسم العيادات الخارجية
112 Management Outpatient Department قسم العيادات الخارجية
113 Medical Reports Outpatient Department قسم العيادات الخارجية
114 Medical Secretarial Outpatient Department قسم العيادات الخارجية
115 Outpatient Reception Outpatient Department قسم العيادات الخارجية
116 Patient Affairs Director Patient Affairs Department قسم علاقات المرضى
117 Secretary Patient Affairs Department قسم علاقات المرضى
118 Patient Experience Director Patient Experience Department قسم تجربة المريض
119 Patient Experience Staff Patient Experience Department قسم تجربة المريض
120 Circumcision Pediatric Department قسم الأطفال
121 General Pediatrics Pediatric Department قسم الأطفال
122 NICU Pediatric Department قسم الأطفال
123 NICU ISOLATOIN Pediatric Department قسم الأطفال
124 NURSERY Pediatric Department قسم الأطفال
125 PICU Pediatric Department قسم الأطفال
126 PICU STEPDOWN Pediatric Department قسم الأطفال
127 Pediatric Cardiology Pediatric Department قسم الأطفال
128 Pediatric Endocrinology & Diabetes Pediatric Department قسم الأطفال
129 Pediatric Gastroenterology Pediatric Department قسم الأطفال
130 Pediatric General Surgery Pediatric Department قسم الأطفال
131 Pediatric Hematology Pediatric Department قسم الأطفال
132 Pediatric Nephrology Pediatric Department قسم الأطفال
133 Pediatric Neurology Pediatric Department قسم الأطفال
134 Pediatric Neurosurgery Pediatric Department قسم الأطفال
135 Pediatric Orthopedic Surgery Pediatric Department قسم الأطفال
136 Pediatric Otolaryngology (ENT) Pediatric Department قسم الأطفال
137 Pediatric Pulmonology Pediatric Department قسم الأطفال
138 Pediatric Urology Pediatric Department قسم الأطفال
139 Pediatric Ward Pediatric Department قسم الأطفال
140 6th Floor Pharmacy Pharmacy Department قسم الصيدلية
141 Central Outpatient Pharmacy Pharmacy Department قسم الصيدلية
142 ER Pharmacy Pharmacy Department قسم الصيدلية
143 General Pharmacy Department قسم الصيدلية
144 IV Admixture Unit Pharmacy Department قسم الصيدلية
145 Inpatient Oncology Pharmacy Pharmacy Department قسم الصيدلية
146 Inpatient Pharmacy Pharmacy Department قسم الصيدلية
147 Narcotic Pharmacy Pharmacy Department قسم الصيدلية
148 Outpatient Oncology Pharmacy Pharmacy Department قسم الصيدلية
149 Pediatric Pharmacy Pharmacy Department قسم الصيدلية
150 Pharmacy Warehouse Pharmacy Department قسم الصيدلية
151 Bone Mineral Density (BMD) Radiology Department قسم الأشعة
152 CT Radiology Department قسم الأشعة
153 Fluoroscopy Radiology Department قسم الأشعة
154 General Radiology Department قسم الأشعة
155 Interventional Radiology (IR) Radiology Department قسم الأشعة
156 MRI Radiology Department قسم الأشعة
157 Mamography Radiology Department قسم الأشعة
158 Ultrasound Radiology Department قسم الأشعة
159 X-Ray Radiology Department قسم الأشعة
160 Camera Room Security Department قسم الأمن
161 Security Guards Security Department قسم الأمن
162 Bariatric Surgery Department قسم الجراحة
163 ENT Surgery Department قسم الجراحة
164 General Surgery Surgery Department قسم الجراحة
165 Neurosurgery Surgery Department قسم الجراحة
166 Orthopedic Surgery Surgery Department قسم الجراحة
167 Plastic Surgery Surgery Department قسم الجراحة
168 Spine Surgery Surgery Department قسم الجراحة
169 Surgical Ward Surgery Department قسم الجراحة
170 Thoracic Surgery Surgery Department قسم الجراحة
171 Urology Surgery Department قسم الجراحة
172 Vscular Surgery Surgery Department قسم الجراحة

384
exports/status_changes.html Normal file
View File

@ -0,0 +1,384 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Status Change Notification Templates</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f4f6; padding: 40px 10px;">
<tr>
<td align="center">
<!-- ========================================================================== -->
<!-- CARD 1: OBSERVATION ASSIGNED -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Observation Assigned
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى السيد / أحمد محمد</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بتعيين ملاحظة لكم للمراجعة واتخاذ الإجراء اللاحق، بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">18/يوليو/2026 09:00 ص</a> برمز التتبع: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0034</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للإطلاع على التفاصيل، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الملاحظة)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو مراجعة التفاصيل وتحديث الحالة خلال 24 ساعة، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج التحديث)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Mr. Ahmed Mohammed</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that an observation has been assigned to you for review and action, dated: <a href="#" style="color: #0056b3; text-decoration: underline;">18/July/2026 09:00 AM</a> with tracking code: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0034</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view the details, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please review the details and update the status within 24 hours, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Update Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 2: OBSERVATION RESOLVED -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Observation Resolved
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 60px;">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى السيد / خالد العنزي</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود إبلاغكم بأنه قد تم <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">حل</span> الملاحظة المسندة إليكم بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">19/يوليو/2026 11:30 ص</a> برمز التتبع: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0041</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع على تفاصيل الحل، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الملاحظة)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو مراجعة تفاصيل الحل والإفادة في حال وجود أي ملاحظات، وللرد إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الرد)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Mr. Khalid Al-Anazi</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to inform you that the observation assigned to you has been <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">resolved</span>, dated: <a href="#" style="color: #0056b3; text-decoration: underline;">19/July/2026 11:30 AM</a> with tracking code: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202607-0041</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view the resolution details, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please review the resolution details and provide feedback if any, and to reply, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Reply Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
<!-- ========================================================================== -->
<!-- CARD 3: OBSERVATION MONTHLY FOLLOW-UP -->
<!-- ========================================================================== -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; margin-bottom: 15px;">
<tr>
<td align="center" style="font-size: 18px; font-weight: bold; color: #4b5563; text-transform: uppercase; letter-spacing: 1px;">
▼ Observation Monthly Follow-Up
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px; background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
<tr>
<td style="padding: 40px 30px;">
<!-- ARABIC SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="rtl" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">إلى قسم الجودة</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">تحية طيبة</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نود تذكيركم بمتابعة <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">المراجعة الشهرية</span> للملاحظة التي تم حلها بتاريخ: <a href="#" style="color: #0056b3; text-decoration: underline;">19/يونيو/2026</a> برمز التتبع: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202606-0019</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
للاطلاع على التفاصيل والتحقق من استمرار فعالية الإجراءات، يمكنكم النقر على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج الملاحظة)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
نرجو تحديث الملاحظة بملاحظات المتابعة، وللإفادة إضغط على
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(نموذج التحديث)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">للإستفسارات أو لمزيد من المعلومات، على التحويلة: 1379</p>
<p style="margin: 0;">رقم التواصل: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">رابط</a>
</td>
</tr>
</table>
<p style="margin: 0 0 30px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
نحن نقدر التزامكم، مع تحيات إدارة قسم علاقات المرضى وتجربة المريض
</p>
</td>
</tr>
</table>
<hr style="border: 0; border-top: 1px dashed #d1d5db; margin: 20px 0 35px 0;" />
<!-- ENGLISH SECTION -->
<table border="0" cellpadding="0" cellspacing="0" width="100%" dir="ltr" style="text-align: center;">
<tr>
<td>
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: bold; color: #000000;">To: Quality Department</h1>
<h2 style="margin: 0 0 25px 0; font-size: 20px; font-weight: 600; color: #374151;">Greetings,</h2>
<p style="margin: 0 0 15px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
We would like to remind you about the <span style="background-color: #ffff00; padding: 0 4px; color: #000000;">monthly follow-up review</span> for the observation resolved on: <a href="#" style="color: #0056b3; text-decoration: underline;">19/June/2026</a> with tracking code: <a href="#" style="color: #0056b3; text-decoration: underline;">OBS-202606-0019</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
To view details and verify the corrective actions remain effective, you can click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Observation Form)</a>
</p>
<p style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
Please update the observation with follow-up notes, and to provide feedback, click on:
</p>
<p style="margin: 0 0 25px 0;">
<a href="#" style="color: #dc2626; font-weight: bold; font-size: 18px; text-decoration: underline;">(Update Form)</a>
</p>
<div style="margin: 0 0 25px 0; font-size: 16px; line-height: 1.6; color: #111827; font-weight: 600;">
<p style="margin: 0 0 4px 0;">For inquiries or more information, via Extension: 1379</p>
<p style="margin: 0;">Contact Number: <a href="tel:0537695000" style="color: #0056b3; text-decoration: underline;">0537695000</a></p>
</div>
<table border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto 35px auto;">
<tr>
<td align="center" bgcolor="#008cc1" style="border-radius: 2px;">
<a href="#" target="_blank" style="padding: 12px 30px; display: inline-block; font-size: 16px; font-weight: bold; color: #004b69; text-decoration: none;">Link</a>
</td>
</tr>
</table>
<p style="margin: 0 0 10px 0; font-size: 16px; color: #1f2937; font-weight: 600;">
We appreciate your commitment.<br />Regards, Patient Relations and Experience Management
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td bgcolor="#f9fafb" style="padding: 20px 30px; border-top: 1px solid #f3f4f6; border-radius: 0 0 4px 4px; text-align: center; font-size: 12px; color: #6b7280; font-weight: 500;">
جميع الحقوق محفوظة &copy; Al Hammadi Hospitals Group
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More