Class: Edi::Wayfair::Orchestrator

Inherits:
BaseOrchestrator show all
Defined in:
app/services/edi/wayfair/orchestrator.rb

Overview

Orchestrates the Wayfair EDI integration (US 7083 / CA 24331) over Wayfair's
GraphQL APIs: inventory feeds, purchase-order retrieval/processing, order
acknowledgements and confirmations, packing-slip retrieval, and the daily
Supplier Catalog product-data pull.

Constant Summary collapse

DOUBLE_QUOTE_CHARACTERS_TO_REPLACE =
['"', '', '', '', '', '«', '»', '', '', ''].freeze
SINGLE_QUOTE_CHARACTERS_TO_REPLACE =

last one is not a comma though it looks like it

['', '', '', '', '', ''].freeze
GOLIVE_DATE_STR =

"November 12, 2020"

'November 10, 2020'

Constants inherited from BaseOrchestrator

BaseOrchestrator::DEFAULT_PENDING_DISCONTINUE_LIFETIME, BaseOrchestrator::ORCHESTRATORS, BaseOrchestrator::RECOMMENDED_EXECUTE_FLOW_EVERY_X_HOUR

Instance Attribute Summary

Attributes inherited from BaseOrchestrator

#config, #logger, #options

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseOrchestrator

all_orchestrators_class, build, build_customer_id_to_partner_key_map, cached_build, cached_orchestrators, catalog_id_to_pending_discontinue_lifetime, catalog_ids_edi_enabled, #confirm_outbound_processing?, #customer, #customer_catalog, customer_id_to_partner_key_map, #customer_ids, customer_ids_edi_enabled, customer_ids_with_invoice_message_enabled, #customers, execute_discontinue_flow, execute_flow, execute_inventory_flow, execute_listing_message_feed_flow, execute_order_flow, execute_price_flow, #execute_price_flow, execute_product_data_flow, #initialize, orchestrator_for_customer_id, orchestrators, #pending_discontinue_lifetime, #price_message_enabled?, #product_data_enabled?, #should_execute_flow?, #should_execute_order_flow?, #should_execute_product_data_flow?, #supports_flow?, #test_mode?

Constructor Details

This class inherits a constructor from Edi::BaseOrchestrator

Class Method Details

.partnersHash{Symbol => Hash}

Partner configuration for both Wayfair regions.

Returns:

  • (Hash{Symbol => Hash})

    partner settings keyed by partner symbol



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/services/edi/wayfair/orchestrator.rb', line 23

def self.partners
  {
    wayfair_us: {
      active: true,
      partner: :wayfair_us,
      warehouse_code: '7083',
      customer_id: 175_360,
      transporter: :http_graphql_api,
      transporter_profile: :wayfair_api,
      inventory_transporter: :http_graphql_api,
      inventory_transporter_profile: :wayfair_api,
      inventory_enabled: true,
      order_enabled: true,
      invoice_enabled: false,
      product_data_enabled: true, # Catalog READ live in prod both regions (2026-06-12); daily worker pulls MSRP + canonical PDP URL + taxonomy
      packing_slip_remote_path: 'https://api.wayfair.com/v1/packing_slip',
      support_contact: 'supplierservicedesk@wayfair.com'
    },
    wayfair_ca: {
      active: true,
      partner: :wayfair_ca,
      warehouse_code: '24331',
      customer_id: 2_727_071,
      transporter: :http_graphql_api,
      transporter_profile: :wayfair_api,
      inventory_transporter: :http_graphql_api,
      inventory_transporter_profile: :wayfair_api,
      inventory_enabled: true,
      order_enabled: true,
      invoice_enabled: false,
      product_data_enabled: true, # Catalog READ live in prod both regions (2026-06-12); daily worker pulls MSRP + canonical PDP URL + taxonomy
      packing_slip_remote_path: 'https://api.wayfair.com/v1/packing_slip',
      support_contact: 'supplierservicedesk@wayfair.com'
    }
  }
end

Instance Method Details

#acknowledge_message_enabled?Boolean

Whether order acknowledgement is enabled (follows order flow enablement).

Returns:

  • (Boolean)


