The ONE doorway to people/team/department data for this module — the staff-optional seam (Phase B of the hub rework, panel-approved design).
Why shadow schemas, not a bridge
The staff DB tables are created by CORE's migrations (V100), not by the
phoenix_kit_staff package — staff ships no migrations; it is the
admin UI + context over core-owned tables. So the tables (and projects'
SQL FKs to them) exist on every install, and projects can keep its
belongs_to graph, preloads, authz relationship grants, and direct
assignee queries by mapping its OWN minimal read-only schemas
(PhoenixKitProjects.People.{Person,Team,Department,TeamMembership})
over those tables. The staff PACKAGE is optional: it is the people
ADMIN surface, not the data's owner.
Rules
- Read-only: no changesets, no writes — people are managed in staff.
- Filter semantics COPIED from staff, not imported: soft-deleted rows
are
status == "trashed"and excluded from listings by default. - Label semantics mirrored from staff:
display_name/1resolves name → user's first/last name → user email → "Unnamed"; team/department names localize via the row'stranslationsmap. staff_admin_available?/0gates ONLY admin-UI affordances (manage- people links, the "Me" chip's person resolution when staff adds value) — data reads never require the staff package.
Summary
Functions
name → user's first/last → user email → "Unnamed" (staff parity).
A person by uuid with [:user, :primary_department] preloaded, or nil.
The person linked to a core user, or nil. STAFF-PARITY: resolves trashed people too (soft-delete keeps assignments/memberships live so restore is clean — filtering here blanked My Tasks and the Me chip for the whole trash→restore window; panel round). Only LISTINGS exclude trashed.
All departments, name-ordered.
A person's team memberships with team: [:department] preloaded.
Non-trashed people ordered by display name source, [:user] preloaded
(the option loaders read the user email). Mirrors staff's default
trashed-exclusion.
All teams with their department preloaded, name-ordered.
A team/department's localized name: translations[lang]["name"] else the primary.
Whether the staff PACKAGE (the people admin surface) is installed and enabled — for UI affordances only; the data paths below never need it.
Functions
@spec display_name(PhoenixKitProjects.People.Person.t() | nil) :: String.t()
name → user's first/last → user email → "Unnamed" (staff parity).
@spec get_person( binary(), keyword() ) :: PhoenixKitProjects.People.Person.t() | nil
A person by uuid with [:user, :primary_department] preloaded, or nil.
@spec get_person_by_user_uuid( binary(), keyword() ) :: PhoenixKitProjects.People.Person.t() | nil
The person linked to a core user, or nil. STAFF-PARITY: resolves trashed people too (soft-delete keeps assignments/memberships live so restore is clean — filtering here blanked My Tasks and the Me chip for the whole trash→restore window; panel round). Only LISTINGS exclude trashed.
@spec list_departments() :: [PhoenixKitProjects.People.Department.t()]
All departments, name-ordered.
@spec list_memberships_for_person(binary()) :: [ PhoenixKitProjects.People.TeamMembership.t() ]
A person's team memberships with team: [:department] preloaded.
@spec list_people(keyword()) :: [PhoenixKitProjects.People.Person.t()]
Non-trashed people ordered by display name source, [:user] preloaded
(the option loaders read the user email). Mirrors staff's default
trashed-exclusion.
@spec list_teams() :: [PhoenixKitProjects.People.Team.t()]
All teams with their department preloaded, name-ordered.
@spec localized_name( PhoenixKitProjects.People.Team.t() | PhoenixKitProjects.People.Department.t() | nil, String.t() | nil ) :: String.t() | nil
A team/department's localized name: translations[lang]["name"] else the primary.
@spec staff_admin_available?() :: boolean()
Whether the staff PACKAGE (the people admin surface) is installed and enabled — for UI affordances only; the data paths below never need it.