Class: Edi::Walmart::OrderMessageProcessor

Inherits:
BaseEdiService show all
Defined in:
app/services/edi/walmart/order_message_processor.rb

Defined Under Namespace

Classes: BatchProcessResult, LineCreationResult, Result

Constant Summary

Constants included from AddressAbbreviator

AddressAbbreviator::MAX_LENGTH

Instance Attribute Summary

Attributes inherited from BaseEdiService

#orchestrator

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseEdiService

#duplicate_po_already_notified?, #initialize, #mark_duplicate_po_as_notified, #report_order_creation_issues, #safe_process_edi_communication_log

Methods included from AddressAbbreviator

#abbreviate_street, #collect_street_originals, #record_address_abbreviation_notes

Methods inherited from BaseService

#initialize, #log_debug, #log_error, #log_warning, #logger, #options, #tagged_logger

Constructor Details

This class inherits a constructor from Edi::BaseEdiService

Class Method Details

.walmart_shipping_dates(ship_by: 2.days.from_now, deliver_by: 7.days.from_now, order_date: Date.current, format: :ms) ⇒ Hash

Generate a hash of shipping date fields ready to merge into a Walmart test order payload.

Examples:

walmart_shipping_dates
#=> { orderDate: 1770847200000, estimatedShipDate: 1771020000000, estimatedDeliveryDate: 1771452000000 }

walmart_shipping_dates(ship_by: "2026-02-15", deliver_by: "2026-02-20", format: :iso)
#=> { orderDate: "2026-02-11T22:00:00.000Z", estimatedShipDate: "2026-02-15T22:00:00.000Z", estimatedDeliveryDate: "2026-02-20T22:00:00.000Z" }

Parameters:

  • ship_by (Date, Time, String) (defaults to: 2.days.from_now)

    estimatedShipDate (default: 2 days from now)

  • deliver_by (Date, Time, String) (defaults to: 7.days.from_now)

    estimatedDeliveryDate (default: 7 days from now)

  • order_date (Date, Time, String) (defaults to: Date.current)

    orderDate (default: today)

  • format (:ms, :iso) (defaults to: :ms)

    Timestamp format (default: :ms)

Returns:

  • (Hash)

    Ready to merge into order JSON



81
82
83
84
85
86
87
# File 'app/services/edi/walmart/order_message_processor.rb', line 81

def self.walmart_shipping_dates(ship_by: 2.days.from_now, deliver_by: 7.days.from_now, order_date: Date.current, format: :ms)
  {
    orderDate: walmart_timestamp(order_date, format),
    estimatedShipDate: walmart_timestamp(ship_by, format),
    estimatedDeliveryDate: walmart_timestamp(deliver_by, format)
  }
end

.walmart_timestamp(value, format = :ms) ⇒ Integer, String

Convert a date to a Walmart-style Unix millisecond timestamp at 4pm Central.

Examples:

walmart_timestamp(Date.today + 2)                    #=> 1771020000000  (4pm Central)
walmart_timestamp("2026-02-13")                      #=> 1771020000000  (4pm Central)
walmart_timestamp("2026-02-13 09:30")                #=> 1770996600000  (9:30am Central)
walmart_timestamp(2.days.from_now, :iso)             #=> "2026-02-13T22:00:00.000Z"
walmart_timestamp(Time.zone.parse("2026-02-13 14:00")) #=> uses 2pm as given

Parameters:

  • date (Date, Time, ActiveSupport::TimeWithZone, String)

    The target date

  • format (:ms, :iso) (defaults to: :ms)

    Return millisecond integer or ISO 8601 string (default: :ms)

Returns:

  • (Integer, String)

    Walmart-formatted timestamp



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'app/services/edi/walmart/order_message_processor.rb', line 42

def self.walmart_timestamp(value, format = :ms)
  tz = ActiveSupport::TimeZone['America/Chicago']
  time = case value
         when Time, ActiveSupport::TimeWithZone
           value.in_time_zone(tz)
         when DateTime
           tz.parse(value.to_s)
         when Date
           tz.local(value.year, value.month, value.day, WALMART_LOCAL_HOUR, 0, 0)
         when String
           parsed = tz.parse(value)
           # If only a date was given (no time component), default to 4pm
           parsed = tz.local(parsed.year, parsed.month, parsed.day, WALMART_LOCAL_HOUR, 0, 0) if value.match?(/\A\d{4}-\d{2}-\d{2}\z/)
           parsed
         else
           raise ArgumentError, "Expected a Date, Time, or String, got #{value.class}"
         end

  case format
  when :ms  then (time.to_f * 1000).to_i
  when :iso then time.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z')
  else raise ArgumentError, "format must be :ms or :iso, got #{format.inspect}"
  end
