Class: Edi::Amazon::ConfirmMessageProcessor
- Inherits:
-
BaseEdiService
- Object
- BaseService
- BaseEdiService
- Edi::Amazon::ConfirmMessageProcessor
- Defined in:
- app/services/edi/amazon/confirm_message_processor.rb
Overview
Service object: confirm message processor.
Constant Summary collapse
- CARRIER_TO_CARRIER_CODE_MAP_HASH =
{ Canadapost: 'Canada Post', Canpar: 'Canpar', FedEx: 'FedEx', Purolator: 'PUROLATOR', Ups: 'UPS', UPS: 'UPS', Usps: 'USPS', USPS: 'USPS', RlCarriers: 'R+L', AmazonSeller: 'Amazon', # ShipEngine LTL carriers — values verified against Amazon's # acceptable-CarrierCode enumeration (amzn-base release_1_9 XSD, # checked 2026-07-18): 'Conway', 'Saia', 'R+L', 'Roadrunner', and # 'Fedex Freight' (Amazon's casing) are listed; SCACs (CNWY…) are not. ShipengineXpo: 'Conway', ShipengineSaia: 'Saia', ShipengineRlCarriers: 'R+L', ShipengineRoadrunner: 'Roadrunner', ShipengineFedExFreight: 'Fedex Freight', ShipengineFedExFreightEconomy: 'Fedex Freight' }.freeze
Constants included from RequestIdentifiable
RequestIdentifiable::REQUEST_ID_HEADERS
Constants included from Edi::AddressAbbreviator
Edi::AddressAbbreviator::MAX_LENGTH
Instance Attribute Summary
Attributes inherited from BaseEdiService
Attributes inherited from BaseService
Instance Method Summary collapse
- #acknowledge_order(order) ⇒ Object
- #back_order(order) ⇒ Object
- #build_acknowledge_message(shipment, order_items) ⇒ Object
- #cancel_order(order) ⇒ Object
- #confirm_invoice(invoice) ⇒ Object
- #map_carrier_code_from_hw_carrier(carrier) ⇒ Object
-
#map_order_items_to_shipments(delivery, order_hash) ⇒ Hash{Shipment => Array<Hash>}
Maps each Amazon order item to exactly one shipment/tracking number, then groups by shipment.
- #process(confirm_message, category, order = nil) ⇒ Object
- #resolve_amazon_order_item_id(parent_li, order_hash) ⇒ Object
Methods inherited from BaseEdiService
#amazon_feed_product_type, #duplicate_po_already_notified?, #initialize, #mark_duplicate_po_as_notified, #onboard_ordered_catalog_items, #report_order_creation_issues, #safe_process_edi_communication_log
Methods included from RequestIdentifiable
Methods included from Edi::AddressAbbreviator
#abbreviate_street, #collect_street_originals, #record_address_abbreviation_notes
Methods inherited from BaseService
#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #tagged_logger
Constructor Details
This class inherits a constructor from Edi::BaseEdiService
Instance Method Details
#acknowledge_order(order) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 32 def acknowledge_order(order) delivery = order.deliveries.first # Ship confirm when we label via Heatwave (including Amazon Buy Shipping labels purchased through our system). # When using Veeqo or Buy Amazon Shipping directly through Seller Central, it already handles the ship confirmation. return unless delivery.ship_labeled_via_heatwave? # Buy Shipping V2 purchaseShipment already notifies Amazon the order is shipped. # A separate confirmShipment is redundant and fails with "PackageToUpdateNotFound". if delivery.carrier == 'AmazonSeller' Rails.logger.info("[Amazon ConfirmMessageProcessor] Skipping confirmShipment for AMZBS order #{order.id} — purchaseShipment already confirmed") return end order_hash = JSON.parse(order.).with_indifferent_access shipment_item_groups = map_order_items_to_shipments(delivery, order_hash) return if shipment_item_groups.empty? shipment_item_groups.map do |shipment, order_items| m = (shipment, order_items) process(m, :order_acknowledge, order) end end |
#back_order(order) ⇒ Object
152 153 154 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 152 def back_order(order) # do nothing for Amazon SC. end |
#build_acknowledge_message(shipment, order_items) ⇒ Object
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 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 115 def (shipment, order_items) # For Amazon Buy Shipping labels, use the actual carrier from metadata effective_carrier = if shipment.carrier == 'AmazonSeller' && shipment.respond_to?(:amz_carrier) && shipment.amz_carrier.present? shipment.amz_carrier else shipment.carrier end carrier_code = map_carrier_code_from_hw_carrier(effective_carrier) { packageDetail: { packageReferenceId: shipment.id.to_s, carrierCode: carrier_code, # carrierName is only required when carrierCode is 'Other'. Echo the # mapped code when we have one; otherwise send the internal carrier # name minus the 'Shipengine' integration prefix (buyer-facing). carrierName: carrier_code == 'Other' ? effective_carrier.to_s.delete_prefix('Shipengine') : carrier_code, shippingMethod: shipment.delivery.shipping_method_friendly, # display_tracking_number resolves the delivery-level PRO for ShipEngine # LTL (the per-pallet tracking_number is blank). Known gap: some LTL # carriers assign the PRO asynchronously after booking, and Amazon # requires trackingNumber — see # doc/tasks/202607181042_LTL_ASYNC_PRO_EDI_SHIP_CONFIRM_GAP.md. trackingNumber: shipment.display_tracking_number, shipDate: (shipment.delivery.shipped_date || shipment.delivery.ship_labeled_at || Time.zone.now)&.iso8601, orderItems: order_items }, codCollectionMethod: "DirectPayment", marketplaceId: orchestrator.marketplace } end |
#cancel_order(order) ⇒ Object
156 157 158 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 156 def cancel_order(order) # do nothing for Amazon SC. end |
#confirm_invoice(invoice) ⇒ Object
160 161 162 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 160 def confirm_invoice(invoice) # do nothing for Amazon SC. end |
#map_carrier_code_from_hw_carrier(carrier) ⇒ Object
148 149 150 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 148 def map_carrier_code_from_hw_carrier(carrier) CARRIER_TO_CARRIER_CODE_MAP_HASH[carrier.to_sym] || 'Other' end |
#map_order_items_to_shipments(delivery, order_hash) ⇒ Hash{Shipment => Array<Hash>}
Maps each Amazon order item to exactly one shipment/tracking number, then groups
by shipment. This is item-centric rather than shipment-centric, which correctly
handles multibox kits (single item shipped across multiple packages).
The SP-API confirmShipment endpoint requires each orderItemId to appear in exactly
one package confirmation. Sending the same item in multiple calls causes 400 errors
("order already fulfilled"). This method ensures every item gets confirmed exactly once.
Algorithm:
- For each completed shipment, count how many components of each parent line item it contains
- Assign each parent to the shipment with the most components (its "primary" package)
- Group parents by their primary shipment
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 69 def map_order_items_to_shipments(delivery, order_hash) all_shipments = delivery.shipments.completed.top_level .includes(shipment_contents: { line_item: :parent }).to_a return {} if all_shipments.empty? shipment_by_id = all_shipments.index_by(&:id) # Count components of each parent line item in each shipment. # For non-kit items (no parent), the line item itself acts as the parent. parent_shipment_component_counts = Hash.new { |h, k| h[k] = Hash.new(0) } all_shipments.each do |shipment| shipment.shipment_contents.each do |sc| parent_id = (sc.line_item.parent || sc.line_item).id parent_shipment_component_counts[parent_id][shipment.id] += sc.quantity end end parent_line_items = delivery.line_items.goods.parents_only.to_a groups = Hash.new { |h, k| h[k] = [] } parent_line_items.each do |parent_li| counts = parent_shipment_component_counts[parent_li.id] next if counts.empty? best_shipment_id = counts.max_by { |_sid, count| count }.first shipment = shipment_by_id[best_shipment_id] next unless shipment order_item_id = resolve_amazon_order_item_id(parent_li, order_hash) next unless order_item_id groups[shipment] << { orderItemId: order_item_id, quantity: parent_li.quantity } end groups end |
#process(confirm_message, category, order = nil) ⇒ Object
164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 164 def process(, category, order = nil) edi_log = nil EdiCommunicationLog.transaction do edi_log = EdiCommunicationLog.create! partner: orchestrator.partner, category: category, data: .to_json, data_type: 'json', file_info: { lines_confirmed: [:packageDetail][:orderItems].size }, transmit_datetime: Time.current end edi_log.edi_documents.create!(order: order) if order.present? edi_log end |
#resolve_amazon_order_item_id(parent_li, order_hash) ⇒ Object
109 110 111 112 113 |
# File 'app/services/edi/amazon/confirm_message_processor.rb', line 109 def resolve_amazon_order_item_id(parent_li, order_hash) order_item_id = parent_li.edi_reference order_item_id = order_hash[:OrderItems][(parent_li.edi_line_number || 1) - 1]&.dig(:OrderItemId) if order_item_id&.include?(order_hash[:AmazonOrderId]) order_item_id end |