A commercial party role — marks an existing CRM company or contact as a
supplier, customer, partner, or manufacturer (Odoo's
supplier_rank/customer_rank, SAP's Business-Partner roles, expressed as
rows). One party can hold several roles at once (a company that's both
supplier and customer has two rows).
manufacturer is the odd one out: in the catalogue a "manufacturer" is
usually a brand — a product-master attribute — and the brand owner, the
legal manufacturer and the supplier you actually buy from are frequently
three different companies. The role is therefore granted on two paths only:
the catalogue's link action (a human naming the real party) and the opt-in
mix phoenix_kit_crm.import_manufacturers_from_catalogue backfill, which an
operator runs deliberately and which is dry-run by default — never
implicitly, and never from a form. Holding both supplier and
manufacturer on one party is the case this shared table exists for.
roleable_type + roleable_uuid point at phoenix_kit_crm_companies or
phoenix_kit_crm_contacts — a soft ref, no FK (a single FK can't
express the polymorphic target; integrity lives here in the changeset,
mirroring the staff_person_uuid precedent on InteractionParty).
metadata is never cast from raw UI params — the Roles checkboxes in the
company/contact forms only ever toggle role membership via
PhoenixKitCRM.PartyRoles.grant_role/3 and revoke_role/2, which don't
accept caller-supplied metadata either. Role-scoped commercial attributes
land here only through a future dedicated context function.
Summary
Functions
Changeset for controlled lifecycle transitions (revoke / reactivate) driven
by the context, not user input. Deliberately skips validate_date_range/1:
stamping valid_to = today when revoking a role whose valid_from is in the
future would otherwise fail the ordering check and turn revoke into a silent
no-op. Only is_active and valid_to are castable here.
Types
@type t() :: %PhoenixKitCRM.Schemas.PartyRole{ __meta__: term(), inserted_at: DateTime.t() | nil, is_active: boolean(), metadata: map(), role: String.t() | nil, roleable_type: String.t() | nil, roleable_uuid: UUIDv7.t() | nil, updated_at: DateTime.t() | nil, uuid: UUIDv7.t() | nil, valid_from: Date.t() | nil, valid_to: Date.t() | nil }
Functions
@spec changeset(t() | Ecto.Changeset.t(t()), map()) :: Ecto.Changeset.t(t())
@spec lifecycle_changeset(t() | Ecto.Changeset.t(t()), map()) :: Ecto.Changeset.t(t())
Changeset for controlled lifecycle transitions (revoke / reactivate) driven
by the context, not user input. Deliberately skips validate_date_range/1:
stamping valid_to = today when revoking a role whose valid_from is in the
future would otherwise fail the ordering check and turn revoke into a silent
no-op. Only is_active and valid_to are castable here.
@spec roleable_types() :: [String.t()]
@spec roles() :: [String.t()]