huginn_bigcommerce_product_agent 1.11.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/client/custom_field.rb +44 -4
- data/lib/client/meta_field.rb +35 -11
- data/lib/client/product.rb +94 -18
- data/lib/huginn_bigcommerce_product_agent.rb +1 -0
- data/lib/huginn_bigcommerce_product_agent/big_commerce_product_error.rb +13 -0
- data/lib/huginn_bigcommerce_product_agent/bigcommerce_product_agent.rb +478 -380
- data/lib/mapper/custom_field_mapper.rb +32 -24
- data/lib/mapper/meta_field_mapper.rb +10 -10
- data/lib/mapper/product_mapper.rb +38 -182
- metadata +3 -9
- data/lib/client/modifier.rb +0 -35
- data/lib/client/modifier_value.rb +0 -11
- data/lib/client/product_option.rb +0 -45
- data/lib/client/product_option_value.rb +0 -72
- data/lib/client/product_variant.rb +0 -40
- data/lib/client/variant.rb +0 -40
- data/lib/mapper/modifier_mapper.rb +0 -83
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89cfc257bee7acf8c2a48655ce32e2e4bd9aa91f09a0e17fb19a54224fc764f4
|
4
|
+
data.tar.gz: ea56efe75b854b60a42bcb6f26b7a105f0910ec7e629dc08e7d2c927d7a91900
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 266955d42682aaf2e8f92f5685ac915767a75070b7cdb6957d7e6b0a23e11ed7854070b04a7218bea809b7269702a2f51fb33d4813e355debcc435684adcb5b6
|
7
|
+
data.tar.gz: 19fef7e12d26c6ac94945204e375d3c86bc016eae304f435851e222ef026e4eb1066159b4601b9d92ee546cb9f21803ee41cb809c0d958207408e5fbe5af7e8e
|
data/lib/client/custom_field.rb
CHANGED
@@ -3,19 +3,48 @@ module BigcommerceProductAgent
|
|
3
3
|
class CustomField < AbstractClient
|
4
4
|
@uri_base = 'catalog/products/:product_id/custom-fields/:custom_field_id'
|
5
5
|
|
6
|
+
def get_for_product(product_id)
|
7
|
+
return [] if product_id.blank?
|
8
|
+
|
9
|
+
response = client.get(uri(product_id: product_id))
|
10
|
+
return response.body['data']
|
11
|
+
end
|
12
|
+
|
6
13
|
def create(product_id, payload)
|
7
|
-
response = client.post(
|
14
|
+
response = client.post(
|
15
|
+
uri(product_id: product_id),
|
16
|
+
payload.to_json
|
17
|
+
)
|
18
|
+
|
8
19
|
return response.body['data']
|
9
20
|
end
|
10
21
|
|
11
22
|
def update(product_id, payload)
|
12
23
|
id = payload.delete('id')
|
13
|
-
response = client.put(
|
24
|
+
response = client.put(
|
25
|
+
uri(product_id: product_id, custom_field_id: id),
|
26
|
+
payload.to_json
|
27
|
+
)
|
28
|
+
|
14
29
|
return response.body['data']
|
15
30
|
end
|
16
31
|
|
17
32
|
def delete(product_id, custom_field_id)
|
33
|
+
begin
|
18
34
|
client.delete(uri(product_id: product_id, custom_field_id: custom_field_id))
|
35
|
+
rescue Faraday::Error::ClientError => e
|
36
|
+
raise BigCommerceProductError.new(
|
37
|
+
e.response[:status],
|
38
|
+
'delete custom field',
|
39
|
+
'Failed to delete custom field',
|
40
|
+
product_id,
|
41
|
+
{
|
42
|
+
custom_field_id: custom_field_id,
|
43
|
+
errors: JSON.parse(e.response[:body])['errors']
|
44
|
+
},
|
45
|
+
e
|
46
|
+
)
|
47
|
+
end
|
19
48
|
end
|
20
49
|
|
21
50
|
def upsert(product_id, payload)
|
@@ -27,8 +56,19 @@ module BigcommerceProductAgent
|
|
27
56
|
return create(product_id, payload)
|
28
57
|
end
|
29
58
|
rescue Faraday::Error::ClientError => e
|
30
|
-
|
31
|
-
|
59
|
+
# include the field ID and name in the error here as _create_ requests have no ID
|
60
|
+
raise BigCommerceProductError.new(
|
61
|
+
e.response[:status],
|
62
|
+
'upsert custom field',
|
63
|
+
'Failed to delete custom field',
|
64
|
+
product_id,
|
65
|
+
{
|
66
|
+
custom_field_id: payload['id'],
|
67
|
+
field_name: payload[:name],
|
68
|
+
errors: JSON.parse(e.response[:body])['errors']
|
69
|
+
},
|
70
|
+
e
|
71
|
+
)
|
32
72
|
end
|
33
73
|
end
|
34
74
|
end
|
data/lib/client/meta_field.rb
CHANGED
@@ -10,34 +10,48 @@ module BigcommerceProductAgent
|
|
10
10
|
return response.body['data']
|
11
11
|
end
|
12
12
|
|
13
|
-
def create(meta_field)
|
13
|
+
def create(product_id, meta_field)
|
14
14
|
response = client.post(
|
15
|
-
uri(product_id:
|
15
|
+
uri(product_id: product_id),
|
16
16
|
meta_field.to_json
|
17
17
|
)
|
18
18
|
|
19
19
|
return response.body['data']
|
20
20
|
end
|
21
21
|
|
22
|
-
def update(meta_field)
|
22
|
+
def update(product_id, meta_field)
|
23
|
+
id = meta_field.delete('id')
|
23
24
|
response = client.put(
|
24
|
-
uri(product_id:
|
25
|
+
uri(product_id: product_id, meta_field_id: id),
|
25
26
|
meta_field.to_json
|
26
27
|
)
|
27
28
|
|
28
29
|
return response.body['data']
|
29
30
|
end
|
30
31
|
|
31
|
-
def upsert(meta_field)
|
32
|
+
def upsert(product_id, meta_field)
|
33
|
+
meta_field['id'] = meta_field.delete(:id) unless meta_field[:id].nil?
|
34
|
+
|
32
35
|
begin
|
33
|
-
if meta_field[
|
34
|
-
return update(meta_field)
|
36
|
+
if meta_field['id']
|
37
|
+
return update(product_id, meta_field)
|
35
38
|
else
|
36
|
-
return create(meta_field)
|
39
|
+
return create(product_id, meta_field)
|
37
40
|
end
|
38
41
|
rescue Faraday::Error::ClientError => e
|
39
|
-
|
40
|
-
raise
|
42
|
+
# include the field ID and name in the error here as _create_ requests have no ID
|
43
|
+
raise BigCommerceProductError.new(
|
44
|
+
e.response[:status],
|
45
|
+
'upsert meta field',
|
46
|
+
'Failed to upsert meta field field',
|
47
|
+
product_id,
|
48
|
+
{
|
49
|
+
meta_field_id: meta_field['id'],
|
50
|
+
field_name: meta_field['key'],
|
51
|
+
errors: JSON.parse(e.response[:body])['errors'],
|
52
|
+
},
|
53
|
+
e
|
54
|
+
)
|
41
55
|
end
|
42
56
|
end
|
43
57
|
|
@@ -45,7 +59,17 @@ module BigcommerceProductAgent
|
|
45
59
|
begin
|
46
60
|
client.delete(uri(product_id: product_id, meta_field_id: meta_field_id))
|
47
61
|
rescue Faraday::Error::ClientError => e
|
48
|
-
|
62
|
+
raise BigCommerceProductError.new(
|
63
|
+
e.response[:status],
|
64
|
+
'delete meta field',
|
65
|
+
'Failed to delete meta field',
|
66
|
+
product_id,
|
67
|
+
{
|
68
|
+
meta_field_id: meta_field_id,
|
69
|
+
errors: JSON.parse(e.response[:body])['errors'],
|
70
|
+
},
|
71
|
+
e
|
72
|
+
)
|
49
73
|
end
|
50
74
|
end
|
51
75
|
end
|
data/lib/client/product.rb
CHANGED
@@ -8,11 +8,44 @@ module BigcommerceProductAgent
|
|
8
8
|
response = client.put(uri(product_id: id), payload.to_json) do |request|
|
9
9
|
request.params.update(params) if params
|
10
10
|
end
|
11
|
+
|
12
|
+
return response.body['data']
|
11
13
|
rescue Faraday::Error::ClientError => e
|
12
|
-
|
14
|
+
raise BigCommerceProductError.new(
|
15
|
+
e.response[:status],
|
16
|
+
'update product',
|
17
|
+
'Failed to update product',
|
18
|
+
id,
|
19
|
+
{
|
20
|
+
sku: payload[:sku],
|
21
|
+
errors: JSON.parse(e.response[:body])['errors'],
|
22
|
+
},
|
23
|
+
e
|
24
|
+
)
|
13
25
|
end
|
26
|
+
end
|
14
27
|
|
15
|
-
|
28
|
+
def update_batch(payload, params={})
|
29
|
+
begin
|
30
|
+
response = client.put(uri(), payload.to_json) do |request|
|
31
|
+
request.params.update(params) if params
|
32
|
+
end
|
33
|
+
|
34
|
+
return response.body['data']
|
35
|
+
rescue Faraday::Error::ClientError => e
|
36
|
+
raise BigCommerceProductError.new(
|
37
|
+
e.response[:status],
|
38
|
+
'update product',
|
39
|
+
'Failed to update product batch',
|
40
|
+
payload.map { |p|
|
41
|
+
{ id: p["id"], sku: p[:sku] }
|
42
|
+
},
|
43
|
+
{
|
44
|
+
errors: JSON.parse(e.response[:body])['errors'],
|
45
|
+
},
|
46
|
+
e
|
47
|
+
)
|
48
|
+
end
|
16
49
|
end
|
17
50
|
|
18
51
|
def delete(id)
|
@@ -25,36 +58,79 @@ module BigcommerceProductAgent
|
|
25
58
|
response = client.post(uri, payload.to_json) do |request|
|
26
59
|
request.params.update(params) if params
|
27
60
|
end
|
61
|
+
|
62
|
+
return response.body['data']
|
28
63
|
rescue Faraday::Error::ClientError => e
|
29
|
-
raise e, "\n#{e.message}\nFailed to create product with payload = #{payload.to_json}\n", e.backtrace
|
30
|
-
end
|
31
64
|
|
32
|
-
|
65
|
+
Rails.logger.info('RESPONSE OBJECT: ------------------------------------------')
|
66
|
+
Rails.logger.info(e.response.inspect)
|
67
|
+
Rails.logger.info('------------------------------------------------------------')
|
68
|
+
|
69
|
+
Rails.logger.info('RESPONSE METHODS: -----------------------------------------')
|
70
|
+
Rails.logger.info(e.response.methods)
|
71
|
+
Rails.logger.info('------------------------------------------------------------')
|
72
|
+
|
73
|
+
raise BigCommerceProductError.new(
|
74
|
+
e.response[:status],
|
75
|
+
'create product',
|
76
|
+
'Failed to create product',
|
77
|
+
nil,
|
78
|
+
{
|
79
|
+
sku: payload[:sku],
|
80
|
+
errors: JSON.parse(e.response[:body])['errors'],
|
81
|
+
},
|
82
|
+
e
|
83
|
+
)
|
84
|
+
end
|
33
85
|
end
|
34
86
|
|
35
87
|
def upsert(payload, params={})
|
36
|
-
|
37
|
-
|
88
|
+
payload['id'] = payload.delete(:id) unless payload[:id].nil?
|
89
|
+
if payload['id']
|
90
|
+
return update(payload['id'], payload, params)
|
38
91
|
else
|
39
92
|
return create(payload, params)
|
40
93
|
end
|
41
94
|
end
|
42
95
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
96
|
+
# When using sku:in you must specify the fields you want returned.
|
97
|
+
def get_by_skus(skus, include = %w[custom_fields modifiers], include_fields = %w[sku categories])
|
98
|
+
products = []
|
99
|
+
skus.each do |sku|
|
100
|
+
begin
|
101
|
+
data = index({
|
102
|
+
'sku': sku,
|
103
|
+
include: include.join(','),
|
104
|
+
include_fields: include_fields.join(','),
|
105
|
+
})
|
106
|
+
if not data.empty?
|
107
|
+
products.push(data[0])
|
108
|
+
end
|
109
|
+
rescue Faraday::Error::ClientError => e
|
110
|
+
raise BigCommerceProductError.new(
|
111
|
+
e.response[:status],
|
112
|
+
'get by sku',
|
113
|
+
'Failed to get existing product data',
|
114
|
+
sku,
|
115
|
+
{
|
116
|
+
related_skus: skus,
|
117
|
+
errors: JSON.parse(e.response[:body])['errors'],
|
118
|
+
},
|
119
|
+
e
|
120
|
+
)
|
121
|
+
end
|
53
122
|
end
|
54
123
|
|
55
|
-
|
124
|
+
return products
|
56
125
|
end
|
57
126
|
|
127
|
+
def disable(productId)
|
128
|
+
upsert({ id: productId, is_visible: false })
|
129
|
+
end
|
130
|
+
|
131
|
+
def enable(productId)
|
132
|
+
upsert({ id: productId, is_visible: true })
|
133
|
+
end
|
58
134
|
end
|
59
135
|
end
|
60
136
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class BigCommerceProductError < StandardError
|
2
|
+
attr_reader :status, :scope, :product_identifier, :data, :original_error
|
3
|
+
|
4
|
+
def initialize(status, scope, message, product_identifier, data, original_error)
|
5
|
+
@status = status
|
6
|
+
@scope = scope
|
7
|
+
@product_identifier = product_identifier
|
8
|
+
@data = data
|
9
|
+
@original_error = original_error
|
10
|
+
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
@@ -3,436 +3,534 @@
|
|
3
3
|
require 'json'
|
4
4
|
|
5
5
|
module Agents
|
6
|
-
|
7
|
-
|
6
|
+
class BigcommerceProductAgent < Agent
|
7
|
+
include WebRequestConcern
|
8
|
+
|
9
|
+
can_dry_run!
|
10
|
+
default_schedule 'never'
|
11
|
+
|
12
|
+
# TODO: Provide a more detailed agent description. Including details of
|
13
|
+
# each option and how that option is used
|
14
|
+
description <<-MD
|
15
|
+
Takes an array of related products and upserts them into BigCommerce.
|
16
|
+
MD
|
17
|
+
|
18
|
+
def default_options
|
19
|
+
{
|
20
|
+
'store_hash' => '',
|
21
|
+
'client_id' => '',
|
22
|
+
'access_token' => '',
|
23
|
+
'custom_fields_map' => {},
|
24
|
+
'meta_fields_map' => {},
|
25
|
+
'meta_fields_namespace' => '',
|
26
|
+
'not_purchasable_format_list' => [],
|
27
|
+
'should_disambiguate' => false,
|
28
|
+
|
29
|
+
}
|
30
|
+
end
|
8
31
|
|
9
|
-
|
10
|
-
|
32
|
+
def validate_options
|
33
|
+
unless options['store_hash'].present?
|
34
|
+
errors.add(:base, 'store_hash is a required field')
|
35
|
+
end
|
11
36
|
|
12
|
-
|
13
|
-
|
14
|
-
|
37
|
+
unless options['client_id'].present?
|
38
|
+
errors.add(:base, 'client_id is a required field')
|
39
|
+
end
|
15
40
|
|
16
|
-
|
17
|
-
|
18
|
-
variants
|
19
|
-
option_list
|
20
|
-
]
|
41
|
+
unless options['access_token'].present?
|
42
|
+
errors.add(:base, 'access_token is a required field')
|
21
43
|
end
|
22
44
|
|
23
|
-
|
24
|
-
|
25
|
-
'store_hash' => '',
|
26
|
-
'client_id' => '',
|
27
|
-
'access_token' => '',
|
28
|
-
'custom_fields_map' => {},
|
29
|
-
'meta_fields_map' => {},
|
30
|
-
'meta_fields_namespace' => '',
|
31
|
-
'mode' => modes[0],
|
32
|
-
'not_purchasable_format_list' => [],
|
33
|
-
'should_disambiguate' => false
|
34
|
-
}
|
45
|
+
unless options['custom_fields_map'].is_a?(Hash)
|
46
|
+
errors.add(:base, 'if provided, custom_fields_map must be a hash')
|
35
47
|
end
|
36
48
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
49
|
+
unless options['meta_fields_map'].is_a?(Hash)
|
50
|
+
errors.add(:base, 'if provided, meta_fields_map must be a hash')
|
51
|
+
end
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
|
53
|
+
if options['meta_fields_map']
|
54
|
+
if options['meta_fields_namespace'].blank?
|
55
|
+
errors.add(:base, 'if meta_fields_map is provided, meta_fields_namespace is required')
|
56
|
+
end
|
57
|
+
end
|
45
58
|
|
46
|
-
|
47
|
-
|
48
|
-
|
59
|
+
if options['not_purchasable_format_list'].present? && !options['not_purchasable_format_list'].is_a?(Array)
|
60
|
+
errors.add(:base, 'not_purchasable_format_list must be an Array')
|
61
|
+
end
|
49
62
|
|
50
|
-
|
51
|
-
|
52
|
-
|
63
|
+
if options.has_key?('should_disambiguate') && boolify(options['should_disambiguate']).nil?
|
64
|
+
errors.add(:base, 'when provided, `should_disambiguate` must be either true or false')
|
65
|
+
end
|
53
66
|
|
54
|
-
|
55
|
-
|
56
|
-
|
67
|
+
if options.has_key?('track_inventory') && boolify(options['track_inventory']).nil?
|
68
|
+
errors.add(:base, 'when provided, `track_inventory` must be true or false')
|
69
|
+
end
|
57
70
|
|
58
|
-
|
59
|
-
if options['meta_fields_namespace'].blank?
|
60
|
-
errors.add(:base, 'if meta_fields_map is provided, meta_fields_namespace is required')
|
61
|
-
end
|
62
|
-
end
|
71
|
+
end
|
63
72
|
|
64
|
-
|
65
|
-
|
66
|
-
|
73
|
+
def working?
|
74
|
+
received_event_without_error?
|
75
|
+
end
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
77
|
+
def check
|
78
|
+
initialize_clients
|
79
|
+
handle interpolated['payload'].presence || {}
|
80
|
+
end
|
71
81
|
|
72
|
-
|
73
|
-
|
74
|
-
|
82
|
+
def receive(incoming_events)
|
83
|
+
initialize_clients
|
84
|
+
incoming_events.each do |event|
|
85
|
+
handle(event)
|
86
|
+
end
|
87
|
+
end
|
75
88
|
|
89
|
+
def handle(event)
|
90
|
+
data = event.payload
|
91
|
+
raw_products = data['products']
|
92
|
+
results = []
|
93
|
+
|
94
|
+
# Loop through the provided raw_products and perform the upsert
|
95
|
+
# This process will upsert the core product record and the custom/meta
|
96
|
+
# fields from the Acumen data.
|
97
|
+
additional_data = {
|
98
|
+
additional_search_terms: [],
|
99
|
+
}
|
100
|
+
|
101
|
+
raw_products.each do |raw_product|
|
102
|
+
additional_data[:additional_search_terms].push(raw_product['sku'])
|
103
|
+
end
|
104
|
+
|
105
|
+
bc_products = lookup_existing_products(raw_products)
|
106
|
+
existing_skus = bc_products.map { |p| p['sku'] }
|
107
|
+
|
108
|
+
# This agent requires us to make several requests due to limitations in the BigCommerce API.
|
109
|
+
# Specifically, Product records must be created and deleted individually, and, though meta fields can
|
110
|
+
# be managed in "bulk", the batch is limited to specific product IDs.
|
111
|
+
#
|
112
|
+
# Additionally, this agent sets a `related_product_ids` field as a CSV string of each product ID in
|
113
|
+
# the bundle. Because this field expects the BigCommerce ID, this has to happen _after_ product
|
114
|
+
# creation / deletion.
|
115
|
+
#
|
116
|
+
# For the sake of performance, we group products into three buckets: create, delete, update.
|
117
|
+
# From there, we run through the following process:
|
118
|
+
#
|
119
|
+
# * Create new products
|
120
|
+
# * Delete discontinued products
|
121
|
+
# * Update existing products & custom fields
|
122
|
+
# * Update meta fields
|
123
|
+
#
|
124
|
+
# The existing product update will include data for products created in step one. This is intentional
|
125
|
+
# because the update step allows us to populate custom fields in bulk (including the `related_product_ids`
|
126
|
+
# field), so we still come out ahead in terms of overall performance.
|
127
|
+
|
128
|
+
to_create = raw_products.select { |p| p['productAvailability'] != 'not available' && !existing_skus.include?(p['sku'])}
|
129
|
+
to_delete = raw_products.select { |p| p['productAvailability'] == 'not available' && existing_skus.include?(p['sku'])}
|
130
|
+
to_update = raw_products.select { |p| p['productAvailability'] != 'not available' && existing_skus.include?(p['sku'])}
|
131
|
+
|
132
|
+
mapped_products = [] # Contains an array of { :bc_payload, :raw_product } hashes
|
133
|
+
|
134
|
+
# Delete all inactive products
|
135
|
+
to_delete.each do |p|
|
136
|
+
bc_product = bc_products.find { |bc| bc['sku'] == p['sku'] }
|
137
|
+
delete_inactive_product(bc_product)
|
138
|
+
end
|
139
|
+
|
140
|
+
# A Note regarding the nil checks below. In order to improve the efficiency
|
141
|
+
# of this agent, we process requests in batch wherever possible. However,
|
142
|
+
# we don't want errors with one product to prevent others in the bundle from
|
143
|
+
# processing.
|
144
|
+
#
|
145
|
+
# Methods that process single records intentionally return `nil` in the event
|
146
|
+
# of an error to facilitate this, and `nil` entries are excluded from the
|
147
|
+
# batch processes. Additionally, any such processing errors are emitted as
|
148
|
+
# error events with `status: 500` to facilitate reporting.
|
149
|
+
|
150
|
+
#----- Handle the creation of any new products -----#
|
151
|
+
to_create.each do |raw_product|
|
152
|
+
bc_product = create_new_product(raw_product, additional_data)
|
153
|
+
unless bc_product.nil?
|
154
|
+
mapped_products.push({ bc_payload: bc_product, raw_product: raw_product })
|
76
155
|
end
|
156
|
+
end
|
77
157
|
|
78
|
-
|
79
|
-
|
80
|
-
|
158
|
+
#----- Process updates for existing products -----#
|
159
|
+
to_update.each do |raw_product|
|
160
|
+
bc_product = bc_products.find { |bc| bc['sku'] == raw_product['sku'] }
|
161
|
+
mapped_product = process_updates(raw_product, bc_product, additional_data)
|
81
162
|
|
82
|
-
|
83
|
-
|
84
|
-
handle interpolated['payload'].presence || {}
|
163
|
+
unless mapped_product.nil?
|
164
|
+
mapped_products.push(mapped_product)
|
85
165
|
end
|
166
|
+
end
|
167
|
+
|
168
|
+
#----- Handle final upserts -----#
|
169
|
+
unless mapped_products.blank?
|
170
|
+
# NOTE: An empty array here likely indicates that a title has been removed
|
171
|
+
# from sale completely and is not available in any format. Most of the
|
172
|
+
# time, mapped_products should have at least one item.
|
173
|
+
#
|
174
|
+
# In rare cases, it may mean that all products resulted in a processing
|
175
|
+
# error, but since those are tracked individually, we don't need to issue
|
176
|
+
# any errors here.
|
177
|
+
upsert_products(mapped_products)
|
178
|
+
end
|
179
|
+
end
|
86
180
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
181
|
+
# Attempt to find an existing BigCommerce product by SKU
|
182
|
+
# Returns nil if no matching product is found.
|
183
|
+
def lookup_existing_products(raw_products)
|
184
|
+
begin
|
185
|
+
bc_products = @product_client.get_by_skus(raw_products.map { |r| r['sku'] })
|
186
|
+
|
187
|
+
return bc_products
|
188
|
+
rescue BigCommerceProductError => e
|
189
|
+
create_event payload: {
|
190
|
+
status: e.status,
|
191
|
+
scope: e.scope,
|
192
|
+
message: e.message,
|
193
|
+
data: e.data,
|
194
|
+
}
|
195
|
+
|
196
|
+
raise e
|
197
|
+
# This exception is intentionally rethrown because it means we were unable
|
198
|
+
# lookup existing BigCommerce records. (If there were no matching SKUs, the
|
199
|
+
# response would be an empty array). In this case, we don't have enough
|
200
|
+
# information to accurately process the incoming raw products, so we must
|
201
|
+
# fail.
|
202
|
+
end
|
203
|
+
end
|
93
204
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
205
|
+
def delete_inactive_product(bc_product)
|
206
|
+
begin
|
207
|
+
@product_client.delete(bc_product['id'])
|
208
|
+
rescue BigCommerceProductError => e
|
209
|
+
emit_error(e)
|
210
|
+
rescue => e
|
211
|
+
emit_error(BigCommerceProductError.new(
|
212
|
+
500,
|
213
|
+
'delete inactive_product',
|
214
|
+
e.message,
|
215
|
+
bc_product['id'],
|
216
|
+
{ sku: bc_product['sku'] },
|
217
|
+
e,
|
218
|
+
))
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Handles the creation of new product records
|
223
|
+
def create_new_product(raw_product, additional_data)
|
224
|
+
begin
|
225
|
+
bc_payload = map_product(raw_product, nil, additional_data)
|
226
|
+
custom_fields = map_custom_fields(raw_product, nil)
|
227
|
+
bc_payload['custom_fields'] = custom_fields[:upsert]
|
228
|
+
return @product_client.create(bc_payload, { include: 'custom_fields' })
|
229
|
+
rescue BigCommerceProductError => e
|
230
|
+
emit_error(e)
|
231
|
+
rescue => e
|
232
|
+
emit_error(BigCommerceProductError.new(
|
233
|
+
500,
|
234
|
+
'create_new_product',
|
235
|
+
e.message,
|
236
|
+
nil,
|
237
|
+
{ sku: raw_product['sku'] },
|
238
|
+
e
|
239
|
+
))
|
240
|
+
end
|
241
|
+
|
242
|
+
return nil
|
243
|
+
end
|
244
|
+
|
245
|
+
# Generates an update payload for the provided product records
|
246
|
+
# Returns a hash containing { :bc_payload, :raw_product }
|
247
|
+
def process_updates(raw_product, bc_product, additional_data)
|
248
|
+
custom_fields = map_custom_fields(raw_product, bc_product)
|
249
|
+
|
250
|
+
custom_fields[:delete].each do |field|
|
251
|
+
begin
|
252
|
+
# Delete custom fields that are no longer used
|
253
|
+
@custom_field_client.delete(bc_product['id'], field['id'])
|
254
|
+
rescue BigCommerceProductError => e
|
255
|
+
emit_error(e)
|
256
|
+
rescue => e
|
257
|
+
emit_error(BigCommerceProductError.new(
|
258
|
+
500,
|
259
|
+
'delete_custom_fields',
|
260
|
+
e.message,
|
261
|
+
bc_product['id'],
|
262
|
+
{ sku: bc_product['sku'], field_id: field['id'] },
|
263
|
+
e
|
264
|
+
))
|
101
265
|
end
|
266
|
+
end
|
102
267
|
|
103
|
-
|
104
|
-
# 2. upsert option & option_values
|
105
|
-
# 3. delete old option_values
|
106
|
-
# - NOTE: deleting an option_value also deletes the variant
|
107
|
-
# associated with the option_value
|
108
|
-
# 4. upsert variants
|
109
|
-
# - NOTE: because deleting option values deletes variants
|
110
|
-
# we need to fetch the variants AFTER deletion has occurred.
|
111
|
-
# - NOTE: by deleting variants in #3 if option_values on an
|
112
|
-
# existing variant changes over time, we're effectively deleting
|
113
|
-
# and then re-adding the variant. Could get weird.
|
114
|
-
def handle_variants(event)
|
115
|
-
product = event.payload
|
116
|
-
|
117
|
-
split = get_mapper(:ProductMapper).split_digital_and_physical(
|
118
|
-
product,
|
119
|
-
interpolated['custom_fields_map']
|
120
|
-
)
|
121
|
-
physical = split[:physical]
|
122
|
-
digital = split[:digital]
|
123
|
-
|
124
|
-
wrapper_skus = {
|
125
|
-
physical: get_mapper(:ProductMapper).get_wrapper_sku(physical),
|
126
|
-
digital: get_mapper(:ProductMapper).get_wrapper_sku(digital),
|
127
|
-
}
|
128
|
-
|
129
|
-
bc_products = @product.get_by_skus(
|
130
|
-
wrapper_skus.map {|k,v| v},
|
131
|
-
%w[custom_fields options]
|
132
|
-
)
|
133
|
-
#save skus
|
134
|
-
digital_skus = []
|
135
|
-
physical_skus = []
|
136
|
-
if split[:digital] and split[:physical]
|
137
|
-
digital_skus.concat([wrapper_skus[:digital]])
|
138
|
-
digital_skus.concat(get_mapper(:ProductMapper).get_product_skus(split[:digital])).join(",")
|
139
|
-
physical_skus.concat([wrapper_skus[:physical]])
|
140
|
-
physical_skus.concat(get_mapper(:ProductMapper).get_product_skus(split[:physical])).join(",")
|
141
|
-
end
|
268
|
+
bc_payload = map_product(raw_product, bc_product, additional_data)
|
142
269
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
151
|
-
|
152
|
-
# Ignatius Press -- some products have the same name and must be disambiguated.
|
153
|
-
# ...by adding a list of the product types (hardback, paperback, etc.) to their names
|
154
|
-
if boolify(options['should_disambiguate'])
|
155
|
-
product['page_title'] = product['name']
|
156
|
-
product['name'] += " |~ " + product['model'].map { |m|
|
157
|
-
m['additionalProperty'].find { |p|
|
158
|
-
p['propertyID'] == 'option'
|
159
|
-
}['value']
|
160
|
-
}.join(", ")
|
161
|
-
end
|
162
|
-
|
163
|
-
wrapper_sku = wrapper_skus[type]
|
164
|
-
bc_product = bc_products[wrapper_sku]
|
165
|
-
variant_option_name = get_mapper(:OptionMapper).variant_option_name
|
166
|
-
bc_option = !bc_product.nil? ? bc_product['options'].select {|opt| opt['display_name'] === variant_option_name}.first : nil
|
167
|
-
|
168
|
-
search_skus = is_digital ? physical_skus : digital_skus
|
169
|
-
# ##############################
|
170
|
-
# 1. update wrapper product
|
171
|
-
# ##############################
|
172
|
-
upsert_result = upsert_product(wrapper_sku, product, bc_product, is_digital, search_skus)
|
173
|
-
bc_product = upsert_result[:product]
|
174
|
-
|
175
|
-
# clean up custom/meta fields. there are not batch operations so we might as well do them here.
|
176
|
-
custom_fields_delete = upsert_result[:custom_fields_delete].select {|field| field['name'] != 'related_product_id'}
|
177
|
-
clean_up_custom_fields(custom_fields_delete)
|
178
|
-
meta_fields = update_meta_fields(
|
179
|
-
upsert_result[:meta_fields_upsert],
|
180
|
-
upsert_result[:meta_fields_delete],
|
181
|
-
)
|
182
|
-
|
183
|
-
bc_product['meta_fields'] = meta_fields
|
184
|
-
|
185
|
-
bc_products[wrapper_sku] = bc_product
|
186
|
-
product_id = bc_products[wrapper_sku]['id']
|
187
|
-
|
188
|
-
# ##############################
|
189
|
-
# 2. upsert option & option_values
|
190
|
-
# ##############################
|
191
|
-
option_values_map = get_mapper(:ProductMapper).get_sku_option_label_map(product)
|
192
|
-
option_values = option_values_map.map {|k,v| v}
|
193
|
-
option_value_operations = get_mapper(:OptionMapper).option_value_operations(bc_option, option_values)
|
194
|
-
option = get_mapper(:OptionMapper).map(product_id, bc_option, option_value_operations[:create])
|
195
|
-
bc_option = @product_option.upsert(product_id, option)
|
196
|
-
|
197
|
-
# ##############################
|
198
|
-
# 3. delete old option_values
|
199
|
-
# ##############################
|
200
|
-
@product_option_value.delete_all(bc_option, option_value_operations[:delete])
|
201
|
-
|
202
|
-
# ##############################
|
203
|
-
# 4. upsert variants
|
204
|
-
# ##############################
|
205
|
-
variant_skus = get_mapper(:ProductMapper).get_product_skus(product)
|
206
|
-
bc_variants = @product_variant.index(product_id)
|
207
|
-
mapped_variants = product['model'].map do |variant|
|
208
|
-
bc_variant = bc_variants.select {|v| v['sku'] === variant['sku']}.first
|
209
|
-
opt = get_mapper(:ProductMapper).get_option(variant)
|
210
|
-
bc_option_value = bc_option['option_values'].select {|ov| ov['label'] == opt}.first
|
211
|
-
|
212
|
-
option_value = get_mapper(:VariantMapper).map_option_value(bc_option_value['id'], bc_option['id'])
|
213
|
-
|
214
|
-
get_mapper(:VariantMapper).map(
|
215
|
-
variant,
|
216
|
-
[option_value],
|
217
|
-
product_id,
|
218
|
-
bc_variant.nil? ? nil : bc_variant['id'],
|
219
|
-
interpolated['not_purchasable_format_list'],
|
220
|
-
bc_option_value
|
221
|
-
)
|
222
|
-
end
|
223
|
-
|
224
|
-
bc_product['variants'] = @variant.upsert(mapped_variants)
|
225
|
-
end
|
270
|
+
if bc_payload.present?
|
271
|
+
bc_payload['custom_fields'] = custom_fields[:upsert]
|
272
|
+
return { bc_payload: bc_payload, raw_product: raw_product }
|
273
|
+
else
|
274
|
+
return nil
|
275
|
+
end
|
276
|
+
end
|
226
277
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
# ##############################
|
233
|
-
# clean up products that no longer exist
|
234
|
-
# ##############################
|
235
|
-
if is_delete_physical
|
236
|
-
bc_product = bc_products[wrapper_skus[:physical]]
|
237
|
-
@product.delete(bc_product['id'])
|
238
|
-
bc_physical = false
|
239
|
-
bc_product.delete(wrapper_skus[:physical])
|
240
|
-
end
|
278
|
+
# Sends a batch update request for the provided products
|
279
|
+
# NOTE: This process also sets the `related_product_ids` custom field
|
280
|
+
def upsert_products(mapped_products)
|
281
|
+
product_ids = mapped_products.map { |p| p[:bc_payload]['id'] }
|
282
|
+
results = {}
|
241
283
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
bc_digital = false
|
246
|
-
bc_product.delete(wrapper_skus[:digital])
|
247
|
-
end
|
284
|
+
product_data = mapped_products.map do |p|
|
285
|
+
bc_payload = p[:bc_payload]
|
286
|
+
raw_product = p[:raw_product]
|
248
287
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
# clean up related_product_id on physical product
|
254
|
-
bc_product = bc_physical
|
255
|
-
related_custom_field = bc_product['custom_fields'].select {|field| field['name'] == 'related_product_id'}.first
|
256
|
-
@custom_field.delete(bc_product['id'], related_custom_field['id']) unless related_custom_field.nil?
|
257
|
-
elsif !bc_physical && bc_digital
|
258
|
-
# clean up related_product_id on digital product
|
259
|
-
bc_product = bc_digital
|
260
|
-
related_custom_field = bc_product['custom_fields'].select {|field| field['name'] == 'related_product_id'}.first
|
261
|
-
@custom_field.delete(bc_product['id'], related_custom_field['id']) unless related_custom_field.nil?
|
262
|
-
elsif bc_physical && bc_digital
|
263
|
-
# update/add related_product_id on both products
|
264
|
-
bc_physical_related = get_mapper(:CustomFieldMapper).map_one(bc_physical, 'related_product_id', bc_digital['id'])
|
265
|
-
bc_digital_related = get_mapper(:CustomFieldMapper).map_one(bc_digital, 'related_product_id', bc_physical['id'])
|
266
|
-
@custom_field.upsert(bc_physical['id'], bc_physical_related)
|
267
|
-
@custom_field.upsert(bc_digital['id'], bc_digital_related)
|
268
|
-
end
|
288
|
+
meta_fields = update_meta_fields(raw_product, bc_payload)
|
289
|
+
results[raw_product['sku']] = {
|
290
|
+
raw_product: raw_product,
|
291
|
+
meta_fields: meta_fields[:upsert],
|
269
292
|
|
270
|
-
|
271
|
-
# emit events
|
272
|
-
# ##############################
|
273
|
-
if bc_physical
|
274
|
-
create_event payload: {
|
275
|
-
product: bc_physical
|
276
|
-
}
|
277
|
-
end
|
293
|
+
}
|
278
294
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
295
|
+
#----- Set related_product_ids -----#
|
296
|
+
related_product_ids = product_ids.select { |id| id != bc_payload['id'] }
|
297
|
+
field = {
|
298
|
+
'name': 'related_product_ids',
|
299
|
+
'value': related_product_ids * ',' # concatenate as a CSV
|
300
|
+
}
|
301
|
+
|
302
|
+
unless related_product_ids.empty?
|
303
|
+
if bc_payload['custom_fields'].blank?
|
304
|
+
bc_payload['custom_fields'] = []
|
305
|
+
end
|
306
|
+
|
307
|
+
bc_payload['custom_fields'].push(field)
|
284
308
|
end
|
285
309
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
skus = get_mapper(:ProductMapper).get_product_skus(product)
|
290
|
-
wrapper_sku = get_mapper(:ProductMapper).get_wrapper_sku(product)
|
291
|
-
all_skus = [].push(*skus).push(wrapper_sku)
|
292
|
-
bc_products = @product.get_by_skus(all_skus)
|
293
|
-
|
294
|
-
# upsert child products
|
295
|
-
bc_children = []
|
296
|
-
custom_fields_delete = []
|
297
|
-
meta_fields_upsert = []
|
298
|
-
meta_fields_delete = []
|
299
|
-
|
300
|
-
skus.each do |sku|
|
301
|
-
bc_product = bc_products[sku]
|
302
|
-
result = upsert_product(sku, product, bc_product)
|
303
|
-
custom_fields_delete += result[:custom_fields_delete]
|
304
|
-
meta_fields_upsert += result[:meta_fields_upsert]
|
305
|
-
meta_fields_delete += result[:meta_fields_delete]
|
306
|
-
bc_children.push(result[:product])
|
307
|
-
end
|
310
|
+
# return the finalized payload
|
311
|
+
bc_payload
|
312
|
+
end
|
308
313
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
sku_option_map = get_mapper(:ProductMapper).get_sku_option_label_map(product)
|
320
|
-
modifier_updates = get_mapper(:ModifierMapper).map(
|
321
|
-
bc_wrapper_product,
|
322
|
-
bc_children,
|
323
|
-
sku_option_map,
|
324
|
-
is_default_map
|
325
|
-
)
|
326
|
-
@modifier.upsert(result[:product]['id'], modifier_updates[:upsert])
|
327
|
-
|
328
|
-
clean_up_custom_fields(custom_fields_delete)
|
329
|
-
clean_up_modifier_values(modifier_updates[:delete])
|
330
|
-
meta_fields = update_meta_fields(meta_fields_upsert, meta_fields_delete)
|
331
|
-
|
332
|
-
product['meta_fields'] = meta_fields
|
333
|
-
product['modifiers'] = modifier_updates[:upsert]
|
334
|
-
create_event payload: {
|
335
|
-
product: product,
|
336
|
-
parent: result[:product],
|
337
|
-
children: bc_children
|
338
|
-
}
|
314
|
+
begin
|
315
|
+
@product_client.update_batch(product_data, { include: 'custom_fields' }).each do |p|
|
316
|
+
result = results[p['sku']]
|
317
|
+
result[:custom_fields] = p['custom_fields']
|
318
|
+
result[:bc_product] = p
|
319
|
+
|
320
|
+
create_event payload: {
|
321
|
+
product: result,
|
322
|
+
status: 200,
|
323
|
+
}
|
339
324
|
end
|
325
|
+
rescue BigCommerceProductError => e
|
326
|
+
emit_error(e)
|
327
|
+
rescue => e
|
328
|
+
emit_error(BigCommerceProductError.new(
|
329
|
+
500,
|
330
|
+
'upsert_products',
|
331
|
+
e.message,
|
332
|
+
nil,
|
333
|
+
{ sku: product_data.map { |p| p[:sku] } },
|
334
|
+
e
|
335
|
+
))
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Map the raw_product record to bc_product fields. The bc_product passed in may be null
|
340
|
+
# if the product does not exist yet in BigCommerce.
|
341
|
+
def map_product(raw_product, bc_product, additional_data)
|
342
|
+
bc_payload = nil
|
340
343
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
@product_option = initialize_client(:ProductOption)
|
347
|
-
@product_option_value = initialize_client(:ProductOptionValue)
|
348
|
-
@product = initialize_client(:Product)
|
349
|
-
@custom_field = initialize_client(:CustomField)
|
350
|
-
@meta_field = initialize_client(:MetaField)
|
351
|
-
@modifier = initialize_client(:Modifier)
|
352
|
-
@modifier_value = initialize_client(:ModifierValue)
|
344
|
+
begin
|
345
|
+
# if track inventory on the agent is true set track inventory to product level track inventory
|
346
|
+
track_inventory = boolify(options['track_inventory']).nil? ? true : boolify(options['track_inventory'])
|
347
|
+
if (track_inventory)
|
348
|
+
track_inventory = boolify(raw_product['trackInventory'])
|
353
349
|
end
|
350
|
+
bc_payload = get_mapper(:ProductMapper).map_payload(raw_product, additional_data, track_inventory)
|
351
|
+
bc_payload['id'] = bc_product['id'] unless bc_product.nil? || bc_product['id'].nil?
|
352
|
+
# NOTE: bc_product will be nil when this is called with `to_create` products
|
354
353
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
interpolated['store_hash'],
|
359
|
-
interpolated['client_id'],
|
360
|
-
interpolated['access_token']
|
361
|
-
)
|
354
|
+
if bc_payload[:categories].empty?
|
355
|
+
# If categories is empty keep existing categories because categories should never be empty
|
356
|
+
bc_payload[:categories] = bc_product['categories'] unless bc_product.nil? || bc_product['categories'].nil?
|
362
357
|
end
|
363
358
|
|
364
|
-
|
365
|
-
|
359
|
+
if bc_payload[:type] == 'digital'
|
360
|
+
bc_payload[:name].concat(' (Digital)')
|
366
361
|
end
|
367
362
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
payload = get_mapper(:ProductMapper).payload(
|
378
|
-
sku,
|
379
|
-
product,
|
380
|
-
product_id,
|
381
|
-
{
|
382
|
-
additional_search_terms: search_skus,
|
383
|
-
custom_fields: custom_fields_updates[:upsert]
|
384
|
-
},
|
385
|
-
is_digital,
|
386
|
-
)
|
387
|
-
|
388
|
-
bc_product = @product.upsert(payload, {
|
389
|
-
include: %w[custom_fields variants options].join(',')
|
390
|
-
})
|
391
|
-
|
392
|
-
# Metafields need to be managed separately. Intentionally get them _AFTER_
|
393
|
-
# the upsert so that we have the necessary resource_id (bc_product.id)
|
394
|
-
meta_fields_updates = get_mapper(:MetaFieldMapper).map(
|
395
|
-
interpolated['meta_fields_map'],
|
396
|
-
product,
|
397
|
-
bc_product,
|
398
|
-
@meta_field.get_for_product(bc_product['id']),
|
399
|
-
interpolated['meta_fields_namespace']
|
400
|
-
)
|
401
|
-
|
402
|
-
{
|
403
|
-
product: bc_product,
|
404
|
-
custom_fields_delete: custom_fields_updates[:delete],
|
405
|
-
meta_fields_upsert: meta_fields_updates[:upsert],
|
406
|
-
meta_fields_delete: meta_fields_updates[:delete]
|
407
|
-
}
|
363
|
+
# BigCommerce requires that product names be unique. In some cases, (like book titles from multiple sources),
|
364
|
+
# this may be hard to enforce. In those cases, the product SKUs should still be unique, so we append the SKU
|
365
|
+
# to the product title with a `|~` separator. We then set the `page_title` to the original product name so
|
366
|
+
# users don't see system values.
|
367
|
+
#
|
368
|
+
# page_title is the user-facing display value for product pages.
|
369
|
+
if boolify(options['should_disambiguate'])
|
370
|
+
bc_payload[:page_title] = bc_payload[:name]
|
371
|
+
bc_payload[:name] = bc_payload[:name] + " |~ " + raw_product['sku']
|
408
372
|
end
|
409
373
|
|
410
|
-
|
411
|
-
|
412
|
-
|
374
|
+
return bc_payload
|
375
|
+
rescue BigCommerceProductError => e
|
376
|
+
emit_error(e)
|
377
|
+
rescue => e
|
378
|
+
emit_error(BigCommerceProductError.new(
|
379
|
+
500,
|
380
|
+
'map_product',
|
381
|
+
e.message,
|
382
|
+
bc_product['id'],
|
383
|
+
{ sku: bc_product['sku'] },
|
384
|
+
e
|
385
|
+
))
|
386
|
+
end
|
387
|
+
|
388
|
+
return nil
|
389
|
+
end
|
390
|
+
|
391
|
+
# Maps custom field values from the raw_product to the bc_payload
|
392
|
+
# NOTE: Because custom fields can be included in product upsert requests,
|
393
|
+
# this function is only _mapping_ the data.
|
394
|
+
def map_custom_fields(raw_product, bc_payload)
|
395
|
+
current_fields = bc_payload.nil? ? [] : bc_payload['custom_fields']
|
396
|
+
|
397
|
+
begin
|
398
|
+
return get_mapper(:CustomFieldMapper).map(options['custom_fields_map'], raw_product, bc_payload, current_fields, options['meta_fields_namespace'])
|
399
|
+
rescue BigCommerceProductError => e
|
400
|
+
emit_error(e)
|
401
|
+
rescue => e
|
402
|
+
emit_error(BigCommerceProductError.new(
|
403
|
+
500,
|
404
|
+
'map_custom_fields',
|
405
|
+
e.message,
|
406
|
+
bc_payload['id'],
|
407
|
+
{ sku: bc_payload['sku'] },
|
408
|
+
e
|
409
|
+
))
|
410
|
+
end
|
411
|
+
|
412
|
+
return nil
|
413
|
+
end
|
414
|
+
|
415
|
+
# Manages meta field values for the provided product records.
|
416
|
+
# NOTE: Because meta fields have to be managed separately, this function will
|
417
|
+
# map the raw_product data and also handle any delete/create/update requests.
|
418
|
+
def update_meta_fields(raw_product, bc_payload)
|
419
|
+
current_fields = nil
|
420
|
+
|
421
|
+
begin
|
422
|
+
current_fields = @meta_field_client.get_for_product(bc_payload['id'])
|
423
|
+
rescue BigCommerceProductError => e
|
424
|
+
emit_error(e)
|
425
|
+
return nil
|
426
|
+
rescue => e
|
427
|
+
emit_error(BigCommerceProductError.new(
|
428
|
+
500,
|
429
|
+
'get_meta_fields',
|
430
|
+
e.message,
|
431
|
+
bc_payload['id'],
|
432
|
+
{ sku: bc_payload['sku'] },
|
433
|
+
e
|
434
|
+
))
|
435
|
+
return nil
|
436
|
+
end
|
437
|
+
|
438
|
+
begin
|
439
|
+
fields = get_mapper(:MetaFieldMapper).map(options['meta_fields_map'], raw_product, bc_payload, current_fields, options['meta_fields_namespace'])
|
440
|
+
|
441
|
+
# Delete fields
|
442
|
+
fields[:delete].each do |field|
|
443
|
+
begin
|
444
|
+
@meta_field_client.delete(bc_payload['id'], field['id'])
|
445
|
+
rescue BigCommerceProductError => e
|
446
|
+
emit_error(e)
|
447
|
+
rescue => e
|
448
|
+
emit_error(BigCommerceProductError.new(
|
449
|
+
500,
|
450
|
+
'delete meta_fields',
|
451
|
+
e.message,
|
452
|
+
bc_payload['id'],
|
453
|
+
{ sku: bc_payload['sku'] },
|
454
|
+
e
|
455
|
+
))
|
413
456
|
end
|
414
457
|
end
|
415
458
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
459
|
+
# Upsert fields
|
460
|
+
fields[:upsert].each do |field|
|
461
|
+
begin
|
462
|
+
@meta_field_client.upsert(bc_payload['id'], field)
|
463
|
+
rescue BigCommerceProductError => e
|
464
|
+
emit_error(e)
|
465
|
+
rescue => e
|
466
|
+
emit_error(BigCommerceProductError.new(
|
467
|
+
500,
|
468
|
+
'upsert_meta_fields',
|
469
|
+
e.message,
|
470
|
+
bc_payload['id'],
|
471
|
+
{ sku: bc_payload['sku'] },
|
472
|
+
e
|
473
|
+
))
|
474
|
+
end
|
420
475
|
end
|
421
476
|
|
422
|
-
|
423
|
-
|
477
|
+
return fields
|
478
|
+
rescue BigCommerceProductError => e
|
479
|
+
emit_error(e)
|
480
|
+
rescue => e
|
481
|
+
emit_error(BigCommerceProductError.new(
|
482
|
+
500,
|
483
|
+
'map_meta_fields',
|
484
|
+
e.message,
|
485
|
+
bc_payload['id'],
|
486
|
+
{ sku: bc_payload['sku'] },
|
487
|
+
e
|
488
|
+
))
|
489
|
+
end
|
490
|
+
|
491
|
+
return nil
|
492
|
+
end
|
424
493
|
|
425
|
-
|
426
|
-
meta_fields << @meta_field.upsert(field)
|
427
|
-
end
|
494
|
+
private
|
428
495
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
496
|
+
def initialize_clients
|
497
|
+
@product_client = initialize_client(:Product)
|
498
|
+
@custom_field_client = initialize_client(:CustomField)
|
499
|
+
@meta_field_client = initialize_client(:MetaField)
|
500
|
+
end
|
434
501
|
|
435
|
-
|
436
|
-
|
502
|
+
def initialize_client(class_name)
|
503
|
+
klass = ::BigcommerceProductAgent::Client.const_get(class_name.to_sym)
|
504
|
+
return klass.new(
|
505
|
+
interpolated['store_hash'],
|
506
|
+
interpolated['client_id'],
|
507
|
+
interpolated['access_token']
|
508
|
+
)
|
509
|
+
end
|
510
|
+
|
511
|
+
def get_mapper(class_name)
|
512
|
+
return ::BigcommerceProductAgent::Mapper.const_get(class_name.to_sym)
|
513
|
+
end
|
514
|
+
|
515
|
+
# Takes a BigCommerceProductError and emits the underlying data as an error payload
|
516
|
+
# to assist with error reporting. It is recommended that these errors be consolidated
|
517
|
+
# with a Digest Agent and reported as a summary.
|
518
|
+
def emit_error(error)
|
519
|
+
|
520
|
+
payload = {
|
521
|
+
status: error.status,
|
522
|
+
message: error.message,
|
523
|
+
scope: error.scope,
|
524
|
+
product_identifier: error.product_identifier,
|
525
|
+
data: error.data,
|
526
|
+
}
|
527
|
+
|
528
|
+
Rails.logger.debug({
|
529
|
+
error: payload,
|
530
|
+
trace: error.backtrace
|
531
|
+
})
|
532
|
+
|
533
|
+
create_event({ payload: payload })
|
437
534
|
end
|
535
|
+
end
|
438
536
|
end
|