11-registration-and-onboardingwave: W6filled4 citations

HR and Inventory — Staff and Equipment Management

Audiences: clinical-buyer, developer

HR and Inventory — Staff and Equipment Management

Dudoxx HMS embeds a full German-labor-law-compliant HR roster and a FHIR-backed medical equipment catalog inside the clinic platform — so clinic admins manage staff contracts, timesheets, leave, and device stock without a separate HR or inventory system.

Business Purpose

Multi-practitioner clinics need to track who is on staff, when they work, and what equipment they have — all within the same tenant boundary as their clinical records. The hr module provides an employee lifecycle system (create staff record → issue contract → clock in/out → request leave → track certifications and skills) linked directly to the User and FHIR Practitioner identities. The inventory module handles the medical device catalog (DeviceDefinition, Device) through FHIR while keeping real-time stock levels (StockItem, StockTransaction, low-stock alerts) in Prisma for fast query performance.

Both modules ship in v6.1.0 and are sold as part of the Clinic Operations bundle. They eliminate the typical clinic workflow of maintaining staff rosters in spreadsheets and equipment lists in a separate SaaS tool.

Audiences

  • Investor: Expands platform stickiness beyond clinical data — HR and inventory retention is much higher than appointment-only tools. Eliminates integration cost with third-party HR/inventory SaaS.
  • Clinical buyer (doctor/nurse/receptionist): Clinic admins manage employee contracts and schedules; nurses submit leave requests; all staff can see their own HR record. Equipment admins track device stock, receive low-stock alerts, and issue supply requests.
  • Developer/partner: Two standalone NestJS sub-modules — HrModule and InventoryModule — each with clean controller→service layering. Inventory uses a hybrid FHIR+Prisma storage pattern with per-resource ResourceFacade DI tokens (DEVICE_FACADE, DEVICE_DEFINITION_FACADE).
  • Internal (ops/support): German labor law compliance (Arbeitsrecht) in contracts; MDR (Medical Device Regulation) compliance for device definitions. Multi-tenant isolation via X-Clinic-ID; full audit trail for all mutations.

Architecture

HR Module (src/organization/hr/)

HrModule owns:

Controller / ServiceResponsibility
EmployeesController / EmployeesServiceStaff records (EMP-YYYY-NNNN auto-numbers)
ContractsController / ContractsServiceEmployment contracts (CTR-YYYY-NNNN, German law)
TimesheetsController / TimesheetsServiceClock in/out, hours, approval workflow. Status: DRAFTSUBMITTEDAPPROVED/REJECTEDLOCKED
LeaveController / LeaveServiceLeave requests, balances, approval flow
SkillsController / SkillsServiceStaff skills with proficiency levels
CertificationsController / CertificationsServiceProfessional certifications + expiry tracking

Employee records are linked to both a User row (for login) and a FHIR Practitioner resource (for clinical references). Payroll/salary is explicitly excluded — it is handled by an external system integration.

Inventory Module (src/organization/inventory/)

InventoryModule owns:

Controller / ServiceResponsibility
DeviceDefinitionsController / DeviceDefinitionsServiceFHIR DeviceDefinition catalog
DevicesController / DevicesServiceFHIR Device instances
StockController / StockServicePrisma StockItem + transactions + alerts
SupplyControllerFHIR SupplyRequest/SupplyDelivery (stub)

The hybrid storage design: FHIR holds regulatory-grade device definitions and individual device instances (required for MDR); Prisma holds real-time stock counts and transaction log (required for query speed). ResourceFacade<DeviceResource> and ResourceFacade<DeviceDefinitionResource> bridge the two layers using the canonical IFhirClient interface.

Tech Stack & Choices