end

Instance Method Details

#apply_payment(order, order_hash) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'app/services/edi/walmart/order_message_processor.rb', line 288

def apply_payment(order, order_hash)
  order.reload
  order.pending_payment!
  total_due = order.total
  order.payments.build.tap do |payment|
    payment.category = Payment::PO
    payment.currency = order.currency
    payment.amount = total_due
    payment.state = 'authorized'
    payment.po_number = order_hash[:purchaseOrderId]
    payment.delivery = order.deliveries.quoting.first
    payment.save!
  end
end

#calculate_shipping_total(order_hash) ⇒ Object



437
438
439
440
441
442
443
444
445
# File 'app/services/edi/walmart/order_message_processor.rb', line 437

def calculate_shipping_total(order_hash)
  order_lines = order_hash.dig(:orderLines, :orderLine) || []
  order_lines.sum do |line_hash|
    charges = line_hash.dig(:charges, :charge) || []
    shipping_charge = charges.find { |c| c[:chargeType] == 'SHIPPING' }
    amount = shipping_charge&.dig(:chargeAmount, :amount)
    amount.present? ? BigDecimal(amount.to_s) : BigDecimal(0)
  end
end

#country_code_for(walmart_country) ⇒ Object



471
472
473
474
475
476
477
478
479
480
# File 'app/services/edi/walmart/order_message_processor.rb', line 471

def country_code_for(walmart_country)
  case walmart_country&.upcase
  when 'USA', 'US', 'UNITED STATES'
    'US'
  when 'CAN', 'CA', 'CANADA'
    'CA'
  else
    walmart_country&.first(2)&.upcase || 'US'
  end
end

#create_line_items(order, order_hash) ⇒ Object



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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# File 'app/services/edi/walmart/order_message_processor.rb', line 347

def create_line_items(order, order_hash)
  # Walmart order line structure:
  # {
  #   "orderLines": {
  #     "orderLine": [
  #       {
  #         "lineNumber": "1",
  #         "item": {
  #           "productName": "Product Name",
  #           "sku": "SKU123"
  #         },
  #         "charges": {
  #           "charge": [
  #             { "chargeType": "PRODUCT", "chargeAmount": { "currency": "USD", "amount": "99.99" } },
  #             { "chargeType": "SHIPPING", "chargeAmount": { "currency": "USD", "amount": "5.99" } }
  #           ]
  #         },
  #         "orderLineQuantity": {
  #           "unitOfMeasurement": "EACH",
  #           "amount": "1"
  #         },
  #         "statusDate": "2024-01-15T10:30:00.000Z",
  #         "orderLineStatuses": { ... }
  #       }
  #     ]
  #   }
  # }

  line_total = BigDecimal(0)
  cust = order.customer
  all_skus_valid = true
  errors = []
  bad_vendor_skus = []

  catalog_items = cust.catalog.catalog_items
                      .where(state: %w[active active_hidden require_vendor_update pending_vendor_update pending_discontinue])
                      .order(state: :asc)

  order_lines = order_hash.dig(:orderLines, :orderLine) || []
  order_lines.each_with_index do |line_hash, i|
    quantity = line_hash.dig(:orderLineQuantity, :amount).to_i
    next if quantity.zero?

    vendor_sku = line_hash.dig(:item, :sku)
    raise "Missing SKU for line #{i + 1}" if vendor_sku.blank?

    matched_catalog_item = catalog_items.by_skus(vendor_sku).first
    if matched_catalog_item
      order.line_items.build.tap do |line_item|
        line_item.quantity = quantity
        line_item.edi_reference = line_hash[:lineNumber]
        line_item.edi_line_number = i + 1
        line_item.catalog_item = matched_catalog_item

        # Get product charge from charges array
        price = extract_product_price(line_hash, quantity)
        line_item.price = price
        line_item.edi_unit_cost = price
        line_total += price * quantity
      end

      if matched_catalog_item.state_requires_edi_warning_on_order?
        subj = "EDI order for orchestrator: #{orchestrator.partner}, catalog item SKU #{matched_catalog_item.sku} in state #{matched_catalog_item.state}"
        msg = "Order #{order.reference_number} processed with catalog item in non-active state."
        InternalMailer.notify_edi_admin_of_warning(subj, msg).deliver_later
      end
    else
      all_skus_valid = false
      bad_vendor_skus << vendor_sku
      errors << "Unknown vendor SKU: #{vendor_sku}"
    end
  end

  LineCreationResult.new(
    line_total: line_total,
    lines_created: order.line_items.size,
    all_skus_valid: all_skus_valid,
    error_message: "#{errors.to_sentence.capitalize}. Please fix the order or catalog items.",
    bad_vendor_skus: bad_vendor_skus
  )
