shopify_transporter 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +39 -31
- data/exe/shopify_transporter +33 -2
- data/lib/shopify_transporter/exporters/exporter.rb +103 -0
- data/lib/shopify_transporter/exporters/magento/customer_exporter.rb +44 -0
- data/lib/shopify_transporter/exporters/magento/database_cache.rb +21 -0
- data/lib/shopify_transporter/exporters/magento/database_table_exporter.rb +68 -0
- data/lib/shopify_transporter/exporters/magento/magento_exporter.rb +28 -0
- data/lib/shopify_transporter/exporters/magento/order_exporter.rb +46 -0
- data/lib/shopify_transporter/exporters/magento/product_exporter.rb +129 -0
- data/lib/shopify_transporter/exporters/magento/product_options.rb +98 -0
- data/lib/shopify_transporter/exporters/magento/soap.rb +101 -0
- data/lib/shopify_transporter/exporters/magento/sql.rb +38 -0
- data/lib/shopify_transporter/exporters/magento.rb +5 -0
- data/lib/shopify_transporter/exporters.rb +3 -0
- data/lib/shopify_transporter/generators/generate.rb +4 -4
- data/lib/shopify_transporter/generators/new.rb +2 -2
- data/lib/shopify_transporter/pipeline/magento/order/line_items.rb +7 -5
- data/lib/shopify_transporter/pipeline/magento/product/top_level_attributes.rb +73 -4
- data/lib/shopify_transporter/pipeline/magento/product/top_level_variant_attributes.rb +50 -0
- data/lib/shopify_transporter/pipeline/magento/product/variant_attributes.rb +28 -0
- data/lib/shopify_transporter/pipeline/magento/product/variant_image.rb +50 -0
- data/lib/shopify_transporter/record_builder/product_record_builder.rb +33 -0
- data/lib/shopify_transporter/{record_builder.rb → record_builder/record_builder.rb} +0 -0
- data/lib/shopify_transporter/shopify/attributes_helpers.rb +1 -1
- data/lib/shopify_transporter/shopify/order.rb +1 -1
- data/lib/shopify_transporter/shopify/product.rb +4 -3
- data/lib/shopify_transporter/shopify/record.rb +1 -1
- data/lib/shopify_transporter/version.rb +1 -1
- data/lib/shopify_transporter.rb +15 -7
- data/lib/tasks/factory_bot.rake +1 -1
- data/lib/templates/magento/config.tt +19 -0
- data/shopify_transporter.gemspec +3 -0
- metadata +61 -3
@@ -4,7 +4,7 @@ require_relative './base_group.rb'
|
|
4
4
|
module ShopifyTransporter
|
5
5
|
class Generate < BaseGroup
|
6
6
|
class_option :object, type: :string, required: :true,
|
7
|
-
enum: %w(customer order), aliases: '-O',
|
7
|
+
enum: %w(customer order product), aliases: '-O',
|
8
8
|
desc: 'The object to add to the pipeline stage'
|
9
9
|
|
10
10
|
def object_type
|
@@ -13,7 +13,7 @@ module ShopifyTransporter
|
|
13
13
|
|
14
14
|
def validate_config_exists
|
15
15
|
unless File.exist?(config_filename)
|
16
|
-
say(
|
16
|
+
say('Cannot find config.yml at project root', :red)
|
17
17
|
exit 1
|
18
18
|
end
|
19
19
|
end
|
@@ -34,7 +34,7 @@ module ShopifyTransporter
|
|
34
34
|
|
35
35
|
def generate_stage
|
36
36
|
template(
|
37
|
-
|
37
|
+
'templates/custom_stage.tt',
|
38
38
|
"lib/custom_pipeline_stages/#{pipeline_snake_name}.rb"
|
39
39
|
)
|
40
40
|
end
|
@@ -42,7 +42,7 @@ module ShopifyTransporter
|
|
42
42
|
def add_stage_to_config
|
43
43
|
config_file = YAML.load_file(config_filename)
|
44
44
|
unless class_included_in?(config_file)
|
45
|
-
pipeline_class_hash = { @pipeline_class.to_s => nil,
|
45
|
+
pipeline_class_hash = { @pipeline_class.to_s => nil, 'type' => 'custom' }
|
46
46
|
config_file['object_types'][@object_type.downcase]['pipeline_stages'] << pipeline_class_hash
|
47
47
|
File.open(config_filename, 'w') { |f| f.write config_file.to_yaml }
|
48
48
|
say('Updated config.yml with the new pipeline stage', :green)
|
@@ -7,7 +7,7 @@ module ShopifyTransporter
|
|
7
7
|
class_option :platform, type: :string, required: :true, enum: %w(magento)
|
8
8
|
|
9
9
|
def snake_name
|
10
|
-
@snake_name ||= name_components.map(&:downcase).join(
|
10
|
+
@snake_name ||= name_components.map(&:downcase).join('_')
|
11
11
|
end
|
12
12
|
|
13
13
|
def platform
|
@@ -16,7 +16,7 @@ module ShopifyTransporter
|
|
16
16
|
|
17
17
|
def generate_config
|
18
18
|
template("templates/#{@platform}/config.tt", "#{@snake_name}/config.yml")
|
19
|
-
template(
|
19
|
+
template('templates/gemfile.tt', "#{@snake_name}/Gemfile")
|
20
20
|
empty_directory("#{@snake_name}/lib/custom_pipeline_stages")
|
21
21
|
end
|
22
22
|
end
|
@@ -42,11 +42,13 @@ module ShopifyTransporter
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def tax_lines(item)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
[
|
46
|
+
{
|
47
|
+
title: 'Tax',
|
48
|
+
price: item['tax_amount'],
|
49
|
+
rate: item['tax_percent'],
|
50
|
+
}.stringify_keys,
|
51
|
+
]
|
50
52
|
end
|
51
53
|
end
|
52
54
|
end
|
@@ -8,25 +8,94 @@ module ShopifyTransporter
|
|
8
8
|
module Product
|
9
9
|
class TopLevelAttributes < Pipeline::Stage
|
10
10
|
def convert(hash, record)
|
11
|
+
warn_if_too_many_options(hash)
|
11
12
|
accumulator = TopLevelAttributesAccumulator.new(record)
|
12
13
|
accumulator.accumulate(hash)
|
13
14
|
end
|
14
15
|
|
16
|
+
private
|
17
|
+
|
18
|
+
MAX_OPTION_COUNT = 3
|
19
|
+
|
20
|
+
def too_many_options?(input)
|
21
|
+
input["option#{MAX_OPTION_COUNT + 1}_name"].present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def warn_if_too_many_options(input)
|
25
|
+
if too_many_options?(input)
|
26
|
+
$stderr.puts "Warning: Product #{input['product_id']} has too many options."\
|
27
|
+
" Only the first #{MAX_OPTION_COUNT} options will be converted."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
15
31
|
class TopLevelAttributesAccumulator < Shopify::AttributesAccumulator
|
16
32
|
COLUMN_MAPPING = {
|
17
|
-
'sku' => 'sku',
|
18
33
|
'name' => 'title',
|
19
34
|
'description' => 'body_html',
|
35
|
+
'url_key' => 'handle',
|
20
36
|
}
|
21
37
|
|
22
38
|
private
|
23
39
|
|
24
|
-
def input_applies?(
|
25
|
-
true
|
40
|
+
def input_applies?(input)
|
41
|
+
true unless input['parent_id'].present?
|
26
42
|
end
|
27
43
|
|
28
44
|
def attributes_from(input)
|
29
|
-
map_from_key_to_val(COLUMN_MAPPING, input)
|
45
|
+
attributes = map_from_key_to_val(COLUMN_MAPPING, input)
|
46
|
+
attributes['published'] = published?(input)
|
47
|
+
attributes['published_scope'] = published?(input) ? 'global' : ''
|
48
|
+
attributes['published_at'] = published?(input) ? input['updated_at'] : ''
|
49
|
+
append_images_to_current_record!(input) if input['images'].present?
|
50
|
+
attributes['tags'] = product_tags(input) if input['tags'].present?
|
51
|
+
attributes['options'] = product_options(input) if input['option1_name'].present?
|
52
|
+
attributes
|
53
|
+
end
|
54
|
+
|
55
|
+
def published?(input)
|
56
|
+
input['visibility'].present? && input['visibility'].to_i != 1
|
57
|
+
end
|
58
|
+
|
59
|
+
def apply_image_column_mapping(input_image)
|
60
|
+
{
|
61
|
+
'src' => input_image['url'],
|
62
|
+
'position' => input_image['position'],
|
63
|
+
'alt' => image_alt_text(input_image['label']),
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def construct_images(input)
|
68
|
+
return [apply_image_column_mapping(input['images'])] if input['images'].is_a?(Hash)
|
69
|
+
images = input['images'].map do |image|
|
70
|
+
apply_image_column_mapping(image).compact
|
71
|
+
end
|
72
|
+
images.sort_by { |image| image['position'] }
|
73
|
+
end
|
74
|
+
|
75
|
+
def append_images_to_current_record!(input)
|
76
|
+
images = construct_images(input)
|
77
|
+
if @output['images'].present?
|
78
|
+
@output['images'].concat(images)
|
79
|
+
else
|
80
|
+
@output['images'] = images
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def product_options(input)
|
85
|
+
%w(option1_name option2_name option3_name).map do |option_name|
|
86
|
+
{ 'name' => input[option_name] }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def image_alt_text(label)
|
91
|
+
if label.is_a? String
|
92
|
+
label
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def product_tags(input)
|
97
|
+
return input['tags']['name'] if input['tags'].is_a?(Hash)
|
98
|
+
input['tags'].map { |tag| tag['name'] }.join(', ')
|
30
99
|
end
|
31
100
|
end
|
32
101
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'shopify_transporter/pipeline/stage'
|
3
|
+
require 'shopify_transporter/shopify'
|
4
|
+
|
5
|
+
module ShopifyTransporter
|
6
|
+
module Pipeline
|
7
|
+
module Magento
|
8
|
+
module Product
|
9
|
+
class TopLevelVariantAttributes < Pipeline::Stage
|
10
|
+
def convert(hash, record)
|
11
|
+
return unless input_applies?(hash)
|
12
|
+
simple_product_in_magento_format = record['variants'].select do |product|
|
13
|
+
product['product_id'] == hash['product_id']
|
14
|
+
end.first
|
15
|
+
accumulator = TopLevelVariantAttributesAccumulator.new(simple_product_in_magento_format)
|
16
|
+
accumulator.accumulate(hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def input_applies?(input)
|
20
|
+
true unless input['parent_id'].nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
class TopLevelVariantAttributesAccumulator < Shopify::AttributesAccumulator
|
24
|
+
COLUMN_MAPPING = {
|
25
|
+
'sku' => 'sku',
|
26
|
+
'weight' => 'weight',
|
27
|
+
'price' => 'price',
|
28
|
+
'inventory_quantity' => 'inventory_qty',
|
29
|
+
}
|
30
|
+
|
31
|
+
def accumulate(input)
|
32
|
+
accumulate_attributes(map_from_key_to_val(COLUMN_MAPPING, input))
|
33
|
+
accumulate_attributes(variant_options(input))
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def variant_options(input)
|
39
|
+
{
|
40
|
+
'option1' => input['option1_value'],
|
41
|
+
'option2' => input['option2_value'],
|
42
|
+
'option3' => input['option3_value'],
|
43
|
+
}.compact
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'shopify_transporter/pipeline/stage'
|
3
|
+
require 'shopify_transporter/shopify'
|
4
|
+
|
5
|
+
module ShopifyTransporter
|
6
|
+
module Pipeline
|
7
|
+
module Magento
|
8
|
+
module Product
|
9
|
+
class VariantAttributes < Pipeline::Stage
|
10
|
+
def convert(hash, record)
|
11
|
+
accumulator = VariantAttributesAccumulator.new(record)
|
12
|
+
accumulator.accumulate(hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
class VariantAttributesAccumulator < Shopify::AttributesAccumulator
|
16
|
+
def accumulate(current_product)
|
17
|
+
if current_product['parent_id'].present?
|
18
|
+
@output['variants'] ||= []
|
19
|
+
@output['variants'] << current_product
|
20
|
+
@output
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'shopify_transporter/pipeline/stage'
|
3
|
+
require 'shopify_transporter/shopify'
|
4
|
+
|
5
|
+
module ShopifyTransporter
|
6
|
+
module Pipeline
|
7
|
+
module Magento
|
8
|
+
module Product
|
9
|
+
class VariantImage < Pipeline::Stage
|
10
|
+
def convert(hash, record)
|
11
|
+
return unless input_applied?(hash)
|
12
|
+
add_variant_image!(hash, record)
|
13
|
+
add_variant_image_to_parent_images!(hash, record)
|
14
|
+
record
|
15
|
+
end
|
16
|
+
|
17
|
+
def input_applied?(input)
|
18
|
+
input['images'].present? && input['parent_id'].present?
|
19
|
+
end
|
20
|
+
|
21
|
+
def current_variant(input, record)
|
22
|
+
record['variants'].select do |variant|
|
23
|
+
variant['product_id'] == input['product_id']
|
24
|
+
end.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_variant_image!(input, record)
|
28
|
+
current_variant(input, record).merge!(
|
29
|
+
'variant_image' => {
|
30
|
+
'src' => variant_image_url(input),
|
31
|
+
}
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def variant_image_url(input)
|
36
|
+
return input['images']['url'] if input['images'].is_a?(Hash)
|
37
|
+
input['images'].sort_by { |image| image['position'] }.first['url']
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_variant_image_to_parent_images!(input, record)
|
41
|
+
record['images'] ||= []
|
42
|
+
record['images'] << {
|
43
|
+
'src' => variant_image_url(input),
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
module ShopifyTransporter
|
4
|
+
class ProductRecordBuilder < RecordBuilder
|
5
|
+
def initialize(key_name, key_required)
|
6
|
+
super(key_name, key_required)
|
7
|
+
end
|
8
|
+
|
9
|
+
def build(input)
|
10
|
+
validate_related(input)
|
11
|
+
|
12
|
+
if has_parent?(input)
|
13
|
+
yield parent_record(input)
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
yield record_from(input)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def has_parent?(product)
|
23
|
+
product['parent_id'].present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def parent_record(product)
|
27
|
+
parent_id = product['parent_id']
|
28
|
+
record = @instances[parent_id] ||= {}
|
29
|
+
record['variants'] ||= []
|
30
|
+
@last_record = record
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
File without changes
|
@@ -61,7 +61,7 @@ module ShopifyTransporter
|
|
61
61
|
company name phone first_name last_name address1 address2 city province province_code zip country country_code
|
62
62
|
)
|
63
63
|
|
64
|
-
LINE_ITEM_PREFIX =
|
64
|
+
LINE_ITEM_PREFIX = 'lineitem_'
|
65
65
|
|
66
66
|
LINE_ITEM_ATTRIBUTES = %w(
|
67
67
|
name quantity price discount compare_at_price sku requires_shipping taxable fulfillment_status
|
@@ -18,7 +18,7 @@ module ShopifyTransporter
|
|
18
18
|
VARIANT_ATTRIBUTES = %w(
|
19
19
|
sku grams inventory_tracker inventory_qty inventory_policy
|
20
20
|
fulfillment_service price compare_at_price requires_shipping
|
21
|
-
taxable barcode weight_unit tax_code
|
21
|
+
taxable barcode weight weight_unit tax_code
|
22
22
|
).freeze
|
23
23
|
|
24
24
|
class << self
|
@@ -33,7 +33,8 @@ module ShopifyTransporter
|
|
33
33
|
'Metafield Value Type', 'Variant Grams', 'Variant Inventory Tracker', 'Variant Inventory Qty',
|
34
34
|
'Variant Inventory Policy', 'Variant Fulfillment Service', 'Variant Price', 'Variant Compare At Price',
|
35
35
|
'Variant Requires Shipping', 'Variant Taxable', 'Variant Barcode', 'Image Attachment', 'Image Src',
|
36
|
-
'Image Position', 'Image Alt Text', 'Variant Image', 'Variant Weight
|
36
|
+
'Image Position', 'Image Alt Text', 'Variant Image', 'Variant Weight', 'Variant Weight Unit',
|
37
|
+
'Variant Tax Code'
|
37
38
|
].to_csv
|
38
39
|
end
|
39
40
|
|
@@ -95,9 +96,9 @@ module ShopifyTransporter
|
|
95
96
|
|
96
97
|
def image_row_values
|
97
98
|
return [] if record_hash['images'].blank?
|
99
|
+
|
98
100
|
record_hash['images'].map do |image_hash|
|
99
101
|
image = {
|
100
|
-
'image_attachment' => image_hash['attachment'],
|
101
102
|
'image_src' => image_hash['src'],
|
102
103
|
'image_position' => image_hash['position'],
|
103
104
|
'image_alt_text' => image_hash['alt'],
|
data/lib/shopify_transporter.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'thor'
|
3
|
+
|
3
4
|
require_relative 'shopify_transporter/generators.rb'
|
5
|
+
require_relative 'shopify_transporter/exporters.rb'
|
4
6
|
require_relative 'shopify_transporter/pipeline.rb'
|
5
7
|
require_relative 'shopify_transporter/shopify.rb'
|
6
|
-
require_relative 'shopify_transporter/record_builder.rb'
|
8
|
+
require_relative 'shopify_transporter/record_builder/record_builder.rb'
|
9
|
+
require_relative 'shopify_transporter/record_builder/product_record_builder'
|
7
10
|
Dir["#{Dir.pwd}/lib/custom_pipeline_stages/**/*.rb"].each { |f| require f }
|
8
11
|
|
9
12
|
module ShopifyTransporter
|
@@ -149,11 +152,11 @@ class TransporterTool
|
|
149
152
|
def stage_class_from(name, type)
|
150
153
|
class_name = stage_classname(name, type)
|
151
154
|
klass = begin
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
155
|
+
k = Object.const_get(class_name)
|
156
|
+
k.is_a?(Class) && k
|
157
|
+
rescue NameError
|
158
|
+
nil
|
159
|
+
end
|
157
160
|
raise StageNotFoundError, name unless class_exists?(klass)
|
158
161
|
klass
|
159
162
|
end
|
@@ -232,7 +235,12 @@ class TransporterTool
|
|
232
235
|
|
233
236
|
def build_classes_based_on_config
|
234
237
|
@record_class = Object.const_get("ShopifyTransporter::Shopify::#{@object_type.capitalize}")
|
235
|
-
|
238
|
+
record_builder_class = if @object_type.capitalize == 'Product'
|
239
|
+
ShopifyTransporter::ProductRecordBuilder
|
240
|
+
else
|
241
|
+
ShopifyTransporter::RecordBuilder
|
242
|
+
end
|
243
|
+
@record_builder = record_builder_class.new(
|
236
244
|
record_key_from_config, key_required_from_config
|
237
245
|
)
|
238
246
|
end
|