spree_core 1.1.3 → 1.1.4
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.
- data/app/assets/images/noimage/large.png +0 -0
- data/app/assets/javascripts/admin/admin.js.erb +14 -14
- data/app/assets/javascripts/admin/checkouts/edit.js +1 -1
- data/app/assets/javascripts/admin/spree_core.js +0 -2
- data/app/assets/javascripts/store/checkout.js +2 -2
- data/app/assets/stylesheets/store/screen.css.scss +1 -1
- data/app/controllers/spree/admin/images_controller.rb +5 -13
- data/app/controllers/spree/admin/mail_methods_controller.rb +7 -0
- data/app/controllers/spree/admin/orders/customer_details_controller.rb +1 -1
- data/app/controllers/spree/admin/payment_methods_controller.rb +7 -1
- data/app/controllers/spree/admin/products_controller.rb +5 -13
- data/app/controllers/spree/admin/prototypes_controller.rb +2 -2
- data/app/controllers/spree/checkout_controller.rb +10 -0
- data/app/controllers/spree/orders_controller.rb +9 -5
- data/app/helpers/spree/base_helper.rb +17 -4
- data/app/helpers/spree/checkout_helper.rb +1 -9
- data/app/helpers/spree/products_helper.rb +0 -4
- data/app/models/spree/address.rb +1 -1
- data/app/models/spree/app_configuration.rb +1 -0
- data/app/models/spree/calculator/price_sack.rb +5 -3
- data/app/models/spree/image.rb +3 -1
- data/app/models/spree/inventory_unit.rb +8 -6
- data/app/models/spree/line_item.rb +2 -1
- data/app/models/spree/order.rb +23 -2
- data/app/models/spree/payment/processing.rb +22 -20
- data/app/models/spree/preferences/preferable_class_methods.rb +2 -0
- data/app/models/spree/preferences/store.rb +20 -2
- data/app/models/spree/product.rb +35 -29
- data/app/models/spree/product/scopes.rb +21 -6
- data/app/models/spree/return_authorization.rb +1 -0
- data/app/models/spree/shipping_method.rb +3 -0
- data/app/models/spree/variant.rb +19 -9
- data/app/views/spree/admin/images/_form.html.erb +4 -8
- data/app/views/spree/admin/return_authorizations/_form.html.erb +17 -33
- data/app/views/spree/admin/shared/_head.html.erb +2 -15
- data/app/views/spree/admin/shared/_order_details.html.erb +1 -1
- data/app/views/spree/admin/shared/_routes.html.erb +8 -0
- data/app/views/spree/admin/shared/_translations.html.erb +17 -0
- data/app/views/spree/admin/tax_rates/index.html.erb +2 -2
- data/app/views/spree/admin/variants/_form.html.erb +2 -2
- data/app/views/spree/checkout/payment/_gateway.html.erb +1 -1
- data/app/views/spree/order_mailer/cancel_email.text.erb +5 -5
- data/app/views/spree/order_mailer/confirm_email.text.erb +7 -6
- data/app/views/spree/products/_thumbnails.html.erb +1 -1
- data/app/views/spree/shared/_google_analytics.html.erb +17 -16
- data/app/views/spree/shared/_head.html.erb +0 -3
- data/app/views/spree/shared/_order_details.html.erb +2 -3
- data/app/views/spree/shipment_mailer/shipped_email.text.erb +7 -7
- data/config/initializers/check_for_orphaned_preferences.rb +1 -1
- data/config/initializers/rails_5868.rb +8 -0
- data/config/locales/en.yml +17 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20121017010007_remove_not_null_constraint_from_products_on_hand.rb +11 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
- data/lib/generators/spree/dummy/templates/rails/database.yml +1 -1
- data/lib/generators/spree/install/templates/app/assets/javascripts/admin/all.js +2 -0
- data/lib/spree/core/mail_settings.rb +2 -1
- data/lib/spree/core/search/base.rb +5 -2
- data/lib/spree/core/testing_support/factories/tax_category_factory.rb +5 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/product_filters.rb +1 -1
- metadata +41 -42
- data/app/helpers/spree/account_helper.rb +0 -4
- data/app/helpers/spree/orders_helper.rb +0 -5
- data/app/helpers/spree/trackers_helper.rb +0 -4
- data/config/initializers/workarounds_for_ruby19.rb +0 -72
- data/lib/spree/core/relation_serialization.rb +0 -9
- data/lib/spree/core/testing_support/env.rb +0 -2
Binary file
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
//<%#encoding: UTF-8%>
|
2
2
|
//= require_self
|
3
3
|
|
4
4
|
/**
|
@@ -61,9 +61,9 @@ format_product_autocomplete = function(item){
|
|
61
61
|
|
62
62
|
html += "<div><h4>" + product['name'] + "</h4>";
|
63
63
|
if (product['master']) {
|
64
|
-
html += "<span><strong
|
64
|
+
html += "<span><strong>" + Spree.translations.sku + ":</strong>" + product['master']['sku'] + "</span>";
|
65
65
|
}
|
66
|
-
html += "<span><strong
|
66
|
+
html += "<span><strong>" + Spree.translations.on_hand + ":</strong>" + product['count_on_hand'] + "</span></div>";
|
67
67
|
}else{
|
68
68
|
// variant
|
69
69
|
var variant = item.data['variant'];
|
@@ -82,8 +82,8 @@ format_product_autocomplete = function(item){
|
|
82
82
|
}).join(", ")
|
83
83
|
|
84
84
|
html += "<div><h4>" + name + "</h4>";
|
85
|
-
html += "<span><strong
|
86
|
-
html += "<span><strong
|
85
|
+
html += "<span><strong>" + Spree.translations.sku + ":</strong>" + variant['sku'] + "</span>";
|
86
|
+
html += "<span><strong>" + Spree.translations.on_hand + ":</strong>" + variant['count_on_hand'] + "</span></div>";
|
87
87
|
}
|
88
88
|
|
89
89
|
return html
|
@@ -164,9 +164,9 @@ $.fn.product_autocomplete = function(){
|
|
164
164
|
$.fn.objectPicker = function(url){
|
165
165
|
$(this).tokenInput(url + "&authenticity_token=" + escape(AUTH_TOKEN), {
|
166
166
|
searchDelay : 600,
|
167
|
-
hintText : Spree.
|
168
|
-
noResultsText : Spree.
|
169
|
-
searchingText : Spree.
|
167
|
+
hintText : Spree.translations.type_to_search,
|
168
|
+
noResultsText : Spree.translations.no_results,
|
169
|
+
searchingText : Spree.translations.searching,
|
170
170
|
prePopulateFromInput : true
|
171
171
|
});
|
172
172
|
};
|
@@ -181,12 +181,12 @@ $.fn.userPicker = function(){
|
|
181
181
|
|
182
182
|
handle_date_picker_fields = function(){
|
183
183
|
$('.datepicker').datepicker({
|
184
|
-
dateFormat:
|
185
|
-
dayNames:
|
186
|
-
dayNamesMin:
|
187
|
-
monthNames:
|
188
|
-
prevText:
|
189
|
-
nextText:
|
184
|
+
dateFormat: Spree.translations.date_picker,
|
185
|
+
dayNames: Spree.translations.abbr_day_names,
|
186
|
+
dayNamesMin: Spree.translations.abbr_day_names,
|
187
|
+
monthNames: Spree.translations.month_names,
|
188
|
+
prevText: Spree.translations.previous,
|
189
|
+
nextText: Spree.translations.next,
|
190
190
|
showOn: "button",
|
191
191
|
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>",
|
192
192
|
buttonImageOnly: true
|
@@ -80,7 +80,7 @@ $(document).ready(function(){
|
|
80
80
|
$('#user_id').val(ui.item.data['id']);
|
81
81
|
$('#guest_checkout_true').prop("checked", false);
|
82
82
|
$('#guest_checkout_false').prop("checked", true);
|
83
|
-
$('#guest_checkout_false').
|
83
|
+
$('#guest_checkout_false').prop("disabled", false);
|
84
84
|
return true;
|
85
85
|
}
|
86
86
|
}).data("autocomplete")._renderItem = function(ul, item) {
|
@@ -16,7 +16,7 @@
|
|
16
16
|
state_input = $('p#' + region + 'state input');
|
17
17
|
|
18
18
|
if(states) {
|
19
|
-
selected = state_select.val();
|
19
|
+
selected = parseInt(state_select.val());
|
20
20
|
state_select.html('');
|
21
21
|
states_with_blank = [["",""]].concat(states);
|
22
22
|
$.each(states_with_blank, function(pos,id_nm) {
|
@@ -72,7 +72,7 @@
|
|
72
72
|
})(jQuery);
|
73
73
|
|
74
74
|
function disableSaveOnClick() {
|
75
|
-
$('form.
|
75
|
+
$('form.edit_order').submit(function() {
|
76
76
|
$(this).find(':submit, :image').attr('disabled', true).removeClass('primary').addClass('disabled');
|
77
77
|
});
|
78
78
|
}
|
@@ -19,30 +19,22 @@ module Spree
|
|
19
19
|
|
20
20
|
|
21
21
|
private
|
22
|
-
|
22
|
+
|
23
23
|
def location_after_save
|
24
24
|
admin_product_images_url(@product)
|
25
25
|
end
|
26
26
|
|
27
27
|
def load_data
|
28
|
-
@product = Product.
|
28
|
+
@product = Product.where(:permalink => params[:product_id]).first
|
29
29
|
@variants = @product.variants.collect do |variant|
|
30
30
|
[variant.options_text, variant.id]
|
31
31
|
end
|
32
|
-
@variants.insert(0, [I18n.t(:all),
|
32
|
+
@variants.insert(0, [I18n.t(:all), @product.master.id])
|
33
33
|
end
|
34
34
|
|
35
35
|
def set_viewable
|
36
|
-
|
37
|
-
|
38
|
-
@image.viewable = @product.master
|
39
|
-
else
|
40
|
-
@image.viewable_type = 'Spree::Variant'
|
41
|
-
@image.viewable_id = params[:image][:viewable_id]
|
42
|
-
end
|
43
|
-
else
|
44
|
-
@image.viewable = @product.master
|
45
|
-
end
|
36
|
+
@image.viewable_type = 'Spree::Variant'
|
37
|
+
@image.viewable_id = params[:image][:viewable_id]
|
46
38
|
end
|
47
39
|
|
48
40
|
def destroy_before
|
@@ -3,6 +3,13 @@ module Spree
|
|
3
3
|
class MailMethodsController < ResourceController
|
4
4
|
after_filter :initialize_mail_settings
|
5
5
|
|
6
|
+
def update
|
7
|
+
if params[:mail_method][:preferred_smtp_password].blank?
|
8
|
+
params[:mail_method].delete(:preferred_smtp_password)
|
9
|
+
end
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
6
13
|
def testmail
|
7
14
|
@mail_method = Spree::MailMethod.find(params[:id])
|
8
15
|
if TestMailer.test_email(@mail_method, respond_to?(:current_user) ? current_user : nil).deliver
|
@@ -10,7 +10,7 @@ module Spree
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def edit
|
13
|
-
country_id = Address.default.country
|
13
|
+
country_id = Address.default.country.id
|
14
14
|
@order.build_bill_address(:country_id => country_id) if @order.bill_address.nil?
|
15
15
|
@order.build_ship_address(:country_id => country_id) if @order.ship_address.nil?
|
16
16
|
end
|
@@ -27,7 +27,13 @@ module Spree
|
|
27
27
|
end
|
28
28
|
|
29
29
|
payment_method_params = params[ActiveModel::Naming.param_key(@payment_method)] || {}
|
30
|
-
|
30
|
+
attributes = params[:payment_method].merge(payment_method_params)
|
31
|
+
attributes.each do |k,v|
|
32
|
+
if k.include?("password") && attributes[k].blank?
|
33
|
+
attributes.delete(k)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
if @payment_method.update_attributes(attributes)
|
31
37
|
invoke_callbacks(:update, :after)
|
32
38
|
flash.notice = I18n.t(:successfully_updated, :resource => I18n.t(:payment_method))
|
33
39
|
respond_with(@payment_method, :location => edit_admin_payment_method_path(@payment_method))
|
@@ -19,13 +19,9 @@ module Spree
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
# override the destory method to set deleted_at value
|
23
|
-
# instead of actually deleting the product.
|
24
22
|
def destroy
|
25
|
-
@product = Product.
|
26
|
-
@product.
|
27
|
-
|
28
|
-
@product.variants_including_master.update_all(:deleted_at => Time.now)
|
23
|
+
@product = Product.where(:permalink => params[:id]).first!
|
24
|
+
@product.delete
|
29
25
|
|
30
26
|
flash.notice = I18n.t('notice_messages.product_deleted')
|
31
27
|
|
@@ -90,13 +86,9 @@ module Spree
|
|
90
86
|
includes([:master, {:variants => [:images, :option_values]}]).
|
91
87
|
page(params[:page]).
|
92
88
|
per(Spree::Config[:admin_products_per_page])
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# It seems to only work when the price is actually being sorted in the query
|
97
|
-
# To be investigated later.
|
98
|
-
@collection = @collection.group("spree_variants.price")
|
99
|
-
end
|
89
|
+
if params[:q][:s].include?("master_price")
|
90
|
+
@collection = @collection.group("spree_variants.price")
|
91
|
+
end
|
100
92
|
else
|
101
93
|
includes = [{:variants => [:images, {:option_values => :option_type}]}, {:master => :images}]
|
102
94
|
|
@@ -29,8 +29,8 @@ module Spree
|
|
29
29
|
private
|
30
30
|
|
31
31
|
def set_habtm_associations
|
32
|
-
@prototype.property_ids = params[:
|
33
|
-
@prototype.option_type_ids = params[:option_type][
|
32
|
+
@prototype.property_ids = params[:option_type].blank? ? [] : params[:property][:id]
|
33
|
+
@prototype.option_type_ids = params[:option_type].blank? ? [] : params[:option_type][:id]
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -6,6 +6,8 @@ module Spree
|
|
6
6
|
ssl_required
|
7
7
|
|
8
8
|
before_filter :load_order
|
9
|
+
before_filter :ensure_valid_state
|
10
|
+
|
9
11
|
rescue_from Spree::Core::GatewayError, :with => :rescue_from_spree_gateway_error
|
10
12
|
|
11
13
|
respond_to :html
|
@@ -36,6 +38,14 @@ module Spree
|
|
36
38
|
end
|
37
39
|
|
38
40
|
private
|
41
|
+
def ensure_valid_state
|
42
|
+
if params[:state] && params[:state] != 'cart' &&
|
43
|
+
!@order.checkout_steps.include?(params[:state])
|
44
|
+
@order.state = 'cart'
|
45
|
+
redirect_to checkout_path
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
39
49
|
# Provides a route to redirect after order completion
|
40
50
|
def completion_route
|
41
51
|
order_path(@order)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Spree
|
2
2
|
class OrdersController < BaseController
|
3
|
+
ssl_required :show
|
4
|
+
|
3
5
|
rescue_from ActiveRecord::RecordNotFound, :with => :render_404
|
4
6
|
helper 'spree/products'
|
5
7
|
|
@@ -13,7 +15,7 @@ module Spree
|
|
13
15
|
def update
|
14
16
|
@order = current_order
|
15
17
|
if @order.update_attributes(params[:order])
|
16
|
-
@order.line_items = @order.line_items.select {|li| li.quantity > 0 }
|
18
|
+
@order.line_items = @order.line_items.select { |li| li.quantity > 0 }
|
17
19
|
fire_event('spree.order.contents_changed')
|
18
20
|
respond_with(@order) { |format| format.html { redirect_to cart_path } }
|
19
21
|
else
|
@@ -57,14 +59,16 @@ module Spree
|
|
57
59
|
|
58
60
|
def empty
|
59
61
|
if @order = current_order
|
60
|
-
@order.
|
62
|
+
@order.empty!
|
61
63
|
end
|
62
64
|
|
63
65
|
redirect_to spree.cart_path
|
64
66
|
end
|
65
67
|
|
66
|
-
|
67
|
-
|
68
|
-
|
68
|
+
private
|
69
|
+
|
70
|
+
def accurate_title
|
71
|
+
@order && @order.completed? ? "#{Order.model_name.human} #{@order.number}" : t(:shopping_cart)
|
72
|
+
end
|
69
73
|
end
|
70
74
|
end
|
@@ -65,13 +65,22 @@ module Spree
|
|
65
65
|
|
66
66
|
def meta_data_tags
|
67
67
|
object = instance_variable_get('@'+controller_name.singularize)
|
68
|
-
meta = {
|
68
|
+
meta = {}
|
69
69
|
|
70
|
-
if object.kind_of?
|
70
|
+
if object.kind_of? ActiveRecord::Base
|
71
71
|
meta[:keywords] = object.meta_keywords if object[:meta_keywords].present?
|
72
72
|
meta[:description] = object.meta_description if object[:meta_description].present?
|
73
73
|
end
|
74
74
|
|
75
|
+
if meta[:description].blank? && object.kind_of?(Spree::Product)
|
76
|
+
meta[:description] = strip_tags(object.description)
|
77
|
+
end
|
78
|
+
|
79
|
+
meta.reverse_merge!({
|
80
|
+
:keywords => Spree::Config[:default_meta_keywords],
|
81
|
+
:description => Spree::Config[:default_meta_description]
|
82
|
+
})
|
83
|
+
|
75
84
|
meta.map do |name, content|
|
76
85
|
tag('meta', :name => name, :content => content)
|
77
86
|
end.join("\n")
|
@@ -86,9 +95,13 @@ module Spree
|
|
86
95
|
link_to image_tag(image_path), root_path
|
87
96
|
end
|
88
97
|
|
89
|
-
def flash_messages
|
98
|
+
def flash_messages(opts = {})
|
99
|
+
opts[:ignore_types] = [:commerce_tracking].concat(opts[:ignore_types] || [])
|
100
|
+
|
90
101
|
flash.each do |msg_type, text|
|
91
|
-
|
102
|
+
unless opts[:ignore_types].include?(msg_type)
|
103
|
+
concat(content_tag :div, text, :class => "flash #{msg_type}")
|
104
|
+
end
|
92
105
|
end
|
93
106
|
nil
|
94
107
|
end
|
@@ -1,15 +1,7 @@
|
|
1
1
|
module Spree
|
2
2
|
module CheckoutHelper
|
3
|
-
def checkout_states
|
4
|
-
if @order.payment and @order.payment.payment_method.payment_profiles_supported?
|
5
|
-
%w(address delivery payment confirm complete)
|
6
|
-
else
|
7
|
-
%w(address delivery payment complete)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
3
|
def checkout_progress
|
12
|
-
states =
|
4
|
+
states = @order.checkout_steps
|
13
5
|
items = states.map do |state|
|
14
6
|
text = t("order_state.#{state}").titleize
|
15
7
|
|
@@ -17,10 +17,6 @@ module Spree
|
|
17
17
|
raw(product.description.gsub(/(.*?)\r?\n\r?\n/m, '<p>\1</p>'))
|
18
18
|
end
|
19
19
|
|
20
|
-
def variant_images_hash(product)
|
21
|
-
product.variant_images.inject({}) { |h, img| (h[img.viewable_id] ||= []) << img; h }
|
22
|
-
end
|
23
|
-
|
24
20
|
def line_item_description(variant)
|
25
21
|
description = variant.product.description
|
26
22
|
if description.present?
|
data/app/models/spree/address.rb
CHANGED
@@ -77,6 +77,7 @@ module Spree
|
|
77
77
|
preference :s3_headers, :string, :default => "{\"Cache-Control\":\"max-age=31557600\"}"
|
78
78
|
preference :use_s3, :boolean, :default => false # Use S3 for images rather than the file system
|
79
79
|
preference :s3_protocol, :string
|
80
|
+
preference :s3_host_alias, :string
|
80
81
|
|
81
82
|
# searcher_class allows spree extension writers to provide their own Search class
|
82
83
|
def searcher_class
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require_dependency 'spree/calculator'
|
2
|
+
# For #to_d method on Ruby 1.8
|
3
|
+
require 'bigdecimal/util'
|
2
4
|
|
3
5
|
module Spree
|
4
6
|
class Calculator::PriceSack < Calculator
|
@@ -17,12 +19,12 @@ module Spree
|
|
17
19
|
# as object we always get line items, as calculable we have Coupon, ShippingMethod
|
18
20
|
def compute(object)
|
19
21
|
if object.is_a?(Array)
|
20
|
-
base = object.map { |o| o.respond_to?(:amount) ? o.amount : o.
|
22
|
+
base = object.map { |o| o.respond_to?(:amount) ? o.amount : BigDecimal(o.to_s) }.sum
|
21
23
|
else
|
22
|
-
base = object.respond_to?(:amount) ? object.amount : object.
|
24
|
+
base = object.respond_to?(:amount) ? object.amount : BigDecimal(object.to_s)
|
23
25
|
end
|
24
26
|
|
25
|
-
if base
|
27
|
+
if base < self.preferred_minimal_amount
|
26
28
|
self.preferred_normal_amount
|
27
29
|
else
|
28
30
|
self.preferred_discount_amount
|
data/app/models/spree/image.rb
CHANGED
@@ -9,7 +9,8 @@ module Spree
|
|
9
9
|
:styles => { :mini => '48x48>', :small => '100x100>', :product => '240x240>', :large => '600x600>' },
|
10
10
|
:default_style => :product,
|
11
11
|
:url => '/spree/products/:id/:style/:basename.:extension',
|
12
|
-
:path => ':rails_root/public/spree/products/:id/:style/:basename.:extension'
|
12
|
+
:path => ':rails_root/public/spree/products/:id/:style/:basename.:extension',
|
13
|
+
:convert_options => { :all => '-strip' }
|
13
14
|
# save the w,h of the original image (from which others can be calculated)
|
14
15
|
# we need to look at the write-queue for images which have not been saved yet
|
15
16
|
after_post_process :find_dimensions
|
@@ -22,6 +23,7 @@ module Spree
|
|
22
23
|
Spree::Image.attachment_definitions[:attachment][:s3_headers] = ActiveSupport::JSON.decode(Spree::Config[:s3_headers])
|
23
24
|
Spree::Image.attachment_definitions[:attachment][:bucket] = Spree::Config[:s3_bucket]
|
24
25
|
Spree::Image.attachment_definitions[:attachment][:s3_protocol] = Spree::Config[:s3_protocol] unless Spree::Config[:s3_protocol].blank?
|
26
|
+
Spree::Image.attachment_definitions[:attachment][:s3_host_alias] = Spree::Config[:s3_host_alias] unless Spree::Config[:s3_host_alias].blank?
|
25
27
|
end
|
26
28
|
|
27
29
|
Spree::Image.attachment_definitions[:attachment][:styles] = ActiveSupport::JSON.decode(Spree::Config[:attachment_styles])
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Spree
|
2
2
|
class InventoryUnit < ActiveRecord::Base
|
3
|
-
belongs_to :variant
|
4
|
-
belongs_to :order
|
5
|
-
belongs_to :shipment
|
6
|
-
belongs_to :return_authorization
|
3
|
+
belongs_to :variant
|
4
|
+
belongs_to :order
|
5
|
+
belongs_to :shipment
|
6
|
+
belongs_to :return_authorization
|
7
7
|
|
8
8
|
scope :backorder, where(:state => 'backordered')
|
9
9
|
|
@@ -107,8 +107,10 @@ module Spree
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def restock_variant
|
110
|
-
|
111
|
-
|
110
|
+
if Spree::Config[:track_inventory_levels]
|
111
|
+
variant.on_hand += 1
|
112
|
+
variant.save
|
113
|
+
end
|
112
114
|
end
|
113
115
|
end
|
114
116
|
end
|