263
264
265
# File 'app/services/edi/wayfair/orchestrator.rb', line 263

def acknowledge_message_enabled?
  try(:order_enabled).present?
end

#acknowledge_message_senderEdi::Wayfair::AcknowledgeMessageSender, Edi::NullProcessor



268
269
270
271
272
# File 'app/services/edi/wayfair/orchestrator.rb', line 268

def acknowledge_message_sender
  return Edi::NullProcessor.new(self, :acknowledge_message_sender, options) unless acknowledge_message_enabled?

  AcknowledgeMessageSender.new(self, options)
end

#catalog_item_information_processorEdi::Wayfair::CatalogItemInformationProcessor



454
455
456
# File 'app/services/edi/wayfair/orchestrator.rb', line 454

def catalog_item_information_processor
  CatalogItemInformationProcessor.new(self, options)
end

#catalog_item_information_retrieverEdi::Wayfair::CatalogItemInformationRetriever

Catalog Item Information API methods
Uses the new Supplier Catalog API at https://api.wayfair.io/v1/product-catalog-api/graphql



449
450
451
# File 'app/services/edi/wayfair/orchestrator.rb', line 449

def catalog_item_information_retriever
  CatalogItemInformationRetriever.new(self, options)
end

#catalog_item_information_updaterEdi::Wayfair::CatalogItemInformationUpdater

Note:

The Wayfair inventory API has LIMITED capabilities:

  • CAN update: quantity, discontinued status, product name
  • CANNOT update: dimensions, pricing, images (these are read-only or Wayfair-managed)

Catalog Item Update methods
Uses the inventory mutation to update product attributes



500
501
502
# File 'app/services/edi/wayfair/orchestrator.rb', line 500

def catalog_item_information_updater
  CatalogItemInformationUpdater.new(self, options)
end

#catalog_item_update_senderEdi::Wayfair::CatalogItemUpdateSender

Catalog Update methods
Uses the Product Catalog Update API with updateMarketSpecificCatalogItems mutation



637
638
639
# File 'app/services/edi/wayfair/orchestrator.rb', line 637

def catalog_item_update_sender
  CatalogItemUpdateSender.new(self, options)
end

#catalog_update_coverage(catalog_item) ⇒ Hash

Get attribute coverage report for a catalog item

Parameters:

  • catalog_item (CatalogItem)

    The catalog item to check

Returns:

  • (Hash)

    { mappable: [...], missing: [...], unmapped_required: [...] }



694
695
696
697
698
699
700
# File 'app/services/edi/wayfair/orchestrator.rb', line 694

def catalog_update_coverage(catalog_item)
  wayfair_schema = wayfair_schema_for(catalog_item.wayfair_taxonomy_category_id_in_effect)
  return { error: 'No schema available' } unless wayfair_schema

  generator = ListingGenerator::ListingGenerator.new(catalog_item, wayfair_schema: wayfair_schema)
  generator.attribute_coverage
end

#check_catalog_update_status(request_id) ⇒ CatalogItemUpdateSender::StatusResult

Check the status of a previous catalog update request

Parameters:

  • request_id (String)

    The request id from push_catalog_update

Returns:



668
669
670
671
672
673
674
675
676
677
678
# File 'app/services/edi/wayfair/orchestrator.rb', line 668

def check_catalog_update_status(request_id)
  catalog_item_update_sender.check_status(request_id)
rescue StandardError => e
  Rails.logger.error "Wayfair check_catalog_update_status error: #{e.message}"
  CatalogItemUpdateSender::StatusResult.new(
    success: false,
    status: 'ERROR',
    message: e.message,
    details: nil
  )
end

#clean_string(str) ⇒ String

Sanitizes a free-text string for Wayfair messages by replacing the various
Unicode double-quote characters with two single quotes and single-quote
variants with a plain apostrophe.

Parameters:

  • str (String)

    the string to clean

Returns:

  • (String)

    the cleaned string



526
527
528
529
530
531
532
533
534
535
536
# File 'app/services/edi/wayfair/orchestrator.rb', line 526

