shopify_transporter 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|