Skip to content

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_documentscreate_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_syncretrieve_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_syncgenerate_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 via transaction.on_commit to ensure the placement row is committed before async tasks read it.
  • on_create_employment_contract creates an EmploymentContract, which triggers the EmploymentContract.on_create_sync_to_salesforce hook in the same request cycle.
  • sync_status_to_salesforce handles all status transitions in a single hook. For CONFIRMED, it chains SF status update → BC sync → SF retrieval to prevent race conditions. For all other statuses it fires update_salesforce_fields independently.
  • on_update_sync_to_salesforce (Placement) fires on every update unconditionally; it checks internally whether any sf_sync_fields actually changed before dispatching.
  • notify_client_on_placement_ended includes the additional condition rejected_at is None to 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.