huginn_acumen_order_agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +7 -0
- data/lib/huginn_acumen_order_agent.rb +10 -0
- data/lib/huginn_acumen_order_agent/acumen_client.rb +116 -0
- data/lib/huginn_acumen_order_agent/acumen_order_agent.rb +153 -0
- data/lib/huginn_acumen_order_agent/acumen_order_error.rb +10 -0
- data/lib/huginn_acumen_order_agent/concerns/acumen_query_concern.rb +53 -0
- data/lib/huginn_acumen_order_agent/concerns/invoice_detail_query_concern.rb +103 -0
- data/lib/huginn_acumen_order_agent/concerns/invoice_query_concern.rb +66 -0
- data/spec/acumen_order_agent_spec.rb +61 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e43425e52ee96e8537e96c346347c2594e0e0da21482cd4630fc1d8a916a355a
|
4
|
+
data.tar.gz: 6ff32c412a21ac888e7a909d4356621fb0ef17fe068e690e7e0f066ee4245650
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7cced5dbc343bf4d889a331025551a6d0d602e4b8cee4e22578a74467bb20b4bb16b3373789eec7dafb3bb34793014522af71ab322ffcddee9c9b2f4e73784a3
|
7
|
+
data.tar.gz: 31dcd11db687b62b6816bc06806d844696a5d2351794854b79c12ce71393dd9e6543e26d7f414437d3e98bf8ebd163fd42f936ebf3b55892f345cd77f7ac3fd5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2020 Jacob Spizziri
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'huginn_agent'
|
2
|
+
|
3
|
+
HuginnAgent.load 'huginn_acumen_order_agent/concerns/acumen_query_concern'
|
4
|
+
HuginnAgent.load 'huginn_acumen_order_agent/concerns/invoice_query_concern'
|
5
|
+
HuginnAgent.load 'huginn_acumen_order_agent/concerns/invoice_detail_query_concern'
|
6
|
+
|
7
|
+
HuginnAgent.load 'huginn_acumen_order_agent/acumen_client'
|
8
|
+
HuginnAgent.load 'huginn_acumen_order_agent/acumen_order_error'
|
9
|
+
|
10
|
+
HuginnAgent.register 'huginn_acumen_order_agent/acumen_order_agent'
|
@@ -0,0 +1,116 @@
|
|
1
|
+
class AcumenOrderClient
|
2
|
+
@faraday
|
3
|
+
@auth
|
4
|
+
|
5
|
+
def initialize(faraday, auth)
|
6
|
+
@faraday = faraday
|
7
|
+
@auth = auth
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_invoices(order_codes)
|
11
|
+
body = build_invoice_request(order_codes)
|
12
|
+
response = execute_in_list_query(body, {})
|
13
|
+
get_results(response, 'Invoice')
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_invoice_details(invoice_ids)
|
17
|
+
body = build_invoice_detail_query(invoice_ids)
|
18
|
+
response = execute_in_list_query(body, {})
|
19
|
+
get_results(response, 'Invoice_Detail')
|
20
|
+
end
|
21
|
+
|
22
|
+
def execute_query(body, headers)
|
23
|
+
response = @faraday.run_request(:post, "#{@auth['endpoint']}Query", body, headers)
|
24
|
+
::MultiXml.parse(response.body, {})
|
25
|
+
end
|
26
|
+
|
27
|
+
def execute_in_list_query(body, headers)
|
28
|
+
response = @faraday.run_request(:post, "#{@auth['endpoint']}QueryByInList", body, headers)
|
29
|
+
::MultiXml.parse(response.body, {})
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_results(response, name)
|
33
|
+
result_set = response['Envelope']['Body']['acusoapResponse']['result_set.' + name]
|
34
|
+
results = result_set.nil? ? [] : result_set[name]
|
35
|
+
results.is_a?(Array) ? results : [results]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_invoice_request(codes)
|
41
|
+
<<~XML
|
42
|
+
<acusoapRequest>
|
43
|
+
#{build_acumen_query_auth()}
|
44
|
+
<query>
|
45
|
+
<statement>
|
46
|
+
<column_name>Invoice.Order_Code</column_name>
|
47
|
+
<comparator>in</comparator>
|
48
|
+
<value>#{codes.join(',')}</value>
|
49
|
+
</statement>
|
50
|
+
</query>
|
51
|
+
<requested_output>
|
52
|
+
<view_owner_table_name>Invoice</view_owner_table_name>
|
53
|
+
<view_name>InvoiceAllRead</view_name>
|
54
|
+
<column_name>Invoice.Invoice_ID</column_name>
|
55
|
+
<column_name>Invoice.Modified_Date</column_name>
|
56
|
+
<column_name>Invoice.Order_Code</column_name>
|
57
|
+
<column_name>Invoice.Order_Date</column_name>
|
58
|
+
<column_name>Invoice.Status</column_name>
|
59
|
+
<column_name>Invoice.Customer_Name</column_name>
|
60
|
+
<column_name>Invoice.Tracking_Num</column_name>
|
61
|
+
</requested_output>
|
62
|
+
</acusoapRequest>
|
63
|
+
XML
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_invoice_detail_query(invoice_ids)
|
67
|
+
<<~XML
|
68
|
+
<acusoapRequest>
|
69
|
+
#{build_acumen_query_auth()}
|
70
|
+
<query>
|
71
|
+
<statement>
|
72
|
+
<column_name>Invoice_Detail.Invoice_ID</column_name>
|
73
|
+
<comparator>in</comparator>
|
74
|
+
<value>#{invoice_ids.join(',')}</value>
|
75
|
+
</statement>
|
76
|
+
</query>
|
77
|
+
<requested_output>
|
78
|
+
<view_owner_table_name>Invoice_Detail</view_owner_table_name>
|
79
|
+
<view_name>Invoice_DetailAllRead</view_name>
|
80
|
+
<column_name>Invoice_Detail.Invoice_DETAIL_ID</column_name>
|
81
|
+
<column_name>Invoice_Detail.Title</column_name>
|
82
|
+
<column_name>Invoice_Detail.ProdCode</column_name>
|
83
|
+
<column_name>Invoice_Detail.Ordered</column_name>
|
84
|
+
<column_name>Invoice_Detail.Ship</column_name>
|
85
|
+
<column_name>Invoice_Detail.BO</column_name>
|
86
|
+
<column_name>Invoice_Detail.List</column_name>
|
87
|
+
<column_name>Invoice_Detail.Back_Order_ID</column_name>
|
88
|
+
<column_name>Invoice_Detail.Mktg_Code</column_name>
|
89
|
+
<column_name>Invoice_Detail.Order_Number</column_name>
|
90
|
+
<column_name>Invoice_Detail.BO_Reason</column_name>
|
91
|
+
<column_name>Invoice_Detail.Quant_Ship_Confirm</column_name>
|
92
|
+
<column_name>Invoice_Detail.Quant_BO_Confirm</column_name>
|
93
|
+
<column_name>Invoice_Detail.BO_Prebill</column_name>
|
94
|
+
<column_name>Invoice_Detail.Back_Order_Special_ID</column_name>
|
95
|
+
<column_name>Invoice_Detail.PO_Number</column_name>
|
96
|
+
<column_name>Invoice_Detail.RF_ShipConfirm_Comment</column_name>
|
97
|
+
<column_name>Invoice_Detail.Old_BO</column_name>
|
98
|
+
<column_name>Invoice_Detail.Invoice_ID</column_name>
|
99
|
+
<column_name>Invoice_Detail.Modified_Date</column_name>
|
100
|
+
<column_name>Invoice_Detail.BO_Original_Invoice_DETAIL_ID</column_name>
|
101
|
+
</requested_output>
|
102
|
+
</acusoapRequest>
|
103
|
+
XML
|
104
|
+
end
|
105
|
+
|
106
|
+
def build_acumen_query_auth()
|
107
|
+
<<~XML
|
108
|
+
<authentication>
|
109
|
+
<site_code>#{@auth['site_code']}</site_code>
|
110
|
+
<password>#{@auth['password']}</password>
|
111
|
+
</authentication>
|
112
|
+
<message_version>1.00</message_version>
|
113
|
+
XML
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agents
|
4
|
+
class AcumenOrderAgent < Agent
|
5
|
+
include WebRequestConcern
|
6
|
+
include AcumenOrderQueryConcern
|
7
|
+
include InvoiceQueryConcern
|
8
|
+
include InvoiceDetailQueryConcern
|
9
|
+
|
10
|
+
default_schedule '12h'
|
11
|
+
|
12
|
+
can_dry_run!
|
13
|
+
default_schedule 'never'
|
14
|
+
|
15
|
+
description <<-MD
|
16
|
+
Huginn agent for retrieving sane ACUMEN invoice data.
|
17
|
+
|
18
|
+
## Agent Options
|
19
|
+
The following outlines the available options in this agent
|
20
|
+
|
21
|
+
### Acumen Connection
|
22
|
+
* endpoint: The root URL for the Acumen API
|
23
|
+
* site_code: The site code from Acumen
|
24
|
+
* password: The Acumen API password
|
25
|
+
* output_mode - not required ('clean' or 'merge', defaults to 'clean')
|
26
|
+
|
27
|
+
### Payload Status
|
28
|
+
|
29
|
+
`status: 200`: Indicates a true success. The agent has output the full
|
30
|
+
range of expected data.
|
31
|
+
|
32
|
+
`status: 206`: Indicates a partial success. The products within the bundle
|
33
|
+
are vaild, but the bundle _may_ be missing products that were somehow invalid.
|
34
|
+
|
35
|
+
`status: 500`: Indicates a processing error. This may represent a complete
|
36
|
+
process failure, but may also be issued in parallel to a `202` payload.
|
37
|
+
|
38
|
+
Because this agent receives an array of Order Codes as input, errors will be issued in
|
39
|
+
such a way that product processing can recover when possible. Errors that occur within
|
40
|
+
a specific product bundle will emit an error event, but the agent will then move
|
41
|
+
forward processing the next bundle.
|
42
|
+
|
43
|
+
For example, if this agent receives two products as input (`A` and `B`), and we fail to
|
44
|
+
load the Inv_Product record for product `A`, the agent would emit an error payload of:
|
45
|
+
|
46
|
+
```
|
47
|
+
{
|
48
|
+
status: 500,
|
49
|
+
scope: 'Fetch Inv_Product Data',
|
50
|
+
message: 'Failed to lookup Inv_Product record for Product A',
|
51
|
+
data: { product_id: 123 },
|
52
|
+
trace: [ ... ]
|
53
|
+
}
|
54
|
+
```
|
55
|
+
|
56
|
+
The goal of this approach is to ensure the agent outputs as much data as reasonably possible
|
57
|
+
with each execution. If there is an error in the Paperback version of a title, that shouldn't
|
58
|
+
prevent this agent from returning the Hardcover version.
|
59
|
+
|
60
|
+
MD
|
61
|
+
|
62
|
+
def default_options
|
63
|
+
{
|
64
|
+
'endpoint' => 'https://example.com',
|
65
|
+
'site_code' => '',
|
66
|
+
'password' => '',
|
67
|
+
'output_mode' => 'clean',
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_options
|
72
|
+
unless options['endpoint'].present?
|
73
|
+
errors.add(:base, 'endpoint is a required field')
|
74
|
+
end
|
75
|
+
|
76
|
+
unless options['site_code'].present?
|
77
|
+
errors.add(:base, 'site_code is a required field')
|
78
|
+
end
|
79
|
+
|
80
|
+
unless options['password'].present?
|
81
|
+
errors.add(:base, 'password is a required field')
|
82
|
+
end
|
83
|
+
|
84
|
+
if options['output_mode'].present? && !options['output_mode'].to_s.include?('{') && !%[clean merge].include?(options['output_mode'].to_s)
|
85
|
+
errors.add(:base, "if provided, output_mode must be 'clean' or 'merge'")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def working?
|
90
|
+
received_event_without_error?
|
91
|
+
end
|
92
|
+
|
93
|
+
def check
|
94
|
+
handle interpolated['payload'].presence || {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def receive(incoming_events)
|
98
|
+
incoming_events.each do |event|
|
99
|
+
handle(event)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def handle(event)
|
106
|
+
# Process agent options
|
107
|
+
endpoint = interpolated['endpoint']
|
108
|
+
endpoint = endpoint += '/' unless endpoint.end_with?('/')
|
109
|
+
site_code = interpolated['site_code']
|
110
|
+
password = interpolated['password']
|
111
|
+
|
112
|
+
# Configure the Acumen Client
|
113
|
+
auth = {
|
114
|
+
'site_code' => site_code,
|
115
|
+
'password' => password,
|
116
|
+
'endpoint' => endpoint,
|
117
|
+
}
|
118
|
+
client = AcumenOrderClient.new(faraday, auth)
|
119
|
+
data = event.payload
|
120
|
+
order_codes = event.payload['order_codes']
|
121
|
+
new_event = interpolated['output_mode'].to_s == 'merge' ? data.dup : {}
|
122
|
+
|
123
|
+
begin
|
124
|
+
invoices = fetch_invoice_data(client, order_codes)
|
125
|
+
|
126
|
+
unless invoices.blank?
|
127
|
+
invoices = fetch_invoice_details(client, invoices)
|
128
|
+
create_event payload: new_event.merge(
|
129
|
+
invoices: invoices,
|
130
|
+
status: 200
|
131
|
+
)
|
132
|
+
end
|
133
|
+
rescue AcumenOrderError => e
|
134
|
+
issue_error(e, new_event)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def issue_error(error, new_event, status = 500)
|
139
|
+
# NOTE: Status is intentionally included on the top-level payload so that other
|
140
|
+
# agents can look for a `payload[:status]` of either 200 or 500 to distinguish
|
141
|
+
# between success and failure states
|
142
|
+
create_event payload: new_event.merge(
|
143
|
+
status: status,
|
144
|
+
scope: error.scope,
|
145
|
+
message: error.message,
|
146
|
+
original_error: error.original_error,
|
147
|
+
data: error.data,
|
148
|
+
trace: error.original_error.backtrace,
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module contains the baseline utility methods used in the more specific
|
4
|
+
# data concerns
|
5
|
+
module AcumenOrderQueryConcern
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
# Maps Acumen XML data to a hash object as specified in the provided `field_map`
|
11
|
+
# The field map is a hash of { source_field: target_field }
|
12
|
+
def response_mapper(data, field_map)
|
13
|
+
result = {}
|
14
|
+
|
15
|
+
field_map.each do |source_field, target_field|
|
16
|
+
result[target_field] = get_field_value(data, source_field)
|
17
|
+
end
|
18
|
+
|
19
|
+
return result
|
20
|
+
end
|
21
|
+
|
22
|
+
# Utility function to retrieve a value from an XML field
|
23
|
+
def get_field_value(data, field_name)
|
24
|
+
data[field_name]['__content__'] if data[field_name]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a quantitative field value (e.g. weight) as a Schema.org/QuantitativeValue
|
28
|
+
# object
|
29
|
+
def get_quantitative_value(value, unit)
|
30
|
+
{
|
31
|
+
'@type' => 'QuantitativeValue',
|
32
|
+
'value' => value,
|
33
|
+
'unitText' => unit,
|
34
|
+
'unitCode' => (UNIT_MAP[unit] if unit),
|
35
|
+
} if value
|
36
|
+
end
|
37
|
+
|
38
|
+
# Emits an error payload event to facilitate better debugging/logging
|
39
|
+
# NOTE: The `error` here is expected to be an instance of AcumenOrderError
|
40
|
+
def issue_error(error, status = 500)
|
41
|
+
# NOTE: Status is intentionally included on the top-level payload so that other
|
42
|
+
# agents can look for a `payload[:status]` of either 200 or 500 to distinguish
|
43
|
+
# between success and failure states
|
44
|
+
create_event payload: {
|
45
|
+
status: status,
|
46
|
+
scope: error.scope,
|
47
|
+
message: error.message,
|
48
|
+
original_error: error.original_error,
|
49
|
+
data: error.data,
|
50
|
+
trace: error.original_error.backtrace,
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module is responsible for reading/processing the Invoice_Detail table. This table
|
4
|
+
# contains information related to individual line items within an invoice.
|
5
|
+
module InvoiceDetailQueryConcern
|
6
|
+
extend AcumenOrderQueryConcern
|
7
|
+
|
8
|
+
# Update the provided products with their associated marketing data
|
9
|
+
# NOTE: The `products` here are Shema.org/Product records mapped from Inv_Product
|
10
|
+
# data
|
11
|
+
def fetch_invoice_details(acumen_client, invoices)
|
12
|
+
|
13
|
+
invoice_ids = invoices.map { |p| p['identifier'] }
|
14
|
+
line_item_data = acumen_client.get_invoice_details(invoice_ids)
|
15
|
+
line_item_data = process_invoice_detail_response(line_item_data)
|
16
|
+
|
17
|
+
return map_line_item_data(invoices, line_item_data)
|
18
|
+
end
|
19
|
+
|
20
|
+
# This function parses the raw data returned from the Prod_Mkt table
|
21
|
+
def process_invoice_detail_response(raw_data)
|
22
|
+
results = []
|
23
|
+
raw_data.each do |invoice_details|
|
24
|
+
|
25
|
+
begin
|
26
|
+
mapped_item = response_mapper(invoice_details, {
|
27
|
+
'Invoice_Detail.Invoice_DETAIL_ID' => 'id',
|
28
|
+
'Invoice_Detail.Title' => 'title',
|
29
|
+
'Invoice_Detail.ProdCode' => 'sku',
|
30
|
+
'Invoice_Detail.Ordered' => 'quantity_ordered',
|
31
|
+
'Invoice_Detail.Ship' => 'quantity_shipped',
|
32
|
+
'Invoice_Detail.BO' => 'quantity_backordered',
|
33
|
+
'Invoice_Detail.BO_Reason' => 'bo_reason',
|
34
|
+
'Invoice_Detail.BO_Prebill' => 'is_prebill',
|
35
|
+
'Invoice_Detail.Invoice_ID' => 'invoice_id',
|
36
|
+
'Invoice_Detail.Modified_Date' => 'modified_date',
|
37
|
+
'Invoice_Detail.BO_Original_Invoice_DETAIL_ID' => 'bo_original_detail_id',
|
38
|
+
|
39
|
+
})
|
40
|
+
|
41
|
+
results << mapped_item
|
42
|
+
rescue => error
|
43
|
+
issue_error(AcumenOrderError.new(
|
44
|
+
'process_invoice_detail_response',
|
45
|
+
'Failed while processing Invoice_Detail record',
|
46
|
+
invoice_details,
|
47
|
+
error,
|
48
|
+
))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
results
|
53
|
+
end
|
54
|
+
|
55
|
+
# This function maps parsed Invoice_Detail records to their matching Invoice record
|
56
|
+
# and updates the invoice object with the additional data
|
57
|
+
def map_line_item_data(invoices, line_item_data)
|
58
|
+
|
59
|
+
# acceptedOffer
|
60
|
+
# identifier
|
61
|
+
# sku
|
62
|
+
# price
|
63
|
+
# priceCurrency
|
64
|
+
# name
|
65
|
+
|
66
|
+
invoices.map do |invoice|
|
67
|
+
items = line_item_data.select { |item| item['invoice_id'] == invoice['identifier'] }
|
68
|
+
|
69
|
+
begin
|
70
|
+
unless items.blank?
|
71
|
+
invoice['acceptedOffer'] = items.map do |i|
|
72
|
+
item = {
|
73
|
+
'@type' => 'Offer',
|
74
|
+
'identifier' => i['identifier'],
|
75
|
+
'sku' => i['sku'],
|
76
|
+
'name' => i['title'],
|
77
|
+
'acumenAttributes' => {},
|
78
|
+
'modifiedDate' => i['modified_date'],
|
79
|
+
'invoiceDetailID' => i['id'],
|
80
|
+
'boOriginalDetailId' => i['bo_original_detail_id'],
|
81
|
+
}
|
82
|
+
|
83
|
+
#---------- Acumen Specific Properties ----------#
|
84
|
+
item['acumenAttributes']['quantity_ordered'] = i['quantity_ordered']
|
85
|
+
item['acumenAttributes']['quantity_shipped'] = i['quantity_shipped']
|
86
|
+
item['acumenAttributes']['quantity_backordered'] = i['quantity_backordered']
|
87
|
+
|
88
|
+
item
|
89
|
+
end
|
90
|
+
end
|
91
|
+
rescue => error
|
92
|
+
issue_error(AcumenOrderError.new(
|
93
|
+
'map_line_item_data',
|
94
|
+
'Failed to map line item data for invoice',
|
95
|
+
{ invoice: invoice, line_items: items },
|
96
|
+
error,
|
97
|
+
))
|
98
|
+
end
|
99
|
+
|
100
|
+
invoice
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module is responsible for reading/processing data recrods from the Invoice
|
4
|
+
# table in Acumen. This table contains the baseline order/shipment information
|
5
|
+
# Shipping method, tracking numbers, etc.
|
6
|
+
module InvoiceQueryConcern
|
7
|
+
extend AcumenOrderQueryConcern
|
8
|
+
|
9
|
+
# Fetch/Process the Acumen data,
|
10
|
+
def fetch_invoice_data(acumen_client, order_codes)
|
11
|
+
invoice_data = acumen_client.get_invoices(order_codes)
|
12
|
+
|
13
|
+
return process_invoice_response(invoice_data)
|
14
|
+
end
|
15
|
+
|
16
|
+
# This function returns an array of Acumen invoices.
|
17
|
+
def process_invoice_response(raw_data)
|
18
|
+
raw_data.map do |i|
|
19
|
+
|
20
|
+
log('------------------------------------------------------------')
|
21
|
+
log(":: raw invoice #{i}")
|
22
|
+
log('------------------------------------------------------------')
|
23
|
+
|
24
|
+
invoice = nil
|
25
|
+
begin
|
26
|
+
invoice = response_mapper(i, {
|
27
|
+
'Invoice.Invoice_ID' => 'identifier',
|
28
|
+
'Invoice.Order_Code' => 'orderNumber',
|
29
|
+
'Invoice.Order_Date' => 'orderDate',
|
30
|
+
'Invoice.Status' => 'orderStatus',
|
31
|
+
'Invoice.Customer_Name' => 'customer',
|
32
|
+
'Invoice.Modified_Date' => 'modified_date',
|
33
|
+
})
|
34
|
+
|
35
|
+
invoice['type'] = '@Order'
|
36
|
+
|
37
|
+
#---------- Parse Tracking Numbers ----------#
|
38
|
+
invoice['orderDelivery'] = []
|
39
|
+
tracking_numbers = get_field_value(i, 'Invoice.Tracking_Num')
|
40
|
+
tracking_numbers = tracking_numbers.blank? ? [] : tracking_numbers.split(/\s| /)
|
41
|
+
|
42
|
+
tracking_numbers.map do |tn|
|
43
|
+
invoice['orderDelivery'] << {
|
44
|
+
'type' => '@ParcelDelivery',
|
45
|
+
'trackingNumber' => tn,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
rescue => error
|
50
|
+
issue_error(AcumenOrderError.new(
|
51
|
+
'process_invoice_response',
|
52
|
+
'Failed to load invoice records',
|
53
|
+
{ raw_data: i },
|
54
|
+
error,
|
55
|
+
))
|
56
|
+
end
|
57
|
+
|
58
|
+
log('------------------------------------------------------------')
|
59
|
+
log(":: parsed invoice #{invoice}")
|
60
|
+
log('------------------------------------------------------------')
|
61
|
+
|
62
|
+
invoice
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'huginn_agent/spec_helper'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require_relative '../lib/huginn_acumen_order_agent/acumen_client'
|
6
|
+
|
7
|
+
spec_folder = File.expand_path(File.dirname(__FILE__))
|
8
|
+
mock_data = YAML.load(File.read(spec_folder + "/acumen_product_agent_spec.yml"))
|
9
|
+
|
10
|
+
def mock_response(ns, ids)
|
11
|
+
records = ids.map {|id| mock_data[ns][id]}
|
12
|
+
response = <<~TEXT
|
13
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
14
|
+
<SOAP-ENV:Envelope
|
15
|
+
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
|
16
|
+
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
17
|
+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
|
18
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
19
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
20
|
+
<SOAP-ENV:Body>
|
21
|
+
<acusoapResponse>
|
22
|
+
<result_set.#{ns}>#{records.join("\n")}</result_set.#{ns}>
|
23
|
+
</acusoapResponse>
|
24
|
+
</SOAP-ENV:Body>
|
25
|
+
</SOAP-ENV:Envelope>
|
26
|
+
TEXT
|
27
|
+
response = ::MultiXml.parse(response, {})
|
28
|
+
AcumenClient::get_results(response, ns)
|
29
|
+
end
|
30
|
+
|
31
|
+
allow(AcumenClient).to receive(:get_products) do |ids|
|
32
|
+
mock_response('Inv_Product', ids)
|
33
|
+
end
|
34
|
+
|
35
|
+
allow(AcumenClient).to receive(:get_products_marketing) do |ids|
|
36
|
+
mock_response('ProdMkt', ids)
|
37
|
+
end
|
38
|
+
|
39
|
+
allow(AcumenClient).to receive(:get_linked_products) do |ids|
|
40
|
+
mock_response('Product_Link', ids)
|
41
|
+
end
|
42
|
+
|
43
|
+
allow(AcumenClient).to receive(:get_product_contributors) do |ids|
|
44
|
+
mock_response('ProdMkt_Contrib_Link', ids)
|
45
|
+
end
|
46
|
+
|
47
|
+
allow(AcumenClient).to receive(:get_product_categories) do |ids|
|
48
|
+
mock_response('ProdMkt_WPC', ids)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
describe Agents::AcumenProductAgent do
|
53
|
+
before(:each) do
|
54
|
+
@valid_options = Agents::AcumenProductAgent.new.default_options
|
55
|
+
@checker = Agents::AcumenProductAgent.new(:name => "AcumenProductAgent", :options => @valid_options)
|
56
|
+
@checker.user = users(:bob)
|
57
|
+
@checker.save!
|
58
|
+
end
|
59
|
+
|
60
|
+
pending "add specs here"
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: huginn_acumen_order_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jacob Spizziri
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-03-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: huginn_agent
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: The Huginn Acumen Order Agent takes in an array of Order Codes, queries
|
56
|
+
the relevant Acumen tables, and emits a set of events with a sane data interface
|
57
|
+
for each.
|
58
|
+
email:
|
59
|
+
- jacob.spizziri@gmail.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- LICENSE.txt
|
65
|
+
- lib/huginn_acumen_order_agent.rb
|
66
|
+
- lib/huginn_acumen_order_agent/acumen_client.rb
|
67
|
+
- lib/huginn_acumen_order_agent/acumen_order_agent.rb
|
68
|
+
- lib/huginn_acumen_order_agent/acumen_order_error.rb
|
69
|
+
- lib/huginn_acumen_order_agent/concerns/acumen_query_concern.rb
|
70
|
+
- lib/huginn_acumen_order_agent/concerns/invoice_detail_query_concern.rb
|
71
|
+
- lib/huginn_acumen_order_agent/concerns/invoice_query_concern.rb
|
72
|
+
- spec/acumen_order_agent_spec.rb
|
73
|
+
homepage: https://github.com/5-Stones/huginn_acumen_order_agent
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.0.3
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Huginn agent for sane ACUMEN order data.
|
96
|
+
test_files:
|
97
|
+
- spec/acumen_order_agent_spec.rb
|