Organization Management — Clinics, Departments and Locations
Audiences: clinical-buyer, developer, internal
After a clinic is provisioned by the tenant provisioner, all day-to-day configuration of its structure — departments, locations, healthcare services, operating hours, branding, and AI-model settings — is managed through the
organizationdomain in Dudoxx HMS.
Business Purpose
Clinic administrators need to configure their organization without engineering support: add a radiology department, open a second location, upload a logo, set their AI diagnostic model preference, or edit emergency contact procedures. The organization management domain handles all post-provisioning configuration. It deliberately cannot create organizations (that is the tenant provisioner's exclusive responsibility) — it can only read, update, and enrich them. This separation prevents accidental partial-provisioning of FHIR partitions or MinIO buckets through the regular REST API.
Audiences
- Investor: Multi-location and multi-department support allows Dudoxx to address group practices and hospital networks, not just single-physician clinics — a significant market expansion.
- Clinical buyer (doctor/nurse/receptionist): Clinic admins configure departments (e.g. Cardiology, Dermatology), assign practitioners to locations, set opening hours, and upload the clinic logo — all through the admin portal without any code deployment.
- Developer/partner: Organization data is aggregated from two sources: the Prisma
Organizationmodel (settings, branding, config) and the FHIROrganizationresource (clinical references).OrganizationsServicemerges both. Never callPOST /organizationsfor creation — usePOST /super-admin/tenants. - Internal (ops/support): Organization settings changes are audited via
SettingsAuditService. Theorganizations-ddxllmcontroller exposes an AI-context descriptor endpoint consumed by TUCAN tools for org-aware clinical suggestions.
Architecture
organization/
├── organizations/ Core org CRUD + settings + FHIR aggregation
│ ├── organizations.service.ts Reads Prisma + FHIR; delegates creation to TenantService
│ ├── organizations.controller.ts GET/PATCH /organizations
│ ├── organizations-settings.service.ts Per-org settings CRUD
│ ├── organizations-settings.controller.ts
│ ├── settings-audit.service.ts Tracks settings changes to audit_trail
│ ├── organizations-logo.service.ts Logo upload → MinIO {slug}-branding bucket
│ ├── organizations-ddxllm.controller.ts AI context descriptor for TUCAN
│ └── organizations.tokens.ts ORGANIZATION_FACADE DI token
│
├── departments/ Department CRUD (per-organization)
│ ├── departments.service.ts
│ └── departments.controller.ts
│
├── locations/ Physical/virtual locations CRUD
│ ├── locations.service.ts
│ └── locations.controller.ts
│
├── healthcare-services/ HealthcareService definitions (FHIR HealthcareService)
│ └── healthcare-services.service.ts
│
├── settings/ Global org settings schema + validation
├── super-admin/ SUPER_ADMIN-only ops (tenant CRUD, stats, platform settings)
├── inventory/ Clinic inventory management
└── hr/ Human resources (employees, contracts, timesheets, leave)
OrganizationsService aggregates data from three sources:
- Prisma (
ddx_api_mainOrganizationmodel) — app-specific settings, branding, timezone, locale - FHIR (
ORGANIZATION_FACADE/FHIR_CLIENT) — FHIROrganizationresource with clinical references - Computed stats — aggregated member counts, appointment stats via
OrganizationStatsResponseDto
Organization creation is exclusively TenantService.createTenant() via POST /super-admin/tenants. The OrganizationsService source code documents this explicitly: "NOTE: Organization creation has been REMOVED from this service." (organizations.service.ts:59-60).
Tech Stack & Choices
| Concern | Choice | Rationale |
|---|---|---|
| Prisma model | ddx_api_main Organization | Settings, branding, slug, timezone; organizationId is the multi-tenant FK across all models |
| FHIR aggregation | ORGANIZATION_FACADE + FHIR_CLIENT tokens | ResourceFacade<OrganizationResource> for FHIR writes; IFhirClient for reads |
| Logo storage | MinIO {slug}-branding bucket via OrganizationsLogoService | Consistent bucket strategy; presigned upload URL pattern |
| Settings audit | SettingsAuditService → audit_trail in ddx_api_log | Every settings change tracked with oldValue + newValue |
| AI context | organizations-ddxllm.controller.ts | Exposes OrganizationDescriptor for TUCAN tools needing org-level clinical context |
| Departments | Prisma Department model (separate table) | One-to-many per org; practitioners are assigned to departments |
| Locations | Prisma Location model + FHIR Location resource | Physical/virtual clinic addresses; used by appointment booking for location-based slots |
Data Flow
Org admin updates settings:
- Admin PATCHes
/organizations/:id/settingswithUpdateOrgSettingsRequestDto. OrganizationsSettingsControllerdelegates toOrganizationsSettingsService.update().SettingsAuditService.record()reads old settings, writes diff toaudit_trail.OrganizationsSettingsServicecallsprisma.organizationSettings.upsert().AuditService.logAudit()captures the change inddx_api_log.
Org admin uploads logo:
- Admin POSTs multipart to
/organizations/:id/logo. OrganizationsLogoServicecallsStorageService.upload()→ MinIO{slug}-brandingbucket.- Returns presigned URL;
Organization.logoUrlupdated in Prisma.
TUCAN reads org context:
organizations-ddxllm.controller.tsreturnsOrganizationDescriptorJSON (AI-optimized representation of the org's departments, services, and settings).- TUCAN tools inject this descriptor as clinical context for diagnosis suggestions.
Dept/location CRUD:
POST /departments→DepartmentsService.create()→prisma.department.create()scoped byorganizationId.POST /locations→LocationsService.create()→prisma.location.create()+ optional FHIRLocationresource.
Implicated Code
ddx-api/src/organization/organizations/organizations.service.ts:41—OrganizationsService; aggregates Prisma + FHIR data; note at:59-60documents that org creation was removed from this service (usePOST /super-admin/tenants)ddx-api/src/organization/organizations/settings-audit.service.ts:64—SettingsAuditService; diff-based audit of settings changes toaudit_trailddx-api/src/organization/departments/departments.service.ts:39—DepartmentsService; per-org department CRUD scoped byorganizationIdddx-api/src/organization/locations/locations.service.ts:34—LocationsService; location CRUD with optional FHIRLocationresource syncddx-api/src/organization/organizations/organizations-settings.service.ts—OrganizationsSettingsService; settings CRUD with upsert patternddx-api/src/organization/organizations/organizations-logo.service.ts—OrganizationsLogoService; MinIO branding bucket uploadddx-api/src/organization/organizations/organizations-ddxllm.controller.ts— AI context descriptor endpoint for TUCANddx-api/src/organization/healthcare-services/healthcare-services.service.ts—HealthcareServicesService; FHIRHealthcareServiceresources
Operational Notes
- Never use
POST /organizationsto create: This endpoint exists for reads only post-migration. All creation goes throughPOST /super-admin/tenants. Violating this leaves the tenant without a FHIR partition and without MinIO buckets. This is listed as a forbidden pattern inddx-api/CLAUDE.md. - Slug is the immutable routing key: Once a tenant slug is set, changing it requires updating MinIO bucket names, FHIR
X-Clinic-IDheader routing, and all cached references. Treat slugs as immutable post-provisioning. - Settings vs. OrganizationSettings: There are two settings tables —
Organization(top-level fields liketimezone,defaultLocale,primaryColor) andOrganizationSettings(a separate joined model for extended config).OrganizationsSettingsServicemanages the latter. - AI/LLM model override:
organizations-ddxllm.controller.tsreturns an org-level AI model preference that TUCAN tools respect. This allows per-clinic LLM model selection without code changes. - FHIR Organization resource: Automatically created by the tenant provisioner at onboarding.
OrganizationsServicereads it viaFHIR_CLIENTto assembleOrganizationSummaryResponseDto. If HAPI FHIR is unavailable, the summary endpoint degrades gracefully (Prisma data returned, FHIR stats empty).
Related Topics
- Tenant Provisioner — the only correct way to create a new organization; provisions FHIR partition + MinIO buckets
- User Management — users are always scoped to an organization; org must exist before staff can be created
- FHIR Partitions & Tenancy — FHIR
Organizationresource is created during provisioning and read byOrganizationsService