def clean_string(str)
  # Ensure to replace all double quotes with two single quotes, along with all the various variations, based on testing and:
  # https://unicode-table.com/en/sets/quotation-marks/
  DOUBLE_QUOTE_CHARACTERS_TO_REPLACE.each do |c|
    str = str.gsub(c, "''")
  end
  SINGLE_QUOTE_CHARACTERS_TO_REPLACE.each do |c|
    str = str.gsub(c, "'")
  end
  str
end

#confirm_message_enabled?Boolean

Whether order confirmation is enabled (follows order flow enablement).

Returns:

  • (Boolean)


242
243
244
# File 'app/services/edi/wayfair/orchestrator.rb', line 242

def confirm_message_enabled?
  try(:order_enabled).present?
end

#confirm_message_processorEdi::Wayfair::ConfirmMessageProcessor, Edi::NullProcessor



247
248
249
250
251
# File 'app/services/edi/wayfair/orchestrator.rb', line 247

def confirm_message_processor
  return Edi::NullProcessor.new(self, :confirm_message_processor, options) unless confirm_message_enabled?

  ConfirmMessageProcessor.new(self, options)
end

#confirm_message_senderEdi::Wayfair::ConfirmMessageSender, Edi::NullProcessor



254
255
256
257
258
# File 'app/services/edi/wayfair/orchestrator.rb', line 254

def confirm_message_sender
  return Edi::NullProcessor.new(self, :confirm_message_sender, options) unless confirm_message_enabled?

  ConfirmMessageSender.new(self, options)
end

#execute_inventory_flowvoid

This method returns an undefined value.

Runs the inventory flow: build the feed and send it.



331
332
333
334
335
336
# File 'app/services/edi/wayfair/orchestrator.rb', line 331

def execute_inventory_flow
  return unless active

  inventory_message_processor.process
  inventory_message_sender.process
end

#execute_order_flowvoid

This method returns an undefined value.

Runs the full purchase-order flow: retrieve, process, acknowledge, confirm.



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'app/services/edi/wayfair/orchestrator.rb', line 312

def execute_order_flow
  return unless active

  order_message_retriever.process
  sleep(1)
  order_message_processor.process
  sleep(1)
  # we get packing slips inline directly from order batch queries
  # packing_slip_processor.process
  # sleep(1)
  acknowledge_message_sender.process
  sleep(1)
  confirm_message_sender.process
  sleep(1)
end

#execute_product_data_flowvoid

This method returns an undefined value.

Pulls catalog information from Wayfair's Supplier Catalog API
Updates retail prices, product identifiers, taxonomy data, and URLs
Called by EdiProductDataFlowWorker (daily at 2:15 AM)



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'app/services/edi/wayfair/orchestrator.rb', line 343

def execute_product_data_flow
  return unless active
  return unless product_data_enabled?

  logger.info "Wayfair: Starting product data flow for #{partner}"

  total_processed = 0
  total_skipped = 0
  total_errors = 0
  page = 1
  max_pages = 100 # Safety limit
  catalog_scan_ecls = []
  catalog_scan_complete = false

  loop do
    if page > max_pages
      logger.warn "Wayfair: Product data flow reached the #{max_pages}-page safety limit; parent coverage was not replaced"
      break
    end

    result = pull_catalog_information_all(page: page, page_size: 25)
    catalog_scan_ecls << result[:ecl] if result[:ecl]

    # Per-item save errors must NOT halt the whole backfill — they make
    # result[:success] false (success = result.errors.empty?), so the old
    # `break` here stalled the sync on page 1 whenever a single item failed.
    # Tally the errors and keep paging; only stop when there is no further
    # page to fetch (page_info absent = hard failure / no data).
    if result[:result]
      total_processed += result[:result].processed
      total_skipped += result[:result].skipped
      total_errors += result[:result].errors.size
    end

    page_info = result[:page_info]
    unless page_info
      logger.warn "Wayfair: Product data flow stopped at page #{page}: #{result[:message] || 'no page info returned'}"
      total_errors += 1 unless result[:success]
      break
    end

    logger.warn "Wayfair: Product data flow page #{page} had #{result[:result]&.errors&.size} item error(s); continuing" unless result[:success]

    unless page_info['hasNextPage']
      catalog_scan_complete = true
      break
    end

    page += 1
    sleep(0.5) # Rate limiting between pages
  end

  if catalog_scan_complete
    coverage_result = RecordCatalogParentCoverage.call(catalog: customer&.catalog, ecls: catalog_scan_ecls)
    total_errors += coverage_result.errors.size
    logger.info "Wayfair: Parent coverage for #{partner}: #{coverage_result.updated} updated, " \
                "#{coverage_result.errors.size} errors"
  else
    logger.warn "Wayfair: Parent coverage for #{partner} was not replaced because the v1 scan did not complete"
  end

  logger.info "Wayfair: Product data flow completed for #{partner}: " \
              "#{total_processed} processed, #{total_skipped} skipped, #{total_errors} errors"

  pull_catalog_read_v2
