Class: AmazonVariation

Inherits:
ApplicationRecord show all
Extended by:
Memery
Defined in:
app/models/amazon_variation.rb

Overview

== Schema Information

Table name: amazon_variations
Database name: primary

id :bigint not null, primary key
amazon_asin :string
amazon_description :text
amazon_desired_product_type :string(30)
fallback_to_main_image_as_swatch :boolean default(FALSE)
inactive :boolean default(FALSE)
name :string not null
retailer_information :jsonb
sku :string not null
variation_theme_name :string not null
created_at :datetime
updated_at :datetime
amazon_marketplace_id :bigint not null
cloned_from_id :bigint

Indexes

index_amazon_variations_on_amazon_marketplace_id (amazon_marketplace_id)
index_amazon_variations_on_cloned_from_id (cloned_from_id)
index_amazon_variations_on_sku_and_marketplace (sku,amazon_marketplace_id) UNIQUE

Foreign Keys

fk_rails_... (amazon_marketplace_id => amazon_marketplaces.id)
fk_rails_... (cloned_from_id => amazon_variations.id)

Constant Summary collapse

VARIATION_THEME_ATTRIBUTE_MAP =

Maps Amazon variation theme components (e.g., SIZE_NAME, COLOR) to the
corresponding JSON listing generator attribute names used by
Edi::Amazon::JsonListingGenerator::Attributes classes.

Example: theme "SIZE_NAME/COLOR_NAME" resolves to [:size, :color]

{
  'SIZE'          => :size,
  'SIZE_NAME'     => :size,
  'COLOR'         => :color,
  'COLOR_NAME'    => :color,
  'STYLE'         => :style,
  'STYLE_NAME'    => :style,
  'VOLTAGE'       => :voltage,
  'CONFIGURATION' => :configuration,
  'MATERIAL'      => :material,
  'MATERIAL_TYPE' => :material,
  'PATTERN'       => :pattern,
  'PATTERN_NAME'  => :pattern
}.freeze

Instance Attribute Summary collapse

Has many collapse

Has and belongs to many collapse

Belongs to collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#nameObject (readonly)



64
# File 'app/models/amazon_variation.rb', line 64

validates :name, presence: true

#skuObject (readonly)



63
# File 'app/models/amazon_variation.rb', line 63

validates :sku, presence: true, uniqueness: true

#variation_theme_nameObject (readonly)



65
# File 'app/models/amazon_variation.rb', line 65

validates :variation_theme_name, presence: true

Class Method Details

.asin_searchActiveRecord::Relation<AmazonVariation>

A relation of AmazonVariations that are asin search. Active Record Scope

Returns:

See Also:



83
84
85
86
87
88
89
90
# File 'app/models/amazon_variation.rb', line 83

scope :asin_search, ->(asin) {
          where(
              "EXISTS (
                SELECT 1 FROM jsonb_each(retailer_information) AS info
                WHERE info.value->>'asin' = ?
              )", asin
            )
}

.find_or_create_variation_for_item(item) ⇒ Object

find or create a variation sku for the provided item



212
213
214
215
216
217
218
219
# File 'app/models/amazon_variation.rb', line 212

def find_or_create_variation_for_item(item)
  parent_sku = parent_identifiers_for_item(item)[:parent_sku]

  amz_var = AmazonVariation.where(sku: parent_sku).first_or_initialize
  set_default_for_item(amz_var, item)
  amz_var.save!
  amz_var
end

.next_version_sku(sku) ⇒ Object

Generates the next version SKU for cloning
Finds the highest existing version and increments it
"SKU123" with existing "SKU123-v2", "SKU123-v4" -> "SKU123-v5"



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/models/amazon_variation.rb', line 108

def next_version_sku(sku)
  # Extract base SKU (strip any existing -vN suffix)
  base_sku = sku.sub(/-v\d+$/, '')

  # Find all existing versions of this base SKU
  existing_skus = AmazonVariation.where("sku = ? OR sku LIKE ?", base_sku, "#{base_sku}-v%").pluck(:sku)

  # Extract version numbers from existing SKUs
  max_version = existing_skus.map do |existing_sku|
    if existing_sku == base_sku
      1 # The original counts as version 1
    elsif existing_sku =~ /^#{Regexp.escape(base_sku)}-v(\d+)$/
      Regexp.last_match(1).to_i
    end
  end.compact.max || 1

  "#{base_sku}-v#{max_version + 1}"
