business_catalyst 0.1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +13 -0
  6. data/Guardfile +13 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +104 -0
  9. data/Rakefile +1 -0
  10. data/business_catalyst.gemspec +24 -0
  11. data/lib/business_catalyst/csv/catalog_row.rb +29 -0
  12. data/lib/business_catalyst/csv/file_splitter.rb +125 -0
  13. data/lib/business_catalyst/csv/product_attribute.rb +129 -0
  14. data/lib/business_catalyst/csv/product_row.rb +110 -0
  15. data/lib/business_catalyst/csv/row.rb +108 -0
  16. data/lib/business_catalyst/csv/transformers/catalog_transformer.rb +31 -0
  17. data/lib/business_catalyst/csv/transformers/currency_transformer.rb +53 -0
  18. data/lib/business_catalyst/csv/transformers/invalid_input_error.rb +9 -0
  19. data/lib/business_catalyst/csv/transformers/product_attributes_transformer.rb +45 -0
  20. data/lib/business_catalyst/csv/transformers/product_code_transformer.rb +21 -0
  21. data/lib/business_catalyst/csv/transformers/seo_friendly_url_transformer.rb +38 -0
  22. data/lib/business_catalyst/csv/transformers/transformer.rb +22 -0
  23. data/lib/business_catalyst/csv/transformers/uri_array_transformer.rb +15 -0
  24. data/lib/business_catalyst/csv/transformers/uri_transformer.rb +14 -0
  25. data/lib/business_catalyst/csv/transformers.rb +58 -0
  26. data/lib/business_catalyst/version.rb +3 -0
  27. data/lib/business_catalyst.rb +40 -0
  28. data/spec/lib/business_catalyst/business_catalyst_spec.rb +34 -0
  29. data/spec/lib/business_catalyst/csv/catalog_row_spec.rb +5 -0
  30. data/spec/lib/business_catalyst/csv/file_splitter_spec.rb +103 -0
  31. data/spec/lib/business_catalyst/csv/product_attribute_spec.rb +65 -0
  32. data/spec/lib/business_catalyst/csv/product_row_spec.rb +5 -0
  33. data/spec/lib/business_catalyst/csv/row_spec.rb +110 -0
  34. data/spec/lib/business_catalyst/csv/transformers/catalog_transformer_spec.rb +37 -0
  35. data/spec/lib/business_catalyst/csv/transformers/currency_transformer_spec.rb +65 -0
  36. data/spec/lib/business_catalyst/csv/transformers/product_attributes_transformer_spec.rb +76 -0
  37. data/spec/lib/business_catalyst/csv/transformers/seo_friendly_url_transformer_spec.rb +36 -0
  38. data/spec/lib/business_catalyst/csv/transformers/transformer_spec.rb +25 -0
  39. data/spec/lib/business_catalyst/csv/transformers/urI_transformer_spec.rb +17 -0
  40. data/spec/lib/business_catalyst/csv/transformers_spec.rb +81 -0
  41. data/spec/spec_helper.rb +6 -0
  42. metadata +140 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3417d7cd299af29d9ef7c26b7fd7f4b6667c2ed5
