Placement Lifecycle¶
The Placement model tracks a worker's assignment to a job, driving Salesforce synchronisation, Business Central integration, screening workflows, employment contract creation, and stakeholder notifications through its lifecycle hooks.
State Machine¶
Covers status-field transitions. Side effects for each transition are detailed in the Hook Reference Table below.
stateDiagram-v2
[*] --> AWAITING: Created
AWAITING --> CONFIRMED: Confirmed
CONFIRMED --> ACTIVE: Activated
ACTIVE --> DISCONTINUED: Discontinued
ACTIVE --> ARCHIVED: Ended or Declined
AWAITING --> ARCHIVED: Declined
Hook Flowchart¶
Covers all hooks across all lifecycle events, with conditions and triggered actions.
flowchart TD
subgraph Placement
AC[AFTER_CREATE]
AU[AFTER_UPDATE]
AC --> H1["on_create_sync_to_salesforce\nSync to SF → screening chain\n→ placement detail in SF"]
AC --> H2["on_create_employment_contract\nCreate EmploymentContract"]
AU --> H3["on_update_sync_to_salesforce\nSync changed sf_sync_fields to SF"]
AU -->|"status changed"| H4["sync_status_to_salesforce\nUpdate SF status field"]
H4 -->|"status == CONFIRMED"| H4a["trigger_bc_object_sync\n→ retrieve_placement_from_sf"]
AU -->|"status == DISCONTINUED\nstatus changed"| H5["send_discontinuation_notification\nPush + email to worker"]
AU -->|"rejected_at changed"| H6["notify_client_on_placement_declined\nNotify company employers"]
AU -->|"item_number changed"| H7["sync_cao_function_to_bc\nPatch CAO function in BC"]
AU -->|"status == ACTIVE\nstatus changed"| H8["notify_client_on_placement_active\nNotify company employers"]
AU -->|"status == ARCHIVED\nstatus changed\nrejected_at is None"| H9["notify_client_on_placement_ended\nNotify company employers"]
end
subgraph EmploymentContract
EC_AC[AFTER_CREATE]
EC_AU[AFTER_UPDATE]
EC_AC --> EC_H1["on_create_sync_to_salesforce\nSF create → set Checked\n→ BC sync → generate docs"]
EC_AU --> EC_H2["on_update_sync_to_salesforce\nSync changed sf_sync_fields to SF"]
end
%% Cross-model: Placement creation triggers EmploymentContract creation
H2 --> EC_AC
Hook Reference Table¶
| Event | Condition | Handler | Side Effects |
|---|---|---|---|
| Placement | |||
AFTER_CREATE |
— | on_create_sync_to_salesforce |
Chains sync_object_to_salesforce (CREATE) → conditionally create_phase_one_envelope + generate_phase_one_documents → create_placement_detail_in_sf. Deferred via transaction.on_commit |
AFTER_CREATE |
— | on_create_employment_contract |
Creates an EmploymentContract with start_date, end_date (+1 year), and hours_per_week copied from the placement (if worker and start_date present) |
AFTER_UPDATE |
— | on_update_sync_to_salesforce |
If any sf_sync_fields (worker_id, company_id, job_id, start_date) changed: UPDATE in SF → retrieve_placement_from_sf, or CREATE if not yet in SF |
AFTER_UPDATE |
status changed |
sync_status_to_salesforce |
Updates msf__Status__c in SF via update_salesforce_fields. When status is CONFIRMED: chains → trigger_bc_object_sync → retrieve_placement_from_sf |
AFTER_UPDATE |
status == DISCONTINUED AND status changed |
send_discontinuation_notification |
Creates in-app AppNotification (with optional FCM push) and sends email to the worker |
AFTER_UPDATE |
rejected_at changed |
notify_client_on_placement_declined |
Creates AppNotification for each user in the job's company |
AFTER_UPDATE |
item_number changed |
sync_cao_function_to_bc |
Enqueues patch_cao_function_in_business_central task (requires salesforce_ref and non-empty item_number) |
AFTER_UPDATE |
status == ACTIVE AND status changed |
notify_client_on_placement_active |
Creates AppNotification for each user in the job's company |
AFTER_UPDATE |
status == ARCHIVED AND status changed AND rejected_at is None |
notify_client_on_placement_ended |
Creates AppNotification for each user in the job's company |
| EmploymentContract | |||
AFTER_CREATE |
— | on_create_sync_to_salesforce |
Chains sync_object_to_salesforce (CREATE) → update_salesforce_fields (set status Checked) → trigger_bc_object_sync → generate_employment_contract_documents. Deferred via transaction.on_commit |
AFTER_UPDATE |
— | on_update_sync_to_salesforce |
If any sf_sync_fields (worker_id, start_date, end_date, hours_per_week) changed: UPDATE in SF, or CREATE if not yet in SF |
Notes¶
on_create_sync_to_salesforce(Placement) is deferred viatransaction.on_committo ensure the placement row is committed before async tasks read it.on_create_employment_contractcreates anEmploymentContract, which triggers theEmploymentContract.on_create_sync_to_salesforcehook in the same request cycle.sync_status_to_salesforcehandles all status transitions in a single hook. ForCONFIRMED, it chains SF status update → BC sync → SF retrieval to prevent race conditions. For all other statuses it firesupdate_salesforce_fieldsindependently.on_update_sync_to_salesforce(Placement) fires on every update unconditionally; it checks internally whether anysf_sync_fieldsactually changed before dispatching.notify_client_on_placement_endedincludes the additional conditionrejected_at is Noneto avoid sending an "ended" notification when a placement was declined by the worker (which also transitions to ARCHIVED).- Client notifications (
notify_client_on_placement_declined,notify_client_on_placement_active,notify_client_on_placement_ended) are created synchronously in the request cycle, not via Celery tasks.