Class: Edi::Walmart::ShipCodeMapper

Inherits:
Object
  • Object
show all
Defined in:
app/services/edi/walmart/ship_code_mapper.rb

Constant Summary collapse

CARRIER_MAP =

Maps internal carrier names to Walmart carrier codes
Official list: https://developer.walmart.com/us-marketplace/docs/supported-carrier-names
Parcel carriers: https://marketplacelearn.walmart.com/guides/Seller%20Fulfillment%20Services/Shipping%20methods/Shipping-methods:-parcel-shipping-carriers

IMPORTANT: Use exact Walmart carrier names - incorrect values cause shipment update failures

{
  # Primary carriers (Walmart preferred)
  'UPS' => 'UPS',
  'Ups' => 'UPS',
  'ups' => 'UPS',
  'USPS' => 'USPS',
  'Usps' => 'USPS',
  'usps' => 'USPS',
  'FedEx' => 'FedEx',
  'Fedex' => 'FedEx',
  'FEDEX' => 'FedEx',
  'fedex' => 'FedEx',

  # UPS variants
  'UPSMI' => 'UPSMI',
  'UPS Mail Innovations' => 'UPSMI',

  # FedEx variants
  'FDX' => 'FDX',
  'FEDEXSP' => 'FEDEXSP',
  'FedEx SmartPost' => 'FEDEXSP',

  # DHL variants
  'DHL' => 'DHL',
  'dhl' => 'DHL',
  'DHL Ecommerce' => 'DHL Ecommerce - US',
  'DHL eCommerce' => 'DHL Ecommerce - US',

  # Regional US carriers
  'OnTrac' => 'OnTrac',
  'ONTRAC' => 'OnTrac',
  'LSO' => 'LSO',
  'Lone Star Overnight' => 'LSO',
  'LS' => 'LS',
  'LaserShip' => 'LS',
  'Lasership' => 'LS',
  'UDS' => 'UDS',
  'United Delivery Service' => 'UDS',
  'AxleHire' => 'AxleHire',
  'Better Trucks' => 'Better Trucks',

  # Freight carriers
  'SAIA' => 'SAIA',
  'Saia' => 'SAIA',
  'Estes' => 'Estes Express',
  'ESTES' => 'Estes Express',
  'Estes Express' => 'Estes Express',
  'XPO' => 'XPO Logistics',
  'XPO Logistics' => 'XPO Logistics',
  'ABF' => 'ABF Freight System',
  'ABF Freight' => 'ABF Freight System',
  'RL Carriers' => 'RL Carriers',
  'R+L Carriers' => 'RL Carriers',
  'R+L' => 'RL Carriers',
  'RlCarriers' => 'RL Carriers',
  'CEVA' => 'CEVA',
  'TForce' => 'TForce Freight',
  'TForce Freight' => 'TForce Freight',
  'AAA Cooper' => 'AAA Cooper',
  'Southeastern' => 'Southeastern Freight Lines',
  'Southeastern Freight' => 'Southeastern Freight Lines',
  'PITT OHIO' => 'PITT OHIO',
  'Roadrunner' => 'Roadrunner Freight',
  'Meyer' => 'Meyer Distribution',
  'RXO' => 'RXO',
  'PILOT' => 'PILOT',
  'AM Trucking' => 'AM Trucking',
  'Yellow Freight' => 'Yellow Freight Sys',
  'YRC' => 'Yellow Freight Sys',
  'AIT' => 'AIT Worldwide Logistics',

  # Canadian carriers
  'Canada Post' => 'Canada Post',
  'Canadapost' => 'Canada Post',
  'CanadaPost' => 'Canada Post',
  'CANADA POST' => 'Canada Post',
  'Purolator' => 'Purolator',
  'PUROLATOR' => 'Purolator',
  'Canpar' => 'Canpar',
  'CANPAR' => 'Canpar',

  # International carriers
  'Royal Mail' => 'Royal Mail',
  'Deutsche Post' => 'Deutsche Post',
  'Japan Post' => 'Japan Post',
  'India Post' => 'India Post',
  'China Post' => 'China Post',
  'Correos de Mexico' => 'Correos de Mexico',
  'PostNord' => 'PostNord Sweden',

  # International express
  'SF Express' => 'SF Express',
  'SFC' => 'SFC',
  'YunExpress' => 'YunExpress',
  'Yanwen' => 'Yanwen',
  '4PX' => '4PX',
  'CNE' => 'CNE',
  'Chukou1' => 'Chukou1',
  'GLS' => 'GLS',
  'Sunyou' => 'Sunyou',
  'Sendle' => 'Sendle',
  'JD Logistics' => 'JD Logistics',
  'JCEX' => 'JCEX',
  'YDH' => 'YDH',
  'Equick' => 'Equick',
  'Tusou' => 'Tusou',
  'WanB' => 'WanB',
  'Flyt' => 'Flyt',
  'Asendia' => 'Asendia',
  'UBI' => 'UBI',
  'ePost Global' => 'ePost Global',
  'YF Logistics' => 'YF Logistics',
  'Shypmax' => 'Shypmax',
  'Shiprocket' => 'Shiprocket',
  'DTDC' => 'DTDC',
  'PTS' => 'PTS',
  'Whistl' => 'Whistl',
  'WIN.IT America' => 'WIN.IT America',

  # Specialty carriers
  'FDS Express' => 'FDS Express',
  'Seko' => 'Seko Worldwide',
  'Seko Worldwide' => 'Seko Worldwide',
  'HIT Delivery' => 'HIT Delivery',
  'OSM' => 'OSM Worldwide',
  'OSM Worldwide' => 'OSM Worldwide',
  'FIRST MILE' => 'FIRST MILE',
  'Landmark Global' => 'Landmark Global',
  'Metropolitan' => 'Metropolitan Warehouse & Delivery',

  # Legacy/alternate names
  'Airborne' => 'Airborne',
  'Forward Air' => 'Forward Air',
  'Conway' => 'Conway',
  'Spee-Dee' => 'Spee-Dee',
  'A1' => 'A1',
  'Averitt' => 'Averitt'
}.freeze
PRIMARY_CARRIER_PREFIXES =

