huginn_bigcommerce_product_agent 1.2.0 → 1.3.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 +4 -4
- data/lib/abstract_client.rb +9 -0
- data/lib/client/custom_field.rb +25 -0
- data/lib/client/product.rb +18 -7
- data/lib/client/product_option.rb +41 -0
- data/lib/client/product_option_value.rb +72 -0
- data/lib/client/product_variant.rb +40 -0
- data/lib/client/variant.rb +41 -0
- data/lib/huginn_bigcommerce_product_agent/bigcommerce_product_agent.rb +218 -47
- data/lib/mapper/custom_field_mapper.rb +17 -2
- data/lib/mapper/option_mapper.rb +65 -0
- data/lib/mapper/option_value_mapper.rb +16 -0
- data/lib/mapper/product_mapper.rb +43 -5
- data/lib/mapper/variant_mapper.rb +47 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a16c935c5f0593bfe513a750ce449cfa38c408cdae2d1d232a1952e910e0e61
|
4
|
+
data.tar.gz: 198cf4537a7751e48ea4563eee131328c1c6e9815e38f0e0e5d5f710b170ab41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b49211e58db9b4c6a84c6ca45009ca08101d9a0fe2163c2b9dbbbf9c91257aa99029e3b4c4dd518d5ce87fe40a03987481d7255339d24b750de1eb0c7e45b62
|
7
|
+
data.tar.gz: 811685124d0e2b4068a8897d24e6130ae2982efb37675f0e3a7c3b4aff6234dc5d839dd5a5afb54edeacb88557716a6d10061f6ec14f9bb0db253906ae2cd3b0
|
data/lib/abstract_client.rb
CHANGED
@@ -72,9 +72,18 @@ module BigcommerceProductAgent
|
|
72
72
|
raise "not implemented yet."
|
73
73
|
end
|
74
74
|
|
75
|
+
def delete(payload)
|
76
|
+
raise "not implemented yet."
|
77
|
+
end
|
78
|
+
|
75
79
|
def upsert(payload)
|
76
80
|
raise "not implemented yet."
|
77
81
|
end
|
82
|
+
|
83
|
+
def get(url_params = {}, params = {})
|
84
|
+
response = client.get(uri(url_params), params)
|
85
|
+
return response.body['data']
|
86
|
+
end
|
78
87
|
end
|
79
88
|
end
|
80
89
|
end
|
data/lib/client/custom_field.rb
CHANGED
@@ -3,9 +3,34 @@ module BigcommerceProductAgent
|
|
3
3
|
class CustomField < AbstractClient
|
4
4
|
@uri_base = 'catalog/products/:product_id/custom-fields/:custom_field_id'
|
5
5
|
|
6
|
+
def create(product_id, payload)
|
7
|
+
response = client.post(uri(product_id: product_id), payload.to_json)
|
8
|
+
return response.body['data']
|
9
|
+
end
|
10
|
+
|
11
|
+
def update(product_id, payload)
|
12
|
+
id = payload.delete('id')
|
13
|
+
response = client.put(uri(product_id: product_id, custom_field_id: id), payload.to_json)
|
14
|
+
return response.body['data']
|
15
|
+
end
|
16
|
+
|
6
17
|
def delete(product_id, custom_field_id)
|
7
18
|
client.delete(uri(product_id: product_id, custom_field_id: custom_field_id))
|
8
19
|
end
|
20
|
+
|
21
|
+
def upsert(product_id, payload)
|
22
|
+
begin
|
23
|
+
payload['id'] = payload.delete(:id) unless payload[:id].nil?
|
24
|
+
if payload['id']
|
25
|
+
return update(product_id, payload)
|
26
|
+
else
|
27
|
+
return create(product_id, payload)
|
28
|
+
end
|
29
|
+
rescue Faraday::Error::ClientError => e
|
30
|
+
puts e.inspect
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
end
|
9
34
|
end
|
10
35
|
end
|
11
36
|
end
|
data/lib/client/product.rb
CHANGED
@@ -3,22 +3,33 @@ module BigcommerceProductAgent
|
|
3
3
|
class Product < AbstractClient
|
4
4
|
@uri_base = 'catalog/products/:product_id'
|
5
5
|
|
6
|
-
def update(id, payload)
|
7
|
-
response = client.put(uri(product_id: id), payload.to_json)
|
6
|
+
def update(id, payload, params={})
|
7
|
+
response = client.put(uri(product_id: id), payload.to_json) do |request|
|
8
|
+
request.params.update(params) if params
|
9
|
+
end
|
10
|
+
|
8
11
|
return response.body['data']
|
9
12
|
end
|
10
13
|
|
11
|
-
def
|
12
|
-
response = client.
|
14
|
+
def delete(id)
|
15
|
+
response = client.delete(uri(product_id: id))
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
|
19
|
+
def create(payload, params={})
|
20
|
+
response = client.post(uri, payload.to_json) do |request|
|
21
|
+
request.params.update(params) if params
|
22
|
+
end
|
23
|
+
|
13
24
|
return response.body['data']
|
14
25
|
end
|
15
26
|
|
16
|
-
def upsert(payload)
|
27
|
+
def upsert(payload, params={})
|
17
28
|
begin
|
18
29
|
if payload['id']
|
19
|
-
return update(payload['id'], payload)
|
30
|
+
return update(payload['id'], payload, params)
|
20
31
|
else
|
21
|
-
return create(payload)
|
32
|
+
return create(payload, params)
|
22
33
|
end
|
23
34
|
rescue Faraday::Error::ClientError => e
|
24
35
|
puts e.inspect
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module BigcommerceProductAgent
|
2
|
+
module Client
|
3
|
+
class ProductOption < AbstractClient
|
4
|
+
@uri_base = 'catalog/products/:product_id/options/:option_id'
|
5
|
+
|
6
|
+
def delete(product_id, option_id)
|
7
|
+
client.delete(uri(product_id: product_id, option_id: option_id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def delete_all(options)
|
11
|
+
options.each do |option|
|
12
|
+
delete(option['product_id'], option['id'])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def upsert(product_id, option)
|
17
|
+
begin
|
18
|
+
if option[:id] || option['id']
|
19
|
+
option['id'] = option[:id] unless option[:id].nil?
|
20
|
+
return update(product_id, option)
|
21
|
+
else
|
22
|
+
return create(product_id, option)
|
23
|
+
end
|
24
|
+
rescue Faraday::Error::ClientError => e
|
25
|
+
puts e.inspect
|
26
|
+
raise e
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(product_id, option)
|
31
|
+
response = client.put(uri(product_id: product_id, option_id: option['id']), option.to_json)
|
32
|
+
return response.body['data']
|
33
|
+
end
|
34
|
+
|
35
|
+
def create(product_id, option)
|
36
|
+
response = client.post(uri(product_id: product_id), option.to_json)
|
37
|
+
return response.body['data']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module BigcommerceProductAgent
|
2
|
+
module Client
|
3
|
+
class ProductOptionValue < AbstractClient
|
4
|
+
@uri_base = 'catalog/products/:product_id/options/:option_id/values/:value_id'
|
5
|
+
|
6
|
+
def delete(product_id, option_id, value_id)
|
7
|
+
client.delete(uri(product_id: product_id, option_id: option_id, value_id: value_id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def delete_all(option, option_values)
|
11
|
+
option_values.each do |option_value|
|
12
|
+
delete(option['product_id'], option['id'], option_value['id'])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def upsert(option, option_value)
|
17
|
+
begin
|
18
|
+
if option[:product_id] || option['product_id']
|
19
|
+
option['product_id'] = option[:product_id] unless option[:product_id].nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
if option[:id] || option['id']
|
23
|
+
option['id'] = option[:id] unless option[:id].nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
if option_value[:id] || option_value['id']
|
27
|
+
option_value['id'] = option_value[:id] unless option_value[:id].nil?
|
28
|
+
return update(option, option_value)
|
29
|
+
else
|
30
|
+
return create(option, option_value)
|
31
|
+
end
|
32
|
+
rescue Faraday::Error::ClientError => e
|
33
|
+
puts e.inspect
|
34
|
+
raise e
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def upsert_all(option, option_values)
|
39
|
+
results = []
|
40
|
+
option_values.each do |option_value|
|
41
|
+
result = upsert(option, option_value)
|
42
|
+
results.push(result)
|
43
|
+
end
|
44
|
+
|
45
|
+
return results
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(option, option_value)
|
49
|
+
response = client.put(
|
50
|
+
uri(
|
51
|
+
product_id: option['product_id'],
|
52
|
+
option_id: option['id'],
|
53
|
+
value_id: option_value['id'],
|
54
|
+
),
|
55
|
+
option_value.to_json,
|
56
|
+
)
|
57
|
+
return response.body['data']
|
58
|
+
end
|
59
|
+
|
60
|
+
def create(option, option_value)
|
61
|
+
response = client.post(
|
62
|
+
uri(
|
63
|
+
product_id: option['product_id'],
|
64
|
+
option_id: option['id'],
|
65
|
+
),
|
66
|
+
option_value.to_json,
|
67
|
+
)
|
68
|
+
return response.body['data']
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module BigcommerceProductAgent
|
2
|
+
module Client
|
3
|
+
class ProductVariant < AbstractClient
|
4
|
+
@uri_base = 'catalog/products/:product_id/variants/:variant_id'
|
5
|
+
|
6
|
+
def index(product_id, params = {})
|
7
|
+
response = client.get(uri(product_id: product_id), params)
|
8
|
+
return response.body['data']
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete(product_id, variant_id)
|
12
|
+
client.delete(uri(product_id: product_id, variant_id: variant_id))
|
13
|
+
end
|
14
|
+
|
15
|
+
def upsert(product_id, variant)
|
16
|
+
begin
|
17
|
+
if variant[:id] || variant['id']
|
18
|
+
variant['id'] = variant[:id] unless variant[:id].nil?
|
19
|
+
return update(product_id, variant)
|
20
|
+
else
|
21
|
+
return create(product_id, variant)
|
22
|
+
end
|
23
|
+
rescue Faraday::Error::ClientError => e
|
24
|
+
puts e.inspect
|
25
|
+
raise e
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def update(product_id, variant)
|
30
|
+
response = client.put(uri(product_id: product_id, variant_id: variant['id']), variant.to_json)
|
31
|
+
return response.body['data']
|
32
|
+
end
|
33
|
+
|
34
|
+
def create(product_id, variant)
|
35
|
+
response = client.post(uri(product_id: product_id), variant.to_json)
|
36
|
+
return response.body['data']
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module BigcommerceProductAgent
|
2
|
+
module Client
|
3
|
+
class Variant < AbstractClient
|
4
|
+
@uri_base = 'catalog/variants'
|
5
|
+
|
6
|
+
def update(payload)
|
7
|
+
raise "this endpoint only has upsert available"
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(payload)
|
11
|
+
raise "this endpoint only has upsert available"
|
12
|
+
end
|
13
|
+
|
14
|
+
def upsert(payload)
|
15
|
+
begin
|
16
|
+
response = client.put(uri, payload.to_json)
|
17
|
+
return response.body['data']
|
18
|
+
rescue Faraday::Error::ClientError => e
|
19
|
+
puts e.inspect
|
20
|
+
raise e
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_by_skus(skus, include = %w[custom_fields modifiers])
|
25
|
+
variants = index({
|
26
|
+
'sku:in': skus.join(','),
|
27
|
+
include: include.join(','),
|
28
|
+
})
|
29
|
+
|
30
|
+
map = {}
|
31
|
+
|
32
|
+
variants.each do |variant|
|
33
|
+
map[variant['sku']] = variant
|
34
|
+
end
|
35
|
+
|
36
|
+
map
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -4,7 +4,6 @@ require 'json'
|
|
4
4
|
|
5
5
|
module Agents
|
6
6
|
class BigcommerceProductAgent < Agent
|
7
|
-
|
8
7
|
include WebRequestConcern
|
9
8
|
|
10
9
|
can_dry_run!
|
@@ -14,6 +13,13 @@ module Agents
|
|
14
13
|
Takes a generic product interface && upserts that product in BigCommerce.
|
15
14
|
MD
|
16
15
|
|
16
|
+
def modes
|
17
|
+
%w[
|
18
|
+
variants
|
19
|
+
option_list
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
17
23
|
def default_options
|
18
24
|
{
|
19
25
|
'store_hash' => '',
|
@@ -22,6 +28,7 @@ module Agents
|
|
22
28
|
'custom_fields_map' => {},
|
23
29
|
'meta_fields_map' => {},
|
24
30
|
'meta_fields_namespace' => '',
|
31
|
+
'mode' => modes[0]
|
25
32
|
}
|
26
33
|
end
|
27
34
|
|
@@ -39,15 +46,21 @@ module Agents
|
|
39
46
|
end
|
40
47
|
|
41
48
|
unless options['custom_fields_map'].is_a?(Hash)
|
42
|
-
errors.add(:base,
|
49
|
+
errors.add(:base, 'if provided, custom_fields_map must be a hash')
|
43
50
|
end
|
44
51
|
|
45
52
|
unless options['meta_fields_map'].is_a?(Hash)
|
46
|
-
errors.add(:base,
|
53
|
+
errors.add(:base, 'if provided, meta_fields_map must be a hash')
|
47
54
|
end
|
48
55
|
|
49
56
|
if options['meta_fields_map']
|
50
|
-
|
57
|
+
if options['meta_fields_namespace'].blank?
|
58
|
+
errors.add(:base, 'if meta_fields_map is provided, meta_fields_namespace is required')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
unless options['mode'].present? && modes.include?(options['mode'])
|
63
|
+
errors.add(:base, "mode is a required field and must be one of: #{modes.join(', ')}")
|
51
64
|
end
|
52
65
|
end
|
53
66
|
|
@@ -56,22 +69,184 @@ module Agents
|
|
56
69
|
end
|
57
70
|
|
58
71
|
def check
|
59
|
-
initialize_clients
|
72
|
+
initialize_clients
|
60
73
|
handle interpolated['payload'].presence || {}
|
61
74
|
end
|
62
75
|
|
63
76
|
def receive(incoming_events)
|
64
|
-
initialize_clients
|
77
|
+
initialize_clients
|
65
78
|
incoming_events.each do |event|
|
66
79
|
handle(event)
|
67
80
|
end
|
68
|
-
|
81
|
+
end
|
69
82
|
|
70
83
|
def handle(event)
|
84
|
+
method_name = "handle_#{interpolated['mode']}"
|
85
|
+
if self.respond_to?(method_name, true)
|
86
|
+
self.public_send(method_name, event)
|
87
|
+
else
|
88
|
+
raise "'#{interpolated['mode']}' is not a supported mode"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# 1. upsert product
|
93
|
+
# 2. upsert option & option_values
|
94
|
+
# 3. delete old option_values
|
95
|
+
# - NOTE: deleting an option_value also deletes the variant
|
96
|
+
# associated with the option_value
|
97
|
+
# 4. upsert variants
|
98
|
+
# - NOTE: because deleting option values deletes variants
|
99
|
+
# we need to fetch the variants AFTER deletion has occurred.
|
100
|
+
# - NOTE: by deleting variants in #3 if option_values on an
|
101
|
+
# existing variant changes over time, we're effectively deleting
|
102
|
+
# and then re-adding the variant. Could get weird.
|
103
|
+
def handle_variants(event)
|
104
|
+
product = event.payload
|
105
|
+
|
106
|
+
wrapper_skus = {
|
107
|
+
physical: get_mapper(:ProductMapper).get_wrapper_sku_physical(product),
|
108
|
+
digital: get_mapper(:ProductMapper).get_wrapper_sku_digital(product),
|
109
|
+
}
|
110
|
+
bc_products = @product.get_by_skus(
|
111
|
+
wrapper_skus.map {|k,v| v},
|
112
|
+
%w[custom_fields options]
|
113
|
+
)
|
114
|
+
|
115
|
+
split = get_mapper(:ProductMapper).split_digital_and_physical(product)
|
116
|
+
physical = split[:physical]
|
117
|
+
digital = split[:digital]
|
118
|
+
|
119
|
+
# upsert wrapper products
|
120
|
+
split.each do |type, product|
|
121
|
+
is_digital = type == :digital ? true : false
|
122
|
+
|
123
|
+
# modify digital
|
124
|
+
if is_digital
|
125
|
+
product['name'] = "#{product['name']} (Digital)"
|
126
|
+
end
|
127
|
+
|
128
|
+
wrapper_sku = wrapper_skus[type]
|
129
|
+
bc_product = bc_products[wrapper_sku]
|
130
|
+
variant_option_name = get_mapper(:OptionMapper).variant_option_name
|
131
|
+
bc_option = !bc_product.nil? ? bc_product['options'].select {|opt| opt['display_name'] === variant_option_name}.first : nil
|
132
|
+
|
133
|
+
# ##############################
|
134
|
+
# 1. update wrapper product
|
135
|
+
# ##############################
|
136
|
+
upsert_result = upsert_product(wrapper_sku, product, bc_product, is_digital)
|
137
|
+
bc_product = upsert_result[:product]
|
138
|
+
bc_products[wrapper_sku] = bc_product
|
139
|
+
product_id = bc_products[wrapper_sku]['id']
|
140
|
+
|
141
|
+
# clean up custom/meta fields. there are not batch operations so we might as well do them here.
|
142
|
+
custom_fields_delete = upsert_result[:custom_fields_delete].select {|field| field['name'] != 'related_product_id'}
|
143
|
+
clean_up_custom_fields(custom_fields_delete)
|
144
|
+
update_meta_fields(
|
145
|
+
upsert_result[:meta_fields_upsert],
|
146
|
+
upsert_result[:meta_fields_delete],
|
147
|
+
)
|
148
|
+
|
149
|
+
# ##############################
|
150
|
+
# 2. upsert option & option_values
|
151
|
+
# ##############################
|
152
|
+
option_values_map = get_mapper(:ProductMapper).get_sku_option_label_map(product)
|
153
|
+
option_values = option_values_map.map {|k,v| v}
|
154
|
+
option_value_operations = get_mapper(:OptionMapper).option_value_operations(bc_option, option_values)
|
155
|
+
option = get_mapper(:OptionMapper).map(product_id, bc_option, option_value_operations[:create])
|
156
|
+
bc_option = @product_option.upsert(product_id, option)
|
157
|
+
|
158
|
+
# ##############################
|
159
|
+
# 3. delete old option_values
|
160
|
+
# ##############################
|
161
|
+
@product_option_value.delete_all(bc_option, option_value_operations[:delete])
|
162
|
+
|
163
|
+
# ##############################
|
164
|
+
# 4. upsert variants
|
165
|
+
# ##############################
|
166
|
+
variant_skus = get_mapper(:ProductMapper).get_product_skus(product)
|
167
|
+
bc_variants = @product_variant.index(product_id)
|
168
|
+
mapped_variants = product['model'].map do |variant|
|
169
|
+
bc_variant = bc_variants.select {|v| v['sku'] === variant['sku']}.first
|
170
|
+
opt = get_mapper(:ProductMapper).get_option(variant)
|
171
|
+
bc_option_value = bc_option['option_values'].select {|ov| ov['label'] == opt}.first
|
172
|
+
|
173
|
+
option_value = get_mapper(:VariantMapper).map_option_value(bc_option_value['id'], bc_option['id'])
|
174
|
+
|
175
|
+
get_mapper(:VariantMapper).map(
|
176
|
+
variant,
|
177
|
+
[option_value],
|
178
|
+
product_id,
|
179
|
+
bc_variant.nil? ? nil : bc_variant['id'],
|
180
|
+
)
|
181
|
+
end
|
182
|
+
|
183
|
+
bc_product['variants'] = @variant.upsert(mapped_variants)
|
184
|
+
end
|
185
|
+
|
186
|
+
bc_physical = bc_products[wrapper_skus[:physical]]
|
187
|
+
bc_digital = bc_products[wrapper_skus[:digital]]
|
188
|
+
is_delete_physical = split[:physical].nil? && bc_physical
|
189
|
+
is_delete_digital = split[:digital].nil? && bc_digital
|
190
|
+
|
191
|
+
# ##############################
|
192
|
+
# clean up products that no longer exist
|
193
|
+
# ##############################
|
194
|
+
if is_delete_physical
|
195
|
+
bc_product = bc_products[wrapper_skus[:physical]]
|
196
|
+
@product.delete(bc_product['id'])
|
197
|
+
bc_physical = false
|
198
|
+
bc_product.delete(wrapper_skus[:physical])
|
199
|
+
end
|
200
|
+
|
201
|
+
if is_delete_digital
|
202
|
+
bc_product = bc_products[wrapper_skus[:digital]]
|
203
|
+
@product.delete(bc_product['id'])
|
204
|
+
bc_digital = false
|
205
|
+
bc_product.delete(wrapper_skus[:digital])
|
206
|
+
end
|
207
|
+
|
208
|
+
# ##############################
|
209
|
+
# clean up custom field relationships
|
210
|
+
# ##############################
|
211
|
+
if bc_physical && !bc_digital
|
212
|
+
# clean up related_product_id on physical product
|
213
|
+
bc_product = bc_physical
|
214
|
+
related_custom_field = bc_product['custom_fields'].select {|field| field['name'] == 'related_product_id'}.first
|
215
|
+
@custom_field.delete(bc_product['id'], related_custom_field['id']) unless related_custom_field.nil?
|
216
|
+
elsif !bc_physical && bc_digital
|
217
|
+
# clean up related_product_id on digital product
|
218
|
+
bc_product = bc_digital
|
219
|
+
related_custom_field = bc_product['custom_fields'].select {|field| field['name'] == 'related_product_id'}.first
|
220
|
+
@custom_field.delete(bc_product['id'], related_custom_field['id']) unless related_custom_field.nil?
|
221
|
+
elsif bc_physical && bc_digital
|
222
|
+
# update/add related_product_id on both products
|
223
|
+
bc_physical_related = get_mapper(:CustomFieldMapper).map_one(bc_physical, 'related_product_id', bc_digital['id'])
|
224
|
+
bc_digital_related = get_mapper(:CustomFieldMapper).map_one(bc_digital, 'related_product_id', bc_physical['id'])
|
225
|
+
@custom_field.upsert(bc_physical['id'], bc_physical_related)
|
226
|
+
@custom_field.upsert(bc_digital['id'], bc_digital_related)
|
227
|
+
end
|
228
|
+
|
229
|
+
# ##############################
|
230
|
+
# emit events
|
231
|
+
# ##############################
|
232
|
+
if bc_physical
|
233
|
+
create_event payload: {
|
234
|
+
product: bc_physical
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
if bc_digital
|
239
|
+
create_event payload: {
|
240
|
+
product: bc_digital
|
241
|
+
}
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def handle_option_list(event)
|
71
246
|
product = event.payload
|
72
247
|
|
73
|
-
skus =
|
74
|
-
wrapper_sku =
|
248
|
+
skus = get_mapper(:ProductMapper).get_product_skus(product)
|
249
|
+
wrapper_sku = get_mapper(:ProductMapper).get_wrapper_sku(product)
|
75
250
|
all_skus = [].push(*skus).push(wrapper_sku)
|
76
251
|
bc_products = @product.get_by_skus(all_skus)
|
77
252
|
|
@@ -83,7 +258,7 @@ module Agents
|
|
83
258
|
|
84
259
|
skus.each do |sku|
|
85
260
|
bc_product = bc_products[sku]
|
86
|
-
result =
|
261
|
+
result = upsert_product(sku, product, bc_product)
|
87
262
|
custom_fields_delete += result[:custom_fields_delete]
|
88
263
|
meta_fields_upsert += result[:meta_fields_upsert]
|
89
264
|
meta_fields_delete += result[:meta_fields_delete]
|
@@ -92,16 +267,16 @@ module Agents
|
|
92
267
|
|
93
268
|
# upsert wrapper
|
94
269
|
bc_wrapper_product = bc_products[wrapper_sku]
|
95
|
-
result =
|
270
|
+
result = upsert_product(wrapper_sku, product, bc_wrapper_product)
|
96
271
|
custom_fields_delete += result[:custom_fields_delete]
|
97
272
|
meta_fields_upsert += result[:meta_fields_upsert]
|
98
273
|
meta_fields_delete += result[:meta_fields_delete]
|
99
274
|
|
100
|
-
is_default_map =
|
275
|
+
is_default_map = get_mapper(:ProductMapper).get_is_default(product)
|
101
276
|
|
102
277
|
# update modifier
|
103
|
-
sku_option_map =
|
104
|
-
modifier_updates =
|
278
|
+
sku_option_map = get_mapper(:ProductMapper).get_sku_option_label_map(product)
|
279
|
+
modifier_updates = get_mapper(:ModifierMapper).map(
|
105
280
|
bc_wrapper_product,
|
106
281
|
bc_children,
|
107
282
|
sku_option_map,
|
@@ -118,46 +293,39 @@ module Agents
|
|
118
293
|
create_event payload: {
|
119
294
|
product: product,
|
120
295
|
parent: result[:product],
|
121
|
-
children: bc_children
|
296
|
+
children: bc_children
|
122
297
|
}
|
123
298
|
end
|
124
299
|
|
125
300
|
private
|
126
301
|
|
127
302
|
def initialize_clients
|
128
|
-
@
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
)
|
133
|
-
|
134
|
-
@
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
)
|
139
|
-
|
140
|
-
@meta_field = ::BigcommerceProductAgent::Client::MetaField.new(
|
141
|
-
interpolated['store_hash'],
|
142
|
-
interpolated['client_id'],
|
143
|
-
interpolated['access_token']
|
144
|
-
)
|
303
|
+
@variant = initialize_client(:Variant)
|
304
|
+
@product_variant = initialize_client(:ProductVariant)
|
305
|
+
@product_option = initialize_client(:ProductOption)
|
306
|
+
@product_option_value = initialize_client(:ProductOptionValue)
|
307
|
+
@product = initialize_client(:Product)
|
308
|
+
@custom_field = initialize_client(:CustomField)
|
309
|
+
@meta_field = initialize_client(:MetaField)
|
310
|
+
@modifier = initialize_client(:Modifier)
|
311
|
+
@modifier_value = initialize_client(:ModifierValue)
|
312
|
+
end
|
145
313
|
|
146
|
-
|
314
|
+
def initialize_client(class_name)
|
315
|
+
klass = ::BigcommerceProductAgent::Client.const_get(class_name.to_sym)
|
316
|
+
return klass.new(
|
147
317
|
interpolated['store_hash'],
|
148
318
|
interpolated['client_id'],
|
149
319
|
interpolated['access_token']
|
150
320
|
)
|
321
|
+
end
|
151
322
|
|
152
|
-
|
153
|
-
|
154
|
-
interpolated['client_id'],
|
155
|
-
interpolated['access_token']
|
156
|
-
)
|
323
|
+
def get_mapper(class_name)
|
324
|
+
return ::BigcommerceProductAgent::Mapper.const_get(class_name.to_sym)
|
157
325
|
end
|
158
326
|
|
159
|
-
def
|
160
|
-
custom_fields_updates =
|
327
|
+
def upsert_product(sku, product, bc_product = nil, is_digital=false)
|
328
|
+
custom_fields_updates = get_mapper(:CustomFieldMapper).map(
|
161
329
|
interpolated['custom_fields_map'],
|
162
330
|
product,
|
163
331
|
bc_product
|
@@ -165,18 +333,21 @@ module Agents
|
|
165
333
|
|
166
334
|
product_id = bc_product['id'] unless bc_product.nil?
|
167
335
|
|
168
|
-
payload =
|
336
|
+
payload = get_mapper(:ProductMapper).payload(
|
169
337
|
sku,
|
170
338
|
product,
|
171
339
|
product_id,
|
172
|
-
{ custom_fields: custom_fields_updates[:upsert] }
|
340
|
+
{ custom_fields: custom_fields_updates[:upsert] },
|
341
|
+
is_digital,
|
173
342
|
)
|
174
343
|
|
175
|
-
bc_product = @product.upsert(payload
|
344
|
+
bc_product = @product.upsert(payload, {
|
345
|
+
include: %w[custom_fields variants options].join(',')
|
346
|
+
})
|
176
347
|
|
177
348
|
# Metafields need to be managed separately. Intentionally get them _AFTER_
|
178
349
|
# the upsert so that we have the necessary resource_id (bc_product.id)
|
179
|
-
meta_fields_updates =
|
350
|
+
meta_fields_updates = get_mapper(:MetaFieldMapper).map(
|
180
351
|
interpolated['meta_fields_map'],
|
181
352
|
product,
|
182
353
|
bc_product,
|
@@ -184,11 +355,11 @@ module Agents
|
|
184
355
|
interpolated['meta_fields_namespace']
|
185
356
|
)
|
186
357
|
|
187
|
-
|
358
|
+
{
|
188
359
|
product: bc_product,
|
189
360
|
custom_fields_delete: custom_fields_updates[:delete],
|
190
361
|
meta_fields_upsert: meta_fields_updates[:upsert],
|
191
|
-
meta_fields_delete: meta_fields_updates[:delete]
|
362
|
+
meta_fields_delete: meta_fields_updates[:delete]
|
192
363
|
}
|
193
364
|
end
|
194
365
|
|
@@ -215,7 +386,7 @@ module Agents
|
|
215
386
|
@meta_field.delete(field[:resource_id], field[:id])
|
216
387
|
end
|
217
388
|
|
218
|
-
|
389
|
+
meta_fields
|
219
390
|
end
|
220
391
|
end
|
221
392
|
end
|
@@ -40,13 +40,28 @@ module BigcommerceProductAgent
|
|
40
40
|
return fields
|
41
41
|
end
|
42
42
|
|
43
|
+
def self.map_one(bc_product, key, value)
|
44
|
+
field = bc_product['custom_fields'].select {|field| key == 'related_product_id'}.first
|
45
|
+
|
46
|
+
mapped = {
|
47
|
+
name: key,
|
48
|
+
value: value.to_s,
|
49
|
+
}
|
50
|
+
|
51
|
+
if field
|
52
|
+
mapped[:id] = field['id']
|
53
|
+
end
|
54
|
+
|
55
|
+
return mapped
|
56
|
+
end
|
57
|
+
|
43
58
|
private
|
44
59
|
|
45
60
|
def self.from_property(product, existing_fields, from_key, to_key)
|
46
61
|
if !product[from_key].nil?
|
47
62
|
field = {
|
48
63
|
name: to_key,
|
49
|
-
value: product[from_key]
|
64
|
+
value: product[from_key].to_s
|
50
65
|
}
|
51
66
|
|
52
67
|
if existing_fields[to_key]
|
@@ -64,7 +79,7 @@ module BigcommerceProductAgent
|
|
64
79
|
if !item.nil?
|
65
80
|
field = {
|
66
81
|
name: to_key,
|
67
|
-
value: item['value']
|
82
|
+
value: item['value'].to_s
|
68
83
|
}
|
69
84
|
|
70
85
|
if existing_fields[to_key]
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module BigcommerceProductAgent
|
2
|
+
module Mapper
|
3
|
+
class OptionMapper
|
4
|
+
|
5
|
+
def self.variant_option_name
|
6
|
+
'Options'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.map(product_id, option, option_values)
|
10
|
+
mapped = {
|
11
|
+
product_id: product_id,
|
12
|
+
display_name: self.variant_option_name,
|
13
|
+
type: 'radio_buttons',
|
14
|
+
sort_order: 0,
|
15
|
+
option_values: option_values,
|
16
|
+
}
|
17
|
+
|
18
|
+
if option && option['id']
|
19
|
+
mapped[:id] = option['id']
|
20
|
+
end
|
21
|
+
|
22
|
+
return mapped
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.option_value_operations(bc_option, option_values)
|
26
|
+
option_value_operations = {
|
27
|
+
create: [],
|
28
|
+
update: [],
|
29
|
+
delete: [],
|
30
|
+
}
|
31
|
+
|
32
|
+
if bc_option && bc_option['option_values']
|
33
|
+
bc_option['option_values'].each do |option_value|
|
34
|
+
if option_values.include?(option_value['label'])
|
35
|
+
option_value_operations[:update].push(option_value)
|
36
|
+
else
|
37
|
+
option_value_operations[:delete].push(option_value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
option_values.each do |option_value_label|
|
42
|
+
options_exists = bc_option['option_values'].any? {|option_value| option_value['label'] == option_value_label}
|
43
|
+
if !options_exists
|
44
|
+
option_value_operations[:create].push(
|
45
|
+
OptionValueMapper.map(option_value_label)
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
else
|
50
|
+
option_values.each do |option_value_label|
|
51
|
+
option_value_operations[:create].push(
|
52
|
+
OptionValueMapper.map(option_value_label)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
return option_value_operations
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -2,12 +2,12 @@ module BigcommerceProductAgent
|
|
2
2
|
module Mapper
|
3
3
|
class ProductMapper
|
4
4
|
|
5
|
-
def self.map(product, variant, additional_data = {})
|
5
|
+
def self.map(product, variant, additional_data = {}, is_digital = false, default_sku='')
|
6
6
|
product = {
|
7
7
|
name: variant.nil? ? product['name'] : "#{product['name']} (#{self.get_option(variant)})",
|
8
|
-
sku: variant ? variant['sku'] :
|
8
|
+
sku: variant ? variant['sku'] : default_sku,
|
9
9
|
is_default: variant && variant['isDefault'],
|
10
|
-
type: variant && variant['isDigital'] == true ? 'digital' : 'physical',
|
10
|
+
type: (variant && variant['isDigital'] == true) || is_digital ? 'digital' : 'physical',
|
11
11
|
description: product['description'],
|
12
12
|
price: variant && variant['offers'] && variant['offers'][0] ? variant['offers'][0]['price'] : '0',
|
13
13
|
categories: self.get_categories(product),
|
@@ -36,9 +36,17 @@ module BigcommerceProductAgent
|
|
36
36
|
"#{product['sku']}-W"
|
37
37
|
end
|
38
38
|
|
39
|
-
def self.
|
39
|
+
def self.get_wrapper_sku_physical(product)
|
40
|
+
self.get_wrapper_sku(product)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.get_wrapper_sku_digital(product)
|
44
|
+
"#{self.get_wrapper_sku_physical(product)}-DIGITAL"
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.payload(sku, product, product_id = nil, additional_data = {}, is_digital = false)
|
40
48
|
variant = self.get_variant_by_sku(sku, product)
|
41
|
-
payload = self.map(product, variant, additional_data)
|
49
|
+
payload = self.map(product, variant, additional_data, is_digital, sku)
|
42
50
|
payload['id'] = product_id unless product_id.nil?
|
43
51
|
|
44
52
|
return payload
|
@@ -68,6 +76,36 @@ module BigcommerceProductAgent
|
|
68
76
|
return map
|
69
77
|
end
|
70
78
|
|
79
|
+
def self.has_digital_variants?(product)
|
80
|
+
product['model'].any? {|m| m['isDigital'] == true}
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.has_physical_variants?(product)
|
84
|
+
product['model'].any? {|m| m['isDigital'] != true}
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.split_digital_and_physical(product)
|
88
|
+
result = {}
|
89
|
+
|
90
|
+
digitals = product['model'].select {|m| m['isDigital'] == true}
|
91
|
+
|
92
|
+
if digitals.length > 0
|
93
|
+
clone = Marshal.load(Marshal.dump(product))
|
94
|
+
clone['model'] = digitals
|
95
|
+
result[:digital] = clone
|
96
|
+
end
|
97
|
+
|
98
|
+
physicals = product['model'].select {|m| m['isDigital'] != true}
|
99
|
+
|
100
|
+
if physicals.length > 0
|
101
|
+
clone = Marshal.load(Marshal.dump(product))
|
102
|
+
clone['model'] = physicals
|
103
|
+
result[:physical] = clone
|
104
|
+
end
|
105
|
+
|
106
|
+
return result
|
107
|
+
end
|
108
|
+
|
71
109
|
private
|
72
110
|
|
73
111
|
def self.get_categories(product)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module BigcommerceProductAgent
|
2
|
+
module Mapper
|
3
|
+
class VariantMapper
|
4
|
+
|
5
|
+
def self.map(variant, option_values, product_id, variant_id=nil)
|
6
|
+
mapped = {
|
7
|
+
product_id: product_id,
|
8
|
+
sku: variant['sku'],
|
9
|
+
price: variant['offers'] && variant['offers'][0] ? variant['offers'][0]['price'] : '0',
|
10
|
+
cost_price: nil,
|
11
|
+
sale_price: nil,
|
12
|
+
retail_price: nil,
|
13
|
+
weight: variant['weight'] ? variant['weight']['value'] : '0',
|
14
|
+
width: variant['width'] ? variant['width']['value'] : '0',
|
15
|
+
depth: variant['depth'] ? variant['depth']['value'] : '0',
|
16
|
+
height: variant['height'] ? variant['height']['value'] : '0',
|
17
|
+
is_free_shipping: false,
|
18
|
+
fixed_cost_shipping_price: nil,
|
19
|
+
purchasing_disabled: false,
|
20
|
+
purchasing_disabled_message: '',
|
21
|
+
upc: variant['gtin12'],
|
22
|
+
inventory_level: nil,
|
23
|
+
inventory_warning_level: nil,
|
24
|
+
bin_picking_number: nil,
|
25
|
+
option_values: option_values,
|
26
|
+
}
|
27
|
+
|
28
|
+
if variant_id
|
29
|
+
mapped[:id] = variant_id
|
30
|
+
end
|
31
|
+
|
32
|
+
return mapped
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.map_option_value(option_value_id, option_id)
|
36
|
+
return {
|
37
|
+
id: option_value_id,
|
38
|
+
option_id: option_id,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: huginn_bigcommerce_product_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jacob Spizziri
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,12 +66,19 @@ files:
|
|
66
66
|
- lib/client/modifier.rb
|
67
67
|
- lib/client/modifier_value.rb
|
68
68
|
- lib/client/product.rb
|
69
|
+
- lib/client/product_option.rb
|
70
|
+
- lib/client/product_option_value.rb
|
71
|
+
- lib/client/product_variant.rb
|
72
|
+
- lib/client/variant.rb
|
69
73
|
- lib/huginn_bigcommerce_product_agent.rb
|
70
74
|
- lib/huginn_bigcommerce_product_agent/bigcommerce_product_agent.rb
|
71
75
|
- lib/mapper/custom_field_mapper.rb
|
72
76
|
- lib/mapper/meta_field_mapper.rb
|
73
77
|
- lib/mapper/modifier_mapper.rb
|
78
|
+
- lib/mapper/option_mapper.rb
|
79
|
+
- lib/mapper/option_value_mapper.rb
|
74
80
|
- lib/mapper/product_mapper.rb
|
81
|
+
- lib/mapper/variant_mapper.rb
|
75
82
|
- spec/bigcommerce_product_agent_spec.rb
|
76
83
|
homepage: https://github.com/5-stones/huginn_bigcommerce_product_agent
|
77
84
|
licenses: []
|