Class: Edi::Walmart::FeedSubmissionResultProcessor
- Inherits:
-
BaseEdiService
- Object
- BaseService
- BaseEdiService
- Edi::Walmart::FeedSubmissionResultProcessor
- Defined in:
- app/services/edi/walmart/feed_submission_result_processor.rb
Overview
Service object: feed submission result processor.
Constant Summary collapse
- INVENTORY_INGESTION_RETRY_LIMIT =
How many times a rejected inventory feed is resubmitted before it becomes an
exception. See #retry_rejected_inventory_feed. 1
Constants included from RequestIdentifiable
RequestIdentifiable::REQUEST_ID_HEADERS
Constants included from AddressAbbreviator
AddressAbbreviator::MAX_LENGTH
Instance Attribute Summary
Attributes inherited from BaseEdiService
Attributes inherited from BaseService
Instance Method Summary collapse
- #get_errors_from_feed_result(json_data) ⇒ Object
- #instantiate_transporter(transporter, transporter_profile = nil) ⇒ Object
- #process(edi_communication_logs = nil) ⇒ Object
-
#retry_rejected_inventory_feed(ecl, result) ⇒ Boolean
Walmart rejects a small share of structurally valid inventory feeds with itemsReceived 0 (ERR_EXT_DATA_0503009, ERR_PDI_0001).
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 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
#get_errors_from_feed_result(json_data) ⇒ Object
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 |
# File 'app/services/edi/walmart/feed_submission_result_processor.rb', line 105 def get_errors_from_feed_result(json_data) errs = [] # see: oh wait there is no *&^%*^&% documentation!!!! Probably because this is a ridiculously stupid ^&%&^% implementation! # There's just this sample operation garbage-y response in postman (see my overview below and full details at the end) # errors: [] # feedId: <string> # feedStatus: PROCESSED # ingestionErrors: {ingestionError: []} # itemsReceived: <integer> # itemsSucceeded: <integer> # itemsFailed: <integer> # itemsProcessing: <integer> # offset: <integer> # limit: <integer> # itemDetails: {itemIngestionStatus: []} hsh = JSON.parse(json_data).with_indifferent_access num_processed = hsh&.dig(:itemsReceived).to_i num_errors = hsh&.dig(:itemsFailed).to_i if num_processed > 0 && num_errors > 0 # First deal with the &*^&*^ errors, I mean this is utter &^*&^& stupidity hsh&.dig(:errors)&.each do |err_hsh| errs << "Error code: #{err_hsh.dig(:code)}, field: #{err_hsh.dig(:field)}, description: #{err_hsh.dig(:description)}, info: #{err_hsh.dig(:info)}, severity: #{err_hsh.dig(:severity)}, category: #{err_hsh.dig(:category)}, component: #{err_hsh.dig(:component)}, type: #{err_hsh.dig(:type)}, serviceName: #{err_hsh.dig(:serviceName)}, gatewayErrorCategory: #{err_hsh.dig(:gatewayErrorCategory)}, causes: #{err_hsh.dig(:causes)}" end # Now deal with the &*^&*^ ingestion errors, more &^*&^& stupidity hsh&.dig(:ingestionErrors)&.dig(:ingestionError)&.each do |ing_err_hsh| errs << "Ingestion Error code: #{ing_err_hsh.dig(:code)}, type: #{ing_err_hsh.dig(:type)}, description: #{ing_err_hsh.dig(:description)}" end # Now deal with the &*^&*^ item details ingestion errors hsh&.dig(:itemDetails)&.dig(:itemIngestionStatus)&.each do |item_ing_status_hsh| next unless item_ing_status_hsh.dig(:ingestionStatus)&.upcase&.include?('ERROR') sku = item_ing_status_hsh.dig(:sku) item_ing_status_hsh.dig(:ingestionErrors)&.dig(:ingestionError)&.each do |item_ing_err_hsh| errs << "SKU: #{sku}, error code: #{item_ing_err_hsh.dig(:code)}, type: #{item_ing_err_hsh.dig(:type)}, description: #{item_ing_err_hsh.dig(:description)}" end end end errs end |
#instantiate_transporter(transporter, transporter_profile = nil) ⇒ Object
96 97 98 99 100 101 102 103 |
# File 'app/services/edi/walmart/feed_submission_result_processor.rb', line 96 def instantiate_transporter(transporter, transporter_profile = nil) case transporter when :http_walmart_seller_api Transport::HttpWalmartSellerApiConnection.new({ profile: transporter_profile }.merge()) else raise "Unknown transporter: #{transporter}" end end |
#process(edi_communication_logs = nil) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 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 59 60 61 62 |
# File 'app/services/edi/walmart/feed_submission_result_processor.rb', line 10 def process(edi_communication_logs = nil) edi_communication_logs ||= EdiCommunicationLog .where(state: 'processing') .where(partner: orchestrator.partner) .where.not(transaction_id: nil) .order(:created_at) [edi_communication_logs].flatten.each do |ecl| logger.info "EdiCommunicationLog:#{ecl.id} - Retrieving API transaction result from #{orchestrator.partner} for transaction id: #{ecl.transaction_id}" begin transport = instantiate_transporter(orchestrator.transporter, orchestrator.transporter_profile) res = transport.send_data('', "#{orchestrator.}/#{ecl.transaction_id}?includeDetails=true", 'GET') # with this, you only get the first 50 by default, see below (from Postman gobbledygook): # includeDetails: Includes details of each entity in the feed. # offset: The object response to start with, where 0 is the first entity that can be requested. It can only be used when includeDetails is set to true. # limit: The number of entities to be returned. It cannot be more than 50 entities. Use it only when the includeDetails is set to true. # Read the response body once (HTTP::Response::Body can only be consumed once) data = res[:http_result]&.body.to_s # Carry the submission's correlation id forward — this update replaces the # notes FeedMessageSender wrote, and that id is what a Walmart support case # needs. The poll's own id is recorded separately; they are different requests. submit_correlation_id = ecl.file_info.to_h['wm_correlation_id'] ecl.update(notes: "HTTP CODE: #{res[:http_result]&.status}, HTTP BODY: #{data}, SUBMIT WM_QOS.CORRELATION_ID: #{submit_correlation_id || 'not recorded'}, POLL WM_QOS.CORRELATION_ID: #{res[:correlation_id]}, Timestamp: #{Time.current.to_datetime.to_fs(:crm_default)}") logger.info "Result: HTTP CODE: #{res[:http_result]&.status}, HTTP BODY: #{data}" if res[:success] && data.present? json_hash = JSON.parse(data).with_indifferent_access status = json_hash.dig('feedStatus') timeout_time = (ecl.transmit_datetime || Time.current) + orchestrator.failure_timeout_in_minutes.minutes # See: https://developer.walmart.com/us-marketplace/docs/feeds-overview if status == 'PROCESSED' upload = Upload.uploadify_from_data(file_name: "#{ecl.transaction_id}.json", data: data, category: 'feed_document_result_json') if upload ecl.uploads << upload if (errors = get_errors_from_feed_result(data)).empty? ecl.complete! else ecl.update(notes: [ecl.notes, "There were #{errors.size} errors reported in feed result: #{errors.join('| ')}"].compact.join(' || ')) ecl.error end else ecl.update(notes: [ecl.notes, "Could not upload feed result #{ecl.transaction_id}"].compact.join(' | ')) ecl.error end elsif Time.current > timeout_time ecl.update(notes: [ecl.notes, "Timed out after #{orchestrator.failure_timeout_in_minutes} minutes"].compact.join(' | ')) ecl.error! elsif %w[ERROR].include?(status) ecl.error! unless retry_rejected_inventory_feed(ecl, json_hash) end else ecl.error end end end end |
#retry_rejected_inventory_feed(ecl, result) ⇒ Boolean
Walmart rejects a small share of structurally valid inventory feeds with
itemsReceived 0 (ERR_EXT_DATA_0503009, ERR_PDI_0001). It is their bug, not our
payload: the byte-identical feed is accepted on resubmission — CA md5 D7C2D442E4
processed 86/86 nine times on 2026-07-29 and was rejected once at 12:00. At the
~11% rejection rate seen since 2026-07-28 a single retry takes a run's odds from
89% to ~99%, instead of leaving inventory stale until the next hourly flow.
Inventory only, and once per feed — a feed Walmart rejects twice is a real
problem and belongs in the ediadmin mail, not in a resubmission loop.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'app/services/edi/walmart/feed_submission_result_processor.rb', line 77 def retry_rejected_inventory_feed(ecl, result) return false unless ecl.category == 'inventory_advice' # itemsReceived > 0 means Walmart parsed the feed and rejected rows on their # merits — resending the same rows would just fail the same way. return false unless result[:itemsReceived].to_i.zero? attempts = ecl.file_info.to_h['ingestion_retries'].to_i return false if attempts >= INVENTORY_INGESTION_RETRY_LIMIT logger.warn "[Walmart FeedResult] EdiCommunicationLog:#{ecl.id} rejected with itemsReceived 0, resubmitting (attempt #{attempts + 1}/#{INVENTORY_INGESTION_RETRY_LIMIT})" ecl.file_info = ecl.file_info.to_h.merge('ingestion_retries' => attempts + 1) # next_attempt so the row is still picked up by `requiring_processing` if the # inline resubmission below dies before it can transition the record. ecl.next_attempt = Time.current ecl.schedule_retry! orchestrator..process(ecl) true end |