HR and Inventory — Staff and Equipment Management
Audiences: clinical-buyer, developer
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 —
HrModuleandInventoryModule— each with clean controller→service layering. Inventory uses a hybrid FHIR+Prisma storage pattern with per-resourceResourceFacadeDI 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 / Service | Responsibility |
|---|---|
EmployeesController / EmployeesService | Staff records (EMP-YYYY-NNNN auto-numbers) |
ContractsController / ContractsService | Employment contracts (CTR-YYYY-NNNN, German law) |
TimesheetsController / TimesheetsService | Clock in/out, hours, approval workflow. Status: DRAFT → SUBMITTED → APPROVED/REJECTED → LOCKED |
LeaveController / LeaveService | Leave requests, balances, approval flow |
SkillsController / SkillsService | Staff skills with proficiency levels |
CertificationsController / CertificationsService | Professional 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 / Service | Responsibility |
|---|---|
DeviceDefinitionsController / DeviceDefinitionsService | FHIR DeviceDefinition catalog |
DevicesController / DevicesService | FHIR Device instances |
StockController / StockService | Prisma StockItem + transactions + alerts |
SupplyController | FHIR 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
| Concern | Choice | Rationale |
|---|---|---|
| HR persistence | prisma-main (ddx_api_main) | HR records need joins to User, Organization; no reason to split |
| Auto-numbering | Service-layer sequence generation | EMP-YYYY-NNNN / CTR-YYYY-NNNN format; avoids DB sequences for portability |
| Device persistence | FHIR R4 (DeviceDefinition, Device) | MDR compliance — medical devices must live in the FHIR repository |
| Stock persistence | prisma-main (StockItem, StockTransaction) | Real-time queries; FHIR is too slow for per-request stock checks |
| Device FHIR bridge | ResourceFacade (canonical pattern) | Consistent with all other FHIR-backed resources; DEVICE_FACADE DI token |
| Timesheet approval | TimesheetStatus FSM (DRAFT→SUBMITTED→APPROVED/REJECTED→LOCKED) | Prevents retroactive edits once locked |
| Compliance | German Arbeitsrecht (contracts); EU MDR (devices) | Clinic base is DACH region |
Data Flow
Onboarding a new employee
- Admin creates
Useraccount viaPOST /admin/users(user-management module). - Admin opens HR panel →
POST /hr/employeeswithuserId+organizationId. EmployeesService.create()verifies user belongs to the org, generatesemployeeNumber(EMP-YYYY-NNNN), persistsEmployeerow linked touserId.- Admin issues contract:
POST /hr/contracts→ContractsServicecreates contract with auto-number (CTR-YYYY-NNNN). - Employee clocks in via
POST /hr/timesheets; status starts as DRAFT; admin approves → APPROVED → LOCKED.
Adding a medical device
- Admin creates device type:
POST /inventory/device-definitions→DeviceDefinitionsServicecallsDEVICE_DEFINITION_FACADE.create()→FHIR_CLIENTstoresDeviceDefinitionin HAPI. - Admin instantiates a device:
POST /inventory/devices→DevicesServicestoresDevicein FHIR. - Admin sets initial stock:
POST /inventory/stock→StockServicewritesStockItemto Prisma. - Stock moves generate
StockTransactionrows;StockServicechecks threshold and can raise a low-stock alert row.
Implicated Code
ddx-api/src/organization/hr/hr.module.ts:1—HrModule; 6 controllers + 6 services; explicitly excludes payrollddx-api/src/organization/hr/employees/employees.service.ts:32—EmployeesService;create()at:45; auto-number generation; user/org membership checkddx-api/src/organization/hr/employees/employees.controller.ts:60—EmployeesController; 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 statesddx-api/src/organization/hr/leave/leave.service.ts— leave balance tracking; approval flowddx-api/src/organization/inventory/inventory.module.ts:1—InventoryModule;DEVICE_FACADEandDEVICE_DEFINITION_FACADEfactory providers at:90–115ddx-api/src/organization/inventory/devices/devices.service.ts—DevicesService; delegates toDEVICE_FACADE(ResourceFacade)ddx-api/src/organization/inventory/stock/stock.service.ts—StockService; real-time stock levels and low-stock alerts in Prisma
Operational Notes
- Payroll excluded by design: The
HrModulecomment explicitly states "EXCLUDES: Payroll/salary (external system integration)" — do not add salary fields here without a CR. - Supply management is a stub:
SupplyControllercurrently returns empty responses pending the FHIR SupplyRequest/SupplyDelivery integration. Do not use for production supply ordering yet. - Device MDR compliance:
DeviceDefinitionresources must be stored in FHIR, never flattened to Prisma. TheDEVICE_FACADEDI token is the only correct way to create/update devices. - FHIR_CLIENT is globally provided:
InventoryModuledoes not importFhirInfrastructureModuleforFHIR_CLIENT— the globalDataContextModuleinAppModuleprovides it. No feature module should re-import it. - Validation commands:
pnpm tsc:check(ddx-api);pnpm prisma:generate:allif HR or inventory Prisma models change.
Related Topics
- User Management — employees are linked to
Useraccounts;Usermust exist beforeEmployeerecord can be created - FHIR ResourceFacade —
DeviceDefinitionandDeviceare FHIR R4 resources;ResourceFacadeis the canonical storage primitive - Organization Management — all HR and inventory records are scoped by
organizationId