PhoenixKitEcommerce.ProductSource.Catalogue.View (PhoenixKitEcommerce v0.5.0)

Copy Markdown View Source

Builds hand-built %PhoenixKitEcommerce.Product{} / %PhoenixKitEcommerce.Category{} view-structs from a phoenix_kit_catalogue item/category, so the facade, Options, PriceDisplay, CartItem and the storefront templates read exactly the field names they read today from phoenix_kit_shop_products/ phoenix_kit_shop_categories — no behavior change on their side.

Every function here is pure: no Repo call, no write. product_view/2 and category_view/2 build the struct with struct(Product|Category, fields)__meta__ stays :built, which is exactly what PhoenixKitEcommerce.update_product/2/delete_product/2 refuse.

Accepts duck-typed records (a real %PhoenixKitCatalogue.Schemas.Item{}/ %PhoenixKitCatalogue.Schemas.Category{}, or a plain map with the same keys) — nothing here pattern-matches on the catalogue structs, so tests can feed plain maps without the optional phoenix_kit_catalogue dependency loaded.

Summary

Functions

Builds a %Category{} view-struct from a catalogue category.

Synthesizes the legacy metadata sub-map every option/price-display reader (Options, variant picker, CartItem.from_product/3) expects: _option_values (labels in the item's stored selection order), _price_modifiers (slug-keyed ecommerce.price_modifiers swapped to the label keys those readers match on), _option_labels (%{set_slug => set display name}, read straight off sets — see the moduledoc note on product_view/2's :language, since a set's OWN translated name has to already be sitting on it by the time it gets here) and _value_slugs (%{set_slug => %{label => value slug}}, every value in the set, not only the ones selected — Block 6/7's future slug-based selected_specs reads this; nothing in THIS block writes it back). All four computed fresh from sets on every call, merged over whatever else data["ecommerce"]["legacy_metadata"] snapshotted (_option_slots, _image_mappings, _price_display, …) — minus those same four keys, so a stale snapshot never shadows the live attachment state.

Builds a %Product{} view-struct from a catalogue item.

Functions

category_view(category, opts \\ [])

@spec category_view(map(), keyword()) :: PhoenixKitEcommerce.Category.t()

Builds a %Category{} view-struct from a catalogue category.

opts[:parent] sets the :parent association field (a %Category{} view-struct, or nil for a root category / when the caller didn't ask for it) — a view-struct can never be Repo.preload/2'd, so leaving the default Ecto.Association.NotLoaded in place (as every OTHER unset belongs_to/has_many field on this struct still does) would crash the first template that does a plain if category.parent do truthy check, same as PhoenixKitEcommerce.ProductSource.Catalogue's single_category/2 already resolves :category for products.

:storefront_filters is read straight off data["ecommerce"]["storefront_filters"] (%{} when absent) — see PhoenixKitEcommerce.merge_storefront_filters/2 for how it overrides the global filter config.

legacy_metadata(item, sets, language \\ nil)

@spec legacy_metadata(map(), list() | map(), String.t() | nil) :: map()

Synthesizes the legacy metadata sub-map every option/price-display reader (Options, variant picker, CartItem.from_product/3) expects: _option_values (labels in the item's stored selection order), _price_modifiers (slug-keyed ecommerce.price_modifiers swapped to the label keys those readers match on), _option_labels (%{set_slug => set display name}, read straight off sets — see the moduledoc note on product_view/2's :language, since a set's OWN translated name has to already be sitting on it by the time it gets here) and _value_slugs (%{set_slug => %{label => value slug}}, every value in the set, not only the ones selected — Block 6/7's future slug-based selected_specs reads this; nothing in THIS block writes it back). All four computed fresh from sets on every call, merged over whatever else data["ecommerce"]["legacy_metadata"] snapshotted (_option_slots, _image_mappings, _price_display, …) — minus those same four keys, so a stale snapshot never shadows the live attachment state.

sets accepts the same shapes product_view/2's :sets option does. language (default nil) picks which translation of each VALUE's label _option_values/_price_modifiers/_value_slugs key on — nil keeps every one of them exactly as product_view/2 built it before this option existed. _option_labels doesn't take language at all: it reads whatever :name already sits on each set in sets.

product_view(item, opts \\ [])

@spec product_view(map(), keyword()) :: PhoenixKitEcommerce.Product.t()

Builds a %Product{} view-struct from a catalogue item.

opts[:sets] is a PhoenixKitCatalogue.Catalogue.AttributeSets.resolve_for_item/2 result (%{schema_version: _, sets: [...]}) — or, for tests and other callers that already have the per-item list, the bare sets list itself. opts[:category] is a category_view/2 result, attached as :category when the caller preloaded one.

opts[:language] (a language code, or nil for the untranslated labels sets already carries) picks the language metadata's _option_values/_price_modifiers/_value_slugs use for each attribute-set VALUE's label — see legacy_metadata/3. It does NOT translate a set's own :name (_option_labels, below) — a value's translation lives right on it (values[].extras, whatever resolve_set/2 read off the value's EntityData.data), so this pure module can pick it with a plain map lookup, but a set's settings["translations"] isn't part of the resolved shape at all; translating :name needs an actual read (ProductSource.Catalogue.Query.set_display_names/2) that only the caller building sets (ProductSource.Catalogue) can do before handing them to this pure function.

opts[:base_currency] overrides the shop's configured base currency code used as the fallback when the item's own data["ecommerce"]["currency"] is absent — every real caller omits it (falling through to base_currency_code/0, which reads PhoenixKitEcommerce.get_base_currency/0), so this stays a pure function for callers (tests included) that want to avoid that DB read entirely.

opts[:languages] overrides which language codes language_keys/2 treats as enabled (see its doc) — every real caller omits it (falling through to Translations.enabled_languages/0, itself backed by the Languages module's settings), so tests can pin a fixed set of languages without that dependency either.