end

.outdoor_living_schemaObject



187
188
189
190
# File 'app/models/amazon_variation.rb', line 187

def outdoor_living_schema
  schema_path = Rails.root.join 'data/json/schemas/amazon/product_type/outdoor_living.json'
  JSON.parse(File.read(schema_path))
end

.parent_identifiers_for_item(item) ⇒ Object

Generates a variation parent sku



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
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
179
180
# File 'app/models/amazon_variation.rb', line 128

def parent_identifiers_for_item(item)
  item_sku = item.sku
  item.primary_product_line_slug_ltree
  item.product_category_url
  if item_sku.match?(/^TCT.*-KIT-.*-MEM-/) # TCT120-KIT-OP-MEM-030
    _, _, control_type, = item_sku.split('-')
    parent_sku = "TCT-#{control_type}-MEM-KIT"
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TCT.*-KIT-O\w+-/) # a non membrane kit TCT240-KIT-OP-115
    _, _, control_type, = item_sku.split('-')
    parent_sku = "TCT-#{control_type}-KIT"
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TCT.*-KIT-(UWG5|UTN5)-MEM/)
    parent_sku = 'TCT-UWG5-MEM-KIT'
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TCT.*-KIT-(UWG5|UTN5)/)
    parent_sku = 'TCT-UWG5-KIT'
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TRT.*-KIT-O\w+-1.5x/) # TRT120-KIT-OP-1.5x18
    _, _, control_type, = item_sku.split('-')
    parent_sku = "TRT-#{control_type}-FLEX-KIT"
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TRT.*-KIT-O\w+-3/) # e.g TRT120-KIT-ON-3.0x08
    _, _, control_type, = item_sku.split('-')
    parent_sku = "TRT-#{control_type}-EASYMAT-KIT"
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TRT.*-KIT-(UWG5|UTN5)/) # e.g TRT120-KIT-UWG5-3.0x08
    _, _, control_type, = item_sku.split('-')
    parent_sku = "TRT-#{control_type}-FLEX-KIT"
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^WHMA-\d{3}-\d{4}$/) # WHMA-120-0205
    item_sku.split('-')
    parent_sku = 'WHMA'
    model_name = "#{parent_sku} Snow Melting Mat"
  elsif item_sku.match?(/^WHCA-\d{3}-\d{4}$/) # WHCA-120-0043
    item_sku.split('-')
    parent_sku = 'WHCA'
    model_name = "#{parent_sku} Snow Melting Cable"
  elsif item_sku.match?(/^TCT/) && item.is_heating_element?
    parent_sku = 'TCT-CABLE'
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TRT\d{3}-1.5x/) && item.is_heating_element?
    parent_sku = 'TRT-FLEXROLL'
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^TRT.*-[23]/) && item.is_heating_element?
    parent_sku = 'TRT-EASYMAT'
    model_name = "#{parent_sku} Floor Heating Kit"
  elsif item_sku.match?(/^ERT\d{3}-3/) # ERT120-3.0x10
    parent_sku = 'ERT-EASYMAT'
    model_name = "#{parent_sku} Environ Easy Mat"
  end
  { parent_sku:, model_name: }
end

.preview_theme_value_for_item(item, attribute) ⇒ Object

Returns the preview value of a variation theme attribute for a given item.
Mirrors the resolution logic of BaseAttribute#fetch_value without needing a catalog_item.



240
241
242
# File 'app/models/amazon_variation.rb', line 240

def self.preview_theme_value_for_item(item, attribute)
  item.spec_output(:"amazon_#{attribute}").presence || item.spec_output(attribute).presence
end

.set_default_for_item(amazon_variation, item) ⇒ Object



221
222
223
224
# File 'app/models/amazon_variation.rb', line 221

def set_default_for_item(amazon_variation, item)
  amazon_variation.name ||= variation_name_for_item(item)
  amazon_variation.variation_theme_name ||= variation_theme_name_for_item(item)
end

.space_heater_schemaObject



182
183
184
185
# File 'app/models/amazon_variation.rb', line 182