ConcernChoiceRationale
HR persistenceprisma-main (ddx_api_main)HR records need joins to User, Organization; no reason to split
Auto-numberingService-layer sequence generationEMP-YYYY-NNNN / CTR-YYYY-NNNN format; avoids DB sequences for portability
Device persistenceFHIR R4 (DeviceDefinition, Device)MDR compliance — medical devices must live in the FHIR repository
Stock persistenceprisma-main (StockItem, StockTransaction)Real-time queries; FHIR is too slow for per-request stock checks
Device FHIR bridgeResourceFacade (canonical pattern)Consistent with all other FHIR-backed resources; DEVICE_FACADE DI token
Timesheet approvalTimesheetStatus FSM (DRAFT→SUBMITTED→APPROVED/REJECTED→LOCKED)Prevents retroactive edits once locked
ComplianceGerman Arbeitsrecht (contracts); EU MDR (devices)Clinic base is DACH region

Data Flow

Onboarding a new employee

  1. Admin creates User account via POST /admin/users (user-management module).
  2. Admin opens HR panel → POST /hr/employees with userId + organizationId.
  3. EmployeesService.create() verifies user belongs to the org, generates employeeNumber (EMP-YYYY-NNNN), persists Employee row linked to userId.
  4. Admin issues contract: POST /hr/contractsContractsService creates contract with auto-number (CTR-YYYY-NNNN).
  5. Employee clocks in via POST /hr/timesheets; status starts as DRAFT; admin approves → APPROVED → LOCKED.

Adding a medical device

  1. Admin creates device type: POST /inventory/device-definitionsDeviceDefinitionsService calls DEVICE_DEFINITION_FACADE.create()FHIR_CLIENT stores DeviceDefinition in HAPI.
  2. Admin instantiates a device: POST /inventory/devicesDevicesService stores Device in FHIR.
  3. Admin sets initial stock: POST /inventory/stockStockService writes StockItem to Prisma.
  4. Stock moves generate StockTransaction rows; StockService checks threshold and can raise a low-stock alert row.

Implicated Code

  • ddx-api/src/organization/hr/hr.module.ts:1HrModule; 6 controllers + 6 services; explicitly excludes payroll
  • ddx-api/src/organization/hr/employees/employees.service.ts:32EmployeesService; create() at :45; auto-number generation; user/org membership check
  • ddx-api/src/organization/hr/employees/employees.controller.ts:60EmployeesController; role guard (@RequireRole); @ApiTags('HR - Employees')
  • ddx-api/src/organization/hr/timesheets/timesheets.service.ts — timesheet status FSM; approval workflow; DRAFT/SUBMITTED/APPROVED/REJECTED/LOCKED states
  • ddx-api/src/organization/hr/leave/leave.service.ts — leave balance tracking; approval flow
  • ddx-api/src/organization/inventory/inventory.module.ts:1InventoryModule; DEVICE_FACADE and DEVICE_DEFINITION_FACADE factory providers at :90–115
  • ddx-api/src/organization/inventory/devices/devices.service.tsDevicesService; delegates to DEVICE_FACADE (ResourceFacade)
  • ddx-api/src/organization/inventory/stock/stock.service.tsStockService; real-time stock levels and low-stock alerts in Prisma

Operational Notes

  • Payroll excluded by design: The HrModule comment explicitly states "EXCLUDES: Payroll/salary (external system integration)" — do not add salary fields here without a CR.
  • Supply management is a stub: SupplyController currently returns empty responses pending the FHIR SupplyRequest/SupplyDelivery integration. Do not use for production supply ordering yet.
  • Device MDR compliance: DeviceDefinition resources must be stored in FHIR, never flattened to Prisma. The DEVICE_FACADE DI token is the only correct way to create/update devices.
  • FHIR_CLIENT is globally provided: InventoryModule does not import FhirInfrastructureModule for FHIR_CLIENT — the global DataContextModule in AppModule provides it. No feature module should re-import it.
  • Validation commands: pnpm tsc:check (ddx-api); pnpm prisma:generate:all if HR or inventory Prisma models change.
  • User Management — employees are linked to User accounts; User must exist before Employee record can be created
  • FHIR ResourceFacadeDeviceDefinition and Device are FHIR R4 resources; ResourceFacade is the canonical storage primitive
  • Organization Management — all HR and inventory records are scoped by organizationId