Employment history for staff people (core V136) — the sole write path for
PhoenixKitStaff.Schemas.Employment spans.
A person has a timeline of spans; the open span (no end date) is the current employment. This context:
- keeps the one-open-span-per-person invariant —
create/2closes the prior open span when a new open one starts (the DB partial-unique index backstops it); - denormalizes the current span onto
Personviasync_current/1, run in the same transaction as every mutation, so existing readers (overview org tree →primary_department_uuid, people list →job_title) need no join and stay consistent; - broadcasts
:person_employment_changedon the person's topic so the profile view refreshes.
job_title is translatable; sync_current/1 mirrors the current span's
job_title + its per-locale overrides onto Person without clobbering the
person's other translated fields (bio, notes).
Summary
Functions
Creates an employment span for a person and denormalizes the current span
onto Person. If the new span is open (no end date) and an open span
already exists, the prior open span is closed at the new span's start date
(today if it has none) so the one-open invariant holds.
The current span (open if any, else the latest by start), dept+team preloaded, or nil.
Deletes a span and re-denormalizes the current span onto Person.
Ends an open span by setting its end date (then re-syncs).
Fetches a span by uuid, or nil.
A person's employment spans, current/open first then newest-first, dept+team preloaded.
Recomputes the person's current span (open, else latest) and writes the
denormalized mirror onto Person (employment_type, job_title + its
translations, dates, primary_department_uuid, work_location) — clearing
them when the person has no spans. Server-owned: never cast from the form.
Call inside the mutating transaction.
Updates a span and re-denormalizes the current span onto Person.
Functions
@spec create(UUIDv7.t() | String.t(), map()) :: {:ok, PhoenixKitStaff.Schemas.Employment.t()} | {:error, Ecto.Changeset.t(PhoenixKitStaff.Schemas.Employment.t())} | {:error, :person_trashed}
Creates an employment span for a person and denormalizes the current span
onto Person. If the new span is open (no end date) and an open span
already exists, the prior open span is closed at the new span's start date
(today if it has none) so the one-open invariant holds.
@spec current_for_person(UUIDv7.t() | String.t()) :: PhoenixKitStaff.Schemas.Employment.t() | nil
The current span (open if any, else the latest by start), dept+team preloaded, or nil.
@spec delete(PhoenixKitStaff.Schemas.Employment.t()) :: {:ok, PhoenixKitStaff.Schemas.Employment.t()} | {:error, Ecto.Changeset.t(PhoenixKitStaff.Schemas.Employment.t())}
Deletes a span and re-denormalizes the current span onto Person.
@spec end_span(PhoenixKitStaff.Schemas.Employment.t(), Date.t() | String.t()) :: {:ok, PhoenixKitStaff.Schemas.Employment.t()} | {:error, Ecto.Changeset.t(PhoenixKitStaff.Schemas.Employment.t())}
Ends an open span by setting its end date (then re-syncs).
@spec get(UUIDv7.t() | String.t()) :: PhoenixKitStaff.Schemas.Employment.t() | nil
Fetches a span by uuid, or nil.
@spec list_for_person(UUIDv7.t() | String.t()) :: [ PhoenixKitStaff.Schemas.Employment.t() ]
A person's employment spans, current/open first then newest-first, dept+team preloaded.
Recomputes the person's current span (open, else latest) and writes the
denormalized mirror onto Person (employment_type, job_title + its
translations, dates, primary_department_uuid, work_location) — clearing
them when the person has no spans. Server-owned: never cast from the form.
Call inside the mutating transaction.
@spec update(PhoenixKitStaff.Schemas.Employment.t(), map()) :: {:ok, PhoenixKitStaff.Schemas.Employment.t()} | {:error, Ecto.Changeset.t(PhoenixKitStaff.Schemas.Employment.t())}
Updates a span and re-denormalizes the current span onto Person.