end

#create_shipping_address(order_hash) ⇒ Object



447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
# File 'app/services/edi/walmart/order_message_processor.rb', line 447

def create_shipping_address(order_hash)
  postal_address = order_hash.dig(:shippingInfo, :postalAddress) || {}

  raw_street1 = postal_address[:address1]
  raw_street2 = postal_address[:address2]
  originals = collect_street_originals(street1: raw_street1, street2: raw_street2)

  address = Address.new
  address.person_name_override = postal_address[:name].to_s.titleize.strip
  address.street1 = abbreviate_street(raw_street1)
  address.street2 = abbreviate_street(raw_street2)
  address.city = postal_address[:city]
  address.country_iso = country_code_for(postal_address[:country])
  address.state_code = State.code_for_string(
    postal_address[:state],
    countries_iso3: [Country.iso3_for_string(address.country_iso)]
  )
  address.zip = postal_address[:postalCode].to_s.upcase.delete(' ').strip
  address.disable_address_correction = true
  address.override_all_address_validation = true
  address.save!
  [address, originals]
end

#customer(segment = nil) ⇒ Object



89
90
91
# File 'app/services/edi/walmart/order_message_processor.rb', line 89

def customer(segment = nil)
  orchestrator.customer(segment)
end

#extract_product_price(line_hash, quantity) ⇒ Object



429
430
431
432
433
434
435
# File 'app/services/edi/walmart/order_message_processor.rb', line 429

def extract_product_price(line_hash, quantity)
  charges = line_hash.dig(:charges, :charge) || []
  product_charge = charges.find { |c| c[:chargeType] == 'PRODUCT' }
  amount = product_charge&.dig(:chargeAmount, :amount)
  total_amount = amount.present? ? BigDecimal(amount.to_s) : BigDecimal(0)
  total_amount / quantity
end

#log_info(msg) ⇒ Object



538
539
540
# File 'app/services/edi/walmart/order_message_processor.rb', line 538

def log_info(msg)
  logger.info "[Walmart OrderProcessor] #{msg}"
end

#orders_with_walmart_po(po_number) ⇒ Object



281
282
283
284
285
286
# File 'app/services/edi/walmart/order_message_processor.rb', line 281

def orders_with_walmart_po(po_number)
  Order.joins(:customer)
       .where(parties: { id: orchestrator.customer_ids })
       .where(edi_po_number: po_number)
       .where('orders.created_at > ?', 13.months.ago)
end

#parse_walmart_date(date_value) ⇒ Object

Parse Walmart date which can be either:

  • Unix timestamp in milliseconds (Integer): 1766636827065
  • ISO 8601 string: "2024-01-15T10:30:00.000Z"


335
336
337
338
339
340
341
342
343
344
345
# File 'app/services/edi/walmart/order_message_processor.rb', line 335

def parse_walmart_date(date_value)
  case date_value
  when Integer
    # Float division preserves millisecond precision
    Time.zone.at(date_value / 1000.0)
  when String
    Time.zone.parse(date_value)
  else
    raise "Unknown date format: #{date_value.class}"
  end
end

#process(edi_logs = nil, edi_transaction_id: nil) ⇒ Object

Picks up the edi communication log in the queue ready to process



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'app/services/edi/walmart/order_message_processor.rb', line 94

