Modules
CRM module for PhoenixKit.
Thin wrapper around PhoenixKit.Activity.log/1 so callers don't need to
duplicate the Code.ensure_loaded?/1 guard and rescue clause everywhere.
Safe to call from any LiveView — never crashes the caller.
Maps CRM activity action strings (e.g. "crm.interaction_logged") to a
{heroicon, human_label} pair plus an optional secondary detail/2 line, for
the contact/company Events tab. Domain labels go through
PhoenixKitCRM.Gettext. Unknown actions fall back to a humanized form of the
action string, so a newly-added action still renders without a change here.
Folder-scoped media attachments for a CRM contact, backed by core
PhoenixKit.Modules.Storage (the same per-resource-folder convention
phoenix_kit_staff/phoenix_kit_catalogue use — no module-owned table, no
migration).
The shared engine behind the catalogue → CRM party backfill tasks
(mix phoenix_kit_crm.import_suppliers_from_catalogue and
mix phoenix_kit_crm.import_manufacturers_from_catalogue).
Per-scope column configuration for CRM tables and card views.
Context for CRM companies — CRUD, soft-delete, and search (for the contact form's company picker).
Context for CRM contacts — CRUD, soft-delete, the (v1 single) company membership, and the optional login-user connection.
Gettext backend for phoenix_kit_crm.
Context for CRM interactions — the History/Interactions tab.
Context for CRM contact lists (PhoenixKitCRM.Schemas.ContactList) and
their memberships (PhoenixKitCRM.Schemas.ListMember).
Pure-ish CSV/plaintext import engine for CRM contact lists (Stage 3 of the
restructuring plan — the user's stated priority: account list import).
Parses rows, applies the restructuring spec's Locked decisions, and writes
only through PhoenixKitCRM.Lists — it never duplicates that context's
insert/counter/broadcast logic.
Result of an import run (PhoenixKitCRM.Lists.Import).
Module-owned versioned migrations for phoenix_kit_crm — the
decentralized-migrations protocol core's mix phoenix_kit.update
discovers via migration_module/0: current_version/0 +
migrated_version_runtime/1 + idempotent up/1 + version-aware
down/1. phoenix_kit_projects is the reference implementation;
PhoenixKit.Modules.Legal.Migrations is the closest precedent — this
chain is the same shape, scaled up to ten adopted tables instead of one.
Field maps, the per-field divergence-diff engine, and the resolver they
feed — shared by both mirror sides: Company ↔ an
account_type: "organization" User, Contact ↔ an
account_type: "person" User (owner decisions Q1-Q6, L006).
Context for CRM party roles — marks an existing company or contact as a
supplier, customer, or partner (see PhoenixKitCRM.Schemas.PartyRole).
Centralized path helpers for the CRM module. All paths go through
PhoenixKit.Utils.Routes.path/1 for prefix/locale handling.
Real-time updates for CRM interactions, backed by PhoenixKit.PubSub.Manager.
Schema for CRM role settings.
Context for managing which roles have CRM access enabled.
Route definitions for the CRM module.
A CRM company / organization record. A first-class record (its own data),
NOT a login user. Contacts link to it via CompanyMembership.
The contact ↔ company link (many-to-many), carrying free-form
role_in_company and department on the edge, plus an is_primary flag.
A CRM contact (client / customer / prospect).
A named, sluggable CRM contact list (PhoenixKitCRM.Lists).
A logged interaction ("client called, we discussed X") — the core of the
CRM v1 interaction tracker. A structured log entry (type + when + body),
anchored to a subject Contact, with N resolvable involved parties.
An "involved party" on an interaction — a flat, resolvable mention.
The list ↔ contact join (PhoenixKitCRM.Lists), carrying a denormalized
email snapshot taken at add-time and its own status/source.
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).
Shared LIKE/ILIKE search-term escaping for contexts that filter by a
free-text search box (Contacts, Companies, PartyRoles, Lists).
Registers CRM role subtabs into PhoenixKit.Dashboard.Registry under the
namespace :phoenix_kit_crm_roles.
Status-column soft-delete shared by Contacts and Companies.
Optional, soft integration with phoenix_kit_staff. Everything here is
guarded — the CRM module works whether or not the staff module is present
or enabled. Used by the interaction "involved parties" picker (a staff
person can be selected) and by the party snapshot.
Context for managing per-user CRM view configuration.
Schema for per-user, per-scope CRM view configuration.
Main admin LiveView for the CRM module — the module's landing page.
Shared formatters for CRM table/card cells.
use macro that injects column-management handle_event/3 callbacks into
a CRM LiveView. The host LV must
Function component that renders the "Customize columns" modal used by both
RoleView and OrganizationsView. UX mirrors PhoenixKit.Users table column
picker: drag-to-reorder selected columns on the left, click-to-add available
columns on the right.
Admin list of CRM companies.
New / edit form for a CRM company.
The Interactions tab of a CRM company — a read-only, aggregated feed of every interaction logged on the company's member contacts. Each entry links to the contact it's about (and shows the interaction's parties + attachments), so you can jump to that person. Interactions are still logged on a contact's page; this is a company-wide rollup.
Show page for a CRM company. Tabs: Overview (details + contacts), Interactions (a read-only rollup of interactions logged on the company's contacts), and Events always; Files + Images when core Storage is enabled; Comments when the comments module is enabled. The header shows a circular logo (icon fallback).
CRM comparison ("сличение") screen — two independent, read-only reports, no auto-actions
The per-field "ask, don't silently overwrite" divergence-resolution
modal (owner Q5) — one row per PhoenixKitCRM.Mirror.diff/2 entry, a
radio choosing which side wins that field.
The "mirror account" status + actions block, shared by the Company and
Contact forms (Tasks G/H) — the CRM-record side of the CRM ↔ system-user
mirror (PhoenixKitCRM.Mirror, Companies/Contacts "connect"
contexts).
One line under a show-page tab heading saying what the tab shows and how something gets into it — with the way to do that as an inline link when it happens on another page.
New / edit form for a CRM contact: the profile fields, a single company block (company + free-form role + department), and the optional "allow login" checkbox (staff-style find-or-create user link).
Show page for a CRM contact. Tabs: Overview, Interactions, Events always;
Orders when the host app's Andi.CRMBridge is available; Files + Images
when core Storage is enabled; Comments when the comments module is
enabled. The header shows a circular avatar (initials fallback).
Admin list of CRM contacts.
The Events tab for a CRM record — a read-only, paginated feed of the
PhoenixKit.Activity entries scoped to it (resource_type + resource_uuid).
Parameterized by :resource_type ("crm_contact" / "crm_company") and
:resource_uuid, so it serves both the contact and company profiles.
Shared render helpers for interaction timelines (contact + company feeds): the involved-party badge and its frozen-snapshot detail/title. A party that resolved to a CRM contact or a staff person links to that page; free-text parties render as a plain badge.
The Interactions / History tab for a contact: a reverse-chronological feed of interactions involving the contact, plus a composer to log a new one with a free-form-but-resolvable "involved parties" picker (CRM contacts + staff).
New / edit form for a CRM contact list.
Import page for a single CRM contact list — paste-text or file (CSV/TXT)
upload, a dry-run preview (no writes), then a chunked real run with
progress so a large file doesn't block the LiveView process for minutes,
finishing with the full ImportReport broken out by skip reason.
Admin page for a single CRM contact list's memberships — paginated member table (status/source/email, remove action) plus a manual add-by-email form.
Admin list of CRM contact lists (card/table toggle view).
The Files and Images tabs for a CRM record (a contact or a company),
parameterized by :resource_type (:contact | :company), the :resource
struct, and :kind (:files | :images). Media is folder-scoped via core
PhoenixKit.Modules.Storage (see PhoenixKitCRM.Attachments): a
crm-<resource>-<uuid> root folder for :files, a nested Images subfolder
for :images.
Admin LiveView for the CRM Organizations subtab — lists users whose
account_type = "organization" with per-user persisted column
configuration. Supports a card/table view toggle (provided by
PhoenixKitWeb.Components.Core.TableDefault).
Shared helpers for the commercial party-role UI (badges on the list pages, the Roles checkbox section on the company/contact forms).
The CRM Client tab for the phoenix_kit_projects hub — this module's
phoenix_kit_project_extensions/0 contribution (see that function in
PhoenixKitCRM).
Admin LiveView for a single CRM role page — lists users assigned to the role
with per-user persisted column configuration. Supports a card/table view
toggle (provided by PhoenixKitWeb.Components.Core.TableDefault).
CRM settings page — exposes the enable/disable toggle and role opt-in.
Mix Tasks
Imports catalogue manufacturers into CRM companies and grants them the
manufacturer role — the twin of
mix phoenix_kit_crm.import_suppliers_from_catalogue, which shipped
with the party federation while this half was forgotten: local
phoenix_kit_cat_manufacturers rows kept feeding the item form's
dropdown as the "old list" while the CRM Companies page's
Manufacturers filter showed nothing (client report, 2026-08-31).
Imports catalogue suppliers into CRM companies and grants them the
supplier role. This is a one-time backfill task following the
SAP-CVI pattern.
One-time data migration for the client → customer party-role rename.