Class: Edi::Commercehub::InvoiceMessageProcessor

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

Constant Summary

Constants included from AddressAbbreviator

AddressAbbreviator::MAX_LENGTH

Instance Attribute Summary

Attributes inherited from BaseEdiService

#orchestrator

Instance Method Summary collapse

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, #options, #tagged_logger

Constructor Details

This class inherits a constructor from Edi::BaseEdiService

Instance Method Details

#build_invoice_message_from_invoice(invoice) ⇒ Object



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

def build_invoice_message_from_invoice(invoice)
  shipments = invoice.delivery.shipments.completed
  line_items = invoice.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 = invoice.reference_number
  msg.trx_date = invoice.gl_date
  if msg.ch_partner_id == 'thehomedepot' # TEMPORARY PATCH TO GET 1/31/24 TAX EXPIRATION/REJECTED INVOICE MESSAGES RESUBMITTED
    msg.trx_tax = 0.0
  else
    msg.trx_tax = invoice.tax_total unless msg.ch_partner_id == 'rona' # according to XSD
  end
  msg.po_number = invoice.order.first_po_number
  msg.order_date = invoice.order.created_at
  msg.invoice_number = invoice.reference_number
  msg.trx_currency = invoice.currency
  msg.trx_balance_due = if msg.ch_partner_id == 'thehomedepot' # TEMPORARY PATCH TO GET 1/31/24 TAX EXPIRATION/REJECTED INVOICE MESSAGES RESUBMITTED
                          invoice.amount_due - invoice.tax_total
                        else
                          invoice.amount_due
                        end
  msg.net_days_due = invoice.terms_in_days
  msg.tax_exempt_number = orchestrator.tax_exempt_number if orchestrator.respond_to?(:tax_exempt_number)

  # Except for Rona, per XSD no CommerceHub partner XSD requires this in the invoice message ... let's skip it entirely, 8/13/19 - Ramie
  # Let;s try to also do this for THDCA
  if %w[thdca rona].include?(msg.ch_partner_id)
    ch_tax_types = { gst: 'CG', qst: 'ST', hst: 'VA' }.with_indifferent_access

    invoice.taxes_grouped_by_type.each do |type, details|
      msg.tax_breakouts << Edi::Commercehub::ConfirmMessage::TaxBreakout.new(
        tax_type: ch_tax_types[type],
        currency_unit: invoice.currency,
        amount: details[:tax_amount]
      )
    end
  end

  if invoice.early_payment_discount && invoice.early_payment_due_date
    msg.payment_discount_breakouts << Edi::Commercehub::ConfirmMessage::PaymentDiscountBreakout.new(
      percent: invoice.early_payment_discount,
      due_date: invoice.early_payment_due_date,
      days_due: invoice.discount_days_due,
      net_due_date: invoice.due_date,
      net_days_due: invoice.terms_in_days
    )
  end

  msg.invoice_remit_id = orchestrator.invoice_remit_id
  msg.packages = shipments.map do |shp|
    Edi::Commercehub::ConfirmMessage::Package.new(
      package_id: shp.reference_number,
      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
    )
  end
  msg.lines = []

  line_items.each do |li|
    new_line = Edi::Commercehub::ConfirmMessage::MerchantLine.new(
      action: 'v_invoice',
      trx_id: li.edi_reference,
      line_number: li.edi_line_number,
      trx_qty: li.quantity,
      trx_unit_cost: li.discounted_price,
      # let's try making tax at the total invoice level for THDCA rather than break it out because some partner XSDs have no reference to line tax, but do keep it for Rona which requires tax broken down at the line level
      trx_line_tax: (msg.ch_partner_id == 'rona' ? li.tax_total : nil),
      # this will be line balance without taxes, except for rona which should include taxes
      trx_line_balance_due: (msg.ch_partner_id == 'rona' ? li.line_total_with_tax : li.discounted_total)
    )
    if msg.ch_partner_id == 'rona'
      li.taxes_grouped_by_type.each do |type, details|
        new_line.tax_breakouts << Edi::Commercehub::ConfirmMessage::TaxBreakout.new(
          tax_type: ch_tax_types[type],
          currency_unit: invoice.currency,
          amount: details[:tax_amount]
        )
      end
    end
    if (shipment_contents = li.shipment_contents.from_completed_shipments).present? # Lines were allocated to shipments
      shipment_contents.each do |shipment_content|
        new_line.package = msg.packages.detect { |p| p.package_id == shipment_content.shipment.reference_number }
      end
    else # We don't know which line is in which box, so take the first package
      new_line.package = msg.packages.first
    end
    msg.lines << new_line
  end

  msg
end

#create_invoice(invoice, options = {}) ⇒ Object



101
102
103
104
# File 'app/services/edi/commercehub/invoice_message_processor.rb', line 101

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

#get_ship_code_from_invoice(invoice) ⇒ Object



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

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 = {}) ⇒ Object

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



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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'app/services/edi/commercehub/invoice_message_processor.rb', line 107