end

#feed_submission_result_enabled?Boolean

Whether feed-submission results are polled for this partner.

Returns:

  • (Boolean)


277
278
279
# File 'app/services/edi/wayfair/orchestrator.rb', line 277

def feed_submission_result_enabled?
  try(:feed_submission_result_remote_path).present?
end

#feed_submission_result_processorEdi::Wayfair::InventoryMessageProcessor, Edi::NullProcessor



282
283
284
285
286
# File 'app/services/edi/wayfair/orchestrator.rb', line 282

def feed_submission_result_processor
  return Edi::NullProcessor.new(self, :inventory_message_processor, options) unless feed_submission_result_enabled?

  InventoryMessageProcessor.new(self, options)
end

#get_orders_queryString

GraphQL query fetching open purchase orders for this partner's supplier id.

Returns:

  • (String)

    the squished GraphQL query body



104
105
106
107
108
109
110
111
112
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
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
181
182
183
184
185
186
# File 'app/services/edi/wayfair/orchestrator.rb', line 104

def get_orders_query
  %(
    query purchaseOrders {
      purchaseOrders(
        filters: [
          {
            field: open,
            equals: \\"true\\"
          },
          {
            field: supplierId,
            equals: \\"#{try(:warehouse_code)}\\"
          },
          {
            field: poDate,
            greaterThan: \\"#{DateTime.parse(GOLIVE_DATE_STR).iso8601}\\"
          }
        ],
        limit: 10,
        ordering: [
          {
           desc: \\"poDate\\"
          }
        ]
      ) {
        id,
        poNumber,
        poDate,
        estimatedShipDate,
        customerName,
        customerAddress1,
        customerAddress2,
        customerCity,
        customerState,
        customerCountry,
        customerPostalCode,
        orderType,
        shippingInfo {
          shipSpeed,
          carrierCode
        },
        packingSlipUrl,
        warehouse {
          id,
          name,
          address {
            name,
            address1,
            address2,
            address3,
            city,
            state,
            country,
            postalCode
          }
        },
        products {
          partNumber,
          quantity,
          price,
          event {
            id,
            type,
            name,
            startDate,
            endDate
          }
        },
        shipTo {
          name,
          address1,
          address2,
          address3,
          city,
          state,
          country,
          postalCode,
          phoneNumber
        }
      }
    }
  ).squish
end

#ignore_back_ordersBoolean

Whether back orders are ignored for this partner (Wayfair: never).

Returns:

  • (Boolean)


441
442
443
# File 'app/services/edi/wayfair/orchestrator.rb', line 441

def ignore_back_orders
  false
end

#inventory_message_enabled?Boolean

Whether the inventory feed flow is enabled for this partner.

Returns:

  • (Boolean)


63
64
65
# File 'app/services/edi/wayfair/orchestrator.rb', line 63

def inventory_message_enabled?
  try(:inventory_enabled).present?
end

#inventory_message_processorEdi::Wayfair::InventoryMessageProcessor, Edi::NullProcessor

Processor that builds the inventory feed payload.



70
71
72
73
74
# File 'app/services/edi/wayfair/orchestrator.rb', line 70

def inventory_message_processor
  return Edi::NullProcessor.new(self, :inventory_message_processor, options) unless inventory_message_enabled?

  InventoryMessageProcessor.new(self, options)
end

