Class: Edi::Commercehub::ConfirmMessageProcessor

Inherits:
BaseEdiService show all
Defined in:
app/services/edi/commercehub/confirm_message_processor.rb

Overview

Service object: confirm message processor.

Constant Summary collapse

ACKNOWLEDGE_MESSAGE_PARTNERS =

NOT dead code, despite reading that way: this is the gate that keeps the
acknowledge message OFF. No CommerceHub partner we run today
(costco / thdca / thehomedepot / rona) requires it — only Lowe's US did,
and that partner is no longer configured. Dropping the guard would start
sending acknowledgements to all four. Add a partner here to turn it on.

['lowes'].freeze

Constants included from RequestIdentifiable

RequestIdentifiable::REQUEST_ID_HEADERS

Constants included from AddressAbbreviator

AddressAbbreviator::MAX_LENGTH

Instance Attribute Summary

Attributes inherited from BaseEdiService

#orchestrator

Attributes inherited from BaseService

#options

Instance Method Summary collapse

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

#partner_request_id

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

#acknowledge_order(order, options = {}) ⇒ EdiCommunicationLog?

Returns the created log.

Parameters:

Options Hash (options):

  • transmit_after (ActiveSupport::Duration, Time, nil)

    delay before transmission

Returns:



204
205
206
207
208
209
210
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 204

def acknowledge_order(order, options = {})
  # only send this for those partners that support/require it
  return unless ACKNOWLEDGE_MESSAGE_PARTNERS.include?(orchestrator.ch_partner_id)

  m = build_acknowledge_message_from_order(order)
  process_acknowledgement(m, options.merge({ order: order }))
end

#auto_cancel_order(order, order_hash, reason_code, _bad_vendor_skus = nil, _bad_merchant_skus = nil) ⇒ Object



221
222
223
224
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 221

def auto_cancel_order(order, order_hash, reason_code, _bad_vendor_skus = nil, _bad_merchant_skus = nil)
  m = build_cancel_message_from_order_hash(order, order_hash, reason_code)
  process(m, options.merge({ order: order }))
end

#auto_reject_order(order, order_hash, reason_code, _bad_vendor_skus = nil, _bad_merchant_skus = nil) ⇒ Object



226
227
228
229
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 226

def auto_reject_order(order, order_hash, reason_code, _bad_vendor_skus = nil, _bad_merchant_skus = nil)
  m = build_acknowledge_reject_message_from_order_hash(order, order_hash, reason_code)
  process_acknowledgement(m, options.merge({ order: order }))
end

#back_order(order, options = {}) ⇒ EdiCommunicationLog?

Returns the created log.