def process(confirm_message, options = {})
  order = options[:order]
  transmit_after = options[:transmit_after]
  b = Nokogiri::XML::Builder.new do |xml|
    args = (confirm_message.ch_partner_id == 'rona' ? { batchNumber: '1' } : {})
    xml.send(:InvoiceMessageBatch, args) do
      args = (confirm_message.ch_partner_id == 'rona' ? {} : { name: 'WarmlyYours Radiant', roleType: 'vendor' })
      xml.send(:partnerID, confirm_message.ch_vendor_id, args)
      xml.send(:hubInvoice) do
        xml.send(:participatingParty, confirm_message.ch_partner_id, name: confirm_message.ch_partner_name, roleType: 'merchant', participationCode: 'To:')
        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)
        # according to XSDs, only thdca and rona specify trxCurrency
        xml.send(:trxCurrency, confirm_message.trx_currency) if %w[thdca rona lowes].include?(confirm_message.ch_partner_id)
        xml.send(:trxTax, confirm_message.trx_tax) if confirm_message.trx_tax.present?
        xml.send(:trxBalanceDue, confirm_message.trx_balance_due)
        # adding it back to thehomedepot because it needs the net60 terms
        # per XSD no CommerceHub partner XSD requires this in the invoice message ... let's skip it entirely, 8/13/19 - Ramie
        # per customersupport@commercehub.com on 8/7/2019 - Ramie
        if %w[thehomedepot thdca lowes].include?(confirm_message.ch_partner_id)
          xml.send(:trxData) do
            # per customersupport@commercehub.com on 8/7/2019 - Ramie
            if confirm_message.ch_partner_id == 'thdca'
              confirm_message.tax_breakouts.each do |tb|
                xml.send(:taxBreakout, tb.amount, taxType: tb.tax_type, currencyUnit: tb.currency_unit)
              end
            end
            if %w[thehomedepot lowes].include?(confirm_message.ch_partner_id)
              if confirm_message.payment_discount_breakouts.present?
                confirm_message.payment_discount_breakouts.each do |pdb|
                  xml.send(:discountBreakout, discPercent: pdb.percent, discDueDate: pdb.due_date.strftime('%Y%m%d'), discDaysDue: pdb.days_due, netDueDate: pdb.net_due_date&.strftime('%Y%m%d'), netDaysDue: pdb.net_days_due)
                end
              elsif confirm_message.net_days_due # For some reason this gets shoved in the same discount breakout
                xml.send(:discountBreakout, netDaysDue: confirm_message.net_days_due)
              end
            end
          end
        end
        confirm_message.lines.each do |li|
          xml.send(:hubAction) do
            xml.send(:action, li.action)
            xml.send(:merchantLineNumber, li.line_number)
            xml.send(:trxQty, li.trx_qty)
            xml.send(:trxUnitCost, li.trx_unit_cost)
            xml.send(:trxLineTax, li.trx_line_tax) if li.trx_line_tax.present?
            # according to XSDs, only thdca and rona specify trxLineBalanceDue
            # let's try making tax at the total invoice level rather than break it out because some partner XSDs have no reference to line tax
            xml.send(:trxLineBalanceDue, li.trx_line_balance_due) if %w[thdca rona].include?(confirm_message.ch_partner_id)
            xml.send(:invoiceDetailLink, invoiceDetailID: confirm_message.trx_id)
            # per XSD except for rona, no CommerceHub partner XSD requires this in the invoice message ... let's skip it entirely, 8/13/19 - Ramie
            if confirm_message.ch_partner_id == 'rona'
              xml.send(:trxItemData) do
                li.tax_breakouts.each do |tb|
                  xml.send(:taxBreakout, tb.amount, taxType: tb.tax_type)
                end
              end
            end
            # according to XSDs, thehomedepot specifies packageDetailLink
            xml.send(:packageDetailLink, packageDetailID: "PK#{li.package.package_id}") if confirm_message.ch_partner_id == 'thehomedepot' and li.package
          end
        end
        xml.send(:invoiceDetail, invoiceDetailID: confirm_message.trx_id) do
          xml.send(:remitTo, personPlaceID: 'PP0000000001') # according to XSD
        end
        if confirm_message.ch_partner_id == 'thehomedepot' and confirm_message.lines.select { |li| li.package.present? }.any?
          confirm_message.lines.select { |li| li.package.present? }.each do |li|
            xml.send(:packageDetail, packageDetailID: "PK#{li.package.package_id}") do
              xml.send(:shipDate, li.package.ship_date.strftime('%Y%m%d'))
              xml.send(:serviceLevel1, li.package.service_level)
              xml.send(:trackingNumber, li.package.tracking_number)
            end
          end
        end
        xml.send(:personPlace, personPlaceID: 'PP0000000001') do # according to XSD
          if confirm_message.ch_partner_id == 'rona'
            xml.send(:personPlaceData) do
              xml.send(:taxExemptNumber, confirm_message.tax_exempt_number)
            end
          end
          xml.send(:partnerPersonPlaceId, confirm_message.invoice_remit_id)
        end
      end
      xml.send(:messageCount, 1) # according to XSD
    end
  end
  invoice = options[:invoice]
  EdiCommunicationLog.create_outbound_file_from_data(data: b.to_xml,
                                                     file_extension: 'invoice',
                                                     partner: orchestrator.partner,
                                                     category: 'invoice',
                                                     data_type: 'xml',
                                                     resources: invoice || order,
                                                     transmit_after: transmit_after,
                                                     file_info: { lines_confirmed: confirm_message.lines.size, packages: confirm_message.packages.size })
end