Class: SpreeSquare::CatalogObjectMapper
- Inherits:
-
Object
- Object
- SpreeSquare::CatalogObjectMapper
- Defined in:
- app/services/spree_square/catalog_object_mapper.rb
Overview
Maps single Square catalog objects (CATEGORY, ITEM, ITEM_VARIATION) onto Spree records. Square is the source of truth for the menu — this is a one-way mirror, never the reverse.
related_objects_by_id is a lookup (built by CatalogImporter from a
SearchCatalogObjects response's related_objects) used to resolve
references like an item's category_id to the full CatalogObjectCategory,
or an image id to its CatalogObjectImage.
Constant Summary collapse
- VARIATION_OPTION_TYPE_NAME =
One shared OptionType for any item that has more than one variation (e.g. sizes). Single-variation items skip this entirely and use the product's master variant — avoids OptionType/OptionValue bookkeeping for the common case, since most menu items don't have real variations.
'square_variation'.freeze
Instance Method Summary collapse
-
#initialize(related_objects_by_id: {}) ⇒ CatalogObjectMapper
constructor
A new instance of CatalogObjectMapper.
- #map_category(square_object) ⇒ Object
- #map_item(square_object) ⇒ Object
- #map_modifier(square_object, modifier_list) ⇒ Object
-
#map_modifier_list(square_object) ⇒ Object
A Square MODIFIER_LIST embeds its full MODIFIER objects inline (same pattern as ITEM#variations) — no follow-up API call needed.
-
#map_tax(square_object) ⇒ Object
Upserts a Square CatalogTax into SpreeSquare::TaxMapping — a pure mirror (percentage/inclusion/enabled), category-agnostic.
- #map_variation(square_object, product) ⇒ Object
Constructor Details
#initialize(related_objects_by_id: {}) ⇒ CatalogObjectMapper
Returns a new instance of CatalogObjectMapper.
19 20 21 22 23 24 |
# File 'app/services/spree_square/catalog_object_mapper.rb', line 19 def initialize(related_objects_by_id: {}) @related_objects_by_id = @store = Spree::Store.default @shipping_category = Spree::ShippingCategory.find_by(name: 'Default') || Spree::ShippingCategory.first @channel = Spree::Channel.find_by(code: 'online') || Spree::Channel.first end |
Instance Method Details
#map_category(square_object) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'app/services/spree_square/catalog_object_mapper.rb', line 26 def map_category(square_object) data = square_object.category_data name = data.name.presence || 'Uncategorized' mapping = SpreeSquare::TaxonMapping.find_or_initialize_by(square_category_id: square_object.id) # Square's SearchCatalogObjects index is only eventually consistent — # a category deleted and recreated (or, as observed in practice, a # stale duplicate momentarily reappearing alongside the fresh one) can # surface a second CatalogObject with a different id but the same # name before a TaxonMapping exists for it. Category name+store is # unique in Spree (see Spree::Category#requires_taxonomy? — no # taxonomy, so uniqueness is scoped to store_id), so falling back to # an existing same-named category avoids a hard crash on that race # instead of trying (and failing) to create a duplicate. taxon = mapping.taxon || Spree::Category.find_by(store: @store, name: name) || Spree::Category.new(store: @store) taxon.name = name taxon.save! mapping.taxon = taxon mapping.square_version = square_object.version mapping.save! taxon end |
#map_item(square_object) ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'app/services/spree_square/catalog_object_mapper.rb', line 118 def map_item(square_object) data = square_object.item_data mapping = SpreeSquare::CatalogMapping.find_or_initialize_by( square_catalog_object_id: square_object.id, square_object_type: SpreeSquare::CatalogMapping::ITEM ) return mapping.product if mapping.persisted? && mapping.stale?(square_object.version) product = mapping.product || Spree::Product.new( store: @store, shipping_category: @shipping_category, status: 'active', slug: generate_slug(data.name, square_object.id) ) product.name = data.name.presence || 'Untitled item' product.description = data.description product.tax_category = resolve_tax_category(data) product.save! publish!(product) assign_taxons!(product, data) assign_modifier_lists!(product, data) mapping.product = product mapping.square_version = square_object.version mapping.last_synced_at = Time.current mapping.save! Array(data.variations).each { |variation| map_variation(variation, product) } import_primary_image(product, data.image_ids&.first) if product.images.empty? revalidate!(product) product end |
#map_modifier(square_object, modifier_list) ⇒ Object
67 68 69 70 71 72 73 74 75 76 |
# File 'app/services/spree_square/catalog_object_mapper.rb', line 67 def map_modifier(square_object, modifier_list) data = square_object.modifier_data modifier = SpreeSquare::Modifier.find_or_initialize_by(square_modifier_id: square_object.id) modifier.modifier_list = modifier_list modifier.name = data.name.presence || 'Option' modifier.price_cents = data.price_money&.amount || 0 modifier.square_version = square_object.version modifier.save! modifier end |
#map_modifier_list(square_object) ⇒ Object
A Square MODIFIER_LIST embeds its full MODIFIER objects inline (same pattern as ITEM#variations) — no follow-up API call needed.
53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'app/services/spree_square/catalog_object_mapper.rb', line 53 def map_modifier_list(square_object) data = square_object.modifier_list_data list = SpreeSquare::ModifierList.find_or_initialize_by(square_modifier_list_id: square_object.id) list.name = data.name.presence || 'Options' list.selection_type = data.selection_type.presence || SpreeSquare::ModifierList::SINGLE list.min_selected_modifiers = data.min_selected_modifiers list.max_selected_modifiers = data.max_selected_modifiers list.square_version = square_object.version list.save! Array(data.modifiers).each { |modifier| map_modifier(modifier, list) } list end |
#map_tax(square_object) ⇒ Object
Upserts a Square CatalogTax into SpreeSquare::TaxMapping — a pure mirror (percentage/inclusion/enabled), category-agnostic. Which Spree::TaxCategory (and therefore Spree::TaxRate) this tax actually backs is resolved per-item in map_item, since that depends on which combination of taxes each item carries — see resolve_tax_category.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'app/services/spree_square/catalog_object_mapper.rb', line 83 def map_tax(square_object) data = square_object.tax_data mapping = SpreeSquare::TaxMapping.find_or_initialize_by(square_tax_id: square_object.id) return mapping if mapping.persisted? && mapping.stale?(square_object.version) was_enabled = mapping.persisted? ? mapping.enabled : nil mapping.name = data.name.presence || 'Square Tax' mapping.percentage = data.percentage.presence&.to_d || 0 mapping.included_in_price = (data.inclusion_type == 'INCLUSIVE') mapping.enabled = data.enabled != false mapping.square_version = square_object.version mapping.last_synced_at = Time.current mapping.save! # Enable/disable first — a re-enable rebuilds the rate's calculator # (see sync_enabled_state!'s own comment for why that's needed), which # the amount/inclusion sync below depends on being present. sync_enabled_state!(mapping, was_enabled) # Already-materialized (and currently live) rates mirror any # percentage/inclusion/name change immediately — Square is the source # of truth here, these rows are never hand-edited in Spree. A rate # that's still disabled is skipped: no live TaxRate exists for it to # update, and the next enable will rebuild it fresh from this mapping # anyway. mapping.tax_category_mappings.includes(:tax_rate).each do |tcm| rate = tcm.tax_rate next unless rate && !rate.paranoia_destroyed? rate.update!(amount: mapping.percentage / 100.0, included_in_price: mapping.included_in_price, name: mapping.name) end mapping end |
#map_variation(square_object, product) ⇒ Object
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'app/services/spree_square/catalog_object_mapper.rb', line 153 def map_variation(square_object, product) data = square_object.item_variation_data mapping = SpreeSquare::CatalogMapping.find_or_initialize_by( square_catalog_object_id: square_object.id, square_object_type: SpreeSquare::CatalogMapping::ITEM_VARIATION ) return mapping.variant if mapping.persisted? && mapping.stale?(square_object.version) variant = mapping.variant || pick_variant(product, data) variant.sku = square_object.id if variant.sku.blank? variant.save! amount = (data.price_money&.amount || 0) / 100.0 variant.set_price(data.price_money&.currency || @store.default_currency, amount) mapping.variant = variant mapping.square_version = square_object.version mapping.last_synced_at = Time.current mapping.save! # A variation's own price/sku change touches the product without # firing Spree's product.updated event (see Revalidator) — the # storefront's product cache needs telling explicitly either way. revalidate!(product) variant end |