@spec primary_membership(PhoenixKitCRM.Schemas.Contact.t()) :: PhoenixKitCRM.Schemas.CompanyMembership.t() | nil
The contact's primary company membership (or the first), or nil.
Context for CRM contacts — CRUD, soft-delete, the (v1 single) company membership, and the optional login-user connection.
The user connection mirrors phoenix_kit_staff's flow but is opt-in: a
contact has no user_uuid until connect_user/2 is called (driven by the
form's "allow login" checkbox). It uses find-or-create — an existing user
by email is linked; if none exists a placeholder is registered (tagged
custom_fields.source = "crm_contact"), which the person can later claim by
registering / signing in with that email.
Connects a contact to a login user by email (staff-style find-or-create). Existing user by email → linked; otherwise a placeholder user is registered. Rolls back a just-created placeholder if the link fails. No-op-safe to call on an already-linked contact (re-links).
Permanently deletes a contact (cascades memberships + interactions).
Disconnects a contact from its login user (unlinks only; never deletes the user).
Finds an existing user by email, or registers a placeholder with no usable
password (tagged custom_fields.source = "crm_contact").
The (at most one) contact linked to a given login user, or nil.
Contacts for the given uuids (any status) — for comment back-link resolution.
Lists contacts. Excludes trashed by default; preloads the primary company membership (with company) and the linked user.
The contact's primary company membership (or the first), or nil.
Searches contacts by name/email (case-insensitive) for the parties picker.
Excludes trashed and any uuids in exclude_uuids (e.g. the contact whose page
the interaction is being logged on — they're already the subject).
Sets the contact's primary company membership to the given company, with free-form role + department. v1 manages exactly one company per contact via the form, so this replaces the contact's membership set. A blank/nil company clears it.
Soft-deletes a contact (status → trashed, stashing the prior status).
@spec change_contact(PhoenixKitCRM.Schemas.Contact.t(), map()) :: Ecto.Changeset.t()
@spec connect_user(PhoenixKitCRM.Schemas.Contact.t(), String.t()) :: {:ok, PhoenixKitCRM.Schemas.Contact.t(), :existing | :created} | {:error, atom() | Ecto.Changeset.t()}
Connects a contact to a login user by email (staff-style find-or-create). Existing user by email → linked; otherwise a placeholder user is registered. Rolls back a just-created placeholder if the link fails. No-op-safe to call on an already-linked contact (re-links).
@spec count_contacts(keyword()) :: non_neg_integer()
@spec create_contact(map()) :: {:ok, PhoenixKitCRM.Schemas.Contact.t()} | {:error, Ecto.Changeset.t()}
@spec delete_contact(PhoenixKitCRM.Schemas.Contact.t()) :: {:ok, PhoenixKitCRM.Schemas.Contact.t()} | {:error, Ecto.Changeset.t()}
Permanently deletes a contact (cascades memberships + interactions).
@spec disconnect_user(PhoenixKitCRM.Schemas.Contact.t()) :: {:ok, PhoenixKitCRM.Schemas.Contact.t()} | {:error, Ecto.Changeset.t()}
Disconnects a contact from its login user (unlinks only; never deletes the user).
@spec find_or_create_user_by_email(String.t()) :: {:ok, PhoenixKit.Users.Auth.User.t(), :existing | :created} | {:error, atom() | Ecto.Changeset.t()}
Finds an existing user by email, or registers a placeholder with no usable
password (tagged custom_fields.source = "crm_contact").
@spec get_by_user_uuid(UUIDv7.t() | String.t() | nil) :: PhoenixKitCRM.Schemas.Contact.t() | nil
The (at most one) contact linked to a given login user, or nil.
@spec get_contact(UUIDv7.t() | String.t() | nil) :: PhoenixKitCRM.Schemas.Contact.t() | nil
@spec list_by_uuids([binary()]) :: [PhoenixKitCRM.Schemas.Contact.t()]
Contacts for the given uuids (any status) — for comment back-link resolution.
@spec list_contacts(keyword()) :: [PhoenixKitCRM.Schemas.Contact.t()]
Lists contacts. Excludes trashed by default; preloads the primary company membership (with company) and the linked user.
@spec primary_membership(PhoenixKitCRM.Schemas.Contact.t()) :: PhoenixKitCRM.Schemas.CompanyMembership.t() | nil
The contact's primary company membership (or the first), or nil.
@spec restore_contact(PhoenixKitCRM.Schemas.Contact.t()) :: {:ok, PhoenixKitCRM.Schemas.Contact.t()} | {:error, atom() | Ecto.Changeset.t()}
@spec search_contacts(String.t(), pos_integer(), [binary()]) :: [ PhoenixKitCRM.Schemas.Contact.t() ]
Searches contacts by name/email (case-insensitive) for the parties picker.
Excludes trashed and any uuids in exclude_uuids (e.g. the contact whose page
the interaction is being logged on — they're already the subject).
@spec set_primary_company( PhoenixKitCRM.Schemas.Contact.t(), UUIDv7.t() | String.t() | nil, String.t() | nil, String.t() | nil ) :: {:ok, PhoenixKitCRM.Schemas.CompanyMembership.t() | nil} | {:error, Ecto.Changeset.t()}
Sets the contact's primary company membership to the given company, with free-form role + department. v1 manages exactly one company per contact via the form, so this replaces the contact's membership set. A blank/nil company clears it.
@spec trash_contact(PhoenixKitCRM.Schemas.Contact.t()) :: {:ok, PhoenixKitCRM.Schemas.Contact.t()} | {:error, atom() | Ecto.Changeset.t()}
Soft-deletes a contact (status → trashed, stashing the prior status).
@spec update_contact(PhoenixKitCRM.Schemas.Contact.t(), map()) :: {:ok, PhoenixKitCRM.Schemas.Contact.t()} | {:error, Ecto.Changeset.t()}