magento 0.15.0 → 0.18.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: 0d7a340d50d7233fcb8e0eb877d49e1c29e0785fb76413c2216bf79c86bb420b
4
- data.tar.gz: 6c1ad15d2b246e145d7d83082749431d89303f9c82a6fe74dd525ed52615fe28
3
+ metadata.gz: 41955a56a9aff31abc7c7c74adcc72eb84a11e00ebef0e169b40e89e218d3d0b
4
+ data.tar.gz: d4e846e5407872f2c2909585b148c85ede8f9552456dfd18886fa791c8b2f1e5
5
5
  SHA512:
6
- metadata.gz: a1e2e8fc442888fc6cd2e45f16d10186a1abf86ca990517cd0b9dbba60b2a57d015c9af2dc25e93d492413491cc4e7e1c37efa479c8e74581ce319b5d623a1ec
7
- data.tar.gz: 219111f025c6189e6c83605e6a08f0479e1080e7ae40ff8f740aa0190df298ab71ef3b1c6c8a3757466e5a74b4d78e64c50827ad84da0a7b1a47774e6571aff8
6
+ metadata.gz: 5178cf1b839df2db8fd2c5c5e9c891ebdff52f2d1ce2f896c6a3a8d0f7b607960a19cfcadfece2c53289b1637c2cc613b3632b0c8bf564513e71033d2cfcfe93
7
+ data.tar.gz: 28522d689e5c6a718275174c531e585c26cef4a07347b9ebd70346761a696cc87260aa809acc98fc0f9b883b9ee0c5140b0ecde3e19101ca426dab36227f003c
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  Add in your Gemfile
6
6
 
7
7
  ```rb
8
- gem 'magento', '~> 0.15.0'
8
+ gem 'magento', '~> 0.15.1'
9
9
  ```
10
10
 
11
11
  or run
@@ -5,6 +5,7 @@ require 'dry/inflector'
5
5
  require 'active_support/core_ext/string/inflections'
6
6
  require 'active_support/core_ext/hash/keys'
7
7
 
8
+ require_relative 'magento/configuration'
8
9
  require_relative 'magento/errors'
9
10
  require_relative 'magento/request'
10
11
  require_relative 'magento/model_mapper'
@@ -21,13 +22,16 @@ require_relative 'magento/order'
21
22
  require_relative 'magento/invoice'
22
23
  require_relative 'magento/guest_cart'
23
24
  require_relative 'magento/sales_rule'
25
+ require_relative 'magento/import'
24
26
 
25
27
  Dir[File.expand_path('magento/shared/*.rb', __dir__)].map { |f| require f }
26
28
  Dir[File.expand_path('magento/params/*.rb', __dir__)].map { |f| require f }
27
29
 
28
30
  module Magento
29
31
  class << self
30
- attr_accessor :url, :open_timeout, :timeout, :token, :store
32
+ attr_writer :configuration
33
+
34
+ delegate :url=, :token=, :store=, :open_timeout=, :timeout=, to: :configuration
31
35
 
32
36
  def inflector
33
37
  @inflector ||= Dry::Inflector.new do |inflections|
@@ -36,26 +40,24 @@ module Magento
36
40
  end
37
41
  end
38
42
 
39
- self.url = ENV['MAGENTO_URL']
40
- self.open_timeout = 30
41
- self.timeout = 90
42
- self.token = ENV['MAGENTO_TOKEN']
43
- self.store = ENV['MAGENTO_STORE'] || :all
43
+ def self.configuration
44
+ @configuration ||= Configuration.new
45
+ end
44
46
 
45
- def self.with_config(url: Magento.url, token: Magento.token, store: Magento.store)
46
- @old_url = self.url
47
- @old_token = self.token
48
- @old_store = self.store
47
+ def self.reset
48
+ @configuration = Configuration.new
49
+ end
49
50
 
