# `PhoenixKitCatalogue.Catalogue.SupplierComments`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/v0.19.1/lib/phoenix_kit_catalogue/catalogue/supplier_comments.ex#L1)

Where a supplier's comments about ONE item live.

A supplier supplies several products, and "he promised a discount on this
one" is logged as a comment. That note is about the item × supplier
relation, not about the company — so it is filed under its own
`phoenix_kit_comments` resource, not the CRM company's thread (the
company page keeps its own, unrelated comments).

## The thread uuid

A comment thread is addressed by `{resource_type, resource_uuid}`. The
obvious uuid — the `item_supplier_info` row's — is NOT stable: a price
revision closes the row and inserts a successor with a new uuid, and
removing the supplier closes the current row. So each row carries a
**thread uuid** in `metadata["comment_thread_uuid"]`:

  * minted once, on the pair's first `create/2`;
  * copied to the successor on every `revise_unit_cost/3`;
  * kept when the supplier is removed (the row is closed, not deleted);
  * inherited when the same supplier is attached to the same item again,
    so the discount history resumes rather than starting over.

The key is server-owned. `create/2` and `update/2` stamp it themselves and
ignore any value arriving in attrs — an import or a custom-field save can
neither drop it nor point a row at somebody else's thread. Rows written
before the key existed use their own uuid as the thread until their first
write pins it (`thread_uuid/1`).

## Back-links

The central Comments admin and the Activity feed link a comment back to
its record through a resolver. `PhoenixKitCatalogue.resource_links/0`
registers this module's (`resolve_resources/1`) for the type, and core
discovers that callback on its own — no host configuration.

# `inherited_thread`

```elixir
@spec inherited_thread(Ecto.UUID.t() | nil, Ecto.UUID.t() | nil) ::
  Ecto.UUID.t() | nil
```

The thread an item/supplier pair already has, or `nil` for a pair with no
history. Newest row first — current, then most recently closed — so the
thread people last looked at wins; a row without a key stands in with its
own uuid, the same fallback `thread_uuid/1` applies.

# `resolve_resources`

```elixir
@spec resolve_resources([binary()]) :: %{
  required(binary()) =&gt; %{title: String.t(), path: String.t()}
}
```

Back-link resolver for the central Comments admin: thread uuids to
`%{title, path}` chips. One query; the current row is preferred, a
closed-only thread (removed supplier) still resolves to its newest row;
uuids that match nothing are omitted, which the admin renders as a neutral
chip. Paths are RAW — the comments module applies the URL prefix itself.

# `resource_type`

```elixir
@spec resource_type() :: String.t()
```

The `phoenix_kit_comments` resource type supplier threads are filed under.

# `stamp`

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

Writes the thread uuid into a metadata map, replacing whatever was there.

# `stamp_changeset`

```elixir
@spec stamp_changeset(Ecto.Changeset.t(), Ecto.UUID.t()) :: Ecto.Changeset.t()
```

Stamps the thread onto a changeset's `metadata`, whatever attrs carried.

# `thread_key`

```elixir
@spec thread_key() :: String.t()
```

The reserved `metadata` key carrying the thread uuid.

# `thread_uuid`

```elixir
@spec thread_uuid(map()) :: Ecto.UUID.t()
```

The comment thread a supplier row belongs to.

The stored key when the row carries a valid one; the row's own uuid
otherwise (rows written before the key existed). Never `nil`.

---

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