# `PhoenixKitCatalogue.Catalogue.Suppliers`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/v0.31.3/lib/phoenix_kit_catalogue/catalogue/suppliers.ex#L1)

Suppliers — delivery companies linked to manufacturers via the
many-to-many `phoenix_kit_cat_manufacturer_suppliers` table.

Same lifecycle as manufacturers: hard-delete only, `"active"` /
`"inactive"` status.

### Cross-module supplier resolution

`resolve/1` and `list_all/1` provide a unified view of suppliers across
sources (local `cat_suppliers` + CRM when available). CRM access is
guarded via `Code.ensure_loaded?` / `function_exported?` — the CRM module
is an optional runtime dependency and may not be present.

Public surface is re-exported from `PhoenixKitCatalogue.Catalogue`.

# `active_info_for`

```elixir
@spec active_info_for(Ecto.UUID.t(), Ecto.UUID.t()) ::
  PhoenixKitCatalogue.Schemas.ItemSupplierInfo.t() | nil
```

Returns the *current* junction row for an item/supplier pair, or `nil`.

"Current" means `valid_to` is `nil`. This is the function warehouse calls
to check whether a receipt line's unit price diverges from the catalogued
cost for the same supplier.

# `change_supplier`

```elixir
@spec change_supplier(PhoenixKitCatalogue.Schemas.Supplier.t(), map()) ::
  Ecto.Changeset.t(PhoenixKitCatalogue.Schemas.Supplier.t())
```

Returns a changeset for tracking supplier changes.

# `create_supplier`

```elixir
@spec create_supplier(map(), keyword()) ::
  {:ok, PhoenixKitCatalogue.Schemas.Supplier.t()}
  | {:error, Ecto.Changeset.t(PhoenixKitCatalogue.Schemas.Supplier.t())}
```

Creates a supplier.

## Required attributes

  * `:name` — supplier name (1-255 chars)

## Optional attributes

  * `:description`, `:website`, `:contact_info`, `:notes`
  * `:status` — `"active"` (default) or `"inactive"`
  * `:data` — flexible JSON map

# `crm_company_uuid`

```elixir
@spec crm_company_uuid(map()) :: Ecto.UUID.t() | nil
```

The CRM **company** uuid a supplier reference points at — directly when
the reference is already a party, or through the xref when it is a local
row linked to one. `nil` when there is no company behind it.

This is the join anyone needs to address a supplier as a CRM company:
comments, activity, anything CRM stores per company. Contacts return
`nil` deliberately — a contact is not a company, and pointing
company-scoped records at one would file them against the wrong party.

# `delete_supplier`

```elixir
@spec delete_supplier(PhoenixKitCatalogue.Schemas.Supplier.t(), keyword()) ::
  {:ok, PhoenixKitCatalogue.Schemas.Supplier.t()}
  | {:error, Ecto.Changeset.t(PhoenixKitCatalogue.Schemas.Supplier.t())}
```

Hard-deletes a supplier from the database.

# `get_supplier`

```elixir
@spec get_supplier(Ecto.UUID.t()) :: PhoenixKitCatalogue.Schemas.Supplier.t() | nil
```

Fetches a supplier by UUID. Returns `nil` if not found.

# `get_supplier!`

```elixir
@spec get_supplier!(Ecto.UUID.t()) :: PhoenixKitCatalogue.Schemas.Supplier.t()
```

Fetches a supplier by UUID. Raises `Ecto.NoResultsError` if not found.

# `items_supplied_by`

```elixir
@spec items_supplied_by(Ecto.UUID.t()) :: [PhoenixKitCatalogue.Schemas.Item.t()]
```

Everything a CRM party currently supplies, for the catalogue panel on that
party's page in CRM.

Matches the party's own uuid AND the uuid of any local supplier row that
projects it — the same resolve-through rule `resolve/1` uses, so sourcing
recorded against the old local row before the party existed still shows up.
Current rows only (`valid_to` is null), deleted items excluded.

Returns plain maps, not schemas: the caller is another module rendering a
read-only list, and handing it structs would invite it to write them back.

# `list_all`

```elixir
@spec list_all(keyword()) :: [map()]
```

Lists all suppliers from all available sources as normalized maps.

Each entry has keys `:uuid`, `:name`, `:email`, `:phone`, `:website`,
`:source` (`:crm_company | :crm_contact | :local`). CRM companies then
CRM contacts are listed first (when available), then local suppliers
ordered by name.

CRM access is guarded — when `PhoenixKitCRM.PartyRoles` is not loaded,
only local suppliers are returned.

# `list_suppliers`

```elixir
@spec list_suppliers(keyword()) :: [PhoenixKitCatalogue.Schemas.Supplier.t()]
```

Lists all suppliers, ordered by name.

## Options

  * `:status` — filter by status (e.g. `"active"`, `"inactive"`).

# `primary_for_item`

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

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

# `resolve`

```elixir
@spec resolve(Ecto.UUID.t()) :: {:ok, map()} | :error
```

Resolves a supplier UUID to a unified map regardless of source.

Returns `{:ok, map}` with keys `:uuid`, `:name`, `:email`, `:phone`,
`:website`, `:source` (`:crm | :local`), or `:error` when the supplier
cannot be found in any source.

The CRM branch reports the generic `:crm` tag rather than
`:crm_company` / `:crm_contact` — `PhoenixKitCRM.PartyRoles.get_supplier/1`
resolves either roleable type in one call but its return shape doesn't
say which; `list_all/1` (backed by per-type role listings) is the
source for the more specific tags used elsewhere in this module.

CRM lookup is guarded — when `PhoenixKitCRM.PartyRoles` is not loaded
(the CRM module is an optional runtime dependency), the CRM path is
skipped and only local suppliers are checked.

# `resolve_many`

```elixir
@spec resolve_many([Ecto.UUID.t()]) :: %{required(Ecto.UUID.t()) =&gt; map()}
```

Batch form of `resolve/1`: `%{uuid => resolved_map}` for many supplier uuids
in a bounded number of queries, whatever mix of local and CRM they are.

Unresolvable uuids are simply absent — callers render their stored name
snapshot, or a placeholder.

# `revise_unit_cost`

```elixir
@spec revise_unit_cost(
  PhoenixKitCatalogue.Schemas.ItemSupplierInfo.t(),
  Decimal.t(),
  keyword()
) ::
  {:ok, PhoenixKitCatalogue.Schemas.ItemSupplierInfo.t()}
  | {:error, :not_current | Ecto.Changeset.t()}
```

Delegates to `ItemSupplierInfos.revise_unit_cost/3`.

This is the stable public surface that warehouse and other consumers should
call. See `ItemSupplierInfos.revise_unit_cost/3` for full documentation.

# `update_supplier`

```elixir
@spec update_supplier(PhoenixKitCatalogue.Schemas.Supplier.t(), map(), keyword()) ::
  {:ok, PhoenixKitCatalogue.Schemas.Supplier.t()}
  | {:error, Ecto.Changeset.t(PhoenixKitCatalogue.Schemas.Supplier.t())}
```

Updates a supplier with the given attributes.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