def process(edi_logs = nil, edi_transaction_id: nil)
  edi_logs ||= EdiCommunicationLog.requiring_processing.where(partner: orchestrator.partner, category: 'order_batch')
  edi_logs = [edi_logs].flatten
  batch_process_results = []

  edi_logs.each do |edi_log|
    log_info "Starting processing edi communication log #{edi_log.id}"
    EdiCommunicationLog.transaction do
      batch_process_result = process_orders(edi_log.data, edi_transaction_id: edi_transaction_id, edi_log_id: edi_log.id)
      edi_log.file_info ||= {}
      edi_log.file_info[:orders_in_batch] = batch_process_result.orders_in_batch
      batch_process_results << batch_process_result
      edi_log.complete!

      batch_process_result.orders_created.each do |order|
        edi_log.edi_documents.create(order: order)
      end
    rescue StandardError => e
      edi_log.notes = "#{e} at #{e&.backtrace_locations&.first(5)}"
      edi_log.error
      ErrorReporting.error(e, edi_communication_log_id: edi_log.id)
    end
  end

  Result.new(batch_process_results: batch_process_results)
end

#process_order(order_hash) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'app/services/edi/walmart/order_message_processor.rb', line 170

def process_order(order_hash)
  # Walmart order structure example:
  # {
  #   "purchaseOrderId": "1234567890123",
  #   "customerOrderId": "1234567890",
  #   "customerEmailId": "customer@example.com",
  #   "orderDate": "2024-01-15T10:30:00.000Z",
  #   "shippingInfo": {
  #     "phone": "1234567890",
  #     "estimatedDeliveryDate": "2024-01-20T00:00:00.000Z",
  #     "estimatedShipDate": "2024-01-16T00:00:00.000Z",
  #     "methodCode": "Standard",
  #     "postalAddress": {
  #       "name": "John Doe",
  #       "address1": "123 Main St",
  #       "city": "Chicago",
  #       "state": "IL",
  #       "postalCode": "60601",
  #       "country": "USA"
  #     }
  #   },
  #   "orderLines": {
  #     "orderLine": [...]
  #   }
  # }

  cust = customer
  order = cust.orders.build
  order.edi_original_order_message = order_hash.to_json
  order.edi_orchestrator_partner = orchestrator.partner.to_s
  order.edi_order_date = parse_walmart_date(order_hash[:orderDate]).to_date
  order.edi_transaction_id = "#{order.edi_order_date}-#{order_hash[:purchaseOrderId]}"
  order.edi_po_number = order_hash[:purchaseOrderId]
  address, address_originals = create_shipping_address(order_hash)
  order.shipping_address = address
  order.shipping_phone = order_hash.dig(:shippingInfo, :phone)

  recipient_name = order.shipping_address.person_name_override || order.shipping_address.company_name_override
  order.customer_reference = "Purchase Order Number: #{order_hash[:purchaseOrderId]}, Customer Order Number: #{order_hash[:customerOrderId]}"
  order.currency = orchestrator.currency
  order.attention_name_override = recipient_name
  order.single_origin = true
  # Walmart allows unbranded packing slips - no custom slip required
  # See: https://marketplacelearn.walmart.com/guides/Policies%20&%20standards/Shipping%20&%20fulfillment/Packaging-policy
  order.edi_is_pick_slip_required = false
  order.disable_auto_coupon = true
  order.save!
  record_address_abbreviation_notes(order, address_originals, order.shipping_address)

  line_creation_result = create_line_items(order, order_hash)
  raise line_creation_result.error_message unless line_creation_result.all_skus_valid

  order.edi_shipping_option_name = set_shipping_option(order, order_hash)
  order.save!

  # Verify order was created correctly
  expected_line_count = (order_hash.dig(:orderLines, :orderLine) || []).count
  order.reload
  actual_line_count = order.line_items.goods.where(parent_id: nil).count
  if actual_line_count != expected_line_count
    raise "Order creation failed: expected #{expected_line_count} line items but order #{order.reference_number} has #{actual_line_count}. Walmart PO: #{order_hash[:purchaseOrderId]}"
  end

  apply_payment(order, order_hash)

  order.price_match = line_creation_result.line_total
  order.state = 'in_cr_hold'
  order.recalculate_shipping = true
  order.save!
  order.reset_discount
  order.save!

  # Update delivery with shipping info
  order.reload
  delivery = order.deliveries.first
  if delivery
    # Calculate shipping price from order lines (Walmart's customer-facing charge).
    # For non-SWW orders this sets the shipping line price from the JSON.
    # For SWW orders, select_cheapest_sww_rate below will override this with the
    # actual SWW rate cost via set_proper_shipping_cost.
    shipping_price_total = calculate_shipping_total(order_hash)
    shipping_line_item = order.line_items.shipping_only.first
    if shipping_line_item
      shipping_line_item.price = shipping_price_total
      shipping_line_item.discounted_price = shipping_price_total
      shipping_line_item.description = order.edi_original_ship_code
      shipping_line_item.save!
    end

    # Select the cheapest SWW rate that meets the delivery deadline.
    # Runs AFTER the Walmart price override above so that delivery.save!
    # triggers set_proper_shipping_cost, which properly sets the shipping
    # line item price from the SWW rate (overriding the $0 JSON charge).
    select_cheapest_sww_rate(delivery, order)
  end

  order.save!
  order.commit_line_items

  # Check for duplicate PO warnings
  same_po_orders = orders_with_walmart_po(order.edi_po_number).select { |o| o.id != order.id }
  if same_po_orders.any?
    subj = "EDI order message for orchestrator: #{orchestrator.partner}, order #{order.reference_number} CREATED, order message PO: #{order.edi_po_number}, duplicate"
    msg = "EDI order for orchestrator: #{orchestrator.partner}, order #{order.reference_number}, has the same PO #{order.edi_po_number} as orders: " \
          "#{same_po_orders.map { |o| "#{o.reference_number} (created #{o.created_at.to_date})" }.join(', ')}."
    InternalMailer.notify_edi_admin_of_warning(subj, msg).deliver_later
  end

  order