def space_heater_schema
  schema_path = Rails.root.join 'data/json/schemas/amazon/product_type/space_heater.json'
  JSON.parse(File.read(schema_path))
end

.variation_name_for_item(item) ⇒ Object



205
206
207
208
209
# File 'app/models/amazon_variation.rb', line 205

def variation_name_for_item(item)
  title = item.title_for_amazon
  # Remove any sqft
  title.gsub(/[\d\.]+ sq ft/, '').gsub(/[\d\.]+ sqft/, '').gsub(/\(\d{3}V\)/, '').squeeze(' ')
end

.variation_theme_name_for_item(item) ⇒ Object



197
198
199
200
201
202
203
# File 'app/models/amazon_variation.rb', line 197

def variation_theme_name_for_item(item)
  if item.sku.start_with?('TCT', 'TRT', 'ERT')
    'STYLE_NAME/SIZE_NAME' # Style will be hijacked for voltage, since voltage isn't available for space_heaters
  elsif item.sku.start_with?('WHMA', 'WHCA')
    'VOLTAGE/SIZE_NAME'
  end
end

.variation_theme_name_for_selectObject



192
193
194
# File 'app/models/amazon_variation.rb', line 192

def variation_theme_name_for_select
  AmazonProductTypeSchema.all_product_type_theme_names
end

.variations_for_selectObject



101
102
103
# File 'app/models/amazon_variation.rb', line 101

def variations_for_select
  AmazonVariation.order(:sku).map { |av| ["#{av.sku} - #{av.name}", av.id] }
end

Instance Method Details

#amazon_browse_nodesActiveRecord::Relation<AmazonBrowseNode>

Returns:

See Also:



57
# File 'app/models/amazon_variation.rb', line 57

has_and_belongs_to_many :amazon_browse_nodes

#amazon_delete_variation_listing(catalog) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
# File 'app/models/amazon_variation.rb', line 289

def amazon_delete_variation_listing(catalog)
  return { status: :skipped, message: 'Orchestrator could not be loaded' } unless (orchestrator = catalog&.load_orchestrator)

  res = orchestrator.delete_listing_from_catalog_item(amazon_variation: self)

  if res.any?(false)
    { status: :error, message: "Amazon Variation #{id}: could not send DELETE listing data to Amazon!" }
  else
    { status: :success, message: "Amazon Variation #{id}: sent DELETE listing data to Amazon!" }
  end
end

#amazon_json_generator(marketplace_id:, attribute_actions: nil, fba: false, language_tag: nil, business_price_available: true) ⇒ Object



262
263
264
265
# File 'app/models/amazon_variation.rb', line 262

def amazon_json_generator(marketplace_id:, attribute_actions: nil, fba: false, language_tag: nil, business_price_available: true)
  catalog_item = catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id).first
  Edi::Amazon::JsonListingGenerator::Factory.generator_for_variation(self, catalog_item:, marketplace_id:, attribute_actions:, language_tag:, business_price_available:)
end

#amazon_product_type_in_effectObject



306
307
308
# File 'app/models/amazon_variation.rb', line 306

def amazon_product_type_in_effect
  amazon_desired_product_type
end

#amazon_pull_listing_information(catalog) ⇒ Object



267
268
269
270
271
272
273
274
275
276
# File 'app/models/amazon_variation.rb', line 267

def amazon_pull_listing_information(catalog)
  return { status: :skipped, message: 'Orchestrator could not be loaded' } unless (orchestrator = catalog.load_orchestrator)

  res = orchestrator.pull_amazon_variation_listing_information(self)
  if res.any? { |h| h.values.any?(false) }
    { status: :error, message: "Amazon Variation #{id}: could not pull listing data from Amazon!" }
  else
    { status: :success, message: "Amazon Variation #{id}: listing data pulled from Amazon!" }
  end
end

#amazon_send_put_listing_information(catalog) ⇒ Object



278
279
280
281
282
283
284
285
286
287
# File 'app/models/amazon_variation.rb', line 278

