PhoenixKitCatalogue.Catalogue (PhoenixKitCatalogue v0.1.9)

Copy Markdown View Source

Context module for managing catalogues, manufacturers, suppliers, categories, and items.

Soft-Delete System

Catalogues, categories, and items support soft-delete via a status field set to "deleted". Manufacturers and suppliers use hard-delete only (they are reference data).

Cascade behaviour

Downward cascade on trash/permanently_delete:

  • Trashing a catalogue → trashes all its categories and their items
  • Trashing a category → trashes all its items
  • Permanently deleting follows the same cascade but removes from DB

Upward cascade on restore:

  • Restoring an item → restores its parent category if deleted
  • Restoring a category → restores its parent catalogue if deleted, plus all items

All cascading operations are wrapped in database transactions.

Usage from IEx

alias PhoenixKitCatalogue.Catalogue

# Create a full hierarchy
{:ok, cat} = Catalogue.create_catalogue(%{name: "Kitchen"})
{:ok, category} = Catalogue.create_category(%{name: "Frames", catalogue_uuid: cat.uuid})
{:ok, item} = Catalogue.create_item(%{name: "Oak Panel", category_uuid: category.uuid, base_price: 25.50})

# Soft-delete and restore
{:ok, _} = Catalogue.trash_catalogue(cat)   # cascades to category + item
{:ok, _} = Catalogue.restore_catalogue(cat)  # cascades back

# Move operations
{:ok, _} = Catalogue.move_category_to_catalogue(category, other_catalogue_uuid)
{:ok, _} = Catalogue.move_item_to_category(item, other_category_uuid)

Summary

Functions

Counts non-deleted categories for a catalogue.