50
- self.url = url
51
- self.token = token
52
- self.store = store
51
+ def self.configure
52
+ yield(configuration)
53
+ end
53
54
 
55
+ def self.with_config(params)
56
+ @old_configuration = configuration
57
+ self.configuration = configuration.copy_with(**params)
54
58
  yield
55
59
  ensure
56
- self.url = @old_url
57
- self.token = @old_token
58
- self.store = @old_store
60
+ @configuration = @old_configuration
59
61
  end
60
62
 
61
63
  def self.production?
@@ -0,0 +1,31 @@
1
+ module Magento
2
+ class Configuration
3
+ attr_accessor :url, :open_timeout, :timeout, :token, :store, :product_image
4
+
5
+ def initialize(url: nil, token: nil, store: nil)
6
+ self.url = url || ENV['MAGENTO_URL']
7
+ self.open_timeout = 30
8
+ self.timeout = 90
9
+ self.token = token || ENV['MAGENTO_TOKEN']
10
+ self.store = store || ENV['MAGENTO_STORE'] || :all
11
+
12
+ self.product_image = ProductImageConfiguration.new
13
+ end
14
+
15
+ def copy_with(params = {})
16
+ clone.tap do |config|
17
+ params.each { |key, value| config.send("#{key}=", value) }
18
+ end
19
+ end
20
+ end
21
+
22
+ class ProductImageConfiguration
23
+ attr_accessor :small_size, :medium_size, :large_size
24
+
25
+ def initialize
26
+ self.small_size = '200x200>'
27
+ self.medium_size = '400x400>'
28
+ self.large_size = '800x800>'
29
+ end
30
+ end
31
+ end
@@ -33,7 +33,7 @@ module Magento
33
33
  end
34
34
 
35
35
  def find_by_token(token)
36
- user_request = Request.new(token: token)
36
+ user_request = Request.new(config: Magento.configuration.copy_with(token: token))
37
37
  customer_hash = user_request.get('customers/me').parse
38
38
  build(customer_hash)
39
39
  end
