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
@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
@spec allowed_default_units() :: [String.t()]
@spec allowed_units() :: [String.t()]
@spec changeset(t() | Ecto.Changeset.t(t()), map()) :: Ecto.Changeset.t(t())
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).
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.
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.
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")
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")