#inventory_message_senderEdi::Wayfair::InventoryMessageSender, Edi::NullProcessor

Sender that pushes the inventory feed to Wayfair.



79
80
81
82
83
# File 'app/services/edi/wayfair/orchestrator.rb', line 79

def inventory_message_sender
  return Edi::NullProcessor.new(self, :inventory_message_sender, options) unless inventory_message_enabled?

  InventoryMessageSender.new(self, options)
end

#invoice_message_enabled?Boolean

Whether the invoice flow is enabled for this partner (currently false).

Returns:

  • (Boolean)


200
201
202
# File 'app/services/edi/wayfair/orchestrator.rb', line 200

def invoice_message_enabled?
  try(:invoice_enabled).present?
end

#invoice_message_processorEdi::Wayfair::InvoiceMessageProcessor, Edi::NullProcessor

Returns:



205
206
207
208
209
# File 'app/services/edi/wayfair/orchestrator.rb', line 205

def invoice_message_processor
  return Edi::NullProcessor.new(self, :invoice_message_processor, options) unless invoice_message_enabled?

  InvoiceMessageProcessor.new(self, options)
end

#invoice_message_senderEdi::Wayfair::InvoiceMessageSender, Edi::NullProcessor

Returns:



212
213
214
215
216
# File 'app/services/edi/wayfair/orchestrator.rb', line 212

def invoice_message_sender
  return Edi::NullProcessor.new(self, :invoice_message_sender, options) unless invoice_message_enabled?

  InvoiceMessageSender.new(self, options)
end

#market_countryString

Get market country based on partner

Returns:

  • (String)

    Wayfair market country code (e.g. "UNITED_STATES", "CANADA")



608
609
610
611
612
613
614
615
616
617
# File 'app/services/edi/wayfair/orchestrator.rb', line 608

def market_country
  case partner
  when :wayfair_us
    'UNITED_STATES'
  when :wayfair_ca
    'CANADA'
  else
    'UNITED_STATES'
  end
end

#market_localeString

Get market locale based on partner

Returns:

  • (String)

    Wayfair market locale (e.g. "en-US", "en-CA")



622
623
624
625
626
627
628
629
630
631
# File 'app/services/edi/wayfair/orchestrator.rb', line 622

def market_locale
  case partner
  when :wayfair_us
    'en-US'
  when :wayfair_ca
    'en-CA'
  else
    'en-US'
  end
end

#order_message_enabled?Boolean

Whether the purchase-order flow is enabled for this partner.

Returns:

  • (Boolean)


88
89
90
# File 'app/services/edi/wayfair/orchestrator.rb', line 88

def order_message_enabled?
  try(:order_enabled).present?
end

#order_message_processorEdi::Wayfair::OrderMessageProcessor, Edi::NullProcessor

Processor that turns a fetched order batch into Order records.



191
192
193
194
195
# File 'app/services/edi/wayfair/orchestrator.rb', line 191

def order_message_processor
  return Edi::NullProcessor.new(self, :order_message_processor, options) unless order_message_enabled?

  OrderMessageProcessor.new(self, options)
end

#order_message_retrieverEdi::Wayfair::OrderMessageRetriever, Edi::NullProcessor

Retriever that pulls open purchase orders from Wayfair's GraphQL API.



95
96
97
98
99
# File 'app/services/edi/wayfair/orchestrator.rb', line 95

def order_message_retriever
  return Edi::NullProcessor.new(self, :order_message_processor, options) unless order_message_enabled?

  OrderMessageRetriever.new(self, options)
end

#packing_slip_enabled?Boolean

Whether packing-slip retrieval is configured for this partner.

Returns:

  • (Boolean)


221
222
223
# File 'app/services/edi/wayfair/orchestrator.rb', line 221

def packing_slip_enabled?
  try(:packing_slip_remote_path).present?
end

#packing_slip_processorEdi::NullProcessor

No-op processor — packing slips are only retrieved, never processed.

Returns:



235
236
237
# File 'app/services/edi/wayfair/orchestrator.rb', line 235

def packing_slip_processor
  Edi::NullProcessor.new(self, :packing_slip_processor, options) # here we don't need to process anything, just retrieve