@@ -0,0 +1,18 @@
1
+ require_relative 'import/image_finder'
2
+ require_relative 'import/csv_reader'
3
+ require_relative 'import/category'
4
+ require_relative 'import/product'
5
+
6
+ module Magento
7
+ module Import
8
+ def self.from_csv(file, images_folder: nil, website_ids: [0])
9
+ products = CSVReader.new(file).get_products
10
+ products = Category.new(products).associate
11
+ Product.new(website_ids, images_folder).import(products)
12
+ end
13
+
14
+ def self.get_csv_template
15
+ File.open(__dir__ + '/import/template/products.csv')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Magento
2
+ module Import
3
+ class ImageFinder
4
+ EXTENTIONS = %w[jpg jpeg png webp gif].freeze
5
+
6
+ def initialize(images_folder)
7
+ @images_folder = images_folder
8
+ end
9
+
10
+ def find_by_name(name)
11
+ prefix = "#{@images_folder}/#{name}"
12
+
13
+ EXTENTIONS.map { |e| ["#{prefix}.#{e}", "#{prefix}.#{e.upcase}"] }.flatten
14
+ .find { |file| File.exist?(file) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ module Magento
2
+ module Import
3
+ class Category
4
+ def initialize(products)
5
+ @products = products
6
+ @category_root = Magento::Category.all
7
+ @cats = @category_root.children_data
8
+ end
9
+
10
+ def associate
11
+ @products.each do |prod|
12
+ cat1 = find_or_create(name: prod.cat1, parent: @category_root) if prod.cat1
13
+ cat2 = find_or_create(name: prod.cat2, parent: cat1) if prod.cat2
14
+ cat3 = find_or_create(name: prod.cat3, parent: cat2) if prod.cat3
15
+
16
+ prod.cat1, prod.cat2, prod.cat3 = cat1&.id, cat2&.id, cat3&.id
17
+
18
+ @cats.push(*[cat1, cat2, cat3].compact)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def find_or_create(name:, parent:)
25
+ find(name, cats: @cats, parent_id: parent.id) || create(name, parent: parent)
26
+ end
27
+
28
+ def find(name, cats:, parent_id:)
29
+ cats.each do |cat|
30
+ return cat if cat.name == name && cat.parent_id == parent_id
31
+
32
+ if cat.respond_to?(:children_data) && cat.children_data&.size.to_i > 0
33
+ result = find(name, cats: cat.children_data, parent_id: parent_id)
34
+ return result if result
35
+ end
36
+ end
37
+ nil
38
+ end
39
+
40
+ def create(name, parent:)
41
+ params = Magento::Params::CreateCategoria.new(
42
+ name: name,
43
+ parent_id: parent.id,
44
+ url: "#{Magento.configuration.store}-#{parent.id}-#{name.parameterize}"
45
+ )
46
+
47
+ Magento::Category.create(params.to_h).tap { |c| puts "Create: #{c.id} => #{c.name}" }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,30 @@
1
+ require 'csv'
2
+ require 'ostruct'
3
+
4
+ module Magento
5
+ module Import
6
+ class CSVReader
7
+ def initialize(csv_file)
8
+ @csv = CSV.read(csv_file, col_sep: ';')
9
+ end
10
+
11
+ def get_products
12
+ @csv[1..].map do |row|
13
+ OpenStruct.new({
14
+ name: row[0],
15
+ sku: row[1],
16
+ ean: row[2],
17
+ description: row[3],
18
+ price: row[4],
19
+ special_price: row[5],
20
+ quantity: row[6],
21
+ cat1: row[7],
22
+ cat2: row[8],
23
+ cat3: row[9],
24
+ main_image: row[10]
25
+ })
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,59 @@
1
+ require 'uri'
2
+
3
+ module Magento
4
+ module Import
5
+ class Product
6
+ def initialize(website_ids, images_folder = nil)
7
+ @website_ids = website_ids
8
+ @image_finder = images_folder ? ImageFinder.new(images_folder) : nil
9
+ end
10
+
11
+ def import(products)
12
+ products.each do |product|
13
+ params = Magento::Params::CreateProduct.new(
14
+ sku: product.sku,
15
+ name: product.name.gsub(/[ ]+/, ' '),
16
+ description: product.description || product.name,
17
+ brand: product.brand,
18
+ price: product.price.to_f,
19
+ special_price: product.special_price ? product.special_price.to_f : nil,
20
+ quantity: numeric?(product.quantity) ? product.quantity.to_f : 0,
21
+ weight: 0.3,
22
+ manage_stock: numeric?(product.quantity),
23
+ attribute_set_id: 4,
24
+ category_ids: [product.cat1, product.cat2, product.cat3].compact,
25
+ website_ids: @website_ids,
26
+ images: images(product)
27
+ ).to_h
28
+
29
+ product = Magento::Product.create(params)
30
+
31
+ puts "Created product: #{product.sku} => #{product.name}"
32
+ rescue => e
33
+ puts "Error on create: #{product.sku} => #{product.name}"
34
+ puts " - Error details: #{e}"
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def images(product)
41
+ return [] unless product.main_image.to_s =~ URI::regexp || @image_finder
42
+
43
+ image = product.main_image || @image_finder.find_by_name(product.sku)
44
+ return [] unless image
45
+
46
+ Magento::Params::CreateImage.new(
47
+ path: image,
48
+ title: product.name,
49
+ position: 0,
50
+ main: true
51
+ ).variants
52
+ end
53
+
54
+ def numeric?(value)
55
+ !!(value.to_s =~ /^[\d]+$/)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,2 @@
1
+ name;sku;gtin;description;price;special price;quantity;category level 1;category level 2;category level 3;main image url
2
+ Apple iPhone 11 Pro;iphone-11-pro;9999999999;"Description: New Apple iPhone 11 Pro Max 64/256/512GB Space Gray Midnight Green Silver Gold\nIncludes all new accessories\n\niPhone is unlocked to work on any GSM carrier!\n\nNo warranty - Warranty can be purchased through squaretrade\n\nShipping: We Normally Ship Out Same or Next Business Day Via USPS. \nDelivery Time Varies Especially During Holidays.\nWe Do NOT Ship Out On Saturday/Sunday\nWe Are Not Responsible For Late Packages, But We Will Find Out If There Are Any Issues.\nWe Only Ship To PayPal Confirmed Address.\nPick Up Also Available In Queens NY (Message For Details)\nPriority Mail USPS Free (1-4 Business Days; Not Guaranteed Service)\nExpress Overnight USPS $25 (Guaranteed By USPS Overnight To Most Places) \n\nAdditional return policy details: \nAs a leading seller of electronics, we want to guarantee that each transaction ends with a five star experience. That's why we offer a FREE no questions asked 30-day return policy. Please message us for more details.\n\nSales Tax:\nNew York orders will have a sales tax of 8.875% add to your cost";1099.99;999.00;45;Technology;Smartphone;Apple;"https://store.storeimages.cdn-apple.com/4982/as-images.apple.com/is/iphone-11-pro-select-2019-family?wid=882&amp;hei=1058&amp;fmt=jpeg&amp;qlt=80&amp;op_usm=0.5,0.5&amp;.v=1586586488946"
@@ -35,7 +35,7 @@ module Magento
35
35
  class << self
36
36
  extend Forwardable
37
37
 
38
- def_delegators :query, :all, :page, :per, :page_size, :order, :select,
38
+ def_delegators :query, :all, :find_each, :page, :per, :page_size, :order, :select,
39
39
  :where, :first, :find_by, :count
40
40
 
41
41
  def find(id)
@@ -4,17 +4,17 @@ module Magento
4
4
  module Params
5
5
  class CreateCategoria < Dry::Struct
6
6
  attribute :name, Type::String
7
- attribute :parent_id, Type::String.optional
8
- attribute :path, Type::String.optional
7
+ attribute :parent_id, Type::Integer.optional
8
+ attribute :url, Type::String.optional.default(nil)
9
9
  attribute :is_active, Type::Bool.default(true)
10
10
 
11
11
  def to_h
12
12
  {
13
- "name": name,
14
- "parent_id": parent_id,
15
- "is_active": is_active,
16
- "path": path
17
- }
13
+ name: name,
14
+ parent_id: parent_id,
15
+ is_active: is_active,
16
+ custom_attributes: url ? [{attribute_code: 'url_key', value: url }] : nil
17
+ }.compact
18
18
  end
