Class: Edi::MiraklSeller::PriceMessageProcessor
- Inherits:
-
BaseEdiService
- Object
- BaseService
- BaseEdiService
- Edi::MiraklSeller::PriceMessageProcessor
- Defined in:
- app/services/edi/mirakl_seller/price_message_processor.rb
Overview
Service object: price message processor.
Constant Summary
Constants included from AddressAbbreviator
AddressAbbreviator::MAX_LENGTH
Instance Attribute Summary
Attributes inherited from BaseEdiService
Attributes inherited from BaseService
Instance Method Summary collapse
- #append_catalog_items(xml, items) ⇒ Object
-
#build_xml(items: nil, catalog_items: nil, states: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
- #create_attribute(xml, key, value) ⇒ Object
- #create_code_value_attribute(xml, key, value, parent_group = nil) ⇒ Object
- #generate_product_inventory_data(item, catalog_id) ⇒ Object
- #load_items ⇒ Object
-
#manufacturer_warranty(item) ⇒ Object
Best Buy's mandatory
manufacturer-warrantyoffer field, expressed as an integer number of days. - #manufacturer_warranty_text(item) ⇒ Object
- #process(catalog_items: nil, states: nil) ⇒ Object
- #target_catalogs ⇒ Object
- #warranty_text_to_days(text) ⇒ Object
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_info, #log_warning, #logger, #tagged_logger
Constructor Details
This class inherits a constructor from Edi::BaseEdiService
Instance Method Details
#append_catalog_items(xml, items) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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 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 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 47 def append_catalog_items(xml, items) items.each do |item| # Keep the offer feed in lockstep with the product feed: only offer # items the product presenter actually maps to a marketplace category. # Without this, the offer feed lists products the product feed skipped # (e.g. accessories with no BB category), which the marketplace rejects. next if orchestrator.try(:skip_unmapped_offer_items) && orchestrator.product_data_presenter(item).to_h.nil? main_ci = item.catalog_items.find_by(catalog_id: orchestrator.main_catalog_id) # Never emit an offer with no/zero price — every marketplace rejects # it ("price must be between min/max limits"). Skip silently so an # unpriced catalog item doesn't spam every nightly run. next if main_ci.nil? || main_ci.edi_offer_price.to_d <= 0 if orchestrator.product_id_type == 'EAN' identifier = item.ean13 elsif orchestrator.product_id_type == 'UPC' identifier = item.upc elsif orchestrator.product_id_type == 'SHOP_SKU' identifier = item.sku end xml.send(:offer) do if orchestrator.offer_id == 'sku' create_attribute(xml, 'sku', item.sku) else create_attribute(xml, 'sku', item.ean13) end create_attribute(xml, 'product-id', identifier) create_attribute(xml, 'product-id-type', orchestrator.product_id_type) create_attribute(xml, 'price', main_ci&.edi_offer_price) if main_ci.edi_offer_sale_price.present? create_attribute(xml, 'discount-price', main_ci.edi_offer_sale_price) create_attribute(xml, 'discount-start-date', begin main_ci.sale_price_effective_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) create_attribute(xml, 'discount-end-date', begin main_ci.sale_price_expiration_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) elsif main_ci.edi_offer_new_sale_price.present? create_attribute(xml, 'discount-price', main_ci.edi_offer_new_sale_price) create_attribute(xml, 'discount-start-date', begin main_ci.new_sale_price_effective_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) create_attribute(xml, 'discount-end-date', begin main_ci.new_sale_price_expiration_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) else create_attribute(xml, 'discount-price', nil) create_attribute(xml, 'discount-start-date', nil) create_attribute(xml, 'discount-end-date', nil) end create_attribute(xml, 'quantity', generate_product_inventory_data(item, orchestrator.main_catalog_id)) create_attribute(xml, 'state', orchestrator.try(:offer_state_code) || 11) # 11=New; partner config can override (e.g. 13=Used-Like New for Best Buy CA Outlet) create_attribute(xml, 'leadtime-to-ship', 4) create_attribute(xml, 'update-delete', 'update') # TODO: allow deleting offers xml.send(:'all-prices') do target_catalogs.each do |catalog| next unless (ci = item.catalog_items.find_by(catalog_id: catalog.id)) xml.send(:pricing) do create_attribute(xml, 'channel-code', orchestrator.catalog_ids.key(catalog.id).to_s) create_attribute(xml, 'price', ci.edi_offer_price) if ci.edi_offer_sale_price.present? create_attribute(xml, 'discount-price', ci.edi_offer_sale_price) create_attribute(xml, 'discount-start-date', begin ci.sale_price_effective_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) create_attribute(xml, 'discount-end-date', begin ci.sale_price_expiration_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) elsif ci.edi_offer_new_sale_price.present? create_attribute(xml, 'discount-price', ci.edi_offer_new_sale_price) create_attribute(xml, 'discount-start-date', begin ci.new_sale_price_effective_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) create_attribute(xml, 'discount-end-date', begin ci.new_sale_price_expiration_date.to_datetime.strftime("%Y-%m-%dT%H:%M:%S+00") rescue StandardError nil end) end end end end if orchestrator.try(:offer_additional_fields_enabled) != false xml.send(:'offer-additional-fields') do create_code_value_attribute(xml, 'vat-lmfr', 'Standard', 'offer-additional-field') create_code_value_attribute(xml, 'vat-bmfr', 'Standard', 'offer-additional-field') create_code_value_attribute(xml, 'vat-lmit', 'Standard', 'offer-additional-field') create_code_value_attribute(xml, 'vat-lmes', 'Standard', 'offer-additional-field') create_code_value_attribute(xml, 'vat-lmpt', 'Standard', 'offer-additional-field') create_code_value_attribute(xml, 'typologie-prix-de-depart', 'prix-de-reference', 'offer-additional-field') create_code_value_attribute(xml, 'tva', main_ci.catalog.tax_rate.to_f * 100, 'offer-additional-field') create_code_value_attribute(xml, 'shipment-origin', 'NL', 'offer-additional-field') end end if orchestrator.try(:offer_manufacturer_warranty) && (warranty = manufacturer_warranty(item)).present? xml.send(:'offer-additional-fields') do create_code_value_attribute(xml, 'manufacturer-warranty', warranty, 'offer-additional-field') end end end end xml end |
#build_xml(items: nil, catalog_items: nil, states: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument
35 36 37 38 39 40 41 42 43 44 45 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 35 def build_xml(items: nil, catalog_items: nil, states: nil) # rubocop:disable Lint/UnusedMethodArgument logger.info "#{items.size} items in price payload" b = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| xml.import('xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance", 'xmlns:xsd': "http://www.w3.org/2001/XMLSchema") do xml.send(:offers) do append_catalog_items(xml, items) end end end b.to_xml end |
#create_attribute(xml, key, value) ⇒ Object
199 200 201 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 199 def create_attribute(xml, key, value) xml.send(key.to_sym, value) end |
#create_code_value_attribute(xml, key, value, parent_group = nil) ⇒ Object
203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 203 def create_code_value_attribute(xml, key, value, parent_group = nil) if parent_group.present? xml.send(parent_group.to_sym) do xml.send(:code, key) xml.send(:value, value) end else xml.send(:attribute) do xml.send(:code, key) xml.send(:value, value) end end end |
#generate_product_inventory_data(item, catalog_id) ⇒ Object
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 217 def generate_product_inventory_data(item, catalog_id) si = StoreItem.joins(catalog_items: :item).available.where(catalog_items: { catalog_id: catalog_id }).find_by(item_id: item.id) if si stock = if si.unlimited_inventory? si.permanent_qty_available else si.qty_available end else msg = "Mirakl data feed - Unable to find suitable inventory for item #{item.sku} for catalog id #{catalog_id}, reporting 0 stock for now" ErrorReporting.warning(msg) Rails.logger.error msg end stock end |
#load_items ⇒ Object
27 28 29 30 31 32 33 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 27 def load_items catalog_items = CatalogItem.for_orchestrator_keys(orchestrator.config[:orchestrator_keys]) items = Item.where.not(upc: [nil, '']).joins(:catalog_items).merge(catalog_items).distinct { items: items, catalog_items: catalog_items } # { items: Item.where(id: 10689), catalog_items: CatalogItem.where(id:46152)} # For testing end |
#manufacturer_warranty(item) ⇒ Object
Best Buy's mandatory manufacturer-warranty offer field, expressed as an
integer number of days. We are the manufacturer, so the product's own
warranty spec is the manufacturer warranty. The spec value is free text
("25 Year Warranty", "2 years", "5-year limited warranty") with no token,
so match by name (prefer the clean Warranty over the verbose
Manufacturer Warranty) and convert the duration to days.
174 175 176 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 174 def manufacturer_warranty(item) warranty_text_to_days(manufacturer_warranty_text(item)) end |
#manufacturer_warranty_text(item) ⇒ Object
178 179 180 181 182 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 178 def manufacturer_warranty_text(item) specs = item.specifications spec = specs.find { |s| s.name == 'Warranty' } || specs.find { |s| s.name == 'Manufacturer Warranty' } spec&.output.presence end |
#process(catalog_items: nil, states: nil) ⇒ Object
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 6 def process(catalog_items: nil, states: nil) ecl = nil EdiCommunicationLog.transaction do logger.info "Creating price advice for partner #{orchestrator.partner}" load_result = load_items items = load_result[:items] catalog_items = load_result[:catalog_items] data_xml = build_xml(items: items, catalog_items: catalog_items, states: states) ecl = EdiCommunicationLog.create_outbound_file_from_data( data: data_xml, file_extension: 'xml', file_name: "wy-api-offer-export-#{Time.zone.now.strftime('%Y%m%d%H%M%S')}.xml", partner: orchestrator.partner, category: 'price_advice', data_type: 'xml', file_info: {} ) end ecl end |
#target_catalogs ⇒ Object
233 234 235 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 233 def target_catalogs Catalog.with_orchestrator_key.where(orchestrator_key: orchestrator.config[:orchestrator_keys]) end |
#warranty_text_to_days(text) ⇒ Object
184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'app/services/edi/mirakl_seller/price_message_processor.rb', line 184 def warranty_text_to_days(text) return if text.blank? match = text.match(/(\d+)[\s-]*(years?|yrs?|months?|mos?|weeks?|days?)/i) return if match.nil? qty = match[1].to_i case match[2].downcase when /\Ayears?\z/, /\Ayrs?\z/ then qty * 365 when /\Amonths?\z/, /\Amos?\z/ then qty * 30 when /\Aweeks?\z/ then qty * 7 else qty end end |