Dentolize · Canary Build Fix (DHS Integration) Walkthrough
On this pageBusiness viewTechnical viewThe DHSUser type

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.

ScreenWhat it doesWhere it lives
DHS Integration Settings3-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 InsuranceLooks 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 EligibilityConfirms 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 SubmissionMulti-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 DetailRead-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 UpdateLets 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.