4
+ data.tar.gz: 5574296fb9838e4fcbf76c4a89b2319866fb0b75
5
+ SHA512:
6
+ metadata.gz: c83fe2e411426b7ab1fb19ed55a10265f33e66f264d75bbe33e821161bfb1896fbfb4ccb74541e34e11e8c36a6452dc68c69df500f6878bdca209b42e417c41a
7
+ data.tar.gz: 814db6e8948d5dde147520763164662b78dae8fed2e8dae662b1739e6641c5d4f225744856125c1dd9bad25d45f46d3827019ed6888d64af98ba83375396d5e9
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ bundler_args: --without guard
2
+ script:
3
+ - "bundle exec rspec spec"
4
+ language: ruby
5
+ rvm:
6
+ - 2.1.0
7
+ - 2.0.0
8
+ - 1.9.3
9
+ - 1.8.7
10
+ - jruby-18mode # JRuby in 1.8 mode
11
+ - jruby-19mode # JRuby in 1.9 mode
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in business_catalyst.gemspec
4
+ gemspec
5
+
6
+ # Skip guard if we are on 1.8 (it won't work).
7
+ # Guard also needs to be in its own group so Travis won't waste time installing it.
8
+ if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new("1.9.3")
9
+ group :guard do
10
+ gem 'ruby_gntp'
11
+ gem 'guard-rspec'
12
+ end
13
+ end
data/Guardfile ADDED
@@ -0,0 +1,13 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec,
5
+ :cmd => "bundle exec rspec",
6
+ :all_after_pass => true,
7
+ :all_on_start => true,
8
+ :failed_mode => :keep do
9
+ watch(%r{^spec/.+_spec\.rb$})
10
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
11
+ watch('spec/spec_helper.rb') { "spec" }
12
+ end
13
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Tortus Technologies, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # BusinessCatalyst
2
+
3
+ Tools for building CSV's for Adobe Business Catalyst e-commerce platform in Ruby.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'business_catalyst', '~> 0.1.0'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ ## Usage
16
+
17
+ Start with subclassing any of the provided Row classes:
18
+
19
+ * BusinessCatalyst::CSV::ProductRow
20
+ * BusinessCatalyst::CSV::CatalogRow
21
+
22
+ Then define methods to return values for the various BC columns.
23
+ Use the #map method to ensure correct naming. (In general, it is just the
24
+ underscored version of the BC column name, with '?' appended for
25
+ boolean columns. The COLUMNS constant in the base class has the complete
26
+ list, and I recommend referring to it extensively.)
27
+
28
+ ### Building a Product CSV
29
+
30
+ ```ruby
31
+ class MyRow < BusinessCatalyst::CSV::ProductRow
32
+
33
+ default_currency "US" # optional, US is already the default
34
+
35
+ def initialize(product)
36
+ @product = product
37
+ super
38
+ end
39
+
40
+ map(:product_code) { @product.code }
41
+ map(:name) { @product.name }
42
+ map(:catalog) { ["Some", "Accessories"] }
43
+ map(:enabled?) { @product.active }
44
+ map(:inventory_control?) { false }
45
+
46
+ # ... (other properties here)
47
+ end
48
+
49
+ # Later, with a CSV opened for writing:
50
+
51
+ csv << MyRow.headers
52
+ products.each do |product|
53
+ csv << MyRow.new(product).to_a
54
+ end
55
+ ```
56
+
57
+ ### Returning Ruby data types in your methods
58
+
59
+ In general, you can define your methods to return Ruby types such as Array,
60
+ Float (try to use BigDecimal or FixNum for currency though), True/False, etc.,
61
+ and the gem will turn it into the correct text for Business Catalyst for you,
62
+ and handle all escaping.
63
+
64
+ ```ruby
65
+ # Array:
66
+ map(:catalog) { ["Value1", "Value2"] } # becomes: "Value1;Value2"
67
+
68
+ # Numeric:
69
+ map(:sell_price) { 10.0 } # becomes: "US/10.0"
70
+
71
+ # boolean:
72
+ map(:enabled?) { true } # becomes: "Y"
73
+ ```
74
+
75
+ ### If you have more than 10k product rows to import, use BusinessCatalyst::CSV::FileSplitter
76
+
77
+ Business Catalyst limits the number of products you can import in a single
78
+ CSV to 10000. However, you can get around this limit by importing multiple
79
+ files. We have a class to help!
80
+
81
+ ```ruby
82
+ # Simplest usage, assuming we already have some products to export:
83
+ splitter = BusinessCatalyst::CSV::FileSplitter.new("output_base_name.csv", header_row: MyRow.headers)
84
+ splitter.start do |splitter|
85
+ product_rows.each do |row|
86
+ splitter.start_row
87
+ splitter.current_file << row.to_a
88
+ end
89
+ end
90
+ ```
91
+ See the class definition for all available options.
92
+
93
+ ## Contributing
94
+
95
+ 1. Fork it
96
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
97
+ 3. Run tests:
98
+
99
+ $ bundle install
100
+ $ bundle exec rspec
101
+
102
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 5. Push to the branch (`git push origin my-new-feature`)
104
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'business_catalyst/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "business_catalyst"
8
+ spec.version = BusinessCatalyst::VERSION
9
+ spec.authors = ["Tortus Tek, Inc."]
10
+ spec.email = ["support@tortus.com"]
11
+ spec.description = %q{Libraries to help with exporting product data to BC.}
12
+ spec.summary = "business_catalyst-#{BusinessCatalyst::VERSION}"
13
+ spec.homepage = "https://github.com/tortus/business_catalyst"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake", "~> 10"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+ end
@@ -0,0 +1,29 @@
1
+ module BusinessCatalyst
2
+ module CSV
3
+ class CatalogRow < Row
4
+
5
+ COLUMNS = [
6
+ ["Catalog Name/Heirarchy", :catalog_name_hierarchy, nil, CatalogTransformer],
7
+ ["Description", :description],
8
+ ["Image", :image],
9
+ ["Weighting", :weighting],
10
+ ["Release Date", :release_date],
11
+ ["Expiry Date", :expiration_date],
12
+ ["Template ID", :template_id, 0, TemplateIDTransformer],
13
+ ["Enabled", :enabled?, true, BooleanTransformer],
14
+ ["Enable XML Feed", :enable_xml_feed?, true, BooleanTransformer],
15
+ ["Show Product Prices", :show_product_prices?, true, BooleanTransformer],
16
+ ["Catalog Title", :catalog_title],
17
+ ["Browse Panel Min Price", :browse_panel_min_price, nil, CurrencyTransformer],
18
+ ["Browse Panel Max Price", :browse_panel_max_price, nil, CurrencyTransformer],
19
+ ["Browse Panel Slots", :browse_panel_min_slots],
20
+ ["SEO Friendly URL", :seo_friendly_url, nil, SEOFriendlyUrlTransformer] # Must be globally unique
21
+ ]
22
+
23
+ def self.columns
24
+ COLUMNS
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+ require 'csv'
3
+ require 'fileutils'
4
+
5
+ module BusinessCatalyst
6
+ module CSV
7
+
8
+ # Business Catalyst can only import 10k product rows at a time.
9
+ # Use this class to help split up files.
10
+ #
11
+ # == Usage
12
+ #
13
+ # headers = MyRowClass.headers
14
+ # splitter = FileSplitter.new("output_base_name", max_rows_per_file: 10_000, header_row: headers)
15
+ # splitter.start do |splitter|
16
+ # product_rows.each do |row|
17
+ # splitter.start_row
18
+ # splitter.current_file << row.to_a
19
+ # end
20
+ # end
21
+ #
22
+ # If you want to watch for when a new file is opened and maybe
23
+ # do some logging, you can use the #on_file_change method:
24
+ #
25
+ # splitter.on_file_change do |splitter|
26
+ # puts "opened #{splitter.current_file.path}"
27
+ # end
28
+ #
29
+ class FileSplitter
30
+
31
+ attr_accessor :base_name, :max_rows_per_file, :header_row, :verbose, :logger
32
+ attr_reader :current_row, :total_rows, :current_file, :all_files
33
+
34
+ alias_method :file_names, :all_files
35
+
36
+ def initialize(base_name, options = {})
37
+ @base_name = base_name
38
+ @max_rows_per_file = options.fetch(:max_rows_per_file) { 10_000 }
39
+ @header_row = options.fetch(:header_row) { nil }
40
+ @verbose = options.fetch(:verbose) { false }
41
+ @logger = options.fetch(:logger) { nil }
42
+
43
+ @current_row = 0
44
+ @total_rows = 0
45
+
46
+ @all_files = []
47
+ @current_file = nil
48
+ end
49
+
50
+ def start
51
+ begin
52
+ increment_file(1, max_rows_per_file) # will be off by one unless we set manually
53
+ yield self
54
+ ensure
55
+ close
56
+ end
57
+ rename_last_file
58
+ end
59
+
60
+ # Call before writing new row
61
+ def start_row
62
+ @current_row += 1
63
+ @total_rows += 1
64
+ if @current_row > max_rows_per_file
65
+ increment_file
66
+ @current_row = 1
67
+ end
68
+ end
69
+
70
+ def on_file_change(&block)
71
+ @on_file_change = Proc.new(&block)
72
+ end
73
+
74
+ def close
75
+ current_file.close if current_file
76
+ end
77
+
78
+ private
79
+
80
+ def increment_file(min_row = total_rows, max_row = total_rows + max_rows_per_file - 1)
81
+ @current_file.close if @current_file
82
+ new_file_name = file_name_with_range(base_name, min_row, max_row)
83
+ @all_files << new_file_name
84
+ log "writing to #{new_file_name}" if verbose
85
+ @current_file = ::CSV.open(new_file_name, 'wb')
86
+ if header_row
87
+ @current_file << header_row
88
+ end
89
+ trigger_on_file_change
90
+ end
91
+
92
+ def trigger_on_file_change
93
+ if @on_file_change.kind_of?(Proc)
94
+ if @on_file_change.arity >= 1
95
+ @on_file_change.call(self)
96
+ else
97
+ @on_file_change.call
98
+ end
99
+ end
100
+ end
101
+
102
+ def file_name_with_range(base_name, min, max)
103
+ "#{base_name}_#{min}-#{max}.csv"
104
+ end
105
+
106
+ def rename_last_file
107
+ last_file = @all_files.pop
108
+ if last_file
109
+ new_name = last_file.sub(/-\d+\.csv\z/i, "-#{total_rows}.csv")
110
+ FileUtils.mv last_file, new_name
111
+ @all_files << new_name
112
+ end
113
+ end
114
+
115
+ def log(msg)
116
+ if logger
117
+ logger.info msg
118
+ else
119
+ puts msg
120
+ end
121
+ end
122
+
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+ module BusinessCatalyst
3
+ module CSV
4
+
5
+ class ProductAttribute
6
+ Option = Struct.new(:name, :image, :price)
7
+
8
+ attr_accessor :name, :required, :keep_stock
9
+ attr_reader :display_as
10
+
11
+ def initialize(name, options = {})
12
+ @name = name
13
+ self.display_as = options.delete(:display_as) { nil }
14
+ @required = options.delete(:required) { false }
15
+ @keep_stock = options.delete(:keep_stock) { false }
16
+ unless options.empty?
17
+ raise ArgumentError, "unrecognized arguments: #{options.keys.inspect}"
18
+ end
19
+ end
20
+
21
+ def display_as=(value)
22
+ @display_as = self.class.normalize_display_as(value)
23
+ end
24
+
25
+ def options
26
+ @options ||= []
27
+ end
28
+
29
+ # Usage:
30
+ #
31
+ # # Simplest:
32
+ # add_option(name, image, price)
33
+ #
34
+ # # Manually create ProductAttribute::Option instance:
35
+ # add_option(ProductAttribute::Option.new(name, image, price))
36
+ #
37
+ def add_option(*args)
38
+ option = nil
39
+ if args.length == 1
40
+ arg = args.first
41
+ if arg.kind_of?(Option)
42
+ option = arg
43
+ elsif arg.kind_of?(Hash)
44
+ option = Option.new(arg.fetch(:name), arg.fetch(:image, nil), arg.fetch(:price, nil))
45
+ else
46
+ raise ArgumentError, "unrecognized argument: #{arg.inspect}"
47
+ end
48
+ else
49
+ name, image, price = *args
50
+ option = Option.new(name, image, price)
51
+ end
52
+ options << option
53
+ option
54
+ end
55
+
56
+ # Usage:
57
+ #
58
+ # # List of names:
59
+ # add_options("Name 1", "Name 2", ...)
60
+ #
61
+ # # Arrays:
62
+ # add_options([name, image, price], [name, image, price], ...)
63
+ #
64
+ # # Hashes:
65
+ # add_options({name: name, image: image, price: price}, ...)
66
+ #
67
+ # # ProductAttribute::Option instances:
68
+ # option_1 = ProductAttribute::Option.new(name, image, price)
69
+ # add_options(option_1, ...)
70
+ #
71
+ def add_options(*args)
72
+ if args.length > 1
73
+ options = args.map {|arg| Array(arg)}
74
+ elsif args.first.respond_to?(:each)
75
+ options = args.first
76
+ else
77
+ raise ArgumentError, "options must be an Array of ProductAttribute::Option"
78
+ end
79
+
80
+ options.each do |option|
81
+ if option.kind_of?(Hash)
82
+ name = option.fetch(:name)
83
+ image = option.fetch(:image, nil)
84
+ price = option.fetch(:price, nil)
85
+ add_option(name, image, price)
86
+
87
+ elsif option.kind_of?(Array)
88
+ name, image, price = *option
89
+ add_option(name, image, price)
90
+
91
+ elsif option.kind_of?(Option)
92
+ add_option(option)
93
+
94
+ else
95
+ raise ArgumentError, "#{option} is not a valid ProductAttribute::Option"
96
+ end
97
+ end
98
+ end
99
+
100
+ # Convert display_as to integer for BC, raises ArgumentError
101
+ # if input cannot be mapped to a valid integer. The following
102
+ # symbols are accepted:
103
+ #
104
+ # * :dropdown = 5
105
+ # * :checkbox = 6
106
+ # * :radio = 7
107
+ def self.normalize_display_as(display_as)
108
+ if display_as.kind_of?(Symbol)
109
+ case display_as
110
+ when :dropdown
111
+ 5
112
+ when :checkbox
113
+ 6
114
+ when :radio
115
+ 7
116
+ else
117
+ raise ArgumentError, "display_as must be :dropdown, :checkbox, or :radio"
118
+ end
119
+ elsif ![5, 6, 7].include?(display_as)
120
+ raise ArgumentError, "display_as must be 5, 6, or 7"
121
+ else
122
+ display_as
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+ module BusinessCatalyst
3
+ module CSV
4
+
5
+ # Subclass and call #map to set values for columns. You may return
6
+ # arrays for columns that take multiple values, like catalog,
7
+ # and true/false for boolean columns.
8
+ #
9
+ # nil will always be interpreted as blank.
10
+ #
11
+ # == What method name to use?
12
+ #
13
+ # Refer to the COLUMNS constant, but in general use the
14
+ # underscored name of the column, with '?' appended to columns
15
+ # that expect a boolean.
16
+ #
17
+ # == Example
18
+ #
19
+ # class MyRow < BusinessCatalyst::CSV::ProductRow
20
+ #
21
+ # def initialize(product)
22
+ # @product = product
23
+ # super
24
+ # end
25
+ #
26
+ # map(:product_code) { @product.code }
27
+ # map(:name) { @product.name }
28
+ # map(:catalog) { ["Some", "Accessories"] }
29
+ # map(:enabled?) { @product.active }
30
+ # map(:inventory_control?) { false }
31
+ #
32
+ # # ... (other properties here)
33
+ # end
34
+ #
35
+ # Later, with a CSV opened for writing:
36
+ #
37
+ # csv << MyRow.headers
38
+ # products.each do |product|
39
+ # csv << MyRow.new(product).to_a
40
+ # end
41
+ #
42
+ class ProductRow < Row
43
+
44
+ def self.columns
45
+ COLUMNS
46
+ end
47
+
48
+ # [Header, method, default, transformer]
49
+ COLUMNS = [
50
+ ["Product Code", :product_code, nil, ProductCodeTransformer],
51
+ ["Name", :name],
52
+ ["Description", :description],
53
+ ["Small Image", :small_image, nil, URITransformer],
54
+ ["Large Image", :large_image, nil, URITransformer],
55
+ ["Catalog", :catalog, [], CatalogTransformer], # Can be an array of path names, or an array of arrays of path names (multiple catalogs)
56
+ ["Sell Price", :sell_price, nil, CurrencyTransformer],
57
+ ["Recommended Retail Price", :recommended_retail_price, nil, CurrencyTransformer],
58
+ ["Tax Code", :tax_code],
59
+ ["SEO friendly URL", :seo_friendly_url],
60
+ ["Grouping Product Codes", :grouping_product_codes, nil, ArrayTransformer],
61
+ ["Grouping Product Descriptions", :grouping_product_descriptions, nil, ArrayTransformer],
62
+ ["Supplier CRM ID", :supplier_crm_id],
63
+ ["Supplier Commission Percentage", :supplier_commission_percentage],
64
+ ["Weighting/Order", :weighting_order],
65
+ ["Related Products", :related_products, [], ArrayTransformer],
66
+ ["Weight In KG/Pounds", :weight_in_kg_or_pounds],
67
+ ["Product Width (Previously Volume)", :product_width],
68
+ ["Keywords", :keywords],
69
+ ["Unit Type", :unit_type],
70
+ ["Min Units", :min_units],
71
+ ["Max Units", :max_units],
72
+ ["In Stock", :in_stock],
73
+ ["On Order", :on_order],
74
+ ["Re-order Threshold", :re_order_threshold],
75
+ ["Inventory Control", :inventory_control?, false, BooleanTransformer],
76
+ ["Can Pre-Order", :can_pre_order?, false, BooleanTransformer],
77
+ ["Custom 1", :custom_1],
78
+ ["Custom 2", :custom_2],
79
+ ["Custom 3", :custom_3],
80
+ ["Custom 4", :custom_4],
81
+ ["Poplets", :poplets, [], URIArrayTransformer],
82
+ ["Enabled", :enabled?, true, BooleanTransformer],
83
+ ["Capture Details", :capture_details?, false, BooleanTransformer],
84
+ ["Limit Download Count", :limit_download_count],
85
+ ["Limit Download IP", :limit_download_ip],
86
+ ["On Sale", :on_sale?, false, BooleanTransformer],
87
+ ["Hide if Out of Stock", :hide_if_out_of_stock?, false, BooleanTransformer],
88
+ ["Attributes", :attributes, nil, ProductAttributesTransformer],
89
+ ["Gift Voucher", :gift_voucher?, false, BooleanTransformer],
90
+ ["Drop Shipping", :drop_shipping?, false, BooleanTransformer],
91
+ ["Product Height", :product_height],
92
+ ["Product Depth", :product_depth],
93
+ ["Exclude From Search Results", :exclude_from_search_results?, false, BooleanTransformer],
94
+ ["Product Title", :product_title],
95
+ ["Wholesale Sale Price", :wholesale_sale_price, nil, CurrencyTransformer],
96
+ ["Tax Code", :tax_code],
97
+ ["Cycle Type", :cycle_type],
98
+ ["Cycle Type Count", :cycle_type_count],
99
+ ["Product URL", :product_url],
100
+ ["Product Affiliate URL", :product_affiliate_url],
101
+ ["Variations Enabled", :variations_enabled?, false, BooleanTransformer],
102
+ ["Variations Code", :variations_code],
103
+ ["Variation Options", :variations_options],
104
+ ["Role Responsible", :role_responsible],
105
+ ["Product Meta Description", :product_meta_description]
106
+ ]
107
+
108
+ end
109
+ end
110
+ end