def amazon_send_put_listing_information(catalog)
  return { status: :skipped, message: 'Orchestrator could not be loaded' } unless (orchestrator = catalog&.load_orchestrator)

  res = orchestrator.push_listing_from_amazon_variation(self)
  if res.any?(false)
    { status: :error, message: "Amazon Variation #{id}: could not send put listing data to Amazon!" }
  else
    { status: :success, message: "Amazon Variation #{id}: sent put listing data to Amazon!" }
  end
end

#asin_for_marketplace(marketplace_identifier) ⇒ Object



251
252
253
# File 'app/models/amazon_variation.rb', line 251

def asin_for_marketplace(marketplace_identifier)
  retailer_information.dig(marketplace_identifier.to_s, :asin)
end

#asins_by_marketplaceObject



244
245
246
247
248
249
# File 'app/models/amazon_variation.rb', line 244

def asins_by_marketplace
  retailer_information.each_with_object({}) do |(marketplace_identifier, info), hash|
    marketplace = AmazonMarketplace.find_by(marketplace_identifier: marketplace_identifier)
    hash[marketplace&.name] = info[:asin] if marketplace
  end
end

#catalog_itemsActiveRecord::Relation<CatalogItem>

Returns:

See Also:



54
# File 'app/models/amazon_variation.rb', line 54

has_many :catalog_items, through: :items

#catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id) ⇒ Object



255
256
257
258
259
260
# File 'app/models/amazon_variation.rb', line 255

def catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id)
  catalog = Catalog.for_amazon_seller_marketplace_identifier(marketplace_id)
  return [] unless catalog

  catalog_items.where(catalog_id: catalog.id)
end

#cloned_fromAmazonVariation

Clone tracking



60
# File 'app/models/amazon_variation.rb', line 60

belongs_to :cloned_from, class_name: 'AmazonVariation', optional: true

#clonesActiveRecord::Relation<AmazonVariation>

Returns:

See Also:



61
# File 'app/models/amazon_variation.rb', line 61

has_many :clones, class_name: 'AmazonVariation', foreign_key: :cloned_from_id, dependent: :nullify

#deep_dupObject



71
72
73
74
75
76
77
78
79
80
81
# File 'app/models/amazon_variation.rb', line 71

def deep_dup
  deep_clone(
    include: :amazon_browse_nodes,
    except: :retailer_information
  ) do |original, copy|
    if copy.is_a?(AmazonVariation)
      copy.sku = AmazonVariation.next_version_sku(original.sku)
      copy.cloned_from = original
    end
  end
end

#edi_communication_logsActiveRecord::Relation<EdiCommunicationLog>

Returns:

See Also:



56
# File 'app/models/amazon_variation.rb', line 56

has_many :edi_communication_logs, through: :edi_documents

#edi_documentsActiveRecord::Relation<EdiDocument>

Returns:

See Also:



55
# File 'app/models/amazon_variation.rb', line 55

has_many :edi_documents, dependent: :destroy

#itemsActiveRecord::Relation<Item>

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



53
# File 'app/models/amazon_variation.rb', line 53

has_many :items, dependent: :nullify

#possible_product_typesObject



92
93
94
95
96
97
# File 'app/models/amazon_variation.rb', line 92

def possible_product_types
  CatalogItem.joins(store_item: :item)
             .merge(items).where.not(amazon_desired_product_type: nil)
             .distinct.order(:amazon_desired_product_type)
             .pluck(:amazon_desired_product_type)
end

#reported_vendor_sku(_orchestrator_partner) ⇒ Object



301
302
303
304
# File 'app/models/amazon_variation.rb', line 301

def reported_vendor_sku(_orchestrator_partner)
  sku # I think this is fine, regardless of marketplace
  # orchestrator_partner.to_s.index('_ca').present? ? "#{sku}-CA" : sku
end

#to_sObject



310
311
312
# File 'app/models/amazon_variation.rb', line 310

def to_s
  "AmazonVariation[#{id}]"
end

#variation_theme_attributesObject

Returns array of attribute symbols for the current variation theme.
e.g., "SIZE_NAME/COLOR_NAME" => [:size, :color]



230
231
232
233
234
235
236
# File 'app/models/amazon_variation.rb', line 230

def variation_theme_attributes
  return [] if variation_theme_name.blank?

  variation_theme_name.split('/').filter_map do |component|
    VARIATION_THEME_ATTRIBUTE_MAP[component]
  end.uniq
end