Class: Edi::MftGateway::InvoiceMessageProcessor
- Inherits:
-
BaseEdiService
- Object
- BaseService
- BaseEdiService
- Edi::MftGateway::InvoiceMessageProcessor
- Defined in:
- app/services/edi/mft_gateway/invoice_message_processor.rb
Constant Summary
Constants included from AddressAbbreviator
AddressAbbreviator::MAX_LENGTH
Instance Attribute Summary
Attributes inherited from BaseEdiService
Instance Method Summary collapse
- #create_credit_memo(credit_memo, options = {}) ⇒ Object
- #create_invoice(invoice, options = {}) ⇒ Object
-
#process(options = {}) ⇒ Object
{ "Header": { "InvoiceHeader": { "TradingPartnerId": "TPIDXXXXWARMLYYOU", "InvoiceNumber": "INVNUM", "InvoiceDate": "2023-01-26", "PurchaseOrderDate": "2023-01-26", "PurchaseOrderNumber": '123456789', "TsetPurposeCode": "00", "BuyersCurrency": "USD", "Vendor": "Warmly_Yours", "ShipDate": "2023-01-26" }, "PaymentTerms": { "TermsType": "01", "TermsDiscountPercentage": "0.0", "TermsDiscountDate": "2023-01-26", "TermsDiscountAmount": "0.0", "TermsDescription": "Terms and discount as previously agreed upon." }, "Address": [ { "AddressTypeCode": "RI", "LocationCodeQualifier": "92", "AddressLocationNumber": "135125", "AddressName": "Build.com", "Address1": "402 OTTERSON DR", "Address2": "", "Address3": "", "City": "CHICO", "State": "CA", "PostalCode": "95928", "Country": "US" }, { "AddressTypeCode": "BT", "LocationCodeQualifier": "92", "AddressLocationNumber": "135125", "AddressName": "Build.com", "Address1": "402 OTTERSON DR", "Address2": "", "Address3": "", "City": "CHICO", "State": "CA", "PostalCode": "95928", "Country": "US" }, { "AddressTypeCode": "ST", "LocationCodeQualifier": "12", "AddressLocationNumber": "1234567890", "AddressName": "Customer Name", "AddressAlternateName": "Alternate Name", "Address1": "XXXXX Street Name Rd", "City": "City Name", "State": "WA", "PostalCode": "12345" } ] }, "Structure": { "LineItem": [ { "InvoiceLine": { "LineSequenceNumber": "1", "BuyerPartNumber": "TW-SR08GS-HP", "VendorPartNumber": "TW-SR08GS-HP", "ConsumerPackageCode": "881308069001", "InvoiceQty": "1", "InvoiceQtyUOM": "EA", "PurchasePrice": "769.3", "PurchasePriceBasis": "PE", "ShipQty": "1", "ShipQtyUOM": "EA", "ExtendedItemTotal": "769.3" }, "ProductOrItemDescription": { "ProductCharacteristicCode": "08", "ProductDescription": "Sierra Towel Warmer Polished Gold Dual Connection 8 Bars" } } ] }, "Summary": { "TotalAmount": "769.3", "TotalSalesAmount": "769.3", "TotalLineItemNumber": "1" } }.
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
#create_credit_memo(credit_memo, options = {}) ⇒ Object
9 10 11 12 |
# File 'app/services/edi/mft_gateway/invoice_message_processor.rb', line 9 def create_credit_memo(credit_memo, = {}) # Here we simply send the amount as negative for credit memo, per Build.com's 810 EDI spec book 4.0 process(.merge({ order: credit_memo.invoice.order, invoice: credit_memo.invoice, credit_memo: })) end |
#create_invoice(invoice, options = {}) ⇒ Object
5 6 7 |
# File 'app/services/edi/mft_gateway/invoice_message_processor.rb', line 5 def create_invoice(invoice, = {}) process(.merge({ order: invoice.order, invoice: invoice })) end |
#process(options = {}) ⇒ Object
{
"Header": {
"InvoiceHeader": {
"TradingPartnerId": "TPIDXXXXWARMLYYOU",
"InvoiceNumber": "INVNUM",
"InvoiceDate": "2023-01-26",
"PurchaseOrderDate": "2023-01-26",
"PurchaseOrderNumber": '123456789',
"TsetPurposeCode": "00",
"BuyersCurrency": "USD",
"Vendor": "Warmly_Yours",
"ShipDate": "2023-01-26"
},
"PaymentTerms": {
"TermsType": "01",
"TermsDiscountPercentage": "0.0",
"TermsDiscountDate": "2023-01-26",
"TermsDiscountAmount": "0.0",
"TermsDescription": "Terms and discount as previously agreed upon."
},
"Address": [
{
"AddressTypeCode": "RI",
"LocationCodeQualifier": "92",
"AddressLocationNumber": "135125",
"AddressName": "Build.com",
"Address1": "402 OTTERSON DR",
"Address2": "",
"Address3": "",
"City": "CHICO",
"State": "CA",
"PostalCode": "95928",
"Country": "US"
},
{
"AddressTypeCode": "BT",
"LocationCodeQualifier": "92",
"AddressLocationNumber": "135125",
"AddressName": "Build.com",
"Address1": "402 OTTERSON DR",
"Address2": "",
"Address3": "",
"City": "CHICO",
"State": "CA",
"PostalCode": "95928",
"Country": "US"
},
{
"AddressTypeCode": "ST",
"LocationCodeQualifier": "12",
"AddressLocationNumber": "1234567890",
"AddressName": "Customer Name",
"AddressAlternateName": "Alternate Name",
"Address1": "XXXXX Street Name Rd",
"City": "City Name",
"State": "WA",
"PostalCode": "12345"
}
]
},
"Structure": {
"LineItem": [
{
"InvoiceLine": {
"LineSequenceNumber": "1",
"BuyerPartNumber": "TW-SR08GS-HP",
"VendorPartNumber": "TW-SR08GS-HP",
"ConsumerPackageCode": "881308069001",
"InvoiceQty": "1",
"InvoiceQtyUOM": "EA",
"PurchasePrice": "769.3",
"PurchasePriceBasis": "PE",
"ShipQty": "1",
"ShipQtyUOM": "EA",
"ExtendedItemTotal": "769.3"
},
"ProductOrItemDescription": {
"ProductCharacteristicCode": "08",
"ProductDescription": "Sierra Towel Warmer Polished Gold Dual Connection 8 Bars"
}
}
]
},
"Summary": {
"TotalAmount": "769.3",
"TotalSalesAmount": "769.3",
"TotalLineItemNumber": "1"
}
}
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 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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 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 |
# File 'app/services/edi/mft_gateway/invoice_message_processor.rb', line 104 def process( = {}) order = [:order] invoice = [:invoice] credit_memo = [:credit_memo] invoice.delivery total = credit_memo ? credit_memo.total : invoice.total transmit_after = [:transmit_after] inv_date, = credit_memo ? (credit_memo.request_date || credit_memo.document_date || Time.current).iso8601.split('T') : Time.current.iso8601.split('T') shipped_date = order.shipped_date&.iso8601&.split('T')&.first order_hash = JSON.parse(order.).with_indifferent_access ship_to_address_hash = order_hash.dig(:Header, :Address)&.detect { |add_hash| add_hash[:AddressTypeCode] == 'ST' } || order_hash.dig(:Header, :Address)&.detect { |add_hash| add_hash[:AddressTypeCode] == 'MA' } # MA for Canadian Tire # This is the workaround for when we do not get a proper MA segment in the PO from DSCO for Canadian Tire # Start workaround part 1/2 =========================== store_number = ship_to_address_hash.dig(:AddressLocationNumber) || ship_to_address_hash.dig(:Address1).to_s.split('#').last.delete(' ') ma_address_hash = nil if orchestrator.partner == :mft_gateway_canadian_tire && store_number.present? && (ship_to_address_hash.dig(:AddressTypeCode) == 'ST') && !ship_to_address_hash.dig(:LocationCodeQualifier).present? # put in the ^&%^& store number for Canadian Tire DSCO in an MA segment, even though they don't send it to us! ma_address_hash = {} ma_address_hash[:AddressTypeCode] = 'MA' # ^&%^&% Canadian Tire requires this ma_address_hash[:LocationCodeQualifier] ||= '92' ma_address_hash[:AddressLocationNumber] ||= store_number ma_address_hash[:Address1] = order&.shipping_address&.company_name_override end # End workaround part 1/2 =========================== # Comment out this old SPS Commerce weirdness with Canadian Tire # if (ship_to_address_hash[:AddressTypeCode] == 'MA' && ship_to_address_hash[:Address1].blank?) # # must populate Canadian Tire actual address fields # ship_to_address_hash[:AddressTypeCode] = 'ST' # ^&%^&% Canadian Tire requires this # ship_to_address_hash[:AddressName] = order&.shipping_address&.company_name_override # ship_to_address_hash[:Address1] = order&.shipping_address&.street1 # ship_to_address_hash[:City] = order&.shipping_address&.city # ship_to_address_hash[:State] = order&.shipping_address&.state_code # ship_to_address_hash[:PostalCode] = order&.shipping_address&.zip # end bill_to_address_hash = order_hash.dig(:Header, :Address)&.detect { |add_hash| add_hash[:AddressTypeCode] == 'BT' } remit_to_address = orchestrator.customer.billing_address remit_to_address_hash = nil if remit_to_address remit_to_address_hash = { AddressTypeCode: 'RI', # from: https://developercenter.spscommerce.com/#/rsx/docs/fields-qualifiers/7.7.6/Invoices/AddressTypeCode: ... RI Remit To ... LocationCodeQualifier: (orchestrator.partner == :mft_gateway_canadian_tire ? '1' : '92'), # just make it work, everyone has their own codes, Canadian Tire wants "1" here # https://developercenter.spscommerce.com/#/rsx/docs/fields-qualifiers/7.7.6/Invoices/LocationCodeQualifier ... 92 Buyer Location Number ... AddressLocationNumber: "#{remit_to_address.id}", # documentation from SPS Commerce requires this so put some&^*&^ thing here AddressName: remit_to_address.company_name, Address1: remit_to_address.street1, Address2: remit_to_address.street2, Address3: remit_to_address.street3, City: remit_to_address.city, State: remit_to_address.state_code, PostalCode: remit_to_address.zip_compact, Country: remit_to_address.country.iso } bill_to_address_hash ||= remit_to_address_hash.dup elsif bill_to_address_hash.present? # this should never happen but populate the RI address as a dupe of the BT address remit_to_address_hash = bill_to_address_hash.dup remit_to_address_hash[:AddressTypeCode] = 'RI' end bill_to_address_hash[:AddressTypeCode] = 'BT' # for Canadian Tire, in case it came from remit_to_address bill_to_address_hash[:LocationCodeQualifier] = (orchestrator.partner == :mft_gateway_canadian_tire ? '1' : '92') # just make it work, everyone has their own codes, Canadian Tire wants "1" here # https://developercenter.spscommerce.com/#/rsx/docs/fields-qualifiers/7.7.6/Invoices/LocationCodeQualifier ... 92 Buyer Location Number ... addresses_arr = [bill_to_address_hash, ship_to_address_hash.merge({ PostalCode: order&.shipping_address&.zip_compact })].compact if orchestrator.customer.is_canadian_tire? && ma_address_hash.present? # Comment out this old SPS Commerce weirdness with Canadian Tire # deal with ^&%$&^% Canadian Tire # # address comes only with AddressTypeCode "MA', and AddressLocationNumber and AddressName populated, i.e. store name/number, nothing else populated, for e.g. # # { # # "AddressTypeCode": "MA", # # "LocationCodeQualifier": "92", # # "AddressLocationNumber": "0087", # # "AddressName": "CT RETAIL - ONLINE ORDER", # # "Address1": "", # # "City": "", # # "State": "", # # "PostalCode": "" # # } # This is the workaround for when we do not get a proper MA segment in the PO from DSCO for Canadian Tire # Start workaround part 2/2 =========================== ship_to_address_hash.delete(:Contacts) # remove Contacts array if any addresses_arr = [remit_to_address_hash, ship_to_address_hash.merge({ PostalCode: order&.shipping_address&.zip_compact }), ma_address_hash].compact end # End workaround part 2/2 =========================== invoice_line_items = invoice.line_items.goods.parents_only.non_shipping.where.not(edi_reference: nil).order(:edi_line_number) line_items_to_use = credit_memo ? credit_memo.line_items.goods.parents_only.non_shipping : invoice_line_items tot_lines = line_items_to_use.size line_items_hash_arr = [] order_line_hash_arr = order_hash.dig(:Structure, :LineItem) summary_hash = { TotalAmount: ('%.2f' % total), TotalSalesAmount: ('%.2f' % total), TotalLineItemNumber: tot_lines } if orchestrator.try(:tax_registration_number).present? summary_hash[:Tax] = { TaxPercent: invoice_line_items.first.tax_rate * 100.0.round(2), TaxIdentification: orchestrator.tax_registration_number, TaxAmount: format('%.2f', (line_items_to_use.sum { |li| li.tax_total })) } if orchestrator.partner == :mft_gateway_canadian_tire # per https://support.dsco.io/hc/en-us/articles/13509498740763-Canadian-Tire-Corporation-Limited-Documentation # Acceptable tax type code values are: - GS: Goods and Services Tax (GST) - ZZ: Harmonized Sales Tax (HST) - SP: Quebec Sales Tax (QST) tax_type = invoice_line_items.first.tax_type tax_type_code = tax_type.downcase == 'hst' ? 'ZZ' : 'GS' # we only maintain hst and gst summary_hash[:Tax][:TaxTypeCode] = tax_type_code summary_hash[:Tax][:Description] = tax_type_code # they require a mirror of tax type code! end end line_items_to_use.each do |li| # do this so we can use the credit_memo line item stuff but inherit the invoice line item EDI stuff in_li = credit_memo ? invoice_line_items.detect{|l| l.sku == li.sku} : li line_number = in_li.edi_line_number.to_s order_line_hash = # deal with *&%&* DSCO padding line numbers with leading zeroes order_line_hash_arr.detect do |olh| olh&.dig(:OrderLine, :LineSequenceNumber) == line_number || in_li.edi_line_number == olh&.dig(:OrderLine, :LineSequenceNumber).to_s.to_i end vendor_sku = order_line_hash.dig(:OrderLine, :VendorPartNumber).presence || li&.item&.sku buyer_part_number = order_line_hash.dig(:OrderLine, :BuyerPartNumber).presence || li&.catalog_item&.third_party_part_number.presence || order_line_hash.dig(:OrderLine, :SKU).presence || vendor_sku sku = buyer_part_number sku = order_line_hash.dig(:OrderLine, :SKU) || vendor_sku || buyer_part_number if orchestrator.partner == :mft_gateway_canadian_tire consumer_package_code = order_line_hash.dig(:OrderLine, :ConsumerPackageCode).presence || li&.item&.upc.presence # documentation from SPS Commerce requires this line_items_hash_arr << { InvoiceLine: { LineSequenceNumber: line_number, BuyerPartNumber: buyer_part_number, SKU: sku, VendorPartNumber: vendor_sku, ConsumerPackageCode: consumer_package_code, InvoiceQty: li.quantity.abs, # can be negative for credit memo InvoiceQtyUOM: 'EA', PurchasePrice: ('%.2f' % in_li.edi_unit_cost), # let's just send this because it will not be accepted unless it matches PurchasePriceBasis: 'PE', ShipQty: li.quantity.abs, ShipQtyUOM: 'EA', ExtendedItemTotal: format('%.2f', (in_li.edi_unit_cost * li.quantity.abs)) }, ProductOrItemDescription: { ProductCharacteristicCode: '08', # from: https://developercenter.spscommerce.com/#/rsx/docs/fields-qualifiers/7.7.6/Invoices/ProductCharacteristicCode: ... 08 Product Description ... ProductDescription: li.catalog_item.item.name[0..79].tr('′', "'").gsub(/[^0-9a-z\s]/i, '').strip } } end inv_hash_data = { Header: { InvoiceHeader: { TradingPartnerId: orchestrator.partner_id, InvoiceNumber: credit_memo ? credit_memo.reference_number : invoice.reference_number, # per Build.com we don't have to limit this to the last 6 digits InvoiceDate: inv_date, # e.g. 2022-12-24 PurchaseOrderDate: order.edi_order_date, # e.g. 2022-12-24 PurchaseOrderNumber: order.edi_po_number, TsetPurposeCode: '00', # 00: Original BuyersCurrency: order.currency, Vendor: orchestrator.vendor_id, VendorId: orchestrator.vendor_id, ShipDate: shipped_date # e.g. 2022-12-24 }, PaymentTerms: { TermsType: '01', # 01 Basic, documentation from SPS Commerce TermsDiscountPercentage: 0.0, TermsDiscountDate: inv_date, TermsDiscountAmount: format('%.2f', 0.0), TermsDescription: 'Terms and discount as previously agreed upon.' }, Address: addresses_arr }, Structure: { LineItem: line_items_hash_arr }, Summary: summary_hash }.compact.to_json invoice = [:invoice] EdiCommunicationLog.create_outbound_file_from_data(data: inv_hash_data, file_extension: 'invoice', partner: orchestrator.partner, category: 'invoice', data_type: 'json', resources: invoice || order, transmit_after: transmit_after, file_info: { lines_confirmed: tot_lines }) end |