User Creation Flows
How each user role is created, what triggers it, and what side effects follow. All logic lives in novahomecareapi/users/managers.py (creation) and role-specific views.
Summary
| Role | How created | Entry point |
|---|---|---|
ADMIN | Admin creates another admin via portal UI | POST /users/ |
EMPLOYEE | 1) Admin manually creates | POST /users/create-employee/ |
| 2) Admin approves an application | PATCH /applications/{id}/approve | |
CLIENT | 1) Admin manually creates | POST /users/create-client/ |
| 2) Referral pipeline completes → admin approves | ReferralStep.done() → PATCH /users/{id}/approve-client | |
MARKETER | Admin creates via portal UI | POST /users/ with role=MARKETER |
MASTER | Database / management command only | No API flow |
ADMIN
Trigger: An existing admin (or master) posts to POST /users/ with role=ADMIN.
View: UserViewSet.create() → AdministratorSerializer
What happens:
Userrecord created withrole=ADMIN- User is added to the requesting admin's organization
NEW_ACCOUNTnotification sent to the new admin (email + portal)
No profile sub-model — admins have only the base User record.
EMPLOYEE
Path 1 — Admin manually creates an employee
Trigger: Admin fills out the "Add Employee" form in the portal.
View: POST /users/create-employee/ → EmployeeUserSerializer → User.employee.create()
What happens:
Userrecord created withrole=EMPLOYEE,manually_created=TrueEmployeeProfilecreated (manager, position, salary, SOS contact, license info)- A dummy
Applicationrecord is created withstatus=APPROVEDand placeholder data — this satisfies the data model's assumption that every employee has an application FormRequestrecords created for all applicable onboarding forms (based on position and org)NEW_ACCOUNTnotification sent
Path 2 — Application approved
Trigger: Admin reviews a job application and approves it via PATCH /applications/{id}/approve.
View: ApplicationViewSet.approve() → User.employee.create_from_application()
What happens:
- Application fields (name, DOB, SSN, address, phone, signature, license/CPR dates) are copied from the
Applicationto create theUser Userrecord created withrole=EMPLOYEEEmployeeProfilecreated with the submittedmanager,position,salary,external_idfrom the approval formApplication.approve(employee)called — links the Application to the new User and setsstatus=APPROVEDFormRequestrecords created for onboarding forms viaFormRequest.objects.create_for_onboarding()- If position has
is_hcc=True:CompetencyTestRequestrecords created for onboarding tests - Approval email sent to the employee
Key difference from Path 1: No dummy Application — the real submitted application is used.
CLIENT
Path 1 — Admin manually creates a client
Trigger: Admin fills out the "Add Client" form in the portal.
View: POST /users/create-client/ → ClientUserSerializer → User.clients.create()
What happens:
Userrecord created withrole=CLIENT,manually_created=TrueClientProfilecreated withstatus=ACTIVE(immediately active)- Client added to the org
client_typesM2M set (HCC / HHP / both) — this drives the dynamic navFormRequestrecords created for all applicable onboarding forms- If
manual_upload_documents=True: a manual-upload form request is also created - New client notifications sent
Path 2 — Referral converts to client
This is a two-step process: the referral pipeline completes, then an admin approves the resulting client record.
Step 2a — Pipeline completion
Trigger: The final ReferralStep is marked done (ReferralStep.done()), which calls Referral.to_client().
What happens:
User.clients.create_from_referral()called — createsUserwithrole=CLIENT,manually_created=FalseClientProfilecreated withstatus=NEW(not yet active — pending admin approval)- User added to org
- No onboarding forms created yet — held until admin approval
Referral.statusset toPD(Pending for Approval)REFERRAL_COMPLETEDnotification sent to all org admins- Marketer metrics update scheduled (Celery task)
Step 2b — Admin approves the client
Trigger: Admin reviews the pending client and approves via PATCH /users/{id}/approve-client.
What happens:
ClientProfileupdated with case manager details (pre-populated from referral data)Referral.approve()called — sets referralstatus=CL(Closed/Converted)FormRequestrecords now created for onboarding forms- New client notifications sent
- Marketer metrics updated (Celery task)
MARKETER
Trigger: Admin posts to POST /users/ with role=MARKETER.
View: UserViewSet.create() → AdministratorSerializer (same serializer as ADMIN)
What happens:
Userrecord created withrole=MARKETER- User added to the requesting admin's organization
MarketerMetricrecord created — tracksactive_referrals,clients,days_by_referralNEW_ACCOUNTnotification sent to the new marketer
No profile sub-model — marketers have only the base User record plus MarketerMetric.
MASTER
No API flow. Created directly in the database or via Django management commands:
docker compose run --rm api python manage.py createsuperuserMASTER users are platform-level — they can access all organizations. Created once at platform setup, not through the portal UI.
Common Side Effects Reference
| Side effect | ADMIN | EMPLOYEE | CLIENT | MARKETER |
|---|---|---|---|---|
NEW_ACCOUNT notification | ✓ | ✓ | ✓ | ✓ |
EmployeeProfile created | — | ✓ | — | — |
ClientProfile created | — | — | ✓ | — |
Onboarding FormRequest records | — | ✓ | ✓ | — |
CompetencyTestRequest records | — | HCC only | — | — |
Dummy Application created | — | Manual path only | — | — |
MarketerMetric created | — | — | — | ✓ |
Key Files
| File | Role |
|---|---|
users/views.py | UserViewSet — endpoints for all role creation |
users/managers.py | UserManager, EmployeeManager, ClientManager — creation logic |
users/serializers/ | Per-role serializers (AdministratorSerializer, EmployeeUserSerializer, ClientUserSerializer) |
applications/views.py | ApplicationViewSet.approve() — employee creation from application |
referrals/models.py | ReferralStep.done(), Referral.to_client() — client creation from referral |
referrals/tasks.py | Celery task: update_marketer_metrics |
job_forms/managers.py | FormRequest.objects.create_for_onboarding() — assigns onboarding forms |