PhoenixKitCRM.CatalogueImport (PhoenixKitCRM v0.9.0)

Copy Markdown View Source

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).

Suppliers got this backfill when the federation shipped; manufacturers were forgotten and the client kept seeing the catalogue's old local list with nothing in the CRM UI (2026-08-31 report). The two flows are the same SAP-CVI-shaped promotion — read the catalogue's local rows, match-or-create a CRM company, grant the party role, stamp the crm_company_uuid cross-reference back — so the machinery lives here once, parameterized by config/1, and each mix task is a thin wrapper.

Behaviour (both tasks)

  • Dry-run by default: --apply writes.
  • Idempotent: rows with a non-null crm_company_uuid are skipped and reported as already-linked.
  • Catalogue-absent guard: a missing source table exits with a clear message rather than crashing.
  • Inactive rows are still imported (they may appear on posted documents); flagged in the report.
  • One projection per party: a row whose match is already the CRM party of another row in the same directory is reported as claimed-by-other and left alone — V178's partial unique index on crm_company_uuid allows exactly one projection per party.

Matching logic (per row)

  1. Normalize the candidate email: regex-extract from free-text contact_info, downcase and trim.
  2. Normalize the website: strip scheme (https?://) and leading www., downcase.
  3. Match an existing CRM company by email first (citext equality), then by normalized website; otherwise create a new company from the source row's own columns (see config/1 for the per-flow mapping).

Summary

Functions

The per-flow configuration. :suppliers is the original backfill's shape; :manufacturers is its twin.

Public for testing — whether the source table carries the xref column.

Extracts the first email-like token from a free-text contact_info string. Returns the downcased, trimmed email or nil.

Strips https?:// scheme and leading www. from a URL, then downcases. Returns nil for nil/empty input.

Public for testing — renders the per-row table + totals footer.

Public for testing — processes a single source-row map through the match/create logic and optionally writes changes. Returns a result map describing the action taken.

The mix-task entry point: guards, fetch, process, report.

Functions

config(atom)

The per-flow configuration. :suppliers is the original backfill's shape; :manufacturers is its twin.

column_map carries the source columns that are neither part of the shared base set nor derived — source column → CRM company field. Both targets are real columns on phoenix_kit_crm_companies, added by this module's own migration chain for exactly these rows: description by V02 ("the last field the catalogue's own supplier rows carried that a CRM company did not") and logo_url by V03 ("so a company can carry the brand mark the catalogue's manufacturer rows used to hold"). Both flows read their source description; only manufacturers have a logo_url to carry.

crm_company_uuid_column?(repo, prefix, config)

Public for testing — whether the source table carries the xref column.

extract_email(text)

@spec extract_email(String.t() | nil) :: String.t() | nil

Extracts the first email-like token from a free-text contact_info string. Returns the downcased, trimmed email or nil.

normalize_website(url)

@spec normalize_website(String.t() | nil) :: String.t() | nil

Strips https?:// scheme and leading www. from a URL, then downcases. Returns nil for nil/empty input.

process_row(row, repo, prefix, apply?, config)

Public for testing — processes a single source-row map through the match/create logic and optionally writes changes. Returns a result map describing the action taken.

run(args, config)

The mix-task entry point: guards, fetch, process, report.