Context for CRM party roles — marks an existing company or contact as a
supplier, client, or partner (see PhoenixKitCRM.Schemas.PartyRole).
Mutations are logged here (not in the LiveViews) because grant_role/3
and revoke_role/2 are called from both the company form and the contact
form's Roles section — a single log point keeps the audit trail consistent
regardless of caller, mirroring PhoenixKitCRM.Interactions. There is no
live-updating tab for roles yet, so unlike interactions this context does
not broadcast over PubSub.
Summary
Functions
Active roles for a batch of companies/contacts in one query — for role
badges on list pages. Returns %{roleable_uuid => [role, ...]} (uuids
with no active roles are absent).
Same filters as list_companies_with_role/2, minus :limit/:offset.
Same filters as list_contacts_with_role/2, minus :limit/:offset.
Resolver entry point for the (future) Catalogue supplier facade: given a
company or contact uuid, returns %{uuid, name, email, phone, website, source: :crm} if that party currently has an active supplier role, or
nil otherwise (unknown uuid, inactive role, or no supplier role at all).
Grants role to a company or contact. Idempotent: granting an already-active
role is a no-op (returns the existing row); granting a previously-revoked
role reactivates it (clears valid_to). attrs may set :valid_from /
:valid_to — never pass caller-supplied metadata here from a UI path.
Whether the company/contact currently has an active role.
Companies holding an active role, name ascending. Excludes trashed
companies by default.
Contacts holding an active role, name ascending. Same options as list_companies_with_role/2.
All role rows (active and inactive) held by a company/contact, role ascending.
Revokes role from a company or contact — sets is_active false and stamps
valid_to with today's date. Never deletes the row (role history is kept).
A no-op if the role isn't currently held (returns {:error, :not_found}) or
is already inactive.
Functions
Active roles for a batch of companies/contacts in one query — for role
badges on list pages. Returns %{roleable_uuid => [role, ...]} (uuids
with no active roles are absent).
@spec count_companies_with_role( String.t(), keyword() ) :: non_neg_integer()
Same filters as list_companies_with_role/2, minus :limit/:offset.
@spec count_contacts_with_role( String.t(), keyword() ) :: non_neg_integer()
Same filters as list_contacts_with_role/2, minus :limit/:offset.
@spec get_supplier(UUIDv7.t() | String.t() | nil) :: %{ uuid: UUIDv7.t(), name: String.t(), email: String.t() | nil, phone: String.t() | nil, website: String.t() | nil, source: :crm } | nil
Resolver entry point for the (future) Catalogue supplier facade: given a
company or contact uuid, returns %{uuid, name, email, phone, website, source: :crm} if that party currently has an active supplier role, or
nil otherwise (unknown uuid, inactive role, or no supplier role at all).
This is the contract PhoenixKitCatalogue.Catalogue.Suppliers.resolve/1
will call in Phase 2 (see the CRM v2 parties design doc, §4.3) — keep the
return shape stable.
@spec grant_role( PhoenixKitCRM.Schemas.Company.t() | PhoenixKitCRM.Schemas.Contact.t(), String.t(), map(), keyword() ) :: {:ok, PhoenixKitCRM.Schemas.PartyRole.t()} | {:error, Ecto.Changeset.t()}
Grants role to a company or contact. Idempotent: granting an already-active
role is a no-op (returns the existing row); granting a previously-revoked
role reactivates it (clears valid_to). attrs may set :valid_from /
:valid_to — never pass caller-supplied metadata here from a UI path.
Pass :actor_uuid in opts so the activity log entry records who granted
the role (mirrors every other logged CRM mutation).
@spec has_role?( PhoenixKitCRM.Schemas.Company.t() | PhoenixKitCRM.Schemas.Contact.t(), String.t() ) :: boolean()
Whether the company/contact currently has an active role.
@spec list_companies_with_role( String.t(), keyword() ) :: [PhoenixKitCRM.Schemas.Company.t()]
Companies holding an active role, name ascending. Excludes trashed
companies by default.
Options
:include_inactive— include revoked role rows too:include_trashed— include trashed companies too:search— name/email ILIKE match:limit/:offset— pagination; no-ops when absent
@spec list_contacts_with_role( String.t(), keyword() ) :: [PhoenixKitCRM.Schemas.Contact.t()]
Contacts holding an active role, name ascending. Same options as list_companies_with_role/2.
@spec list_roles( PhoenixKitCRM.Schemas.Company.t() | PhoenixKitCRM.Schemas.Contact.t() ) :: [ PhoenixKitCRM.Schemas.PartyRole.t() ]
All role rows (active and inactive) held by a company/contact, role ascending.
@spec revoke_role( PhoenixKitCRM.Schemas.Company.t() | PhoenixKitCRM.Schemas.Contact.t(), String.t(), keyword() ) :: {:ok, PhoenixKitCRM.Schemas.PartyRole.t()} | {:error, :not_found | Ecto.Changeset.t()}
Revokes role from a company or contact — sets is_active false and stamps
valid_to with today's date. Never deletes the row (role history is kept).
A no-op if the role isn't currently held (returns {:error, :not_found}) or
is already inactive.
Pass :actor_uuid in opts so the activity log entry records who revoked
the role (mirrors every other logged CRM mutation).