19
19
  end
20
20
  end
@@ -7,9 +7,9 @@ module Magento
7
7
  module Params
8
8
  class CreateImage < Dry::Struct
9
9
  VARIANTS = {
10
- 'large' => { size: '800x800', type: :image },
11
- 'medium' => { size: '300x300', type: :small_image },
12
- 'small' => { size: '100x100', type: :thumbnail }
10
+ 'large' => :image,
11
+ 'medium' => :small_image,
12
+ 'small' => :thumbnail
13
13
  }.freeze
14
14
 
15
15
  attribute :title, Type::String
@@ -30,7 +30,7 @@ module Magento
30
30
  "type": mini_type,
31
31
  "name": filename
32
32
  },
33
- "types": main ? [VARIANTS[size][:type]] : []
33
+ "types": main ? [VARIANTS[size]] : []
34
34
  }
35
35
  end
36
36
 
@@ -48,13 +48,23 @@ module Magento
48
48
 
49
49
  def file
50
50
  @file ||= MiniMagick::Image.open(path).tap do |b|
51
- b.resize VARIANTS[size][:size]
52
- b.strip
51
+ b.resize(Magento.configuration.product_image.send(size + '_size'))
52
+ bigger_side = b.dimensions.max
53
+ b.combine_options do |c|
54
+ c.background '#FFFFFF'
55
+ c.alpha 'remove'
56
+ c.gravity 'center'
57
+ c.extent "#{bigger_side}x#{bigger_side}"
58
+ c.strip
59
+ end
60
+ b.format 'jpg'
53
61
  end