end

#process_orders(orders_data, edi_transaction_id: nil, edi_log_id: nil) ⇒ Object

Iterates through the batch order data



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
# File 'app/services/edi/walmart/order_message_processor.rb', line 122

def process_orders(orders_data, edi_transaction_id: nil, edi_log_id: nil)
  json_hash = JSON.parse(orders_data).with_indifferent_access
  orders_created = []
  orders_in_batch = 0

  # Walmart order structure: { list: { elements: { order: [...] } } }
  order_hashes = json_hash.dig(:list, :elements, :order) || []

  order_hashes.each do |order_hash|
    log_info "Processing Walmart order #{order_hash[:purchaseOrderId]}"
    orders_in_batch += 1
    po_number = order_hash[:purchaseOrderId]

    begin
      same_po_orders = orders_with_walmart_po(po_number)
      if same_po_orders.select { |o| o.created_at > 12.months.ago }.none?
        orders_created << process_order(order_hash)
      elsif (recent_same_po_orders = same_po_orders.select { |o| o.created_at < 4.days.ago && o.created_at > 12.months.ago }).any?
        # Only send notification if we haven't already notified about this duplicate PO
        unless duplicate_po_already_notified?(po_number, edi_log_id)
          subj = "EDI order message for orchestrator: #{orchestrator.partner}, SKIPPED, order message PO: #{po_number}, duplicate"
          msg = "EDI order message for orchestrator: #{orchestrator.partner}, EDI Communication log ID: #{edi_log_id}, order message PO: #{po_number}, " \
                "has the same PO as the following recent EDI orders: #{recent_same_po_orders.map { |o| "#{o.reference_number} (created #{o.created_at.to_date})" }.join(', ')}. " \
                "It has been SKIPPED as this is an indication of a duplicate order."
          InternalMailer.notify_edi_admin_of_warning(subj, msg).deliver_later
          mark_duplicate_po_as_notified(po_number, edi_log_id)
        end
      end
    rescue StandardError => e
      subj = "EDI order message for orchestrator: #{orchestrator.partner}, ERROR, SKIPPED, order message PO: #{po_number}, #{e.message}"
      msg = "Exception #{e.message} while processing Walmart order #{po_number} with orchestrator #{orchestrator.partner}, and edi log id #{edi_log_id}"
      ErrorReporting.error(e, msg)
      InternalMailer.notify_edi_admin_of_warning(subj, msg).deliver_later
    end
  end

  # Notify orders team of new Walmart orders (using existing EDI notification)
  report_order_creation_issues([BatchProcessResult.new(batch_number: nil, orders_created: orders_created, orders_in_batch: orders_in_batch)]) if orders_created.any?

  BatchProcessResult.new(
    batch_number: nil,
    orders_created: orders_created,
    orders_in_batch: orders_in_batch
  )
