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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adaa89e639b5ab86d9a6d5a7115d600ced404287d7423a6bd916be655812f0e0
4
- data.tar.gz: 33ccd02f821ec39473b22c459902e5f8f95d0f43abbf384187a621a1e2a9444d
3
+ metadata.gz: 0a16c935c5f0593bfe513a750ce449cfa38c408cdae2d1d232a1952e910e0e61
4
+ data.tar.gz: 198cf4537a7751e48ea4563eee131328c1c6e9815e38f0e0e5d5f710b170ab41
5
5
  SHA512:
6
- metadata.gz: 473d32273d6e23e5e1fdce6ae6f1cfe423d8d57261c1b6db18493f8b8a914ee687737f290be2632691b2cc2edd3d0a1639cdf2b0a2144cd9fd7361c9610d2baf
7
- data.tar.gz: 95c85baae2de9bc618f1e4c39539e2cc1dd33830b5c6306fa5f6c6439e5228e7f76bb4d1ed77cf69e05349ec08f351347979c99303289a61db1ecd517ebc7801
6
+ metadata.gz: 7b49211e58db9b4c6a84c6ca45009ca08101d9a0fe2163c2b9dbbbf9c91257aa99029e3b4c4dd518d5ce87fe40a03987481d7255339d24b750de1eb0c7e45b62
7
+ data.tar.gz: 811685124d0e2b4068a8897d24e6130ae2982efb37675f0e3a7c3b4aff6234dc5d839dd5a5afb54edeacb88557716a6d10061f6ec14f9bb0db253906ae2cd3b0
@@ -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
@@ -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
@@ -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 create(payload)
12
- response = client.post(uri, payload.to_json)
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, "if provided, custom_fields_map must be a hash")
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, "if provided, meta_fields_map must be a hash")
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
- errors.add(:base, "if meta_fields_map is provided, meta_fields_namespace is required") if options['meta_fields_namespace'].blank?
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
- end
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 = ::BigcommerceProductAgent::Mapper::ProductMapper.get_product_skus(product)
74
- wrapper_sku = ::BigcommerceProductAgent::Mapper::ProductMapper.get_wrapper_sku(product)
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 = upsert(sku, product, bc_product)
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 = upsert(wrapper_sku, product, bc_wrapper_product)
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 = ::BigcommerceProductAgent::Mapper::ProductMapper.get_is_default(product)
275
+ is_default_map = get_mapper(:ProductMapper).get_is_default(product)
101
276
 
102
277
  # update modifier
103
- sku_option_map = ::BigcommerceProductAgent::Mapper::ProductMapper.get_sku_option_label_map(product)
104
- modifier_updates = ::BigcommerceProductAgent::Mapper::ModifierMapper.map(
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
- @product = ::BigcommerceProductAgent::Client::Product.new(
129
- interpolated['store_hash'],
130
- interpolated['client_id'],
131
- interpolated['access_token']
132
- )
133
-
134
- @custom_field = ::BigcommerceProductAgent::Client::CustomField.new(
135
- interpolated['store_hash'],
136
- interpolated['client_id'],
137
- interpolated['access_token']
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
- @modifier = ::BigcommerceProductAgent::Client::Modifier.new(
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
- @modifier_value = ::BigcommerceProductAgent::Client::ModifierValue.new(
153
- interpolated['store_hash'],
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 upsert(sku, product, bc_product = nil)
160
- custom_fields_updates = ::BigcommerceProductAgent::Mapper::CustomFieldMapper.map(
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 = ::BigcommerceProductAgent::Mapper::ProductMapper.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 = ::BigcommerceProductAgent::Mapper::MetaFieldMapper.map(
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
- return {
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
- return meta_fields
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
@@ -0,0 +1,16 @@
1
+ module BigcommerceProductAgent
2
+ module Mapper
3
+ class OptionValueMapper
4
+
5
+ def self.map(label)
6
+ {
7
+ label: label,
8
+ sort_order: 0,
9
+ }
10
+ end
11
+
12
+ private
13
+
14
+ end
15
+ end
16
+ 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'] : self.get_wrapper_sku(product),
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.payload(sku, product, product_id = nil, additional_data = {})
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.2.0
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-03-31 00:00:00.000000000 Z
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: []