11-registration-and-onboardingwave: W6filled5 citations

User Management — Practitioners, Staff and Accounts

Audiences: developer, internal

User Management — Practitioners, Staff and Accounts

Dudoxx HMS manages all clinical and administrative identities — doctors, nurses, receptionists, patients, and platform administrators — through a unified user-mgmt domain that handles creation, role assignment, FHIR Practitioner synchronization, invitation flows, and lifecycle events within strict tenant boundaries.

Business Purpose

A multi-tenant clinical platform must answer: who can log in, what can they see, and in which clinic? User management solves all three. The user-mgmt domain handles: (1) CRUD for every account type in the system — SUPER_ADMIN to PATIENT; (2) FHIR Practitioner resource creation when a doctor or nurse joins a clinic (so the FHIR layer has the practitioner reference for appointments, observations, and prescriptions); (3) invitation-based onboarding so staff can join clinics without super-admin involvement; (4) bulk operations and statistics for ops dashboards. Patient and doctor registration are not separate subsystems — they converge here into a unified identity model gated by role assignment.

Audiences

  • Investor: A single identity model across all eight roles eliminates the fragmented user silos typical of legacy EMR systems. Combined with RBAC, it supports the granular access control that hospital procurement requires.
  • Clinical buyer (doctor/nurse/receptionist): Doctors exist simultaneously as a Prisma User (with login credentials) and a FHIR Practitioner resource (with clinical reference IDs). Admins manage all staff from one panel without touching the FHIR server directly.
  • Developer/partner: UserManagementService is a facade orchestrating six domain services. All admin user operations go through POST/GET/PATCH /admin/users with X-Clinic-ID header scoping. Patient registration goes through POST /patients (clinical module), not this module directly.
  • Internal (ops/support): Role hierarchy: SUPER_ADMIN > ORG_ADMIN > CLINIC_ADMIN > DOCTOR/NURSE > RECEPTIONIST > ACCOUNTANT > STAFF > PATIENT. Password resets, status changes, and bulk deactivations all flow through UserStatusService.

Architecture

diagram
user-mgmt/
├── users/                     User profile CRUD + avatar upload (MinIO)
│   ├── users.service.ts       Top-level user queries (profile, search)
│   ├── user-avatar.service.ts MinIO avatar upload + presigned URL
│   └── controllers/
│       ├── users-crud.controller.ts         GET/PATCH /users (self-profile)
│       └── users-ddxllm.controller.ts       AI-context user descriptors
│
├── user-management/           Admin user operations (org-scoped)
│   ├── user-management.service.ts   Orchestrator facade (6 domain services)
│   ├── practitioner.descriptor.ts   FHIR Practitioner resource shape
│   └── services/
│       ├── user-crud.service.ts           Admin CRUD, pagination
│       ├── user-role.service.ts           Role assignment + FHIR Practitioner create
│       ├── user-status.service.ts         Status changes, password reset
│       ├── user-bulk-operations.service.ts Bulk deactivate/reassign
│       ├── user-invitation.service.ts     Invite flow (email token)
│       └── user-statistics.service.ts     Activity log, stats
│
├── roles/                     Role definitions (platform-scoped)
├── user-onboarding/           First-login wizard (profile completion)
├── user-settings/             TTS preferences, UI preferences
├── user-preferences/          Per-user notification + locale prefs
└── demo-accounts/             Demo/seed account helpers (dev tenants)

Identity unification: patients and doctors are both User rows in ddx_api_main. What differentiates them is the UserRole assignment (via user_role_assignments join table) and the presence of a FHIR Practitioner or FHIR Patient resource. Patient registration (POST /patients) creates both the User row AND the FHIR Patient resource. Doctor registration creates both the User row AND the FHIR Practitioner resource via UserRoleService.

Tech Stack & Choices

ConcernChoiceRationale
DB storageprisma-main User modelUnified table for all roles; organizationId scoping enforced by TenantIsolationInterceptor
Role managementuser_role_assignments join tableMany-to-many so a user can hold roles in multiple clinics (e.g. DOCTOR at clinic A, ORG_ADMIN at clinic B)
FHIR syncFhirClientService (via FHIR_CLIENT token)Canonical FHIR client; UserRoleService calls it when assigning DOCTOR/NURSE role
Avatar uploadStorageService → MinIO {slug}-attachments bucketPresigned upload URLs; same storage pattern as all other attachments
InvitationEmail token via MailModuleTime-limited invite link; UserInvitationService validates token on acceptance
Bulk opsUserBulkOperationsServiceDeactivate/reassign N users in one call; used by admin dashboards
Onboarding wizarduser-onboarding/ moduleFirst-login profile completion flow; separate module, not part of the core user-management service