Parameters:

  • order (Order)

    the order to back-order

  • options (Hash) (defaults to: {})

    processing options (passed to #process)

Options Hash (options):

  • transmit_after (ActiveSupport::Duration, Time, nil)

    delay before transmission

Returns:



235
236
237
238
239
240
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 235

def back_order(order, options = {})
  return if orchestrator.ignore_back_orders

  m = build_back_order_message_from_order(order)
  process(m, options.merge({ order: order }))
end

#build_acknowledge_message_from_order(order) ⇒ Object



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
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 74

def build_acknowledge_message_from_order(order)
  line_items = order.line_items.non_shipping.where.not(edi_reference: nil).order(:edi_line_number)

  msg = Edi::Commercehub::ConfirmMessage.new
  msg.ch_partner_id = orchestrator.ch_partner_id
  msg.ch_partner_name = orchestrator.ch_partner_name
  msg.ch_vendor_id = orchestrator.ch_vendor_id
  msg.trx_id = order.reference_number
  msg.trx_date = order.created_at
  msg.po_number = order.first_po_number
  msg.order_date = order.created_at

  msg.lines = []
  line_items.each do |li|
    new_line = Edi::Commercehub::ConfirmMessage::MerchantLine.new(
      action: 'accept',
      action_code: nil,
      trx_id: li.edi_reference,
      line_number: li.edi_line_number,
      trx_qty: li.quantity
    )
    msg.lines << new_line
  end

  msg
end

#build_acknowledge_reject_message_from_order_hash(order, order_hash, reason_code) ⇒ Object



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
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 101

def build_acknowledge_reject_message_from_order_hash(order, order_hash, reason_code)
  order_xml = Nokogiri::XML(order_hash[:xml]).xpath('hubOrder')
  msg = Edi::Commercehub::ConfirmMessage.new
  msg.ch_partner_id = orchestrator.ch_partner_id
  msg.ch_partner_name = orchestrator.ch_partner_name
  msg.ch_vendor_id = orchestrator.ch_vendor_id
  msg.trx_id = order.reference_number
  msg.trx_date = Date.parse(order_xml.xpath('orderDate').text)
  msg.po_number = order_xml.xpath('poNumber').text
  msg.order_date = Date.parse(order_xml.xpath('orderDate').text)

  msg.lines = []
  order_xml.xpath('lineItem').each do |line_xml|
    # merchant_sku = line_xml.xpath('merchantSKU')&.text
    # vendor_sku = line_xml.xpath('vendorSKU')&.text
    new_line = Edi::Commercehub::ConfirmMessage::MerchantLine.new(
      action: 'reject',
      action_code: reason_code,
      trx_id: line_xml.xpath('lineItemId').text,
      line_number: line_xml.xpath('merchantLineNumber').text,
      trx_qty: line_xml.xpath('qtyOrdered').text.to_i
    )
    msg.lines << new_line
  end

  msg
end

#build_back_order_message_from_order(order) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 173

def build_back_order_message_from_order(order)
  msg = Edi::Commercehub::ConfirmMessage.new
  msg.ch_partner_id = orchestrator.ch_partner_id
  msg.ch_partner_name = orchestrator.ch_partner_name
  msg.trx_id = order.reference_number
  msg.trx_date = order.created_at
  msg.po_number = order.first_po_number
  msg.order_date = order.created_at
  msg.lines = order.line_items.where.not(edi_reference: nil).order(:edi_line_number).map do |li|
    Edi::Commercehub::ConfirmMessage::MerchantLine.new(
      action: 'v_backorder',
      trx_id: li.edi_reference,
      line_number: li.edi_line_number,
      trx_qty: li.quantity,
      expected_date: 5.days.from_now.to_date
    )
  end
  msg
end

#build_cancel_message_from_order(order) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 129

def build_cancel_message_from_order(order)
  msg = Edi::Commercehub::ConfirmMessage.new
  msg.ch_partner_id = orchestrator.ch_partner_id
  msg.ch_partner_name = orchestrator.ch_partner_name
  msg.trx_id = order.reference_number
  msg.trx_date = order.created_at
  msg.po_number = order.first_po_number
  msg.order_date = order.created_at
  msg.lines = order.line_items.where.not(edi_reference: nil).order(:edi_line_number).map do |li|
    Edi::Commercehub::ConfirmMessage::MerchantLine.new(
      action: 'v_cancel',
      action_code: order.cancellation_reason.presence || 'merchant_request',
      trx_id: li.edi_reference,
      line_number: li.edi_line_number,
      trx_qty: li.quantity
    )
  end
  msg
end

#build_cancel_message_from_order_hash(order, order_hash, _reason_code) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 149

def build_cancel_message_from_order_hash(order, order_hash, _reason_code)
  order_xml = Nokogiri::XML(order_hash[:xml]).xpath('hubOrder')
  msg = Edi::Commercehub::ConfirmMessage.new
  msg.ch_partner_id = orchestrator.ch_partner_id
  msg.ch_partner_name = orchestrator.ch_partner_name
  msg.trx_id = order.reference_number
  msg.trx_date = Date.parse(order_xml.xpath('orderDate').text)
  msg.po_number = order_xml.xpath('poNumber').text
  msg.order_date = Date.parse(order_xml.xpath('orderDate').text)
  order_xml.xpath('lineItem').each do |line_xml|
    # merchant_sku = line_xml.xpath('merchantSKU')&.text
    # vendor_sku = line_xml.xpath('vendorSKU')&.text
    new_line = Edi::Commercehub::ConfirmMessage::MerchantLine.new(
      action: 'v_cancel',
      action_code: order.cancellation_reason.presence || 'merchant_request',
      trx_id: line_xml.xpath('lineItemId').text,
      line_number: line_xml.xpath('merchantLineNumber').text,
      trx_qty: line_xml.xpath('qtyOrdered').text.to_i
    )
    msg.lines << new_line
  end
  msg
end

#build_confirm_message_from_invoice(invoice) ⇒ Object



6
7
8
9
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
63
64
65
66
67
68
69
70
71
72
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 6

def build_confirm_message_from_invoice(invoice)
  shipments = invoice.delivery.shipments.completed
  line_items = invoice.delivery.line_items.where.not(edi_reference: nil).order(:edi_line_number)

  msg = Edi::Commercehub::ConfirmMessage.new
  msg.ch_partner_id = orchestrator.ch_partner_id
  msg.ch_partner_name = orchestrator.ch_partner_name
  msg.vendor_warehouse_id = orchestrator.vendor_warehouse_id if orchestrator.try(:vendor_warehouse_id).present?
  msg.trx_id = invoice.order.reference_number
  msg.trx_date = invoice.order.created_at
  msg.po_number = invoice.order.first_po_number
  msg.order_date = invoice.order.created_at
  # If we send an invoice to CH, we don't do it here
  msg.invoice_number = invoice.reference_number unless orchestrator.invoice_message_enabled?
  oa = invoice.delivery.origin_address
  vendor_ship_info = Edi::Commercehub::ConfirmMessage::VendorShipInfo.new(
    vendor_ship_id: oa.id,
    name1: oa.company_name,
    address1: oa.street1,
    address2: oa.street2,
    address3: oa.street3,
    city: oa.city,
    state: oa.state_code,
    country: oa.country_iso3,
    postal_code: oa.zip
  )
  msg.vendor_ship_infos << vendor_ship_info
  msg.packages = shipments.non_pallets.completed.map do |shp|
    Edi::Commercehub::ConfirmMessage::Package.new(
      package_id: shp.reference_number,
      pallet_id: shp.parent_shipment&.reference_number,
      length: shp.length,
      width: shp.width,
      height: shp.height,
      weight: shp.weight,
      service_level: get_ship_code_from_invoice(invoice),
      tracking_number: shp.tracking_number || invoice.delivery.master_tracking_number || invoice.delivery.carrier_bol,
      ship_date: shp.date_shipped,
      vendor_ship_info: vendor_ship_info
    )
  end
  msg.lines = []
  line_items.each do |li|
    if (shipment_contents = li.shipment_contents.from_completed_shipments).present? # Lines were allocated to shipments
      shipment_contents.each do |shipment_content|
        msg.lines << Edi::Commercehub::ConfirmMessage::MerchantLine.new(
          action: 'v_ship',
          trx_id: li.edi_reference,
          line_number: li.edi_line_number,
          trx_qty: shipment_content.quantity,
          package: msg.packages.find { |p| p.package_id == shipment_content.shipment.reference_number },
          serial_numbers: li.serial_numbers.map(&:number)
        )
      end
    else # We don't know which line is in which box, so take the first package
      msg.lines << Edi::Commercehub::ConfirmMessage::MerchantLine.new(
        action: 'v_ship',
        trx_id: li.edi_reference,
        line_number: li.edi_line_number,
        trx_qty: li.quantity,
        package: msg.packages.first,
        serial_numbers: li.serial_numbers.map(&:number)
      )
    end
  end
  msg
end

#cancel_order(order, options = {}) ⇒ EdiCommunicationLog?

Returns the created log.

Parameters:

  • order (Order)

    the order to cancel

  • options (Hash) (defaults to: {})

    processing options (passed to #process)

Options Hash (options):

  • transmit_after (ActiveSupport::Duration, Time, nil)

    delay before transmission

Returns:



216
217
218
219
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 216

def cancel_order(order, options = {})
  m = build_cancel_message_from_order(order)
  process(m, options.merge({ order: order }))
end

#confirm_invoice(invoice, options = {}) ⇒ EdiCommunicationLog?

Returns the created log.

Parameters:

  • invoice (Invoice)

    the invoice to confirm

  • options (Hash) (defaults to: {})

    processing options (passed to #process)

Options Hash (options):

  • transmit_after (ActiveSupport::Duration, Time, nil)

    delay before transmission

Returns:



246
247
248
249
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 246

def confirm_invoice(invoice, options = {})
  m = build_confirm_message_from_invoice(invoice)
  process(m, options.merge({ order: invoice.order }))
end

#get_ship_code_from_invoice(invoice) ⇒ Object



377
378
379
380
381
382
383
384
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 377

def get_ship_code_from_invoice(invoice)
  shipping_method_name = invoice.delivery.shipping_option.name
  if shipping_method_name.downcase.include?('override') # handle override shipping to just match the original and don;t bother with mappings
    invoice.delivery.order.edi_original_ship_code
  else
    orchestrator.ship_code_mapper.hw_to_ch(shipping_method_name, signature: invoice.delivery.signature_confirmation, saturday: invoice.delivery.saturday_delivery)
  end
end

#process(confirm_message, options) ⇒ EdiCommunicationLog

Picks up the edi communication log in the queue ready to process, generate confirms

Parameters:

Options Hash (options):

  • order (Order)

    the order being confirmed

  • transmit_after (ActiveSupport::Duration, Time, nil)

    delay before transmission

Returns:



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 257

def process(confirm_message, options)
  order = options[:order]
  transmit_after = options[:transmit_after]
  b = Nokogiri::XML::Builder.new do |xml|
    xml.send(:ConfirmMessageBatch) do
      xml.send(:partnerID, confirm_message.ch_partner_id)
      xml.send(:hubConfirm) do
        participating_party_attributes = { roleType: 'merchant', participationCode: 'To:' }
        participating_party_attributes[:name] = confirm_message.ch_partner_id
        xml.send(:participatingParty, confirm_message.ch_partner_id, participating_party_attributes)
        xml.send(:partnerTrxID, confirm_message.trx_id)
        xml.send(:partnerTrxDate, confirm_message.trx_date.strftime('%Y%m%d'))
        xml.send(:poNumber, confirm_message.po_number)
        if confirm_message.invoice_number
          xml.send(:trxData) do
            xml.send(:orderDate, confirm_message.order_date.strftime('%Y%m%d'))
            xml.send(:vendorsInvoiceNumber, confirm_message.invoice_number)
          end
        end
        confirm_message.lines.each do |li|
          hub_action_attributes = {}
          hub_action_attributes[:transactionItemID] = li.trx_id
          xml.send(:hubAction, hub_action_attributes) do
            xml.send(:action, li.action)
            xml.send(:actionCode, li.action_code) if li.action_code
            xml.send(:merchantLineNumber, li.line_number)
            xml.send(:trxQty, li.trx_qty)
            xml.send(:expectedDate, li.expected_date.strftime('%Y%m%d')) if li.expected_date
            xml.send(:packageDetailLink, packageDetailID: "ID#{li.package.package_id}") if li.package
            if confirm_message.ch_partner_id == 'costco' && li.serial_numbers.any?
              li.serial_numbers.each do |sn|
                xml.send(:serialNumber, sn)
              end
            end
            if confirm_message.ch_partner_id == 'costco' && confirm_message.vendor_warehouse_id.present?
              xml.send(:trxItemData) do
                xml.send(:vendorWarehouseId, confirm_message.vendor_warehouse_id)
              end
            end
          end
        end
        confirm_message.packages.each do |pkg|
          xml.send(:packageDetail, packageDetailID: "ID#{pkg.package_id}") do
            xml.send(:shipDate, pkg.ship_date.strftime('%Y%m%d'))
            xml.send(:serviceLevel1, pkg.service_level)
            xml.send(:trackingNumber, pkg.tracking_number)
            xml.send(:shipFrom, vendorShipID: pkg.vendor_ship_info.vendor_ship_id) if pkg.vendor_ship_info
          end
        end
        confirm_message.vendor_ship_infos.each do |vsi|
          xml.send(:vendorShipInfo, vendorShipID: vsi.vendor_ship_id) do
            xml.send :name1, vsi.name1
            xml.send :address1, vsi.address1
            xml.send :address2, vsi.address2
            xml.send :address3, vsi.address3
            xml.send :city, vsi.city
            xml.send :state, vsi.state
            xml.send :country, vsi.country
            xml.send :postalCode, vsi.postal_code
          end
        end
      end
    end
  end
  EdiCommunicationLog.create_outbound_file_from_data(data: b.to_xml,
                                                     file_extension: 'confirm',
                                                     partner: orchestrator.partner,
                                                     category: 'order_confirm',
                                                     data_type: 'xml',
                                                     transmit_after: transmit_after,
                                                     resources: order,
                                                     file_info: { lines_confirmed: confirm_message.lines.size, packages: confirm_message.packages.size })
end

#process_acknowledgement(acknowledgement_message, options) ⇒ EdiCommunicationLog

Returns the created log.

Parameters:

Options Hash (options):

  • order (Order)

    the order being acknowledged

  • transmit_after (ActiveSupport::Duration, Time, nil)

    delay before transmission

Returns:



336
337
338
339
340
341
342
343
344
345
346
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
# File 'app/services/edi/commercehub/confirm_message_processor.rb', line 336

def process_acknowledgement(acknowledgement_message, options)
  order = options[:order]
  transmit_after = options[:transmit_after]
  b = Nokogiri::XML::Builder.new do |xml|
    xml.send(:PO_Acknowledgements, 'batch-number': 1) do
      xml.send(:partnerID, acknowledgement_message.ch_partner_id)
      xml.send(:PO_Acknowledgement, 'ack-type': 'initial') do
        xml.send(:messageControlNumber, acknowledgement_message.trx_id)
        xml.send(:originatingSystemTrxId, acknowledgement_message.trx_id, trxDate: acknowledgement_message.trx_date.strftime('%Y%m%d'))
        xml.send(:participatingParty, acknowledgement_message.ch_partner_id, role: 'merchant', participationCode: 'To:')
        xml.send(:poNumber, acknowledgement_message.po_number)
        xml.send(:scheduledShipDate, order.get_scheduled_ship_date_time.strftime('%Y%m%d'))
        acknowledgement_message.lines.each do |li|
          xml.send(:lineitem_ack) do
            xml.send(:poLineNumber, li.line_number)
            xml.send(:quantityOpen, li.trx_qty)
            xml.send(:action) do
              xml.send(:quantity, li.trx_qty)
              if %w[reject backorder].include?(li.action)
                # sigh, they use the backorder element for reject, sheesh, a real hacky thing but it is what it is...
                xml.send(:backorder, li.trx_qty, 'backorder-reason': li.action_code)
              else
                xml.send(:accept)
              end
            end
          end
        end
      end
      xml.send(:messageCount, 1)
    end
  end
  EdiCommunicationLog.create_outbound_file_from_data(data: b.to_xml,
                                                     file_extension: 'acknowledge',
                                                     partner: orchestrator.partner,
                                                     category: 'order_acknowledge',
                                                     data_type: 'xml',
                                                     resources: order,
                                                     transmit_after: transmit_after,
                                                     file_info: { lines_confirmed: acknowledgement_message.lines.size })
end