PhoenixKitEcommerce.Shopify.VariantMapper (PhoenixKitEcommerce v0.5.0)

Copy Markdown View Source

Pure mapping from a Shopify Admin API product payload's "options"/ "variants" into the shape PhoenixKitEcommerce.Catalogue.Writer. sync_variants/2 attaches as catalogue attribute sets: one set per real option (Shopify's auto-generated single-option "no variants" product — option name "Title", its lone value "Default Title" — is skipped, same as it never becomes a _option_slots entry on the legacy CSV path), values in the order they first appear across variants[], and a per-value price modifier.

No I/O, no catalogue/entities lookups — build/1 never needs a live set to exist. slug (both the set's and each modifier map's key) is PhoenixKitEcommerce.Catalogue.SetSlug.normalise/1 of the option's OWN name; resolving a raw variant-option VALUE label to a value slug is a separate, stateful step (PhoenixKitEcommerce.Catalogue. ValueResolver) Writer.sync_variants/2 runs afterward, against whatever catalogue set that slug now names.

Modifier rule

For each option, group variants[] by that option's value (variant["option<position>"]); a value's modifier is min(price of variants carrying that value) − min(price of every priced variant on the product). Grouping by MIN rather than "first variant seen" (the legacy Import.OptionBuilder's rule, correct only when a single option drives price) matters here because two options combine on one variant — the cheapest variant carrying value X is the fair anchor for X's modifier regardless of what its OTHER option happened to be. The overall cheapest value's modifier is exactly Decimal.new("0.00") (equal minima, not a rounded near-zero) as long as Shopify's own price strings carry two decimals, which Decimal.sub/2 preserves.

Summary

Functions

Builds %{sets: [...], modifiers: %{set_slug => %{label => Decimal}}} from shopify_product's "options" and "variants". Both keys default to [] when absent (a payload with no options at all — every variant on the default "Title" option — yields sets: [], modifiers: %{}).

Types

set()

@type set() :: %{
  name: String.t(),
  slug: String.t(),
  values: [String.t()],
  position: pos_integer()
}

t()

@type t() :: %{
  sets: [set()],
  modifiers: %{required(String.t()) => %{required(String.t()) => Decimal.t()}}
}

Functions

build(shopify_product)

@spec build(map()) :: t()

Builds %{sets: [...], modifiers: %{set_slug => %{label => Decimal}}} from shopify_product's "options" and "variants". Both keys default to [] when absent (a payload with no options at all — every variant on the default "Title" option — yields sets: [], modifiers: %{}).