Data Flow

Admin creates a doctor:

  1. Admin POSTs AdminCreateUserDto to POST /admin/users with X-Clinic-ID.
  2. TenantIsolationInterceptor validates X-Clinic-ID → injects request.clinicId.
  3. UserManagementService.createUser() delegates to UserCrudService.createUser().
  4. UserCrudService creates the User row in ddx_api_main with hashed password.
  5. UserRoleService.assignRole(DOCTOR) creates user_role_assignment row.
  6. UserRoleService calls FHIR_CLIENT.create('Practitioner', ...) → HAPI FHIR stores Practitioner resource in the clinic's partition.
  7. fhirPractitionerId stored on the User row for downstream FHIR references.
  8. AuditService.log() records the creation event.

Invitation flow:

  1. Admin calls POST /admin/users/invite with email + desired role.
  2. UserInvitationService generates a time-limited token and sends email via MailModule.
  3. Invitee clicks link → POST /admin/users/invite/accept with token + password.
  4. UserInvitationService validates token, calls UserCrudService.createUser() + UserRoleService.assignRole().

Implicated Code

  • ddx-api/src/user-mgmt/user-management/user-management.service.ts:41UserManagementService orchestrator; 6 injected domain services at :44–51; all admin user operations delegated from here
  • ddx-api/src/user-mgmt/user-management/services/user-role.service.ts:52UserRoleService; FHIR Practitioner creation on DOCTOR/NURSE role assignment
  • ddx-api/src/user-mgmt/user-management/services/user-crud.service.ts:44UserCrudService; AdminCreateUserDto, pagination query with QueryUsersDto
  • ddx-api/src/user-mgmt/user-management/services/user-status.service.ts:35UserStatusService; ChangeUserStatusDto, password reset, deactivation
  • ddx-api/src/user-mgmt/user-management/services/user-invitation.service.ts:44UserInvitationService; InviteUserDto, token generation and email dispatch
  • ddx-api/src/user-mgmt/users/users.service.tsUsersService; self-profile queries (non-admin path)
  • ddx-api/src/user-mgmt/roles/roles.service.tsRolesService; platform-scoped role definitions (decorated @PlatformScoped() — no X-Clinic-ID required)
  • ddx-api/src/user-mgmt/user-onboarding/user-onboarding.service.tsUserOnboardingService; first-login wizard completion flow

Operational Notes

  • RBAC bootstrap order: The init seeder phase must run before user creation — it seeds permissions and role_permissions tables via POST /super-admin/permissions/seed. Without this, DynamicPermissionGuard has no data to authorize against.
  • Patient registration drift note: Patients are NOT registered through this module's admin endpoint. They are created via POST /patients in the clinical/patients module, which handles both the User row and the FHIR Patient resource. This module covers practitioners, staff, and administrative accounts.
  • Multi-clinic users: A user can belong to multiple clinics via user_role_assignments. X-Clinic-ID header on each request determines which clinic's data context applies.
  • Password storage: Passwords are bcrypt-hashed. Plain-text passwords never leave UserStatusService. Default seed password is Dudoxx123! (dev only — not for production).
  • Avatar upload: UserAvatarService generates a presigned MinIO URL; the client uploads directly to MinIO. The URL path is stored on the User row, not the binary.
  • Roles are @PlatformScoped(): RolesController bypasses X-Clinic-ID enforcement because role definitions are global, not per-clinic. The @PlatformScoped() decorator is applied at class level.
  • RBAC / Roles — role hierarchy and permission grants; init seeder seeds the permission catalog before user creation
  • Multi-Portal Architecture — each portal (doctor, nurse, receptionist, patient, admin) maps to a user role
  • Organization Management — users are always scoped to an organization; org must exist before users are created
  • Tenant Provisioner — tenant must be provisioned (FHIR partition + MinIO) before FHIR Practitioner resources can be created for new users
    User Management — Practitioners, Staff and Accounts — Dudoxx Docs | Dudoxx