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_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
cloned_from_id :bigint

Indexes

index_amazon_variations_on_cloned_from_id (cloned_from_id)
index_amazon_variations_on_sku (sku) UNIQUE

Foreign Keys

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

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

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 Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#nameObject (readonly)



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

validates :name, presence: true

#skuObject (readonly)



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

validates :sku, presence: true, uniqueness: true

#variation_theme_nameObject (readonly)



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

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:



80
81
82
83
84
85
86
87
# File 'app/models/amazon_variation.rb', line 80

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



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

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"



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

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.filter_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.max || 1

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

.outdoor_living_schemaObject



185
186
187
188
# File 'app/models/amazon_variation.rb', line 185

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



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
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
# File 'app/models/amazon_variation.rb', line 126

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.



237
238
239
# File 'app/models/amazon_variation.rb', line 237

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



219
220
221
222
# File 'app/models/amazon_variation.rb', line 219

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



180
181
182
183
# File 'app/models/amazon_variation.rb', line 180

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



203
204
205
206
207
# File 'app/models/amazon_variation.rb', line 203

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



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

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



190
191
192
# File 'app/models/amazon_variation.rb', line 190

def variation_theme_name_for_select
  AmazonProductTypeSchema.all_product_type_theme_names
end

.variations_for_selectObject



99
100
101
# File 'app/models/amazon_variation.rb', line 99

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:



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

has_and_belongs_to_many :amazon_browse_nodes

#amazon_delete_variation_listing(catalog) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
# File 'app/models/amazon_variation.rb', line 286

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

rubocop:disable Lint/UnusedMethodArgument



259
260
261
262
# File 'app/models/amazon_variation.rb', line 259

def amazon_json_generator(marketplace_id:, attribute_actions: nil, fba: false, language_tag: nil, business_price_available: true) # rubocop:disable Lint/UnusedMethodArgument
  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



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

def amazon_product_type_in_effect
  amazon_desired_product_type
end

#amazon_pull_listing_information(catalog) ⇒ Object



264
265
266
267
268
269
270
271
272
273
# File 'app/models/amazon_variation.rb', line 264

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



275
276
277
278
279
280
281
282
283
284
# File 'app/models/amazon_variation.rb', line 275

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



248
249
250
# File 'app/models/amazon_variation.rb', line 248

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

#asins_by_marketplaceObject



241
242
243
244
245
246
# File 'app/models/amazon_variation.rb', line 241

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:



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

has_many :catalog_items, through: :items

#catalog_items_for_amazon_seller_marketplace_identifier(marketplace_id) ⇒ Object



252
253
254
255
256
257
# File 'app/models/amazon_variation.rb', line 252

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



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

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

#clonesActiveRecord::Relation<AmazonVariation>

Returns:

See Also:



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

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

#deep_dupObject



68
69
70
71
72
73
74
75
76
77
78
# File 'app/models/amazon_variation.rb', line 68

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:



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

has_many :edi_communication_logs, through: :edi_documents

#edi_documentsActiveRecord::Relation<EdiDocument>

Returns:

See Also:



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

has_many :edi_documents, dependent: :destroy

#itemsActiveRecord::Relation<Item>

Returns:

  • (ActiveRecord::Relation<Item>)

See Also:



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

has_many :items, dependent: :nullify

#possible_product_typesObject



89
90
91
92
93
94
# File 'app/models/amazon_variation.rb', line 89

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



298
299
300
301
# File 'app/models/amazon_variation.rb', line 298

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



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

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]



227
228
229
230
231
232
233
# File 'app/models/amazon_variation.rb', line 227

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