Class: Edi::MiraklSeller::BestbuyCa::ProductDataPresenter

Inherits:
Edi::MiraklSeller::BaseMiraklPresenter show all
Defined in:
app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb

Overview

Service object: product data presenter.

Constant Summary collapse

HEATER_CATEGORY =

BBYCat is the literal Best Buy category code (CAT_NNNNN), not the human
path you see in their portal. Verified via live /api/hierarchies against
the Outlet shop. Sending the path returns 1001|The category … is unknown.

'CAT_32872'
BATHROOM_CATEGORY =

Heaters & Fireplaces

'CAT_328963'
DEFAULT_PRODUCT_CONDITION =

Brand New for the marketplace storefront. Outlet subclass overrides
this with a per-item lookup against item.condition.

'Brand New'

Instance Attribute Summary

Attributes inherited from Edi::MiraklSeller::BaseMiraklPresenter

#image_locales

Attributes inherited from BasePresenter

#current_account, #options, #url_helper

Instance Method Summary collapse

Methods inherited from Edi::MiraklSeller::BaseMiraklPresenter

#initialize, #video

Methods inherited from BasePresenter

#can?, #capture, #concat, #content_tag, #fa_icon, #h, #initialize, #link_to, #number_to_currency, #present, presents, #r, #safe_present, #simple_format, #u

Constructor Details

This class inherits a constructor from Edi::MiraklSeller::BaseMiraklPresenter

Instance Method Details

#accessory?Boolean

Ember Towel Warmer Bars, stands, and other parts marketed as accessories
in our catalog don't map to a Best Buy product category — they're not
sold as standalone listings on BB. Returning nil from family (via this
predicate) tells the configurator to skip them.

Returns:

  • (Boolean)


107
108
109
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 107

def accessory?
  item.sku.to_s.start_with?('IP-EM-ACC-')
end

#bathroom_material_familyObject



328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 328

def bathroom_material_family
  raw = spec_material.to_s
  case raw
  when /stainless|steel|metal/i    then 'Stainless Steel'
  when /aluminium|aluminum/i       then 'Aluminum'
  when /glass/i                    then 'Glass'
  when /ceramic|porcelain/i        then 'Ceramic'
  when /plastic|acrylic/i          then 'Plastic'
  when /brass/i                    then 'Brass'
  else
    @error_list << "Best Buy bathroom_material_family unmapped for #{item.sku}: #{raw.inspect}"
    nil
  end
end

#bathroom_mirror?Boolean

Heatwave's is_mirror_defogger? covers anti-fog defoggers; LED backlit
mirrors are a separate product line — detect by SKU prefix as a fallback
until/unless an is_led_backlit_mirror? predicate lands on Item. The
-MIR- infix catches Ember infrared-heating-panel/mirror hybrids
(e.g. IP-EM-GLS-MIR-0600).

Returns:

  • (Boolean)


84
85
86
87
88
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 84

def bathroom_mirror?
  item.is_mirror_defogger? ||
    item.sku.to_s.start_with?('MR-') ||
    item.sku.to_s.include?('-MIR-')
end

#bathroom_mirror_dataObject



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 180

def bathroom_mirror_data
  {
    required: {
      _ProductCondition_20257570_CAT_328963_EN: product_condition,
      _Type_17305230_CAT_328963_EN: 'Bathroom Mirror'
    },
    optional: {
      _Height_15404_CAT_35719_EN: dim_cm(:height),
      _HeightInches_25131_CAT_35719_EN: dim_inches(:height),
      _Width_6823_CAT_35719_EN: dim_cm(:width),
      _WidthInches_25132_CAT_35719_EN: dim_inches(:width),
      _Depth_14236_CAT_35719_EN: dim_cm(:depth),
      _DepthInches_25133_CAT_35719_EN: dim_inches(:depth),
      _Weight_5302_CAT_35719_EN: weight_kg,
      _Colour_5105_CAT_35719_EN: spec_color,
      _ColourFamily_30576_CAT_328963_EN: colour_family,
      _Finish_9214_CAT_35719_EN: spec_finish,
      _Material_23642_CAT_35719_EN: spec_material,
      _BathroomMaterialFamily_14021032_CAT_328963_EN: bathroom_material_family,
      _MountingHardwareIncluded_29139_CAT_35719_EN: yes_no(true),
      _CountryofOrigin_26726_CAT_35719_EN: country_of_origin
    }
  }