Prefix matching for carrier strings that include service-level suffixes
e.g., "FedEx Ground Economy" → "FedEx", "UPS 2nd Day Air" → "UPS"
Only primary carriers need this — regional/specialty carriers rarely appear with suffixes

{
  'fedex' => 'FedEx',
  'ups' => 'UPS',
  'usps' => 'USPS',
  'dhl' => 'DHL'
}.freeze
METHOD_MAP =

Walmart method codes for shipping service levels
Valid values: Standard, Express, TwoDay, NextDay, Value (case-sensitive)
See: https://developer.walmart.com/us-marketplace/docs/get-an-order

{
  # Standard shipping (3-5 days)
  'Ground' => 'Standard',
  'Standard' => 'Standard',
  'standard' => 'Standard',
  'FedEx Ground' => 'Standard',
  'UPS Ground' => 'Standard',

  # Express shipping (2-3 days)
  'Express' => 'Express',
  'express' => 'Express',
  'Expedited' => 'Express',
  'Priority' => 'Express',
  '3-Day' => 'Express',

  # Two-day shipping
  '2-Day' => 'TwoDay',
  '2Day' => 'TwoDay',
  'TwoDay' => 'TwoDay',
  'Two Day' => 'TwoDay',
  'FedEx 2Day' => 'TwoDay',
  'UPS 2nd Day Air' => 'TwoDay',

  # Next-day shipping
  'Overnight' => 'NextDay',
  'NextDay' => 'NextDay',
  'Next Day' => 'NextDay',
  '1-Day' => 'NextDay',
  'FedEx Priority Overnight' => 'NextDay',
  'FedEx Standard Overnight' => 'NextDay',
  'UPS Next Day Air' => 'NextDay',

  # Value shipping (5-8 days, economy)
  'Value' => 'Value',
  'value' => 'Value',
  'Economy' => 'Value',
  'Saver' => 'Value',

  # Freight (maps to Standard for now - verify with Walmart if freight-specific code exists)
  'Freight' => 'Standard',
  'LTL' => 'Standard',
  'White Glove' => 'Standard'
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(orchestrator) ⇒ ShipCodeMapper

Returns a new instance of ShipCodeMapper.



208
209
210
# File 'app/services/edi/walmart/ship_code_mapper.rb', line 208

def initialize(orchestrator)
  @orchestrator = orchestrator
end

Instance Attribute Details

#orchestratorObject (readonly)

Returns the value of attribute orchestrator.



206
207
208
# File 'app/services/edi/walmart/ship_code_mapper.rb', line 206

def orchestrator
  @orchestrator
end

Instance Method Details

#carrier_code(carrier) ⇒ String

Map internal carrier name to Walmart carrier code

Parameters:

  • carrier (String)

    Internal carrier name

Returns:

  • (String)

    Walmart carrier code, or nil to use otherCarrier field



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'app/services/edi/walmart/ship_code_mapper.rb', line 215

def carrier_code(carrier)
  return nil if carrier.blank?

  carrier_str = carrier.to_s.strip
  # Try exact match first
  return CARRIER_MAP[carrier_str] if CARRIER_MAP.key?(carrier_str)

  # Try case-insensitive match
  CARRIER_MAP.each do |key, value|
    return value if key.casecmp?(carrier_str)
  end

  # Try prefix match for carriers with service-level suffixes
  # e.g., "FedEx Ground Economy" should match "FedEx"
  carrier_lower = carrier_str.downcase
  PRIMARY_CARRIER_PREFIXES.each do |prefix, walmart_code|
    return walmart_code if carrier_lower.start_with?(prefix)
  end

  # Not found - will use otherCarrier field
  nil
end

#carrier_info(shipment) ⇒ Hash

Build the complete carrier info hash for ship confirmation
Walmart API expects carrierName as:
{ "carrier": "UPS" } for known carriers
{ "otherCarrier": "Custom Carrier Name" } for unknown carriers

Parameters:

  • shipment (Shipment)

    The shipment object

Returns:

  • (Hash)

    Carrier info with carrierName and methodCode



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'app/services/edi/walmart/ship_code_mapper.rb', line 272

def carrier_info(shipment)
  # For Ship with Walmart shipments, use the actual carrier from sww_metadata
  # shipment.carrier = "WalmartSeller" indicates SWW, actual carrier is in sww_carrier
  is_sww = shipment.carrier == 'WalmartSeller'
  actual_carrier = if is_sww && shipment.sww_carrier.present?
                     shipment.sww_carrier
                   else
                     shipment.carrier
                   end

  carrier = carrier_code(actual_carrier)
  carrier_name = if carrier
                   { carrier: carrier }
                 else
                   { otherCarrier: actual_carrier.to_s }
                 end

  # Build tracking URL using the actual carrier.
  # Shipment.tracking_link can return "#" when the carrier/tracking-number combo
  # isn't recognized — reject anything that isn't a real http(s) URL.
  tracking_url = if shipment.tracking_number.present? && actual_carrier.present?
    url = Shipment.tracking_link(actual_carrier, shipment.tracking_number)
    url.present? && url.start_with?('http') ? url : nil
  else
    nil
  end

  # For SWW shipments, extract method from delivery's selected_shipping_cost.description
  # e.g., "Ship with Walmart: FedEx Ground Economy" -> "Ground Economy"
  # For regular shipments, use the standard method_code mapping
  method = if is_sww
             extract_method_from_delivery(shipment.delivery&.selected_shipping_cost&.description, actual_carrier)
           else
             method_code(shipment.delivery&.shipping_method_friendly)
           end

  {
    carrierName: carrier_name,
    methodCode: method,
    trackingNumber: shipment.tracking_number,
    trackingURL: tracking_url
  }.compact
end

#extract_method_from_delivery(description, carrier) ⇒ String

Extract method name from delivery's shipping cost description

Parameters:

  • description (String)

    e.g., "Ship with Walmart: FedEx Ground Economy"

  • carrier (String)

    e.g., "FedEx", "USPS"

Returns:

  • (String)

    Method name, e.g., "Ground Economy"



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'app/services/edi/walmart/ship_code_mapper.rb', line 320

def extract_method_from_delivery(description, carrier)
  return 'Standard' if description.blank?

  method_str = description.to_s

  # Strip "Ship with Walmart: " prefix if present
  method_str = method_str.sub(/^Ship with Walmart:\s*/i, '')

  # Strip carrier name prefix (e.g., "FedEx Ground Economy" -> "Ground Economy")
  if carrier.present?
    # Try exact carrier match first, then normalized
    method_str = method_str.sub(/^#{Regexp.escape(carrier)}\s+/i, '')
  end

  method_str.strip.presence || 'Standard'
end

#known_carrier?(carrier) ⇒ Boolean

Check if a carrier is in Walmart's known carrier list

Parameters:

  • carrier (String)

    Carrier name

Returns:

  • (Boolean)


340
341
342
# File 'app/services/edi/walmart/ship_code_mapper.rb', line 340

def known_carrier?(carrier)
  carrier_code(carrier).present?
end

#method_code(method) ⇒ String

Map internal shipping method to Walmart method code

Parameters:

  • method (String)

    Internal shipping method

Returns:

  • (String)

    Walmart method code or 'Standard' if not found



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'app/services/edi/walmart/ship_code_mapper.rb', line 241

def method_code(method)
  return 'Standard' if method.blank?

  method_str = method.to_s.strip
  # Try exact match first
  return METHOD_MAP[method_str] if METHOD_MAP.key?(method_str)

  # Try case-insensitive match
  METHOD_MAP.each do |key, value|
    return value if key.casecmp?(method_str)
  end

  # Try keyword extraction for longer strings like "FedEx Ground Economy"
  method_lower = method_str.downcase
  return 'NextDay' if method_lower.match?(/overnight|next.?day|1.?day/)
  return 'TwoDay' if method_lower.match?(/2.?day|two.?day|2nd.?day/)
  return 'Express' if method_lower.match?(/express|expedit|priority|3.?day/)
  return 'Value' if method_lower.match?(/economy|saver|value/)
  return 'Standard' if method_lower.match?(/ground|standard|home.?delivery/)

  # Default to Standard
  'Standard'
end