62
+ rescue => e
63
+ raise "Error on read image #{path}: #{e}"
54
64
  end
55
65
 
56
66
  def filename
57
- title.parameterize
67
+ "#{title.parameterize}-#{VARIANTS[size]}.jpg"
58
68
  end
59
69
 
60
70
  def mini_type
@@ -54,7 +54,7 @@ module Magento
54
54
  attribute :sku, Type::String
55
55
  attribute :name, Type::String
56
56
  attribute :description, Type::String
57
- attribute :brand, Type::String
57
+ attribute :brand, Type::String.optional.default(nil)
58
58
  attribute :price, Type::Coercible::Float
59
59
  attribute :special_price, Type::Float.optional.default(nil)
60
60
  attribute :attribute_set_id, Type::Integer
@@ -128,10 +128,11 @@ module Magento
128
128
  default_attributes = [
129
129
  CustomAttribute.new(attribute_code: 'description', value: description),
130
130
  CustomAttribute.new(attribute_code: 'url_key', value: name.parameterize ),
131
- CustomAttribute.new(attribute_code: 'product_brand', value: brand ),
132
131
  CustomAttribute.new(attribute_code: 'featured', value: featured)
133
132
  ]
134
133
 
134
+ default_attributes.push(CustomAttribute.new(attribute_code: 'product_brand', value: brand)) if brand
135
+
135
136
  if special_price.to_f > 0
136
137
  default_attributes << CustomAttribute.new(attribute_code: 'special_price', value: special_price.to_s)
137
138
  end
@@ -2,8 +2,8 @@ module Magento
2
2
  class Product < Model
3
3
  self.primary_key = :sku
4
4
 
5
- def method_missing(m)
6
- attr(m) || super
5
+ def method_missing(m, *params, &block)
6
+ attr(m) || super(m, *params, &block)
7
7
  end
8
8
 
9
9
  # returns custom_attribute value by custom_attribute code
@@ -16,8 +16,26 @@ module Magento
16
16
  super || @custom_attributes&.any? { |a| a.attribute_code == attribute_code.to_s }
17
17
  end
18
18
 
19
+ def add_media(attributes)
20
+ self.class.add_media(sku, attributes)
21
+ end
22
+
23
+ # returns true if the media was deleted
24
+ def remove_media(media_id)
25
+ self.class.remove_media(sku, media_id)
26
+ end
27
+
19
28
  class << self
20
29
  alias_method :find_by_sku, :find
30
+
31
+ def add_media(sku, attributes)
32
+ request.post("products/#{sku}/media", { entry: attributes }).parse
33
+ end
34
+
35
+ # returns true if the media was deleted
36
+ def remove_media(sku, media_id)
37
+ request.delete("products/#{sku}/media/#{media_id}").parse
38
+ end
21
39
  end
22
40
  end
23
41
  end
@@ -58,7 +58,7 @@ module Magento
58
58
  alias_method :per, :page_size
59
59
 
60
60
  def select(*fields)
61
- fields = fields.map { |field| parse_field(field) }
61
+ fields = fields.map { |field| parse_field(field, root: true) }
62
62
 
63
63
  if model == Magento::Category
64
64
  self.fields = "children_data[#{fields.join(',')}]"
@@ -85,8 +85,33 @@ module Magento
85
85
 
86
86
  def all
87
87
  result = request.get("#{endpoint}?#{query_params}").parse