end

#best_buy_categoryObject

─── Common value helpers ────────────────────────────────────────────



245
246
247
248
249
250
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 245

def best_buy_category
  case family
  when :indoor_heater, :outdoor_heater   then HEATER_CATEGORY
  when :towel_warmer, :bathroom_mirror   then BATHROOM_CATEGORY
  end
end

#colour_familyObject

Best Buy LOV — accepts a small set of strings. Map our raw finish/color
text into the canonical option. Unknowns surface in the error_list so
they can be added explicitly without breaking the upload silently.



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 310

def colour_family
  raw = (spec_color || spec_finish).to_s
  case raw
  when /white|ivory|cream/i        then 'White'
  when /black|matte/i              then 'Black'
  when /silver|chrome|stainless/i  then 'Silver'
  when /gold|brass/i               then 'Gold'
  when /bronze|copper/i            then 'Bronze'
  when /grey|gray/i                then 'Grey'
  when /beige|tan|natural/i        then 'Beige'
  when /brown/i                    then 'Brown'
  when /blue/i                     then 'Blue'
  else
    @error_list << "Best Buy colour_family unmapped for #{item.sku}: #{raw.inspect}"
    nil
  end
end

#country_of_originObject



343
344
345
346
347
348
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 343

def country_of_origin
  # Manufacturer origin isn't currently exposed on Item; leave nil and
  # rely on the optional-flag in Best Buy's schema. Wire in once
  # `item.country_of_manufacture` (or similar) lands.
  nil
end

#dim_cm(field) ⇒ Object



281
282
283
284
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 281

def dim_cm(field)
  v = item.spec_value(field, output_unit: 'cm')
  v.presence && v.to_f.round(2)
end

#dim_inches(field) ⇒ Object



286
287
288
289
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 286

def dim_inches(field)
  v = item.spec_value(field, output_unit: 'inch')
  v.presence && v.to_f.round(2)
end

#electric_heater_technologyObject



358
359
360
361
362
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 358

def electric_heater_technology
  # LOV: Infrared, Convection, Radiant, etc. Default to nil rather than
  # guess wrong; Best Buy allows the field empty for non-applicable items.
  nil
end

#familyObject

─── Family dispatch ──────────────────────────────────────────────────



60
61
62
63
64
65
66
67
68
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 60

def family
  return nil              if accessory?
  return :towel_warmer    if item.is_towel_warmer?
  return :bathroom_mirror if bathroom_mirror?
  return :outdoor_heater  if outdoor_heater?
  return :indoor_heater   if indoor_heater?

  nil
end

#family_dataObject



70
71
72
73
74
75
76
77
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 70

def family_data
  case family
  when :indoor_heater   then indoor_heater_data
  when :outdoor_heater  then outdoor_heater_data
  when :towel_warmer    then towel_warmer_data
  when :bathroom_mirror then bathroom_mirror_data
  end
end

#french_compliant_flagObject



270
271
272
273
274
275
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 270

def french_compliant_flag
  # Heatwave doesn't track French-compliance per SKU yet. Default to 'Y'
  # (compliant) for newly-shipped marketplace items; the validation team
  # can override per-SKU once the data lands on Item.
  'Y'
end

#generic_dataObject

─── Generic (cross-category) required + optional fields ──────────────



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 113