end

#packing_slip_retrieverEdi::Wayfair::PackingSlipRetriever, Edi::NullProcessor



226
227
228
229
230
# File 'app/services/edi/wayfair/orchestrator.rb', line 226

def packing_slip_retriever
  return Edi::NullProcessor.new(self, :packing_slip_retriever, options) unless packing_slip_enabled?

  PackingSlipRetriever.new(self, options)
end

#preview_catalog_update(catalog_item) ⇒ Hash

Preview what would be sent for a catalog item (for debugging)

Parameters:

  • catalog_item (CatalogItem)

    The catalog item to preview

Returns:

  • (Hash)

    Preview data



683
684
685
686
687
688
689
# File 'app/services/edi/wayfair/orchestrator.rb', line 683

def preview_catalog_update(catalog_item)
  wayfair_schema = wayfair_schema_for(catalog_item.wayfair_taxonomy_category_id_in_effect)
  return { error: 'No schema available' } unless wayfair_schema

  generator = ListingGenerator::ListingGenerator.new(catalog_item, wayfair_schema: wayfair_schema)
  generator.preview
end

#pull_catalog_information(sku) ⇒ Hash

Pull catalog information for a specific SKU

Parameters:

  • sku (String)

    Our internal SKU (supplierPartNumber in Wayfair terms)

Returns:

  • (Hash)

    Result with :success, :ecl, :result keys



461
462
463
464
465
466
467
468
469
470
# File 'app/services/edi/wayfair/orchestrator.rb', line 461

def pull_catalog_information(sku)
  ecl = catalog_item_information_retriever.process(supplier_part_number: sku)
  return { success: false, ecl: nil, result: nil, message: 'No data returned from Wayfair API' } unless ecl

  result = catalog_item_information_processor.process(ecl)
  { success: result.errors.empty?, ecl: ecl, result: result }
rescue StandardError => e
  Rails.logger.error "Wayfair pull_catalog_information error for #{sku}: #{e.message}"
  { success: false, ecl: nil, result: nil, message: e.message }
end

#pull_catalog_information_all(page: 1, page_size: 25, dry_run: false) ⇒ Hash

Pull catalog information for all products (paginated)

Parameters:

  • page (Integer) (defaults to: 1)

    Page number (default: 1)

  • page_size (Integer) (defaults to: 25)

    Products per page (10, 20, or 25)

  • dry_run (Boolean) (defaults to: false)

    If true, only reports what would be changed

Returns:

  • (Hash)

    Result with pagination info and processing results



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'app/services/edi/wayfair/orchestrator.rb', line 477

def pull_catalog_information_all(page: 1, page_size: 25, dry_run: false)
  ecl = catalog_item_information_retriever.process_all(page: page, page_size: page_size)
  return { success: false, ecl: nil, result: nil, message: 'No data returned from Wayfair API' } unless ecl

  result = catalog_item_information_processor.process(ecl, dry_run: dry_run)
  page_info = begin
    JSON.parse(ecl.data).dig('data', 'supplierCatalog', 'pageInfo')
  rescue StandardError
    nil
  end
  { success: result.errors.empty?, ecl: ecl, result: result, page_info: page_info }
rescue StandardError => e
  Rails.logger.error "Wayfair pull_catalog_information_all error: #{e.message}"
  { success: false, ecl: nil, result: nil, message: e.message }
end

#pull_catalog_read_v2void

This method returns an undefined value.

Catalog Read v2 pass — Wayfair's own insights, live catalogItemStatus, and
the listing's populated taxonomy attributes. Feeds
ListingIssues::WayfairAdapter, which reconciles at 05:30 CT (this flow runs
02:15 CT), so without this call the whole v2 signal sits frozen at whenever
someone last ran the retriever by hand.

Deliberately runs AFTER the v1 pass: v1's isLive is always true even for a
discontinued SKU, so v1 writes status: LIVE unconditionally and would
clobber v2's authoritative catalogItemStatus if the order were reversed.

Isolated from the v1 result — a v2 outage must not fail the product-data
flow that keeps PDP URLs and taxonomy current.