end

#select_cheapest_sww_rate(delivery, order) ⇒ Object

Select the cheapest Ship with Walmart (SWW) rate that meets the delivery
deadline. Once applied, the existing selection_is_intentional guard in
Delivery#set_proper_shipping_cost will protect this selection from being
overridden by non-SWW options on subsequent saves/rate refreshes.

Priority order:

  1. Cheapest SWW rate where Walmart confirms delivery promise fulfilled
  2. Cheapest SWW rate whose estimated delivery meets requested_deliver_by
  3. Cheapest SWW rate overall (fallback — still better than FedEx Ground default)

Parameters:

  • delivery (Delivery)

    The order's delivery (must have shipping_costs loaded)

  • order (Order)

    The parent order (for deadline + logging)



494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'app/services/edi/walmart/order_message_processor.rb', line 494

def select_cheapest_sww_rate(delivery, order)
  sww_costs = delivery.shipping_costs.select(&:is_sww?)
  if sww_costs.empty?
    log_info("No SWW rates available for order #{order.reference_number}, skipping SWW auto-select")
    return
  end

  deliver_by = order.requested_deliver_by
  best = nil

  # 1. Prefer rates where Walmart's own API says delivery promise is met
  promise_met = sww_costs.select { |sc| sc.sww_delivery_promise_fulfilled }
                         .sort_by { |sc| sc.cost.to_f }
  best = promise_met.first

  # 2. Fall back to checking estimated delivery against our deadline
  if best.nil? && deliver_by.present?
    on_time = sww_costs.select do |sc|
      est = sc.carrier_estimated_delivery_date
      est.present? && est <= deliver_by
    end.sort_by { |sc| sc.cost.to_f }
    best = on_time.first
  end

  # 3. Last resort: cheapest SWW rate regardless of deadline
  best ||= sww_costs.min_by { |sc| sc.cost.to_f }

  # Set the SWW rate on the delivery and save with callbacks so that
  # set_proper_shipping_cost runs naturally — it will find this SWW rate,
  # treat it as intentional (selection_is_intentional = true), and properly
  # create/update the shipping line item with the correct price and item.
  delivery.shipping_option_id = best.shipping_option_id
  delivery.selected_shipping_cost_id = best.id
  delivery.force_shipping_cost_update = true
  delivery.save!

  log_info(
    "Auto-selected cheapest SWW rate for order #{order.reference_number}: " \
    "#{best.shipping_option&.description} @ $#{best.cost.to_f.round(2)}, " \
    "deliver_by=#{deliver_by}, est_delivery=#{best.carrier_estimated_delivery_date}, " \
    "promise_fulfilled=#{best.sww_delivery_promise_fulfilled || false}"
  )
end

#set_shipping_option(order, order_hash) ⇒ Object



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'app/services/edi/walmart/order_message_processor.rb', line 303

def set_shipping_option(order, order_hash)
  # Extract shipping info from Walmart order
  shipping_info = order_hash[:shippingInfo] || {}
  method_code = shipping_info[:methodCode] || 'Standard'

  order.edi_original_ship_code = method_code

  # For Walmart orders, we use Ship with Walmart (SWW) shipping options
  # The specific carrier/service will be selected when rates are retrieved
  # Setting to 'sww' indicates any WalmartSeller shipping option is valid
  order.edi_shipping_option_name = 'sww'
  order.shipping_method = 'sww'

  # Early label purchase - controlled by orchestrator.early_label_purchase_enabled?
  # When enabled, labels are purchased at order release instead of at ship-label time
  order.purchase_label_early = orchestrator.early_label_purchase_enabled?

  # Set ship dates based on Walmart's requirements
  order.requested_ship_on_or_after = Date.current
  if (estimated_ship = shipping_info[:estimatedShipDate])
    order.requested_ship_before = parse_walmart_date(estimated_ship).to_date
  end
  if (estimated_delivery = shipping_info[:estimatedDeliveryDate])
    order.requested_deliver_by = parse_walmart_date(estimated_delivery).to_date.prev_day
  end

  order.edi_shipping_option_name
end