def generic_data
  {
    required: {
      BBYCat: best_buy_category,
      shop_sku: item.sku.to_s[0, 30],
      _Title_BB_Category_Root_EN: name(locale: :en).to_s[0, 200],
      _Short_Description_BB_Category_Root_EN: short_description_for_bb.to_s[0, 400],
      _Brand_Name_Category_Root_EN: 'WarmlyYours',
      _Primary_UPC_Category_Root_EN: item.ean13,
      _Model_Number_Category_Root_EN: item.sku.to_s[0, 20],
      _Manufacturers_Part_Number_Category_Root_EN: item.sku.to_s[0, 30],
      _MP_Source_Image_URL_01_Category_Root_EN: images[0]
    },
    optional: {
      _Long_Description_BB_Category_Root_EN: detailed_description_html(locale: :en),
      _MP_Source_Image_URL_02_Category_Root_EN: images[1],
      _MP_Source_Image_URL_03_Category_Root_EN: images[2],
      _MP_Source_Image_URL_04_Category_Root_EN: images[3],
      _MP_Source_Image_URL_05_Category_Root_EN: images[4],
      _MP_Source_Image_URL_06_Category_Root_EN: images[5],
      _MP_Source_Image_URL_07_Category_Root_EN: images[6],
      _MP_Source_Image_URL_08_Category_Root_EN: images[7],
      _MP_Source_Image_URL_09_Category_Root_EN: images[8],
      _MP_Source_Image_URL_10_Category_Root_EN: images[9],
      _French_Compliant_Category_Root_EN: french_compliant_flag,
      _Energy_Star_Indicator_Category_Root_EN: nil # TODO: spec_value(:energy_star) once exposed on Item
    }
  }
end

#heater_type_labelObject

Indoor-heater-specific LOV mappings — placeholder defaults that match
the actual values seen in the manual export. Refine when item specs
expose a real value.



354
355
356
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 354

def heater_type_label
  'Radiant' # The LOV includes Fan / Fireplace / Radiant / etc.
end

#imagesObject

Up to 10 images for Best Buy (base presenter caps at 9).



144
145
146
147
148
149
150
151
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 144

def images
  @images ||= item.image_profiles
                  .website_image_profiles
                  .image_order
                  .includes(:image)
                  .limit(10)
                  .map { |p| p.image.image_url(width: 2000, height: 2000, encode_format: :jpeg) }
end

#indoor_heater?Boolean

Returns:

  • (Boolean)


97
98
99
100
101
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 97

def indoor_heater?
  item.is_snow_melting_product? ||
    item.is_pipe_freeze_protection? ||
    item.is_infrared_heating_panel?
end

#indoor_heater_dataObject



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 205

def indoor_heater_data
  {
    required: {
      _ProductCondition_20257570_CAT_32872_EN: product_condition,
      _HeaterFireplaceType_11251344_CAT_32872_EN: 'Indoor Heater'
    },
    optional: {
      _HeatMethodType_12058619_CAT_32872_EN: 'Electric',
      _HeaterType_5644_CAT_32872_EN: heater_type_label,
      _ElectricHeaterTechnology_18431374_CAT_32872_EN: electric_heater_technology,
      # _HeaterDesign_18431376_CAT_32872_EN omitted: Best Buy CA's value
      # list on CAT_32872 doesn't include any of "Wall/Tabletop/Hanging/
      # Floor"; sending "Floor" produced 37 warnings/upload (one per SKU)
      # and the value was dropped on Mirakl's side anyway. Leave empty
      # until we map real values from BB's hierarchy API.
      _Portable_7310_CAT_32872_EN: yes_no(false),
      _Thermostat_5646_CAT_32872_EN: thermostat_label,
      _Height_15404_CAT_32872_EN: dim_cm(:height),
      _HeightInches_25131_CAT_32872_EN: dim_inches(:height),
      _Width_6823_CAT_32872_EN: dim_cm(:width),
      _WidthInches_25132_CAT_32872_EN: dim_inches(:width),
      _Depth_14236_CAT_32872_EN: dim_cm(:depth),
      _DepthInches_25133_CAT_32872_EN: dim_inches(:depth),
      _Weight_5302_CAT_32872_EN: weight_kg,
      _Colour_5105_CAT_32872_EN: spec_color,
      _Material_23642_CAT_32872_EN: spec_material
    }
  }
end

#outdoor_heater?Boolean

Returns:

  • (Boolean)


90
91
92
93
94
95
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 90