423
424
425
426
427
428
429
# File 'app/services/edi/wayfair/orchestrator.rb', line 423

def pull_catalog_read_v2
  result = CatalogReadV2Retriever.new(self).pull
  logger.info "Wayfair: Catalog Read v2 for #{partner}: #{result.to_h}"
rescue StandardError => e
  logger.error "Wayfair: Catalog Read v2 failed for #{partner}: #{e.class}: #{e.message}"
  Appsignal.report_error(e)
end

#pull_taxonomy_schema(taxonomy_category_id, brand: 'WAYFAIR', country: 'UNITED_STATES', locale: 'en-US') ⇒ WayfairSchema?

Pull taxonomy schema for a specific category

Parameters:

  • taxonomy_category_id (Integer)

    The Wayfair taxonomy category ID (e.g., 997)

  • brand (String) (defaults to: 'WAYFAIR')

    Brand context (default: WAYFAIR)

  • country (String) (defaults to: 'UNITED_STATES')

    Country context (default: UNITED_STATES)

  • locale (String) (defaults to: 'en-US')

    Locale (default: en-US)

Returns:



553
554
555
556
557
558
559
560
561
562
563
# File 'app/services/edi/wayfair/orchestrator.rb', line 553

def pull_taxonomy_schema(taxonomy_category_id, brand: 'WAYFAIR', country: 'UNITED_STATES', locale: 'en-US')
  taxonomy_attribute_retriever.process(
    taxonomy_category_id: taxonomy_category_id,
    brand: brand,
    country: country,
    locale: locale
  )
rescue StandardError => e
  Rails.logger.error "Wayfair pull_taxonomy_schema error for category #{taxonomy_category_id}: #{e.message}"
  nil
end

#pull_taxonomy_schema_for_category(taxonomy_category_id, wayfair_schema: nil) ⇒ WayfairSchema?

Pull taxonomy schema for a category and update an existing WayfairSchema record.
Forwards arbitrary keyword arguments (**) to the taxonomy_attribute_retriever
for additional market-context options.

Parameters:

  • taxonomy_category_id (Integer)

    The Wayfair taxonomy category ID

  • wayfair_schema (WayfairSchema) (defaults to: nil)

    The existing schema to update

Returns:



572
573
574
575
576
577
578
579
580
581
# File 'app/services/edi/wayfair/orchestrator.rb', line 572

def pull_taxonomy_schema_for_category(taxonomy_category_id, wayfair_schema: nil, **)
  taxonomy_attribute_retriever.process(
    taxonomy_category_id: taxonomy_category_id,
    wayfair_schema: wayfair_schema,
    **
  )
rescue StandardError => e
  Rails.logger.error "Wayfair pull_taxonomy_schema_for_category error: #{e.message}"
  nil
end

#push_catalog_update(catalog_item, attributes: nil, dry_run: false, validate_only: false) ⇒ CatalogItemUpdateSender::UpdateResult

Push a catalog item update to Wayfair

Parameters:

  • catalog_item (CatalogItem)

    The catalog item to update

  • attributes (Array<String>, nil) (defaults to: nil)

    Optional list of attribute names to send (nil = all available)

  • dry_run (Boolean) (defaults to: false)

    If true, only generates payload without sending

  • validate_only (Boolean) (defaults to: false)

    If true, Wayfair validates server-side without applying

Returns:



647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'app/services/edi/wayfair/orchestrator.rb', line 647

def push_catalog_update(catalog_item, attributes: nil, dry_run: false, validate_only: false)
  catalog_item_update_sender.process(
    catalog_item: catalog_item,
    attributes: attributes,
    dry_run: dry_run,
    validate_only: validate_only
  )
rescue StandardError => e
  Rails.logger.error "Wayfair push_catalog_update error: #{e.message}"
  CatalogItemUpdateSender::UpdateResult.new(
    success: false,
    request_id: nil,
    errors: [e.message],
    ecl: nil,
    payload: nil
  )
end

#push_inventory_update(sku, inventory_data:, dry_run: false) ⇒ Edi::Wayfair::CatalogItemInformationUpdater::UpdateResult

