The DHS Screens
Business view
"DHS" is the Dubai Health Authority, and the DHS integration is Dentolize's link to its insurance clearinghouse — the same category of integration as NPHIES for Saudi clinics, but for UAE clinics billing DHS-regulated insurers. It covers the everyday insurance workflow around a dental visit: is this patient's policy active, what does it cover, and can this specific treatment be pre-approved and later claimed.
The integration is six screens, gated behind a FEATURE_DHS_INTEGRATION flag and a set of permissions (viewDhsIntegration, checkDhsInsurance, checkDhsEligibility, createDhsApproval, createManualDhsApproval, viewDhsApprovals) so clinics that don't use DHS never see any of it.
| Screen | What it does | Where it lives |
|---|---|---|
| DHS Integration Settings | 3-step wizard (client secret → load & map branches → confirm) for a clinic admin to connect the clinic's DHS account. | Settings → Integrations → "DHS Integration" tab |
| Check Insurance | Looks up a patient's actual coverage from DHS (company, policy, class) and lets staff attach the right one to the patient record. | Patient registration form, and the patient profile page |
| Check Eligibility | Confirms a patient's insurance is currently active before treatment, showing an Eligible / Not Eligible tag with a last-checked timestamp. | Patient profile page, and nested inside the Check Insurance review step |
| Approval Submission | Multi-step wizard that packages selected chart procedures (encounter info, diagnosis, services, attachments) and submits them to DHS for pre-authorization. | Patient chart page, in the operations table footer ("Get Approval") |
| Approval Detail | Read-only view of one submitted approval: its status tag, approval number, and the raw request/response payloads DHS returned (useful for support debugging). | Reached from the clinic's DHS Approvals log |
| Manual Update | Lets staff manually mark a pending DHS submission as approved/rejected when the automatic status sync fails or is slow. | Patient chart page, per operation row, while a submission is pending |
None of this behavior changes in this PR. What changes is that these six screens now actually exist in the production bundle a live clinic downloads — see The Build Fix: Prop Injection.
Technical view
Each screen lives in packages/clinic-web-canary/src/features/DHS* and is mounted from clinic-web behind useFeatureFlag('FEATURE_DHS_INTEGRATION') plus a permission check on user.permissions. The permission checks and FEATURE_DHS_INTEGRATION flag are unchanged by this PR; what changed is how each component gets the user object it needs to evaluate those permissions.
Integration Settings
packages/clinic-web-canary/src/features/DHSIntegrationSettings/ — mounted at packages/clinic-web/src/components/dashboard/settings/SettingsIntegrations.js:52 as:
<DHSIntegrationSettings i18next={i18next} useTranslation={useTranslation} user={user} />
Server side: packages/server/src/resolvers/mutations/DHS/saveDHSIntegration.js, rotateDHSClientSecret.js, testDHSConnection.js — the client secret is encrypted at rest (decrypt/encrypt in packages/server/src/utils/encryption.js) and the "test connection" step performs a live POST to ${DHS.AUTH_URL}/api/Login (packages/server/src/resolvers/mutations/DHS/testDHSConnection.js:33).
Check Insurance
packages/clinic-web-canary/src/features/DHSCheckInsurance/DHSCheckInsurance.tsx — mounted from PatientCommonFields.js:734 (registration form) and PatientProfile.js:167 (profile page), both now passing user={user}. Internally it renders CheckInsuranceModal.tsx, which nests ReviewStep.tsx, which conditionally renders DHSCheckEligibility — the deepest prop-injection chain in the PR, three explicit hops (DHSCheckInsurance → CheckInsuranceModal → ReviewStep → DHSCheckEligibility, ReviewStep.tsx:196-203).
Check Eligibility
packages/clinic-web-canary/src/features/DHSCheckEligibility/ — mounted directly from PatientProfile.js:162 and nested inside ReviewStep.tsx as above.
Approval Submission
packages/clinic-web-canary/src/features/DHSApprovalSubmission/ — mounted from ChartTableFooter.js:409-414. Eligibility for (re)submission is computed by canSubmitOperation in packages/clinic-web-canary/src/features/DHS/shared/dhsApprovalStatus.ts:19-20 — true only if an operation has no DHS approval yet, or its prior approval errored without ever getting an approval number. That file is a new canary-owned copy; the same logic must stay in sync by hand with packages/server/src/utils/dhsApprovalStatus.js (authoritative) and packages/clinic-mobile/src/shared/utils/dhsApprovalStatus.js — nothing enforces the three copies agree.
The submission mutation's cache refetch also changed shape slightly in this PR: it now refetches by the Apollo operation name string 'PATIENT_DETAILS' (useDHSApprovalSubmission.tsx:32) instead of importing the PATIENT_DETAILS query object from clinic-mobile's 5,556-line query barrel just to use it as a refetch key. This is lower-risk than it sounds structurally, but it is a silent no-op if the host's query is ever renamed without updating this string — see For Quality.
Approval Detail
packages/clinic-web-canary/src/features/DHSApprovalDetail/DHSApprovalDetail.tsx — routed from clinic-web's DashboardRouter.js at /logs/dhsApproval/:approvalId. Its status tag used to be clinic-web's DHSStatusPopover (a 203-line component with action buttons, a 485-line style module, and a 1,110-line tooth-helper dependency); this PR replaces it with a canary-owned DHSStatusTag.tsx (121 lines) that renders only the read-only tag, because the one call site never passed the props that would have activated the popover's action buttons in the first place (DHSStatusTag.tsx:9-14).
Manual Update
Exported as PendingActions, packages/clinic-web-canary/src/features/DHSManualUpdate/ — mounted from ChartTable.js:355, rendered per operation row.
The DHSUser type
All six features now share one prop-injection contract, defined in packages/clinic-web-canary/src/features/DHS/shared/DHSUser.ts:5-11:
export type DHSUser = Pick<User, "id" | "name" | "isDoctor" | "medicalNumber"> & {
permissions: Record<string, boolean>;
company?: Pick<Company, "country">;
};
permissions isn't a Prisma column — clinic-web derives it in packages/clinic-web/src/shared/utils/getUserPermissions.js and attaches it to the session user before ever handing it to a canary component. Every one of the nine canary component interfaces types user as required DHSUser, so a host call site that forgets to pass it is a tsc build failure inside canary — but see the known gap in The Build Fix: Prop Injection.