def outdoor_heater?
  # De-icing / roof+gutter / ice-shield products. SKU prefix `ETC` covers
  # the Electric Trace Cable de-icing line; tighten when an
  # `is_de_icing_product?` predicate is available.
  item.sku.to_s.start_with?('ETC')
end

#outdoor_heater_dataObject



235
236
237
238
239
240
241
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 235

def outdoor_heater_data
  # Same Best Buy category as indoor (CAT_32872), only HeaterFireplaceType
  # differs. Reuse the indoor block + flip the required type label.
  indoor_heater_data.tap do |data|
    data[:required][:_HeaterFireplaceType_11251344_CAT_32872_EN] = 'Outdoor Heater'
  end
end

#product_conditionObject



252
253
254
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 252

def product_condition
  DEFAULT_PRODUCT_CONDITION
end

#short_description_for_bbObject

Best Buy requires a short description on every product (REQUIRED for all
categories). Some Heatwave items legitimately don't have one populated —
mirror Item#default_short_description and fall back through seo_description
then the item name so the integration stays unblocked instead of failing
the whole upload over a single empty column.



261
262
263
264
265
266
267
268
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 261

def short_description_for_bb
  raw = (item.try(:short_description).presence ||
         item.try(:seo_description).presence  ||
         item.name).to_s
  # Strip simple HTML so a name like "WarmlyYours<sup>®</sup> …" doesn't
  # land in BB's plain-text column with stray markup.
  raw.gsub(/<[^>]+>/, '').squish
end

#spec_colorObject



295
296
297
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 295

def spec_color
  item.spec_value(:color)
end

#spec_finishObject



299
300
301
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 299

def spec_finish
  item.spec_value(:finish)
end

#spec_materialObject



303
304
305
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 303

def spec_material
  item.spec_value(:material)
end

#thermostat_labelObject



364
365
366
367
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 364

def thermostat_label
  # LOV: Digital, Analog, Programmable, None
  nil
end

#to_hObject

Override the base — Best Buy has four (Outlet: five) product families,
not the two-way towel-warmer / infrared-heating-panel split the base assumes.
Returns nil for items we can't classify so the configurator can skip
them instead of aborting the entire feed.



52
53
54
55
56
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 52

def to_h
  return nil if family.nil?

  generic_data.deep_merge(family_data)
end

#towel_warmer_dataObject

─── Per-family data ────────────────────────────────────────────────



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/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 155

def towel_warmer_data
  {
    required: {
      _ProductCondition_20257570_CAT_328963_EN: product_condition,
      _Type_17305230_CAT_328963_EN: 'Towel Warmer'
    },
    optional: {
      _Height_15404_CAT_35719_EN: dim_cm(:height),
      _HeightInches_25131_CAT_35719_EN: dim_inches(:height),
      _Width_6823_CAT_35719_EN: dim_cm(:width),
      _WidthInches_25132_CAT_35719_EN: dim_inches(:width),
      _Depth_14236_CAT_35719_EN: dim_cm(:depth),
      _DepthInches_25133_CAT_35719_EN: dim_inches(:depth),
      _Weight_5302_CAT_35719_EN: weight_kg,
      _Colour_5105_CAT_35719_EN: spec_color,
      _ColourFamily_30576_CAT_328963_EN: colour_family,
      _Finish_9214_CAT_35719_EN: spec_finish,
      _Material_23642_CAT_35719_EN: spec_material,
      _BathroomMaterialFamily_14021032_CAT_328963_EN: bathroom_material_family,
      _MountingHardwareIncluded_29139_CAT_35719_EN: yes_no(true),
      _CountryofOrigin_26726_CAT_35719_EN: country_of_origin
    }
  }
end

#weight_kgObject



291
292
293
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 291

def weight_kg
  item.base_weight_converted(unit: 'kg')&.round(2)
end

#yes_no(value) ⇒ Object



277
278
279
# File 'app/services/edi/mirakl_seller/bestbuy_ca/product_data_presenter.rb', line 277

def yes_no(value)
  value ? 'Yes' : 'No'
end