huginn_bigcommerce_product_agent 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: []