Skip to content

Releases: BeamLabEU/phoenix_kit_crm

0.10.0 - 2026-09-02

Choose a tag to compare

@ddon ddon released this 02 Sep 08:56

Added

  • Company-anchored interactions — schema V05 (#32). An interaction now
    anchors to exactly one record: contact_uuid XOR company_uuid, both hard
    FKs, pinned by a DB CHECK (num_nonnulls(...) = 1) added NOT VALID and
    validated separately. "Anchor" is the deliberate word — subject is the
    title column. The anchor is immutable after create:
    Interaction.changeset/2 is now create-only (the only changeset that casts
    anchor fields) and Interaction.update_changeset/2 is its edit twin, so an
    edit can never move a row between feeds, activity ownership and broadcasts.
    No data migration — every existing row has a contact and no company.
  • A dual-anchor interactions component (#32). InteractionsComponent
    serves the contact page and the company page from one module;
    CompanyInteractionsComponent (a read-only member rollup) is removed. In
    company mode it composes company-anchored interactions and merges the
    members' own behind an All | Company | People filter, with
    Interactions.list_for_company/2 combining both arms in one query so
    ordering and :limit apply to the merged window.
  • crm:company:<uuid>:interactions (#32) — a company interaction-feed
    PubSub topic, the symmetric twin of the contact one, deliberately separate
    from topic_company/1 (which signals record/roster changes and triggers
    heavier reloads than a feed refresh needs).
  • Search-independent counts on every index tab (#32).
    Contacts.status_counts/0, Companies.status_counts/0 and
    PartyRoles.role_counts/1 each answer in one grouped query, zeros included.
  • Companies.list_companies/1 :without_contacts (#32) — companies with
    no membership to a live contact, using the roster's own visibility rule, so
    a company listed there really does have an empty Members tab.
  • Interactions.list_recent/1 (#32) — the newest interactions across the
    CRM with the anchor preloaded, excluding rows whose anchor is trashed.
  • Role-access changes are logged (#32). RoleSettings.set_enabled/3 takes
    :actor_uuid and writes crm.role_access_enabled / crm.role_access_disabled
    — this grants or revokes CRM access for everyone holding the role and was the
    module's only unlogged mutation.

Changed

  • The overview is a front door, not a count strip (#32). Companies and
    contacts hero cards, a by-role band whose counts deep-link the pre-filtered
    index, a conditional needs-attention row (companies with no live contacts →
    ?filter=no-contacts), the newest interactions, and a demoted Lists row.
    Portal access moved to the CRM settings page, where each enabled role now
    shows its user count and a link into its portal view.
  • Honest filter strips on both index pages (#32). The default tab is now
    All — everything not trashed, exactly the scope it always was but no
    longer mislabelled "Active". The Active/Inactive pair appears only once
    inactive records exist, role and status tabs render only with results (or
    while active), every label carries its count, the toolbar count names what
    the filter shows, and a tab click resets search.
  • The company page starts at its tabs (#32). The in-body header band is
    gone — the name lives in the layout header, Edit rides the layout's
    page_action chip, and the identity block (logo, status, role badges) opens
    the Overview tab.
  • The project Client tab reads list_for_company/2 (#32), so "recent
    interactions with this client" now includes the company's own, not only its
    members'.
  • tz_offset/1, format_local/2, current_user_uuid/1 and
    current_user_name/1 are single definitions in InteractionHelpers (#32) —
    they were verbatim copies across the show LiveViews and the overview, so an
    interaction now shows the same time on every page that renders one.

Fixed

  • A hard-deleted parent's interactions vanished without cleanup (#32). The
    FK CASCADE bypasses delete_interaction/2, the only path that purges an
    interaction's media folder and broadcasts its deletion. Both delete contexts
    now collect the doomed rows inside their transaction and hand them to
    Interactions.cleanup_cascaded/1 after the commit.
  • Trashing a company left open contact feeds stale (#32). A trashed
    company's anchored rows are hidden from every involving feed;
    notify_company_visibility/1 now tells each affected party contact's page so
    it stops showing them without a reload. Restore does the mirror.
  • One unknown extension cost every attachment its upload (#32).
    allow_upload refuses any :accept extension the mime library cannot name,
    and the raise landed in a silent rescue that disabled the dropzone entirely
    (.m4a / .ogg / .mkv are unknown to mime 2.0.7). The accept list is
    filtered through MIME.has_type?/1, the rescue now logs, and a rejected
    upload entry shows why instead of sitting at a frozen progress bar.
  • A dropped attachment left no trace (#32). attach_files/3 swallowed a
    missing-folder failure after the interaction had already saved and the
    composer had cleared its staged list; it now logs what was lost.
  • Deleting an interaction from a page that does not anchor it (#32). The
    feed's Delete had one gate (the row is in this feed); it now has two — the
    row must also be anchored here, so a row that merely spills in via party
    involvement or the member rollup is read-only and managed from its own page.
    A concurrent delete's Ecto.StaleEntryError refreshes instead of crashing
    the LiveView into a reconnect.
  • Role-checkbox alignment on the contact and company forms (#32) — daisyUI's
    label + fieldset-legend classes carry their own block spacing and sat the
    text visibly off the checkbox.
  • The migration prefix guard used ~r/^...$/, which PCRE also matches before a
    trailing newline; it is now anchored with \A / \z (#32).

Hex: https://hex.pm/packages/phoenix_kit_crm/0.10.0 · Docs: https://phoenix-kit-crm.hexdocs.pm/0.10.0

0.8.1 - 2026-08-25

Choose a tag to compare

@ddon ddon released this 25 Aug 08:35

Added

  • SchemaOwnerGuard (#28). When PGDATABASE points the test suite at a shared Postgres database, Ecto.Migrator's schema_migrations bookkeeping is keyed only by version number with no package namespace — another package's already-applied migration can silently mask this package's migration of the same number. The guard stamps an owner marker on schema_migrations after a successful boot and refuses (with a clear error) any future boot that finds a marker naming a different package, instead of silently colliding.

Changed

  • Dependency bump: phoenix_kit 2.13.7 → 2.13.8, phoenix_kit_comments 0.4.2 → 0.4.3.

0.8.0 - 2026-08-24

Choose a tag to compare

@ddon ddon released this 24 Aug 08:50

0.8.0 - 2026-08-24

Live company page and tab intros (#27), plus review follow-up.

Added

  • Company page follows roster, member interactions, and catalogue data without a reload
  • Tab intros on contact and company show pages; Members tab links to a preselected new-contact form

Changed

  • Mirror resolution rebuilds the form from the resolved record so Save cannot undo it
  • CRM settings remounts after a role toggle so the sidebar follows
  • Role view reloads the CRM-contact map when that column is ticked on

Fixed

  • Contact Files interaction roll-up refreshes live
  • List-members locale-apply preview stays in step with membership
  • Comparison duplicate-email groups re-query list, count, and rows on reopen; citext keys are lowercased
  • Company Catalogue tab also refreshes on category changes
  • delete_contact / set_primary_company notify companies from inside the transaction
  • Mirror resolution broadcasts after commit

0.7.2 - 2026-08-22

Choose a tag to compare

@ddon ddon released this 22 Aug 22:29

Patch release for PRs #24, #25, and #26.

  • CRM tab strips use core nav_tabs (border variant)
  • Catalogue tab no longer 500s on first load
  • Supplier import no longer stores raw-binary uuids in company metadata
  • rustler declared optionally so mdex_native can force-build

See CHANGELOG.md for details.

0.7.1 - 2026-08-21

Choose a tag to compare

@ddon ddon released this 21 Aug 15:04

Added

  • manufacturer party role, alongside supplier / customer / partner
    (#23). Granted the same way as the others; a company or contact can hold
    supplier and manufacturer at once — the case the shared role table
    exists for.
  • Roles now carry a validity window. valid_from / valid_to used to be
    decorative — every query filtered on is_active alone, so a role stamped
    with an expired valid_to still resolved as live forever. Every role query
    now goes through PartyRoles.in_force/1, so an out-of-window role stops
    resolving even while is_active stays true, and re-granting a lapsed role
    starts a fresh tenure instead of silently returning the stale row.
  • Batch party resolution: get_suppliers/1, get_manufacturers/1, and
    list_parties_with_role/2 (with list_suppliers/1, list_manufacturers/1,
    list_customers/1 wrappers) resolve many party uuids in one pair of
    queries instead of one call per row — the N+1 a catalogue page rendering
    100 items would otherwise hit.
  • A Catalogue tab on the company page (#23), shown when the catalogue
    module is installed and enabled. Lists items this company supplies or
    manufactures, with a column picker and a warning banner when items still
    reference a role the company no longer holds. Soft-dependency guarded
    (Code.ensure_loaded? + apply/3) — the CRM has no compile-time
    dependency on the catalogue.
  • Company gained description and logo_url — the fields the catalogue's
    own supplier/manufacturer rows used to carry, now that those parties are
    managed here.
  • A V04 migration adds a CHECK constraint on the role vocabulary and a
    partial unique index (roleable_uuid, role) WHERE is_active, so a
    party can hold at most one active row per role — closing a state
    get_supplier/1 previously had to defend against with limit(1). Legacy
    client rows (pre-rename) are normalized to customer first, since
    ADD CONSTRAINT validates existing rows.

Fixed

  • get_manufacturer/1 and its batch/list counterparts now return
    logo_url.
    V04's own description/logo_url addition to Company was
    meant to let the catalogue read a manufacturer's brand mark from CRM, but
    none of the federation resolvers included the field — it was captured on
    the company form and reachable by nothing.
  • Removed seven priv/gettext catalog entries ("Item", "Their code",
    "Unit cost", "Lead time", "primary", "%{n} d", "SKU", with real
    Estonian and Russian translations) that no gettext/1 call in this repo
    produces.
  • phoenix_kit_crm.import_suppliers_from_catalogue: raw-SQL uuid parameters
    are now dumped to their 16-byte binary form before use (Ecto.UUID.dump/1)
    and uuid columns read back from raw SQL are loaded to text before display
    (Ecto.UUID.load/1) — both directions previously raised on a row that had
    actually been linked.

Changed

  • Dependency updates (mix.lock): phoenix_kit 2.13.4, phoenix 1.8.12,
    phoenix_live_view 1.2.10, and routine bumps to ecto, bandit, swoosh,
    req, tesla, and others.

0.7.0 - 2026-08-14

Choose a tag to compare

@ddon ddon released this 14 Aug 10:54

Manual two-way mirror between CRM records and system users, and module-owned migrations.

Added

  • Company ↔ organization-user and Contact ↔ person-user mirroring, as explicit admin actions (Create mirror / Link existing / Unlink) from both sides. Nothing is automatic — no PubSub, no background sync. The form you act from is the master, and a diverging field raises a per-field conflict modal rather than silently overwriting. The diff is recomputed fresh at submit time, so a stale selection or an out-of-band edit cannot drive a bad write, and every two-record write runs in one transaction.
  • Module-owned migrations (migration_module/0), following the phoenix_kit_legal precedent. V01 idempotently adopts the 10 phoenix_kit_crm_* tables the core chain historically created and adds companies.user_uuid with a partial unique index — so any number of companies may be unlinked while a linked user maps to at most one. down/1 never drops a table, and a test pins that.

user_uuid is never cast from form params on either schema — only through a dedicated link_user_changeset/2.

Note for hosts: because companies.user_uuid is outside core's manifest, mix phoenix_kit.repair reports it as an info-level extra_object. Repair takes no action on it and will not drop it.

586 tests, 0 failures; mix precommit exit 0. Lockfile moves to phoenix_kit 2.4.0; the ~> 2.0 pin is unchanged.

0.6.1 - 2026-08-11

Choose a tag to compare

@ddon ddon released this 11 Aug 19:02

Fixed

  • The remaining untranslated Estonian and Russian strings are filled in (#21). A large block of msgids had empty msgstr values, so those parts of the CRM rendered in English inside an otherwise translated UI.

Changed

  • The CRM page subtitle now spells out what the module is: "Customer relationship management — companies, contacts and the roles that can reach them".
  • Dependency updates (phoenix_kit 2.2.0, phoenix_kit_comments 0.4.0, phoenix 1.8.10, hackney 4.7.3).

0.6.0 - 2026-08-10

Choose a tag to compare

@ddon ddon released this 10 Aug 16:53

⚠️ Requires phoenix_kit ~> 2.0

Core 2.0.0 refuses to migrate a database below its squashed V135 floor — check mix phoenix_kit.status before upgrading. A host below V135 must install phoenix_kit 1.7.236 (the migration bridge) and migrate to at least V135 first.

Sibling pins were raised in step. Each sibling's new minor is the first release of that package requiring core 2.0, so the previous pins could only have resolved versions still requiring core 1.7 — an unsatisfiable set. See CHANGELOG.md for the full entry.

0.3.2 - 2026-07-20

Choose a tag to compare

@ddon ddon released this 20 Jul 00:01

Fixed

  • Lists.recount_list/1 raised if the list row it was recounting was deleted concurrently between the caller loading it and the recount's own UPDATE — a narrower race left open by 0.3.1's fix. It now returns :missing instead of raising, and Contacts.delete_contact/1's recount step tolerates it: a moot counter on an already-deleted list no longer rolls back the whole contact deletion (PR #15).

0.3.0 - 2026-07-19

Choose a tag to compare

@ddon ddon released this 19 Jul 13:30

Stage 3 of the restructuring plan (PR #13): CRM contact lists, a CSV/text account importer, per-list locale with bulk-apply, contact opt-out/consent, and a duplicate-email/list-overlap comparison screen.

Requires phoenix_kit >= 1.7.203.

Post-merge review + fixes (mount/3 database-query timing on 3 new LiveViews, and a dependency-floor correction) are documented in dev_docs/pull_requests/2026/13-crm-contact-lists/CLAUDE_REVIEW.md.

See CHANGELOG.md for the full list.