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:
--applywrites. - Idempotent: rows with a non-null
crm_company_uuidare skipped and reported asalready-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-otherand left alone — V178's partial unique index oncrm_company_uuidallows exactly one projection per party.
Matching logic (per row)
- Normalize the candidate email: regex-extract from free-text
contact_info, downcase and trim. - Normalize the website: strip scheme (
https?://) and leadingwww., downcase. - 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/1for 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
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.
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.