spree_promo 1.1.1 → 1.1.2.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/app/assets/javascripts/admin/promotions.js +5 -9
- data/app/models/spree/order_decorator.rb +6 -1
- data/app/models/spree/promotion/actions/create_adjustment.rb +38 -33
- data/app/models/spree/promotion/actions/create_line_items.rb +26 -21
- data/app/models/spree/promotion/rules/first_order.rb +8 -4
- data/app/models/spree/promotion/rules/item_total.rb +12 -8
- data/app/models/spree/promotion/rules/product.rb +34 -21
- data/app/models/spree/promotion/rules/user.rb +17 -11
- data/app/models/spree/promotion/rules/user_logged_in.rb +13 -8
- data/app/models/spree/promotion.rb +2 -0
- data/app/models/spree/promotion_action.rb +3 -1
- data/app/models/spree/promotion_action_line_item.rb +1 -1
- data/app/models/spree/promotion_rule.rb +1 -1
- data/app/views/spree/admin/promotions/actions/_create_line_items.html.erb +1 -1
- metadata +11 -14
@@ -40,19 +40,15 @@ var initProductActions = function(){
|
|
40
40
|
|
41
41
|
// Remove line item
|
42
42
|
var setupRemoveLineItems = function(){
|
43
|
-
$(".
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
var index = $row.parents('table').find('tr').index($row.get(0));
|
48
|
-
// Remove variant_id quantity pair from the string
|
49
|
-
var items = _($hiddenField.val().split(',')).compact();
|
50
|
-
items.splice(index - 1, 1);
|
51
|
-
$hiddenField.val(items.join(','));
|
43
|
+
$(".remove_promotion_line_item").click(function(){
|
44
|
+
line_items_el = $($('.line_items_string')[0])
|
45
|
+
finder = RegExp($(this).data("variant-id") + "x\\d+")
|
46
|
+
line_items_el.val(line_items_el.val().replace(finder, ""))
|
52
47
|
$(this).parents('tr').remove();
|
53
48
|
hideOrShowItemTables();
|
54
49
|
});
|
55
50
|
};
|
51
|
+
|
56
52
|
setupRemoveLineItems();
|
57
53
|
// Add line item to list
|
58
54
|
$(".promotion_action.create_line_items button.add").unbind('click').click(function(e){
|
@@ -18,7 +18,12 @@ Spree::Order.class_eval do
|
|
18
18
|
update_adjustments_without_promotion_limiting
|
19
19
|
return if adjustments.promotion.eligible.none?
|
20
20
|
most_valuable_adjustment = adjustments.promotion.eligible.max{|a,b| a.amount.abs <=> b.amount.abs}
|
21
|
-
(
|
21
|
+
current_adjustments = (adjustments.promotion.eligible - [most_valuable_adjustment])
|
22
|
+
current_adjustments.each do |adjustment|
|
23
|
+
if adjustment.originator.calculator.is_a?(Spree::Calculator::PerItem)
|
24
|
+
adjustment.update_attribute_without_callbacks(:eligible, false)
|
25
|
+
end
|
26
|
+
end
|
22
27
|
end
|
23
28
|
alias_method_chain :update_adjustments, :promotion_limiting
|
24
29
|
end
|
@@ -1,43 +1,48 @@
|
|
1
1
|
module Spree
|
2
|
-
class Promotion
|
3
|
-
|
2
|
+
class Promotion
|
3
|
+
module Actions
|
4
|
+
class CreateAdjustment < PromotionAction
|
5
|
+
calculated_adjustments
|
4
6
|
|
5
|
-
|
7
|
+
delegate :eligible?, :to => :promotion
|
6
8
|
|
7
|
-
|
9
|
+
before_validation :ensure_action_has_calculator
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def perform(options = {})
|
12
|
+
return unless order = options[:order]
|
13
|
+
# Nothing to do if the promotion is already associated with the order
|
14
|
+
return if order.promotion_credit_exists?(promotion)
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
order.adjustments.promotion.reload.clear
|
17
|
+
order.update!
|
18
|
+
create_adjustment("#{I18n.t(:promotion)} (#{promotion.name})", order, order)
|
19
|
+
order.update!
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
# override of CalculatedAdjustments#create_adjustment so promotional
|
23
|
+
# adjustments are added all the time. They will get their eligability
|
24
|
+
# set to false if the amount is 0
|
25
|
+
def create_adjustment(label, target, calculable, mandatory=false)
|
26
|
+
amount = compute_amount(calculable)
|
27
|
+
params = { :amount => amount,
|
28
|
+
:source => calculable,
|
29
|
+
:originator => self,
|
30
|
+
:label => label,
|
31
|
+
:mandatory => mandatory }
|
32
|
+
target.adjustments.create(params, :without_protection => true)
|
33
|
+
end
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
# Ensure a negative amount which does not exceed the sum of the order's item_total and ship_total
|
36
|
+
def compute_amount(calculable)
|
37
|
+
[(calculable.item_total + calculable.ship_total), super.to_f.abs].min * -1
|
38
|
+
end
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
private
|
41
|
+
def ensure_action_has_calculator
|
42
|
+
return if self.calculator
|
43
|
+
self.calculator = Calculator::FlatPercentItemTotal.new
|
44
|
+
end
|
45
|
+
end
|
41
46
|
end
|
42
47
|
end
|
43
|
-
end
|
48
|
+
end
|
@@ -1,31 +1,36 @@
|
|
1
1
|
module Spree
|
2
|
-
class Promotion
|
3
|
-
|
2
|
+
class Promotion
|
3
|
+
module Actions
|
4
|
+
class CreateLineItems < PromotionAction
|
5
|
+
has_many :promotion_action_line_items, :foreign_key => 'promotion_action_id'
|
4
6
|
|
5
|
-
|
7
|
+
attr_accessor :line_items_string
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
def perform(options = {})
|
10
|
+
return unless order = options[:order]
|
11
|
+
promotion_action_line_items.each do |item|
|
12
|
+
current_quantity = order.quantity_of(item.variant)
|
13
|
+
if current_quantity < item.quantity
|
14
|
+
order.add_variant(item.variant, item.quantity - current_quantity)
|
15
|
+
order.update!
|
16
|
+
end
|
17
|
+
end
|
13
18
|
end
|
14
|
-
end
|
15
|
-
end
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
def line_items_string
|
21
|
+
promotion_action_line_items.map { |li| "#{li.variant_id}x#{li.quantity}" }.join(',')
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
def line_items_string=(value)
|
25
|
+
promotion_action_line_items.destroy_all
|
26
|
+
value.to_s.split(',').each do |str|
|
27
|
+
variant_id, quantity = str.split('x')
|
28
|
+
if variant_id && quantity && variant = Variant.find_by_id(variant_id)
|
29
|
+
promotion_action_line_items.create({:variant => variant, :quantity => quantity.to_i}, :without_protection => true)
|
30
|
+
end
|
31
|
+
end
|
27
32
|
end
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
31
|
-
end
|
36
|
+
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
module Spree
|
2
|
-
class Promotion
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
class Promotion
|
3
|
+
module Rules
|
4
|
+
class FirstOrder < PromotionRule
|
5
|
+
def eligible?(order, options = {})
|
6
|
+
user = order.try(:user) || options[:user]
|
7
|
+
!!(user && user.orders.complete.count == 0)
|
8
|
+
end
|
9
|
+
end
|
6
10
|
end
|
7
11
|
end
|
8
12
|
end
|
@@ -1,16 +1,20 @@
|
|
1
1
|
# A rule to limit a promotion to a specific user.
|
2
2
|
module Spree
|
3
|
-
class Promotion
|
4
|
-
|
5
|
-
|
3
|
+
class Promotion
|
4
|
+
module Rules
|
5
|
+
class ItemTotal < PromotionRule
|
6
|
+
preference :amount, :decimal, :default => 100.00
|
7
|
+
preference :operator, :string, :default => '>'
|
6
8
|
|
7
|
-
|
9
|
+
attr_accessible :preferred_amount, :preferred_operator
|
8
10
|
|
9
|
-
|
11
|
+
OPERATORS = ['gt', 'gte']
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
def eligible?(order, options = {})
|
14
|
+
item_total = order.line_items.map(&:amount).sum
|
15
|
+
item_total.send(preferred_operator == 'gte' ? :>= : :>, BigDecimal.new(preferred_amount.to_s))
|
16
|
+
end
|
17
|
+
end
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
@@ -2,32 +2,45 @@
|
|
2
2
|
# Can require all or any of the products to be present.
|
3
3
|
# Valid products either come from assigned product group or are assingned directly to the rule.
|
4
4
|
module Spree
|
5
|
-
class Promotion
|
6
|
-
|
5
|
+
class Promotion
|
6
|
+
module Rules
|
7
|
+
class Product < PromotionRule
|
8
|
+
has_and_belongs_to_many :products, :class_name => '::Spree::Product', :join_table => 'spree_products_promotion_rules', :foreign_key => 'promotion_rule_id'
|
9
|
+
validate :only_one_promotion_per_product
|
7
10
|
|
8
|
-
|
9
|
-
|
11
|
+
MATCH_POLICIES = %w(any all)
|
12
|
+
preference :match_policy, :string, :default => MATCH_POLICIES.first
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
# scope/association that is used to test eligibility
|
15
|
+
def eligible_products
|
16
|
+
products
|
17
|
+
end
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
def eligible?(order, options = {})
|
20
|
+
return true if eligible_products.empty?
|
21
|
+
if preferred_match_policy == 'all'
|
22
|
+
eligible_products.all? {|p| order.products.include?(p) }
|
23
|
+
else
|
24
|
+
order.products.any? {|p| eligible_products.include?(p) }
|
25
|
+
end
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
def product_ids_string
|
29
|
+
product_ids.join(',')
|
30
|
+
end
|
28
31
|
|
29
|
-
|
30
|
-
|
32
|
+
def product_ids_string=(s)
|
33
|
+
self.product_ids = s.to_s.split(',').map(&:strip)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def only_one_promotion_per_product
|
39
|
+
if Spree::Promotion::Rules::Product.all.map(&:products).flatten.uniq!
|
40
|
+
errors[:base] << "You can't create two promotions for the same product"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
31
44
|
end
|
32
45
|
end
|
33
46
|
end
|
@@ -1,18 +1,24 @@
|
|
1
1
|
module Spree
|
2
|
-
class Promotion
|
3
|
-
|
4
|
-
|
2
|
+
class Promotion
|
3
|
+
module Rules
|
4
|
+
class User < PromotionRule
|
5
|
+
attr_accessible :user_ids_string
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
end
|
7
|
+
belongs_to :user, :class_name => "Spree::User"
|
8
|
+
has_and_belongs_to_many :users, :class_name => '::Spree::User', :join_table => 'spree_promotion_rules_users', :foreign_key => 'promotion_rule_id'
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def eligible?(order, options = {})
|
11
|
+
users.none? or users.include?(order.user)
|
12
|
+
end
|
13
|
+
|
14
|
+
def user_ids_string
|
15
|
+
user_ids.join(',')
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
|
18
|
+
def user_ids_string=(s)
|
19
|
+
self.user_ids = s.to_s.split(',').map(&:strip)
|
20
|
+
end
|
21
|
+
end
|
16
22
|
end
|
17
23
|
end
|
18
24
|
end
|
@@ -1,15 +1,20 @@
|
|
1
1
|
module Spree
|
2
|
-
class Promotion
|
3
|
-
|
2
|
+
class Promotion
|
3
|
+
module Rules
|
4
|
+
class UserLoggedIn < PromotionRule
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
def eligible?(order, options = {})
|
7
|
+
# this is tricky. We couldn't use any of the devise methods since we aren't in the controller.
|
8
|
+
# we need to rely on the controller already having done this for us.
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
# The thinking is that the controller should have some sense of what state
|
11
|
+
# we should be in before firing events,
|
12
|
+
# so the controller will have to set this field.
|
11
13
|
|
12
|
-
|
14
|
+
return options && options[:user_signed_in]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
@@ -14,6 +14,8 @@ module Spree
|
|
14
14
|
alias_method :actions, :promotion_actions
|
15
15
|
accepts_nested_attributes_for :promotion_actions
|
16
16
|
|
17
|
+
validates_associated :rules
|
18
|
+
|
17
19
|
attr_accessible :name, :event_name, :code, :match_policy,
|
18
20
|
:path, :advertise, :description, :usage_limit,
|
19
21
|
:starts_at, :expires_at, :promotion_rules_attributes,
|
@@ -2,10 +2,12 @@
|
|
2
2
|
# PromotionActions perform the necessary tasks when a promotion is activated by an event and determined to be eligible.
|
3
3
|
module Spree
|
4
4
|
class PromotionAction < ActiveRecord::Base
|
5
|
-
belongs_to :promotion, :foreign_key => 'activator_id'
|
5
|
+
belongs_to :promotion, :foreign_key => 'activator_id', :class_name => "Spree::Promotion"
|
6
6
|
|
7
7
|
scope :of_type, lambda {|t| {:conditions => {:type => t}}}
|
8
8
|
|
9
|
+
attr_accessible :line_items_string
|
10
|
+
|
9
11
|
# This method should be overriden in subclass
|
10
12
|
# Updates the state of the order or performs some other action depending on the subclass
|
11
13
|
# options will contain the payload from the event that activated the promotion. This will include
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Base class for all promotion rules
|
2
2
|
module Spree
|
3
3
|
class PromotionRule < ActiveRecord::Base
|
4
|
-
belongs_to :promotion, :foreign_key => 'activator_id'
|
4
|
+
belongs_to :promotion, :foreign_key => 'activator_id', :class_name => "Spree::Promotion"
|
5
5
|
|
6
6
|
scope :of_type, lambda {|t| {:conditions => {:type => t}}}
|
7
7
|
|
metadata
CHANGED
@@ -1,38 +1,38 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spree_promo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.2.rc1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- David North
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-25 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: spree_core
|
16
|
-
requirement: &
|
16
|
+
requirement: &70249530014760 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - =
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.1.
|
21
|
+
version: 1.1.2.rc1
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70249530014760
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: spree_auth
|
27
|
-
requirement: &
|
27
|
+
requirement: &70249530003060 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - =
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.1.
|
32
|
+
version: 1.1.2.rc1
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70249530003060
|
36
36
|
description: Required dependency for Spree
|
37
37
|
email: david@spreecommerce.com
|
38
38
|
executables: []
|
@@ -127,12 +127,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
128
|
none: false
|
129
129
|
requirements:
|
130
|
-
- - ! '
|
130
|
+
- - ! '>'
|
131
131
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
133
|
-
segments:
|
134
|
-
- 0
|
135
|
-
hash: -2121439115021991443
|
132
|
+
version: 1.3.1
|
136
133
|
requirements:
|
137
134
|
- none
|
138
135
|
rubyforge_project:
|