PhoenixKitCatalogue.Catalogue.ItemSupplierInfos (PhoenixKitCatalogue v0.12.2)

Copy Markdown View Source

Context for managing the phoenix_kit_cat_item_supplier_info junction table.

Each row links a catalogue item to a supplier (local or CRM-sourced) and carries a snapshot of the supplier's name at write time so the record stays readable even after renaming or deletion.

At most one row per item may have is_primary: true — enforced by a partial unique index and by set_primary/1, which uses a transaction to clear any existing primary before promoting the target row.

Price history via validity windows

The schema carries valid_from/valid_to date fields. A row is considered current when valid_to is nil. revise_unit_cost/3 implements a non-destructive price revision: the current row is closed (valid_to: today, is_primary: false) and a successor row is inserted with the new cost, valid_from: today, and valid_to: nil. This produces an append-only price history ordered by valid_from descending. The canonical "current" predicate is is_nil(valid_to).

Summary

Functions

Creates a supplier-info row.

Deletes a supplier-info row.

Fetches a supplier-info row by UUID. Returns nil if not found.

Returns all rows for an item/supplier pair ordered newest-first.

Lists current supplier-info rows for an item, ordered by position then inserted_at.

Returns the current primary supplier-info row for an item, or nil if none is marked primary.

Closes the current junction row and inserts a successor with the new unit cost, creating an append-only price revision history.

Promotes a supplier-info row to primary for its item.

Updates a supplier-info row.

Functions

create(attrs, opts \\ [])

Creates a supplier-info row.

When the item has no primary supplier yet, the newly linked row is auto-promoted to primary (an item with suppliers but no primary is a valid state only when the user explicitly demotes it).

delete(info, opts \\ [])

Deletes a supplier-info row.

get(uuid)

Fetches a supplier-info row by UUID. Returns nil if not found.

history_for_pair(item_uuid, supplier_uuid)

Returns all rows for an item/supplier pair ordered newest-first.

Includes both current (valid_to: nil) and closed rows so callers can display a full price revision history. Ordered by valid_from descending (nulls last), then inserted_at descending.

list_for_item(item_uuid)

Lists current supplier-info rows for an item, ordered by position then inserted_at.

A row is current when valid_to is nil. Closed rows (produced by revise_unit_cost/3) are excluded; use history_for_pair/2 to retrieve the full history for a specific item/supplier pair.

primary_for_item(item_uuid)

@spec primary_for_item(Ecto.UUID.t()) ::
  PhoenixKitCatalogue.Schemas.ItemSupplierInfo.t() | nil

Returns the current primary supplier-info row for an item, or nil if none is marked primary.

Only current rows (valid_to: nil) are considered — closed revision rows carry is_primary: false by construction and are excluded.

revise_unit_cost(info, new_cost, opts \\ [])

Closes the current junction row and inserts a successor with the new unit cost, creating an append-only price revision history.

Guards

  • {:error, :not_current}info.valid_to is not nil; only the current row can be revised.
  • {:ok, info} (no-op) — new_cost equals info.unit_cost (or both effectively zero when unit_cost is nil), and opts[:currency] is either absent or matches the row's current currency. A currency-only change (same cost, different :currency) still creates a revision.

Transaction

Within a single Ecto.Multi transaction:

  1. The current row is closed: valid_to is set to today and is_primary is forced to false via force_change/3, freeing the partial-unique index inside the tx so the successor can inherit the primary flag.
  2. A successor row is inserted copying item_uuid, supplier_uuid, supplier_source, supplier_name_snapshot, supplier_sku, currency, lead_time_days, min_order_qty, position, and metadata from the closed row. unit_cost is set to new_cost; valid_from is today; valid_to is nil; is_primary inherits the original row's value.

Options

  • :actor_uuid — UUID of the user performing the revision (persisted in the activity log).
  • :source — free-form source label (e.g. "goods_receipt").
  • :source_uuid — UUID of the originating resource (e.g. receipt UUID).
  • :currency — when provided and different from the row's currency, the successor stores the new currency; both old and new currencies are recorded in the activity metadata.

Currency awareness

The successor always keeps the row's own currency unless opts[:currency] is given and differs, in which case the new currency is stored and the activity log captures both.

set_primary(info, opts \\ [])

Promotes a supplier-info row to primary for its item.

Runs in a transaction: clears is_primary on all sibling rows first, then sets is_primary: true on the target. Respects the partial unique index — concurrent callers produce a constraint violation rather than double-marking.

update(info, attrs, opts \\ [])

Updates a supplier-info row.