Returns a map of catalogue_uuid => non_deleted_category_count, in a single query. Useful for displaying category counts alongside a catalogue list (e.g. in the import wizard's catalogue picker) without N+1 lookups.

Returns a changeset for tracking catalogue changes.

Returns a changeset for tracking category changes.

Returns a changeset for tracking item changes.

Returns a changeset for tracking manufacturer changes.

Returns a changeset for tracking supplier changes.

Returns the total number of items across all non-deleted catalogues that match a search query, using the same matching rules as search_items/2. Runs independently of :limit/:offset.

Returns the total number of items in a catalogue that match a search query, using the same matching rules as search_items_in_catalogue/3.

Returns the total number of non-deleted items in a category that match a search query, using the same matching rules as search_items_in_category/3. Runs independently of :limit/:offset.

Creates a category within a catalogue.

Creates a manufacturer.

Hard-deletes a catalogue. Prefer trash_catalogue/1 for soft-delete.

Hard-deletes a category. Prefer trash_category/1 for soft-delete.

Hard-deletes an item. Prefer trash_item/1 for soft-delete.

Hard-deletes a manufacturer from the database.

Hard-deletes a supplier from the database.

Returns the count of soft-deleted catalogues.

Counts deleted categories for a catalogue.

Total count of deleted entities (items + categories) for a catalogue.

Counts deleted items in a catalogue, including items without a category.

Fetches a catalogue by UUID without preloading categories or items. Raises Ecto.NoResultsError if not found. Prefer this over get_catalogue!/2 in read paths that don't need the nested preloads (e.g. the infinite-scroll detail view, which pages categories and items separately).

Fetches a catalogue by UUID without preloads. Returns nil if not found.

Fetches a catalogue by UUID with preloaded categories and items. Raises Ecto.NoResultsError if not found.

Fetches a category by UUID. Returns nil if not found.

Fetches a category by UUID. Raises Ecto.NoResultsError if not found.

Fetches an item by UUID without preloads. Returns nil if not found.

Fetches an item by UUID with preloaded category and manufacturer. Raises Ecto.NoResultsError if not found.

Fetches a manufacturer by UUID. Returns nil if not found.

Fetches a manufacturer by UUID. Raises Ecto.NoResultsError if not found.

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

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

Gets translated field data for a record in a specific language.

Counts non-deleted items in a catalogue, including items without a category.

Counts items in a single category (ignoring its catalogue scope).

Returns a map of %{catalogue_uuid => non_deleted_item_count} for all catalogues.

Returns a map of %{category_uuid => item_count} for every category in a catalogue in a single grouped query. Used by the infinite-scroll detail view so each category card can show its total count without a separate per-card round trip.

Returns pricing info for an item within a catalogue.

Creates a many-to-many link between a manufacturer and a supplier.

Returns a list of manufacturer UUIDs linked to a supplier.

Returns a list of supplier UUIDs linked to a manufacturer.

Lists all non-deleted categories across all non-deleted catalogues.

Lists catalogues, ordered by name. Excludes deleted by default.

Lists non-deleted categories for a catalogue, ordered by position then name.

Lists categories for a catalogue without preloading items, ordered by position then name. Used by the infinite-scroll detail view to walk categories in display order without fetching potentially thousands of items up front.

Lists all non-deleted items across all catalogues, ordered by name.

Lists non-deleted items for a catalogue, ordered by category position then item name. Includes uncategorized items (those with no category) at the end.

Lists non-deleted items for a category, ordered by name.

Lists a page of items for a single category, ordered by name.

Lists all manufacturers, ordered by name.

Lists all manufacturers linked to a supplier, ordered by name.

Lists all suppliers, ordered by name.

Lists all suppliers linked to a manufacturer, ordered by name.

Lists uncategorized items (no category assigned) for a specific catalogue.

Lists a page of uncategorized items for a catalogue, ordered by name.

Moves a category (and all its items) to a different catalogue.

Moves an item to a different category.

Returns the next available position for a new category in a catalogue.

Permanently deletes a catalogue and all its contents from the database.

Permanently deletes a category and all its items from the database.

Permanently deletes an item from the database. This cannot be undone.

Restores a soft-deleted catalogue by setting its status to "active".

Restores a soft-deleted category by setting its status to "active".

Restores a soft-deleted item by setting its status to "active".

Searches items across all non-deleted catalogues.

Searches items within a specific catalogue.

Searches items within a specific category.

Updates the multilang data field for a record with language-specific field data.

Atomically swaps the positions of two categories within a transaction.

Syncs the supplier links for a manufacturer to match the given list of supplier UUIDs.

Syncs the manufacturer links for a supplier to match the given list of manufacturer UUIDs.

Soft-deletes a catalogue by setting its status to "deleted".

Soft-deletes a category by setting its status to "deleted".

Soft-deletes an item by setting its status to "deleted".

Bulk soft-deletes all non-deleted items in a category.

Counts non-deleted uncategorized items for a catalogue (items with category_uuid IS NULL). Used to decide whether the infinite-scroll detail view needs to show an "Uncategorized" card at all.

Removes the link between a manufacturer and a supplier.

Updates a catalogue with the given attributes.

Updates a category with the given attributes.

Updates an item with the given attributes.

Updates a manufacturer with the given attributes.

Updates a supplier with the given attributes.

Functions

category_count_for_catalogue(catalogue_uuid)

Counts non-deleted categories for a catalogue.

category_counts_by_catalogue()

Returns a map of catalogue_uuid => non_deleted_category_count, in a single query. Useful for displaying category counts alongside a catalogue list (e.g. in the import wizard's catalogue picker) without N+1 lookups.

change_catalogue(catalogue, attrs \\ %{})

Returns a changeset for tracking catalogue changes.

change_category(category, attrs \\ %{})

Returns a changeset for tracking category changes.

change_item(item, attrs \\ %{})

Returns a changeset for tracking item changes.

change_manufacturer(manufacturer, attrs \\ %{})

Returns a changeset for tracking manufacturer changes.

change_supplier(supplier, attrs \\ %{})

Returns a changeset for tracking supplier changes.

count_search_items(query)

Returns the total number of items across all non-deleted catalogues that match a search query, using the same matching rules as search_items/2. Runs independently of :limit/:offset.

Examples

Catalogue.count_search_items("oak")
#=> 1204

count_search_items_in_catalogue(catalogue_uuid, query)

Returns the total number of items in a catalogue that match a search query, using the same matching rules as search_items_in_catalogue/3.

Useful for paginating or driving "N of M" summaries alongside paged search results. Runs independently of :limit/:offset — this is the unbounded total.

Examples

Catalogue.count_search_items_in_catalogue(catalogue_uuid, "panel")
#=> 237

count_search_items_in_category(category_uuid, query)

Returns the total number of non-deleted items in a category that match a search query, using the same matching rules as search_items_in_category/3. Runs independently of :limit/:offset.

Examples

Catalogue.count_search_items_in_category(category_uuid, "panel")
#=> 42

create_catalogue(attrs, opts \\ [])

Creates a catalogue.

Required attributes

  • :name — catalogue name (1-255 chars)

Optional attributes

  • :description — text description
  • :status"active" (default), "archived", or "deleted"
  • :data — flexible JSON map

Examples

Catalogue.create_catalogue(%{name: "Kitchen Furniture"})

create_category(attrs, opts \\ [])

Creates a category within a catalogue.

Required attributes

  • :name — category name (1-255 chars)
  • :catalogue_uuid — the parent catalogue

Optional attributes

  • :description, :position (default 0), :status ("active" or "deleted")
  • :data — flexible JSON map

Examples

Catalogue.create_category(%{name: "Frames", catalogue_uuid: catalogue.uuid})

create_item(attrs, opts \\ [])

Creates an item.

Required attributes

  • :name — item name (1-255 chars)
  • :catalogue_uuid — the parent catalogue (required). Auto-derived from :category_uuid when omitted and a category is provided.

Optional attributes

  • :description — text description
  • :sku — stock keeping unit (unique, max 100 chars)
  • :base_price — decimal, must be >= 0 (cost/purchase price before markup)
  • :unit"piece" (default), "m2", or "running_meter"
  • :status"active" (default), "inactive", "discontinued", or "deleted"
  • :category_uuid — the parent category (optional — leave nil for uncategorized items)
  • :manufacturer_uuid — the manufacturer (optional)
  • :data — flexible JSON map

Examples

Catalogue.create_item(%{name: "Oak Panel 18mm", catalogue_uuid: cat.uuid, base_price: 25.50})
Catalogue.create_item(%{name: "Hinge", category_uuid: category.uuid, manufacturer_uuid: m.uuid})

create_manufacturer(attrs, opts \\ [])

Creates a manufacturer.

Required attributes

  • :name — manufacturer name (1-255 chars)

Optional attributes

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

Examples

Catalogue.create_manufacturer(%{name: "Blum", website: "https://blum.com"})

create_supplier(attrs, opts \\ [])

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

Examples

Catalogue.create_supplier(%{name: "Regional Distributors"})

delete_catalogue(catalogue, opts \\ [])

Hard-deletes a catalogue. Prefer trash_catalogue/1 for soft-delete.

delete_category(category, opts \\ [])

Hard-deletes a category. Prefer trash_category/1 for soft-delete.

delete_item(item, opts \\ [])

Hard-deletes an item. Prefer trash_item/1 for soft-delete.

delete_manufacturer(manufacturer, opts \\ [])

Hard-deletes a manufacturer from the database.

delete_supplier(supplier, opts \\ [])

Hard-deletes a supplier from the database.

deleted_catalogue_count()

Returns the count of soft-deleted catalogues.

deleted_category_count_for_catalogue(catalogue_uuid)

Counts deleted categories for a catalogue.

deleted_count_for_catalogue(catalogue_uuid)

Total count of deleted entities (items + categories) for a catalogue.

Used to determine whether to show the "Deleted" tab.

deleted_item_count_for_catalogue(catalogue_uuid)

Counts deleted items in a catalogue, including items without a category.

fetch_catalogue!(uuid)

Fetches a catalogue by UUID without preloading categories or items. Raises Ecto.NoResultsError if not found. Prefer this over get_catalogue!/2 in read paths that don't need the nested preloads (e.g. the infinite-scroll detail view, which pages categories and items separately).

get_catalogue(uuid)

Fetches a catalogue by UUID without preloads. Returns nil if not found.

get_catalogue!(uuid, opts \\ [])

Fetches a catalogue by UUID with preloaded categories and items. Raises Ecto.NoResultsError if not found.

Options

  • :mode:active (default) or :deleted
    • :active — preloads non-deleted categories with non-deleted items
    • :deleted — preloads all categories with only deleted items (so you can see which categories contain trashed items)

Examples

Catalogue.get_catalogue!(uuid)                  # active view
Catalogue.get_catalogue!(uuid, mode: :deleted)  # deleted view

get_category(uuid)

Fetches a category by UUID. Returns nil if not found.

get_category!(uuid)

Fetches a category by UUID. Raises Ecto.NoResultsError if not found.

get_item(uuid)

Fetches an item by UUID without preloads. Returns nil if not found.

get_item!(uuid)

Fetches an item by UUID with preloaded category and manufacturer. Raises Ecto.NoResultsError if not found.

get_manufacturer(uuid)

Fetches a manufacturer by UUID. Returns nil if not found.

get_manufacturer!(uuid)

Fetches a manufacturer by UUID. Raises Ecto.NoResultsError if not found.

get_supplier(uuid)

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

get_supplier!(uuid)

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

get_translation(record, lang_code)

Gets translated field data for a record in a specific language.

Returns merged data (primary language as base + overrides for the requested language).

Examples

data = Catalogue.get_translation(catalogue, "ja")
# => %{"_name" => "キッチン", "_description" => "..."}

item_count_for_catalogue(catalogue_uuid)

Counts non-deleted items in a catalogue, including items without a category.

item_count_for_category(category_uuid, opts \\ [])

Counts items in a single category (ignoring its catalogue scope).

Used by the infinite-scroll detail view to show the total under each category header (the number in "Category Name (N items)") without loading the items themselves.

Options

  • :mode:active (default) or :deleted

item_counts_by_catalogue()

Returns a map of %{catalogue_uuid => non_deleted_item_count} for all catalogues.

Single-query batch version of item_count_for_catalogue/1 — avoids N+1 when displaying item counts alongside a catalogue list. Includes items both in categories and directly attached to a catalogue (uncategorized).

item_counts_by_category_for_catalogue(catalogue_uuid, opts \\ [])

Returns a map of %{category_uuid => item_count} for every category in a catalogue in a single grouped query. Used by the infinite-scroll detail view so each category card can show its total count without a separate per-card round trip.

Items without a category (uncategorized) are excluded here — use uncategorized_count_for_catalogue/2 for those.

Options

  • :mode:active (default) or :deleted

item_pricing(item)

Returns pricing info for an item within a catalogue.

Looks up the catalogue's markup percentage directly on the item, then computes the sale price. Never raises — if the catalogue can't be loaded (e.g. DB hiccup, connection timeout), falls back to 0% markup and logs a warning so the caller still gets a renderable result instead of crashing a template.

Returns a map with:

  • :base_price — the item's stored base price (or nil if unset)
  • :catalogue_markup — the parent catalogue's markup_percentage (the inherited default for items without an override)
  • :item_markup — the item's markup_percentage override, or nil when the item inherits from the catalogue
  • :markup_percentage — the markup actually applied to compute :price — the item's override if set, otherwise the catalogue's
  • :price — the computed sale price (or nil if no base price)

Returns nil for :price if the item has no base price.

Examples

# Item inherits the catalogue's markup
Catalogue.item_pricing(item)
#=> %{
#=>   base_price: Decimal.new("100.00"),
#=>   catalogue_markup: Decimal.new("15.0"),
#=>   item_markup: nil,
#=>   markup_percentage: Decimal.new("15.0"),
#=>   price: Decimal.new("115.00")
#=> }

# Item overrides to 50%
Catalogue.item_pricing(item_with_override)
#=> %{
#=>   base_price: Decimal.new("100.00"),
#=>   catalogue_markup: Decimal.new("15.0"),
#=>   item_markup: Decimal.new("50.0"),
#=>   markup_percentage: Decimal.new("50.0"),
#=>   price: Decimal.new("150.00")
#=> }

linked_manufacturer_uuids(supplier_uuid)

Returns a list of manufacturer UUIDs linked to a supplier.

linked_supplier_uuids(manufacturer_uuid)

Returns a list of supplier UUIDs linked to a manufacturer.

list_all_categories()

Lists all non-deleted categories across all non-deleted catalogues.

Category names are prefixed with their catalogue name (e.g. "Kitchen / Frames"). Useful for item move dropdowns.

list_catalogues(opts \\ [])

Lists catalogues, ordered by name. Excludes deleted by default.

Options

  • :status — when provided, returns only catalogues with this exact status (e.g. "active", "archived", "deleted"). When nil (default), returns all non-deleted catalogues.

Examples

Catalogue.list_catalogues()                     # active + archived
Catalogue.list_catalogues(status: "deleted")    # only deleted
Catalogue.list_catalogues(status: "active")     # only active

list_categories_for_catalogue(catalogue_uuid)

Lists non-deleted categories for a catalogue, ordered by position then name.

Preloads items (non-deleted only).

list_categories_metadata_for_catalogue(catalogue_uuid, opts \\ [])

Lists categories for a catalogue without preloading items, ordered by position then name. Used by the infinite-scroll detail view to walk categories in display order without fetching potentially thousands of items up front.

Options

  • :mode:active (default, excludes deleted categories) or :deleted (all categories — deleted categories can still contain trashed items we want to show).

list_items(opts \\ [])

Lists all non-deleted items across all catalogues, ordered by name.

Preloads category (with catalogue) and manufacturer.

Options

  • :status — filter by status (e.g. "active", "inactive"). When nil (default), returns all non-deleted items.
  • :limit — max results to return (default: no limit)

Examples

Catalogue.list_items()                          # all non-deleted
Catalogue.list_items(status: "active")          # only active
Catalogue.list_items(limit: 100)                # first 100

list_items_for_catalogue(catalogue_uuid)

Lists non-deleted items for a catalogue, ordered by category position then item name. Includes uncategorized items (those with no category) at the end.

Preloads catalogue, category (with catalogue) and manufacturer.

list_items_for_category(category_uuid)

Lists non-deleted items for a category, ordered by name.

Preloads category (with catalogue) and manufacturer.

list_items_for_category_paged(category_uuid, opts \\ [])

Lists a page of items for a single category, ordered by name.

Used by the infinite-scroll detail view; returns at most :limit items starting at :offset. Preloads :catalogue and :manufacturer so the table cell renderers can access them without extra queries.

Options

  • :mode:active (default, excludes deleted items) or :deleted (only deleted items)
  • :offset — default 0
  • :limit — default 50

list_manufacturers(opts \\ [])

Lists all manufacturers, ordered by name.

Options

  • :status — filter by status (e.g. "active", "inactive"). When nil (default), returns all manufacturers.

Examples

Catalogue.list_manufacturers()
Catalogue.list_manufacturers(status: "active")

list_manufacturers_for_supplier(supplier_uuid)

Lists all manufacturers linked to a supplier, ordered by name.

list_suppliers(opts \\ [])

Lists all suppliers, ordered by name.

Options

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

Examples

Catalogue.list_suppliers()
Catalogue.list_suppliers(status: "active")

list_suppliers_for_manufacturer(manufacturer_uuid)

Lists all suppliers linked to a manufacturer, ordered by name.

list_uncategorized_items(catalogue_uuid, opts \\ [])

Lists uncategorized items (no category assigned) for a specific catalogue.

Options

  • :mode:active (default) excludes deleted items; :deleted returns only deleted items.

Examples

Catalogue.list_uncategorized_items(catalogue_uuid)
Catalogue.list_uncategorized_items(catalogue_uuid, mode: :deleted)

list_uncategorized_items_paged(catalogue_uuid, opts \\ [])

Lists a page of uncategorized items for a catalogue, ordered by name.

Same shape as list_items_for_category_paged/2, but for items where category_uuid IS NULL AND catalogue_uuid = ?. Used as the final section of the infinite-scroll detail view.

Options

  • :mode:active (default) or :deleted
  • :offset — default 0
  • :limit — default 50

move_category_to_catalogue(category, target_catalogue_uuid, opts \\ [])

Moves a category (and all its items) to a different catalogue.

Automatically assigns the next available position in the target catalogue.

Examples

{:ok, moved} = Catalogue.move_category_to_catalogue(category, target_catalogue_uuid)

move_item_to_category(item, category_uuid, opts \\ [])

Moves an item to a different category.

If the target category lives in a different catalogue, the item's catalogue_uuid is updated to match. Passing nil for category_uuid detaches the item from any category while keeping it in its current catalogue.

Examples

{:ok, item} = Catalogue.move_item_to_category(item, new_category_uuid)
{:ok, item} = Catalogue.move_item_to_category(item, nil)  # make uncategorized

next_category_position(catalogue_uuid)

Returns the next available position for a new category in a catalogue.

Returns 0 if no categories exist, otherwise max_position + 1.

permanently_delete_catalogue(catalogue, opts \\ [])

Permanently deletes a catalogue and all its contents from the database.

Cascades downward in a transaction:

  1. Hard-deletes all items in the catalogue's categories
  2. Hard-deletes all categories
  3. Hard-deletes the catalogue

This cannot be undone.

Examples

{:ok, _} = Catalogue.permanently_delete_catalogue(catalogue)

permanently_delete_category(category, opts \\ [])

Permanently deletes a category and all its items from the database.

Cascades downward in a transaction: hard-deletes all items, then the category. This cannot be undone.

permanently_delete_item(item, opts \\ [])

Permanently deletes an item from the database. This cannot be undone.

Examples

{:ok, _} = Catalogue.permanently_delete_item(item)

restore_catalogue(catalogue, opts \\ [])

Restores a soft-deleted catalogue by setting its status to "active".

Cascades downward in a transaction:

  1. All deleted categories → status "active"
  2. All deleted items in those categories → status "active"
  3. The catalogue itself → status "active"

Examples

{:ok, catalogue} = Catalogue.restore_catalogue(catalogue)

restore_category(category, opts \\ [])

Restores a soft-deleted category by setting its status to "active".

Cascades both directions in a transaction:

  • Upward: if the parent catalogue is deleted, restores it too
  • Downward: restores all deleted items in this category

Examples

{:ok, _} = Catalogue.restore_category(category)

restore_item(item, opts \\ [])

Restores a soft-deleted item by setting its status to "active".

Cascades upward in a transaction: if the parent category is deleted, restores it too (so the item is visible in the active view).

Examples

{:ok, item} = Catalogue.restore_item(item)

search_items(query, opts \\ [])

Searches items across all non-deleted catalogues.

Matches against item name, description, and SKU using case-insensitive partial matching. Only returns non-deleted items in non-deleted categories of non-deleted catalogues.

Preloads category (with catalogue) and manufacturer.

Returns a list of items ordered by name.

Options

  • :limit — max results to return (default 50)
  • :offset — number of results to skip, for paging (default 0)

Examples

Catalogue.search_items("oak")
Catalogue.search_items("OAK-18", limit: 10)
Catalogue.search_items("oak", limit: 100, offset: 100)

search_items_in_catalogue(catalogue_uuid, query, opts \\ [])

Searches items within a specific catalogue.

Matches against item name, description, and SKU using case-insensitive partial matching. Only returns non-deleted items in non-deleted categories.

Preloads category and manufacturer.

Returns a list of items ordered by category position then item name.

Options

  • :limit — max results to return (default 50)
  • :offset — number of results to skip, for paging (default 0)

Examples

Catalogue.search_items_in_catalogue(catalogue_uuid, "panel")
Catalogue.search_items_in_catalogue(catalogue_uuid, "SKU", limit: 25)
Catalogue.search_items_in_catalogue(catalogue_uuid, "panel", limit: 100, offset: 100)

search_items_in_category(category_uuid, query, opts \\ [])

Searches items within a specific category.

Matches against item name, description, and SKU using case-insensitive partial matching. Only returns non-deleted items.

Preloads category (with catalogue) and manufacturer.

Options

  • :limit — max results to return (default 50)
  • :offset — number of results to skip, for paging (default 0)

Examples

Catalogue.search_items_in_category(category_uuid, "panel")
Catalogue.search_items_in_category(category_uuid, "panel", limit: 100, offset: 100)

set_translation(record, lang_code, field_data, update_fn, opts \\ [])

Updates the multilang data field for a record with language-specific field data.

For primary language: stores ALL fields. For secondary languages: stores only overrides (differences from primary).

The update_fn should be the entity's update function. It receives (record, attrs) for 2-arity or (record, attrs, opts) for 3-arity when activity logging opts are provided.

Examples

Catalogue.set_translation(catalogue, "ja", %{"_name" => "キッチン"}, &Catalogue.update_catalogue/2)
Catalogue.set_translation(catalogue, "ja", %{"_name" => "キッチン"}, &Catalogue.update_catalogue/3, actor_uuid: user.uuid)

swap_category_positions(cat_a, cat_b, opts \\ [])

Atomically swaps the positions of two categories within a transaction.

Examples

{:ok, _} = Catalogue.swap_category_positions(cat_a, cat_b)

sync_manufacturer_suppliers(manufacturer_uuid, supplier_uuids, opts \\ [])

Syncs the supplier links for a manufacturer to match the given list of supplier UUIDs.

Adds missing links and removes extra ones via set difference. Returns {:ok, :synced} on success or {:error, reason} on the first failure.

sync_supplier_manufacturers(supplier_uuid, manufacturer_uuids, opts \\ [])

Syncs the manufacturer links for a supplier to match the given list of manufacturer UUIDs.

Adds missing links and removes extra ones via set difference. Returns {:ok, :synced} on success or {:error, reason} on the first failure.

trash_catalogue(catalogue, opts \\ [])

Soft-deletes a catalogue by setting its status to "deleted".

Cascades downward in a transaction:

  1. All non-deleted items in the catalogue's categories → status "deleted"
  2. All non-deleted categories → status "deleted"
  3. The catalogue itself → status "deleted"

Examples

{:ok, catalogue} = Catalogue.trash_catalogue(catalogue)

trash_category(category, opts \\ [])

Soft-deletes a category by setting its status to "deleted".

Cascades downward in a transaction:

  1. All non-deleted items in this category → status "deleted"
  2. The category itself → status "deleted"

Examples

{:ok, _} = Catalogue.trash_category(category)

trash_item(item, opts \\ [])

Soft-deletes an item by setting its status to "deleted".

Examples

{:ok, item} = Catalogue.trash_item(item)

trash_items_in_category(category_uuid, opts \\ [])

Bulk soft-deletes all non-deleted items in a category.

Returns {count, nil} where count is the number of items affected.

Examples

{3, nil} = Catalogue.trash_items_in_category(category_uuid)

uncategorized_count_for_catalogue(catalogue_uuid, opts \\ [])

Counts non-deleted uncategorized items for a catalogue (items with category_uuid IS NULL). Used to decide whether the infinite-scroll detail view needs to show an "Uncategorized" card at all.

update_catalogue(catalogue, attrs, opts \\ [])

Updates a catalogue with the given attributes.

update_category(category, attrs, opts \\ [])

Updates a category with the given attributes.

update_item(item, attrs, opts \\ [])

Updates an item with the given attributes.

update_manufacturer(manufacturer, attrs, opts \\ [])

Updates a manufacturer with the given attributes.

update_supplier(supplier, attrs, opts \\ [])

Updates a supplier with the given attributes.