Update inventory data on Wayfair (quantity, discontinued status, etc.)

Parameters:

  • sku (String)

    Our internal SKU (supplierPartNumber in Wayfair terms)

  • inventory_data (Hash)

    { quantity_on_hand:, discontinued:, product_name_and_options: }

  • dry_run (Boolean) (defaults to: false)

    If true, only logs what would be sent

Returns:



509
510
511
512
513
514
515
516
517
518
# File 'app/services/edi/wayfair/orchestrator.rb', line 509

def push_inventory_update(sku, inventory_data:, dry_run: false)
  catalog_item_information_updater.process(
    supplier_part_number: sku,
    inventory_data: inventory_data,
    dry_run: dry_run
  )
rescue StandardError => e
  Rails.logger.error "Wayfair push_inventory_update error for #{sku}: #{e.message}"
  CatalogItemInformationUpdater::UpdateResult.new(success: false, handle: nil, errors: [e.message], ecl: nil)
end

#return_notification_message_enabled?Boolean

Whether the return-notification flow is configured for this partner.

Returns:

  • (Boolean)


291
292
293
# File 'app/services/edi/wayfair/orchestrator.rb', line 291

def return_notification_message_enabled?
  try(:return_notification_message_remote_path).present?
end

#return_notification_message_processorEdi::Wayfair::ReturnNotificationMessageProcessor, Edi::NullProcessor

Returns:



296
297
298
299
300
# File 'app/services/edi/wayfair/orchestrator.rb', line 296

def return_notification_message_processor
  return Edi::NullProcessor.new(self, :return_notification_message_processor, options) unless return_notification_message_enabled?

  ReturnNotificationMessageProcessor.new(self, options)
end

#return_notification_message_senderEdi::Wayfair::ReturnNotificationMessageSender, Edi::NullProcessor

Returns:



303
304
305
306
307
# File 'app/services/edi/wayfair/orchestrator.rb', line 303

def return_notification_message_sender
  return Edi::NullProcessor.new(self, :return_notification_message_sender, options) unless return_notification_message_enabled?

  ReturnNotificationMessageSender.new(self, options)
end

#ship_code_mapperEdi::Wayfair::ShipCodeMapper

Maps Wayfair ship codes to Heatwave shipping options.



434
435
436
# File 'app/services/edi/wayfair/orchestrator.rb', line 434

def ship_code_mapper
  ShipCodeMapper.new(self)
end

#taxonomy_attribute_retrieverEdi::Wayfair::TaxonomyAttributeRetriever

Taxonomy Attribute methods
Uses the Product Catalog API to fetch and cache taxonomy attribute definitions
Similar to Amazon's product type schema system



543
544
545
# File 'app/services/edi/wayfair/orchestrator.rb', line 543

def taxonomy_attribute_retriever
  TaxonomyAttributeRetriever.new(self, options)
end

#wayfair_schema_for(taxonomy_category_id, refresh: false) ⇒ WayfairSchema?

Get or create WayfairSchema for a category, fetching from API if not cached

Parameters:

  • taxonomy_category_id (Integer)

    The Wayfair taxonomy category ID

  • refresh (Boolean) (defaults to: false)

    Force refresh from API even if cached

Returns:



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'app/services/edi/wayfair/orchestrator.rb', line 587

def wayfair_schema_for(taxonomy_category_id, refresh: false)
  schema = WayfairSchema.for_category(taxonomy_category_id)
                        .for_brand('WAYFAIR')
                        .for_country(market_country)
                        .first

  # Return cached schema if available and not stale (less than 7 days old)
  return schema if schema.present? && !refresh && schema.updated_at > 7.days.ago

  # Fetch fresh schema from API. A failed refresh must NOT discard a schema we
  # already hold: `pull_taxonomy_schema` swallows its error and returns nil, so
  # returning it directly turned every stale-but-usable schema into "no schema
  # available" and blocked the caller outright. Canada hit this — its 7441/363
  # schemas were 30 days old and the CA taxonomy pull fails, so every CA
  # re-class refused to build a payload despite a perfectly good cached schema.
  pull_taxonomy_schema(taxonomy_category_id, country: market_country) || schema
end