88
- field = model == Magento::Category ? 'children_data' : 'items'
89
- RecordCollection.from_magento_response(result, model: model, iterable_field: field)
88
+ if model == Magento::Category
89
+ model.build(result)
90
+ else
91
+ RecordCollection.from_magento_response(result, model: model)
92
+ end
93
+ end
94
+
95
+ #
96
+ # Loop all products on each page, starting from the first to the last page
97
+ def find_each
98
+ if @model == Magento::Category
99
+ raise NoMethodError, 'undefined method `find_each` for Magento::Category'
100
+ end
101
+
102
+ @current_page = 1
103
+
104
+ loop do
105
+ redords = all
106
+
107
+ redords.each do |record|
108
+ yield record
109
+ end
110
+
111
+ break if redords.last_page?
112
+
113
+ @current_page = redords.next_page
114
+ end
90
115
  end
91
116
 
92
117
  def first
@@ -146,8 +171,8 @@ module Magento
146
171
  value
147
172
  end
148
173
 
149
- def parse_field(value)
150
- return verify_id(value) unless value.is_a? Hash
174
+ def parse_field(value, root: false)
175
+ return (root ? verify_id(value) : value) unless value.is_a? Hash
151
176
 
152
177
  value.map do |k, v|
153
178
  fields = v.is_a?(Array) ? v.map { |field| parse_field(field) } : [parse_field(v)]
@@ -5,11 +5,10 @@ require 'http'
5
5
 
6
6
  module Magento
7
7
  class Request
8
- attr_reader :token, :store
8
+ attr_reader :config
9
9
 
10
- def initialize(token: Magento.token, store: Magento.store)
11
- @token = token
12
- @store = store
10
+ def initialize(config: Magento.configuration)
11
+ @config = config
13
12
  end
14
13
 
15
14
  def get(resource)
@@ -36,12 +35,13 @@ module Magento
36
35
  private
37
36
 
38
37
  def http_auth
39
- HTTP.auth("Bearer #{token}")
38
+ HTTP.auth("Bearer #{config.token}")
39
+ .timeout(connect: config.timeout, read: config.open_timeout)
40
40
  end
41
41
 
42
42
  def base_url
43
- url = Magento.url.to_s.sub(%r{/$}, '')
44
- "#{url}/rest/#{store}/V1"
43
+ url = config.url.to_s.sub(%r{/$}, '')
44
+ "#{url}/rest/#{config.store}/V1"
45
45
  end
46
46
 
47
47
  def url(resource)
@@ -53,10 +53,8 @@ module Magento
53
53
 
54
54
  begin
55
55
  msg = resp.parse['message']
56
- errors = resp.parse['errors']
57
- if resp.parse['parameters'].is_a? Hash
58
- resp.parse['parameters'].each { |k, v| msg.sub! "%#{k}", v }
59
- end
56
+ errors = resp.parse['errors'] || resp.parse['parameters']
57
+ resp.parse['parameters'].each { |k, v| msg.sub! "%#{k}", v } if resp.parse['parameters'].is_a? Hash
60
58
  rescue StandardError
61
59
  msg = 'Failed access to the magento server'
62
60
  errors = []
@@ -1,3 +1,3 @@
1
1
  module Magento
2
- VERSION = '0.15.0'
2
+ VERSION = '0.18.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magento
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wallas Faria
@@ -92,10 +92,17 @@ files:
92
92
  - README.md
93
93
  - lib/magento.rb
94
94
  - lib/magento/category.rb
95
+ - lib/magento/configuration.rb
95
96
  - lib/magento/country.rb
96
97
  - lib/magento/customer.rb
97
98
  - lib/magento/errors.rb
98
99
  - lib/magento/guest_cart.rb
100
+ - lib/magento/import.rb
101
+ - lib/magento/import/Image_finder.rb
102
+ - lib/magento/import/category.rb
103
+ - lib/magento/import/csv_reader.rb
104
+ - lib/magento/import/product.rb
105
+ - lib/magento/import/template/products.csv
99
106
  - lib/magento/invoice.rb
100
107
  - lib/magento/model.rb
101
108
  - lib/magento/model_mapper.rb