PhoenixKitCatalogue.Schemas.Item (PhoenixKitCatalogue v0.1.14)

Copy Markdown View Source

Schema for catalogue items — individual products/materials with SKU and pricing.

V102 added discount + smart-catalogue fields: discount_percentage (per-item override of the catalogue discount, NULL = inherit), and default_value / default_unit (smart-only fallbacks consumed by CatalogueRule.effective/2 when a rule row leaves either leg NULL). The optional :catalogue_rules association mirrors the V102 rules table — only populated for items in a smart catalogue.

Summary

Functions

Returns the Decimal amount subtracted by the discount for an item — i.e. sale_price - final_price. Useful for "You save $X" UI.

Returns the discount percentage that actually applies to an item — the item's own discount_percentage if set, otherwise catalogue_discount.

Returns the markup percentage that actually applies to an item — the item's own markup_percentage if set, otherwise catalogue_markup.

Returns the final price for an item — base_price with the effective markup applied, then the effective discount subtracted.

Calculates the sale price for an item.

Types

t()

@type t() :: %PhoenixKitCatalogue.Schemas.Item{
  __meta__: term(),
  base_price: term(),
  catalogue: term(),
  catalogue_rules: term(),
  catalogue_uuid: term(),
  category: term(),
  category_uuid: term(),
  data: term(),
  default_unit: term(),
  default_value: term(),
  description: term(),
  discount_percentage: term(),
  inserted_at: term(),
  manufacturer: term(),
  manufacturer_uuid: term(),
  markup_percentage: term(),
  name: term(),
  sku: term(),
  status: term(),
  unit: term(),
  updated_at: term(),
  uuid: term()
}

Functions

allowed_default_units()

@spec allowed_default_units() :: [String.t()]

allowed_units()

@spec allowed_units() :: [String.t()]

changeset(item, attrs)

@spec changeset(t() | Ecto.Changeset.t(t()), map()) :: Ecto.Changeset.t(t())

discount_amount(item, catalogue_markup, catalogue_discount)

@spec discount_amount(t(), Decimal.t() | nil, Decimal.t() | nil) :: Decimal.t() | nil

Returns the Decimal amount subtracted by the discount for an item — i.e. sale_price - final_price. Useful for "You save $X" UI.

Returns nil when base_price is nil or when no discount applies (both catalogue and item discount are nil).

effective_discount(item, catalogue_discount)

@spec effective_discount(t(), Decimal.t() | nil) :: Decimal.t() | nil

Returns the discount percentage that actually applies to an item — the item's own discount_percentage if set, otherwise catalogue_discount.

Mirrors effective_markup/2: nil on the item means "inherit the catalogue's discount", any Decimal (including 0) overrides. nil on both sides means "no discount at all".

Use this when you need to display which discount is active without computing the final price.

effective_markup(item, catalogue_markup)

@spec effective_markup(t(), Decimal.t() | nil) :: Decimal.t() | nil

Returns the markup percentage that actually applies to an item — the item's own markup_percentage if set, otherwise catalogue_markup.

nil on both sides means "no markup at all" and the item should be sold at its base price. Callers that only need to display which markup is active (without computing a price) can use this directly.

final_price(item, catalogue_markup, catalogue_discount)

@spec final_price(t(), Decimal.t() | nil, Decimal.t() | nil) :: Decimal.t() | nil

Returns the final price for an item — base_price with the effective markup applied, then the effective discount subtracted.

The chain is base → markup → discount:

sale_price  = base_price * (1 + effective_markup   / 100)
final_price = sale_price  * (1 -  effective_discount / 100)

catalogue_markup and catalogue_discount are the fallbacks used when the item has no matching override of its own. nil on either side means "no markup / no discount on that leg"; the other leg still applies.

Returns nil when base_price is nil. Result is rounded to 2 decimal places. Percentage values should be Decimals (e.g. Decimal.new("15.0")).

Examples

# 100 * 1.20 * 0.90 = 108.00
Item.final_price(
  %Item{base_price: Decimal.new("100"), markup_percentage: nil, discount_percentage: nil},
  Decimal.new("20"),
  Decimal.new("10")
)
#=> Decimal.new("108.00")

# Per-item discount 0 overrides a catalogue discount of 10 →
# final equals sale_price
Item.final_price(
  %Item{base_price: Decimal.new("100"), discount_percentage: Decimal.new("0")},
  Decimal.new("20"),
  Decimal.new("10")
)
#=> Decimal.new("120.00")

sale_price(item, catalogue_markup)

@spec sale_price(t(), Decimal.t() | nil) :: Decimal.t() | nil

Calculates the sale price for an item.

catalogue_markup is the fallback markup used when the item has no override of its own. The item's markup_percentage takes precedence if set (including an explicit 0, which means "sell at base price even if the catalogue has a markup"). A nil catalogue_markup with a nil item override returns the base price unchanged.

Returns nil if the item has no base price. Both percentage values should be Decimals (e.g., Decimal.new("15.0") for 15%).

Examples

# Item has no override — inherits catalogue's 20%
Item.sale_price(%Item{base_price: Decimal.new("100"), markup_percentage: nil}, Decimal.new("20"))
#=> Decimal.new("120.00")

# Item explicitly overrides to 50% — catalogue markup is ignored
Item.sale_price(%Item{base_price: Decimal.new("100"), markup_percentage: Decimal.new("50")}, Decimal.new("20"))
#=> Decimal.new("150.00")

# Item override of 0 means "sell at base price" even if catalogue marks up
Item.sale_price(%Item{base_price: Decimal.new("100"), markup_percentage: Decimal.new("0")}, Decimal.new("20"))
#=> Decimal.new("100.00")