spree-line_item_discount 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +10 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +24 -0
- data/app/controllers/spree/admin/promotions_controller_decorator.rb +15 -0
- data/app/models/spree/adjustment_decorator.rb +22 -0
- data/app/models/spree/line_item_decorator.rb +20 -0
- data/app/models/spree/line_item_discount/adjust.rb +102 -0
- data/app/models/spree/line_item_discount/percent.rb +16 -0
- data/app/models/spree/line_item_discount/promotion_pool.rb +67 -0
- data/app/models/spree/order_decorator.rb +44 -0
- data/app/models/spree/order_updater_decorator.rb +40 -0
- data/app/models/spree/tax_rate_decorator.rb +12 -0
- data/app/views/spree/admin/promotions/actions/_adjust.html.erb +22 -0
- data/config/locales/en.yml +6 -0
- data/lib/spree/line_item_discount.rb +2 -0
- data/lib/spree/line_item_discount/engine.rb +25 -0
- data/lib/spree/line_item_discount/version.rb +5 -0
- data/lib/spree_line_item_discount.rb +1 -0
- data/spec/models/spree/line_item_discount/adjust_spec.rb +107 -0
- data/spec/models/spree/line_item_discount/promotion_pool_spec.rb +58 -0
- data/spec/models/spree/line_item_spec.rb +18 -0
- data/spec/models/spree/order_spec.rb +21 -0
- data/spec/models/spree/order_updater_spec.rb +64 -0
- data/spec/spec_helper.rb +37 -0
- data/spree_line_item_discount.gemspec +24 -0
- metadata +133 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: f9f715a59787c41fa9aa861b9eeb2e7b85a491cd
|
|
4
|
+
data.tar.gz: 6e97f1872121803605274f50b26d6ce0b0e0c089
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8d1b8275cb2650d48a2a2f857f348f97e72afa1813efd080c74e2fe4110025028972b19f9f21eabc7fe23e7b4e862bda9d8639af03e89f1bdbac2407a19e859b
|
|
7
|
+
data.tar.gz: 9878ca2787faf412ced9626779bed972c1b90e9d88560789dadcadebcfd9367366821264247842630af2faba8996257d9d48d9aebafdb5b100c0c4ccb9010ca5
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Washington Luiz
|
|
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,40 @@
|
|
|
1
|
+
# Spree Line Item Discounts
|
|
2
|
+
|
|
3
|
+
* Apply promo adjustments at the LineItem level on a Spree ~> 2.0 install
|
|
4
|
+
|
|
5
|
+
* Item Discount promos don't require `event_name` they should be set to nil
|
|
6
|
+
|
|
7
|
+
* Item Discount promos should only have one single action
|
|
8
|
+
|
|
9
|
+
* Adjustments should be inserted at the line item creation before the order
|
|
10
|
+
receive `update!`. Hopefully that will give room to make order changes way more
|
|
11
|
+
perfomant
|
|
12
|
+
|
|
13
|
+
* The same promo may apply adjustments to multiple line items on the order
|
|
14
|
+
|
|
15
|
+
* Multiple adjustments from different promos may be applied to the same line item
|
|
16
|
+
|
|
17
|
+
* The same adjustment cannot apply twice on the same item
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Add to your Gemfile, bundle install and you should be ready to go.
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem 'spree-line_item_discount', github: 'huoxito/spree-line_item_discount'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
1. Create a Promotion but do not choose any event name for the promotion.
|
|
30
|
+
2. Set up promotion rules as you please
|
|
31
|
+
3. Add a _Create line item discount_ action to the promotion
|
|
32
|
+
4. Set up the percent as you please (currently has only one Calculator)
|
|
33
|
+
|
|
34
|
+
## Contributing
|
|
35
|
+
|
|
36
|
+
1. Fork it
|
|
37
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
38
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
39
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
40
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'rake/testtask'
|
|
4
|
+
require 'rake/packagetask'
|
|
5
|
+
require 'rubygems/package_task'
|
|
6
|
+
require 'rspec/core/rake_task'
|
|
7
|
+
require 'spree/testing_support/common_rake'
|
|
8
|
+
|
|
9
|
+
Bundler::GemHelper.install_tasks
|
|
10
|
+
RSpec::Core::RakeTask.new
|
|
11
|
+
|
|
12
|
+
task :default => :spec
|
|
13
|
+
|
|
14
|
+
spec = eval(File.read('spree_line_item_discount.gemspec'))
|
|
15
|
+
|
|
16
|
+
Gem::PackageTask.new(spec) do |p|
|
|
17
|
+
p.gem_spec = spec
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Generates a dummy app for testing"
|
|
21
|
+
task :test_app do
|
|
22
|
+
ENV['LIB_NAME'] = 'spree_line_item_discount'
|
|
23
|
+
Rake::Task['common:test_app'].invoke
|
|
24
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
PromotionsController.class_eval do
|
|
4
|
+
protected
|
|
5
|
+
# Overriden from Spree core
|
|
6
|
+
#
|
|
7
|
+
# Give frontend users a chance to ignore event_name as promos by the
|
|
8
|
+
# extension should have event_name set to nil
|
|
9
|
+
def load_event_names
|
|
10
|
+
@event_names = Spree::Activator.event_names.map { |name| [Spree.t("events.#{name}"), name] }
|
|
11
|
+
@event_names.unshift []
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
Adjustment.class_eval do
|
|
3
|
+
scope :source_order, -> { where(source_type: "Spree::Order") }
|
|
4
|
+
|
|
5
|
+
# Overriden from Spree core
|
|
6
|
+
#
|
|
7
|
+
# Allow originator of the adjustment to perform an additional eligibility of the adjustment
|
|
8
|
+
# Should return _true_ if originator is absent or doesn't implement _eligible?_
|
|
9
|
+
#
|
|
10
|
+
# Pass the +adjustable+ object instead of the +source+ to +eligible?+ to allow
|
|
11
|
+
# the extension to check concurrent discounts for a line item. This shouldn't
|
|
12
|
+
# make any difference on the original mtehod for spree_core beacuse there
|
|
13
|
+
# both +adjustable+ and +source+ is the same Order object
|
|
14
|
+
#
|
|
15
|
+
# FIXME maybe we don't need to care about concurrent line item adjustments
|
|
16
|
+
# right now so passing just the order should be fine
|
|
17
|
+
def eligible_for_originator?
|
|
18
|
+
return true if originator.nil?
|
|
19
|
+
!originator.respond_to?(:eligible?) || originator.eligible?(adjustable)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
LineItem.class_eval do
|
|
3
|
+
# Trigger LineItemDiscount promos
|
|
4
|
+
#
|
|
5
|
+
# It makes more sense to run it here because the order did not receive
|
|
6
|
+
# +update!+ just yet. If it complied with the usual Spree AS::Notifications
|
|
7
|
+
# events promo triggers we would have to run +update!+ on the order for each
|
|
8
|
+
# promo which is not perfomant at all
|
|
9
|
+
after_create :activate_discounts
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
def activate_discounts
|
|
13
|
+
unless order.completed?
|
|
14
|
+
LineItemDiscount::Adjust.all.each do |adjust_discount|
|
|
15
|
+
adjust_discount.perform(self)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module LineItemDiscount
|
|
3
|
+
class Adjust < PromotionAction
|
|
4
|
+
include Core::CalculatedAdjustments
|
|
5
|
+
|
|
6
|
+
attr_reader :order, :line_items, :adjustable
|
|
7
|
+
|
|
8
|
+
has_many :adjustments, :as => :originator
|
|
9
|
+
|
|
10
|
+
before_validation :ensure_action_has_calculator
|
|
11
|
+
|
|
12
|
+
# decide whether adjustment should be deleted or not
|
|
13
|
+
before_destroy :deals_with_adjustments
|
|
14
|
+
|
|
15
|
+
# Create the adjustment
|
|
16
|
+
#
|
|
17
|
+
# It needs to create the adjustment here anyway because the action only
|
|
18
|
+
# performs when the line item is created.
|
|
19
|
+
#
|
|
20
|
+
# Doesn't need care about adjustment eligibility as that's done later on
|
|
21
|
+
# down the stack
|
|
22
|
+
def perform(line_item)
|
|
23
|
+
@order, @line_items = line_item.order, line_item
|
|
24
|
+
|
|
25
|
+
unless has_applied?(line_item)
|
|
26
|
+
amount = self.compute_amount(line_item)
|
|
27
|
+
adjustment = create_adjustment(amount: amount, adjustable: line_item, source: order)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Receives an adjustable object (here a LineItem)
|
|
32
|
+
#
|
|
33
|
+
# FIXME Remove concurrent discount logic
|
|
34
|
+
def eligible?(adjustable)
|
|
35
|
+
@adjustable, @order = adjustable, adjustable.order
|
|
36
|
+
self.promotion.eligible?(order) && best_than_concurrent_discounts?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Receives an adjustable object (here a LineItem)
|
|
40
|
+
#
|
|
41
|
+
# Returns total discount for the adjustable
|
|
42
|
+
def compute_amount(adjustable)
|
|
43
|
+
amount = self.calculator.compute(adjustable).to_f.abs
|
|
44
|
+
[adjustable.total, amount].min * -1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
def has_applied?(item)
|
|
49
|
+
self.adjustments.map(&:adjustable).flatten.include? item
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def create_adjustment(params)
|
|
53
|
+
self.adjustments.create(default_adjustment_params.merge(params), :without_protection => true)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def best_than_concurrent_discounts?
|
|
57
|
+
return false if current_discount == 0
|
|
58
|
+
if current_discount == best_concurrent_amount && best_concurrent_discount.eligible
|
|
59
|
+
return false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
current_discount <= best_concurrent_amount || !best_concurrent_discount.eligible
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def current_adjustment
|
|
66
|
+
@adjustment ||= self.adjustments.where("adjustable_id = ? AND adjustable_type = ?", adjustable.id, adjustable.class.name).first
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def current_discount
|
|
70
|
+
current_adjustment.amount
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def best_concurrent_amount
|
|
74
|
+
best_concurrent_discount ? best_concurrent_discount.amount : 0
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def best_concurrent_discount
|
|
78
|
+
adjustable.adjustments.promotion.eligible
|
|
79
|
+
.where('id NOT IN (?)', [current_adjustment]).max { |a,b| a.amount.abs <=> b.amount.abs }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def default_adjustment_params
|
|
83
|
+
{ :label => self.promotion.name, :mandatory => false }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def ensure_action_has_calculator
|
|
87
|
+
self.calculator = Percent.new unless self.calculator
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def deals_with_adjustments
|
|
91
|
+
self.adjustments.each do |adjustment|
|
|
92
|
+
if adjustment.source.complete?
|
|
93
|
+
adjustment.originator = nil
|
|
94
|
+
adjustment.save
|
|
95
|
+
else
|
|
96
|
+
adjustment.destroy
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module LineItemDiscount
|
|
3
|
+
class Percent < Calculator
|
|
4
|
+
preference :percent, :decimal, :default => 0
|
|
5
|
+
attr_accessible :preferred_percent
|
|
6
|
+
|
|
7
|
+
def self.description
|
|
8
|
+
Spree.t(:percent_per_item)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def compute(item)
|
|
12
|
+
((item.price * item.quantity) * preferred_percent) / 100
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module LineItemDiscount
|
|
3
|
+
# Compute adjustment amount and set their eligibility for all eligible promos
|
|
4
|
+
#
|
|
5
|
+
# The reason for doing it in a different class is that we only check the
|
|
6
|
+
# promotion eligibility once. It also should make it pretty easy to manage
|
|
7
|
+
# which adjustments should actually be computed. It doesn't make sense
|
|
8
|
+
# to compute values for adjustments that are not eligible. As the extension
|
|
9
|
+
# allows multiple adjustments for the same line item.
|
|
10
|
+
#
|
|
11
|
+
# e.g.
|
|
12
|
+
# Say we have 10 LineItemDiscount promos. An order with 5 items may have
|
|
13
|
+
# 50 adjustments. That would make order changes really slow as it will run
|
|
14
|
+
# +eligible?+ on 50 adjustments. Instead we group the data so that it
|
|
15
|
+
# only runs eligible? once on each promotion on each order change
|
|
16
|
+
#
|
|
17
|
+
# ps. watch out for scenarios where the promo eligibilty changes as adjustments
|
|
18
|
+
# are applied to the order total. Hopefully such cases don't exist and if
|
|
19
|
+
# they do we might just consider that adjustments or any probably other
|
|
20
|
+
# promotion action should not change the order eligibility among existing
|
|
21
|
+
# promotions
|
|
22
|
+
class PromotionPool
|
|
23
|
+
attr_reader :actions, :order
|
|
24
|
+
|
|
25
|
+
def initialize(order)
|
|
26
|
+
@order = order
|
|
27
|
+
@actions = Adjust.includes(:promotion).all
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns array of all eligible actions for the current order
|
|
31
|
+
def eligible
|
|
32
|
+
actions.select { |action| action.promotion.eligible? order }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns array of all line item adjustments eligible for current order
|
|
36
|
+
def valid_discounts
|
|
37
|
+
adjustments(eligible.map(&:id))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def invalid_discounts
|
|
41
|
+
adjustments(not_eligible.map(&:id))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def not_eligible
|
|
45
|
+
actions.select { |action| !action.promotion.eligible? order }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Update adjustment amounts for eligible actions
|
|
49
|
+
#
|
|
50
|
+
# Use +update_adjustment+ as it will not run any callbacks making
|
|
51
|
+
def adjust!
|
|
52
|
+
valid_discounts.each do |adjustment|
|
|
53
|
+
adjustment.originator.update_adjustment(adjustment, adjustment.adjustable)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
valid_discounts.update_all(eligible: true)
|
|
57
|
+
invalid_discounts.update_all(eligible: false)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
def adjustments(originator_ids)
|
|
62
|
+
Adjustment.promotion.source_order.includes(:originator)
|
|
63
|
+
.where(originator_id: originator_ids, source_id: order.id)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
Order.class_eval do
|
|
3
|
+
has_many :line_item_adjustments, through: :line_items, source: :adjustments
|
|
4
|
+
|
|
5
|
+
# Overriden from spree core
|
|
6
|
+
# So that we can also lock line_item adjustments
|
|
7
|
+
#
|
|
8
|
+
# TODO Refactoring spree core by adding an Order `finalize_hooks` method
|
|
9
|
+
# similar to what we currently have in OrderUpdater. That gives room
|
|
10
|
+
# to implement custom logic without having to override the +finalize!+ method
|
|
11
|
+
#
|
|
12
|
+
# Finalizes an in progress order after checkout is complete.
|
|
13
|
+
# Called after transition to complete state when payments will have been processed
|
|
14
|
+
def finalize!
|
|
15
|
+
touch :completed_at
|
|
16
|
+
|
|
17
|
+
# lock all adjustments (coupon promotions, etc.)
|
|
18
|
+
adjustments.update_all "state = 'closed'"
|
|
19
|
+
|
|
20
|
+
# As of Spree::LineItemDiscount
|
|
21
|
+
line_item_adjustments.update_all "state = 'closed'"
|
|
22
|
+
|
|
23
|
+
# update payment and shipment(s) states, and save
|
|
24
|
+
updater.update_payment_state
|
|
25
|
+
shipments.each do |shipment|
|
|
26
|
+
shipment.update!(self)
|
|
27
|
+
shipment.finalize!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
updater.update_shipment_state
|
|
31
|
+
save
|
|
32
|
+
updater.run_hooks
|
|
33
|
+
|
|
34
|
+
deliver_order_confirmation_email
|
|
35
|
+
|
|
36
|
+
self.state_changes.create({
|
|
37
|
+
previous_state: 'cart',
|
|
38
|
+
next_state: 'complete',
|
|
39
|
+
name: 'order' ,
|
|
40
|
+
user_id: self.user_id
|
|
41
|
+
}, without_protection: true)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
OrderUpdater.class_eval do
|
|
3
|
+
# Overriden from Spree core
|
|
4
|
+
# So that it sums line_items_adjustments total to order.adjustment_total
|
|
5
|
+
#
|
|
6
|
+
# Updates the following Order total values:
|
|
7
|
+
#
|
|
8
|
+
# +payment_total+ The total value of all finalized Payments (NOTE: non-finalized Payments are excluded)
|
|
9
|
+
# +item_total+ The total value of all LineItems
|
|
10
|
+
# +adjustment_total+ The total value of all adjustments (promotions, credits, etc.)
|
|
11
|
+
# +total+ The so-called "order total." This is equivalent to +item_total+ plus +adjustment_total+.
|
|
12
|
+
def update_totals
|
|
13
|
+
order.payment_total = payments.completed.map(&:amount).sum
|
|
14
|
+
order.item_total = line_items.map(&:amount).sum
|
|
15
|
+
order.adjustment_total = adjustments.eligible.map(&:amount).sum + items_adjustments_total
|
|
16
|
+
order.total = order.item_total + order.adjustment_total
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Overriden from Spree core
|
|
20
|
+
# As of LineItemDiscount it updates adjustments value and eligibility
|
|
21
|
+
#
|
|
22
|
+
# Updates each of the Order adjustments.
|
|
23
|
+
#
|
|
24
|
+
# This is intended to be called from an Observer so that the Order can
|
|
25
|
+
# respond to external changes to LineItem, Shipment, other Adjustments, etc.
|
|
26
|
+
#
|
|
27
|
+
# Adjustments will check if they are still eligible. Ineligible adjustments
|
|
28
|
+
# are preserved but not counted towards adjustment_total.
|
|
29
|
+
def update_adjustments
|
|
30
|
+
order.adjustments.reload.each { |adjustment| adjustment.update! }
|
|
31
|
+
choose_best_promotion_adjustment
|
|
32
|
+
LineItemDiscount::PromotionPool.new(order).adjust!
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
def items_adjustments_total
|
|
37
|
+
order.line_item_adjustments.eligible.sum(&:amount)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
TaxRate.class_eval do
|
|
3
|
+
def self.adjust(order)
|
|
4
|
+
order.adjustments.tax.destroy_all
|
|
5
|
+
order.line_item_adjustments.where(originator_type: 'Spree::TaxRate').destroy_all
|
|
6
|
+
|
|
7
|
+
self.match(order).each do |rate|
|
|
8
|
+
rate.adjust(order)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div class="calculator-fields row">
|
|
2
|
+
<div class="field alpha four columns">
|
|
3
|
+
<% field_name = "#{param_prefix}[calculator_type]" %>
|
|
4
|
+
<%= label_tag field_name, t(:calculator) %>
|
|
5
|
+
<%= select_tag field_name, options_from_collection_for_select([Spree::LineItemDiscount::Percent], :to_s, :description, promotion_action.calculator.type),
|
|
6
|
+
:class => 'type-select select2 fullwidth' %>
|
|
7
|
+
<% if promotion_action.calculator.respond_to?(:preferences) %>
|
|
8
|
+
<span class="warning info"><%= t(:calculator_settings_warning) %></span>
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="settings field omega four columns">
|
|
13
|
+
<% promotion_action.calculator.preferences.keys.map do |key| %>
|
|
14
|
+
<% field_name = "#{param_prefix}[calculator_attributes][preferred_#{key}]" %>
|
|
15
|
+
<%= label_tag field_name, t(key.to_s) %>
|
|
16
|
+
<%= preference_field_tag(field_name,
|
|
17
|
+
promotion_action.calculator.get_preference(key),
|
|
18
|
+
:type => promotion_action.calculator.preference_type(key)) %>
|
|
19
|
+
<% end %>
|
|
20
|
+
<%= hidden_field_tag "#{param_prefix}[calculator_attributes][id]", promotion_action.calculator.id %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module LineItemDiscount
|
|
3
|
+
class Engine < Rails::Engine
|
|
4
|
+
engine_name 'spree/line_item_discount'
|
|
5
|
+
|
|
6
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
|
7
|
+
|
|
8
|
+
config.generators do |g|
|
|
9
|
+
g.test_framework :rspec
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
initializer 'spree.promo.register.promotions.actions' do |app|
|
|
13
|
+
app.config.spree.promotions.actions << Adjust
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.activate
|
|
17
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../../../app/**/*_decorator*.rb")) do |c|
|
|
18
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
config.to_prepare &method(:activate).to_proc
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'spree/line_item_discount'
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module LineItemDiscount
|
|
5
|
+
describe Adjust do
|
|
6
|
+
let(:order) { create(:order) }
|
|
7
|
+
let(:promotion) { create(:promotion) }
|
|
8
|
+
let(:action) { Adjust.new }
|
|
9
|
+
let!(:line_item) { create(:line_item, :order => order) }
|
|
10
|
+
|
|
11
|
+
before { action.stub(:promotion => promotion) }
|
|
12
|
+
|
|
13
|
+
context "#perform" do
|
|
14
|
+
before { promotion.promotion_actions = [action] }
|
|
15
|
+
|
|
16
|
+
it "computes amount before creating adjustment" do
|
|
17
|
+
action.should_receive(:compute_amount).with(line_item).ordered
|
|
18
|
+
action.should_receive(:create_adjustment).ordered
|
|
19
|
+
action.perform(line_item)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "creates adjustment with item as adjustable" do
|
|
23
|
+
action.perform(line_item)
|
|
24
|
+
line_item.adjustments.should == action.adjustments
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "creates adjustment with order as source" do
|
|
28
|
+
action.perform(line_item)
|
|
29
|
+
expect(line_item.adjustments.first.source).to eq order
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "does not perform twice on the same item" do
|
|
33
|
+
2.times { action.perform(line_item) }
|
|
34
|
+
action.adjustments.count.should == 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context "#eligible" do
|
|
39
|
+
context "promotion is not eligible" do
|
|
40
|
+
before { promotion.stub(eligible?: false) }
|
|
41
|
+
it { action.should_not be_eligible(line_item) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context "has a concurrent discount" do
|
|
45
|
+
let(:adjustment) { mock_model(Spree::Adjustment, :amount => -100, :adjustable => line_item) }
|
|
46
|
+
let(:concurrent) { mock_model(Spree::Adjustment, :amount => -100, :adjustable => line_item) }
|
|
47
|
+
|
|
48
|
+
before(:each) do
|
|
49
|
+
action.should_receive(:current_adjustment).at_least(:once).and_return(adjustment)
|
|
50
|
+
action.should_receive(:best_concurrent_discount).at_least(:once).and_return(concurrent)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context "with same amount" do
|
|
54
|
+
context "but not eligible" do
|
|
55
|
+
before { concurrent.stub(:eligible => false) }
|
|
56
|
+
specify { action.should be_eligible(line_item) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "and eligible" do
|
|
60
|
+
before { concurrent.stub(:eligible => true) }
|
|
61
|
+
specify { action.should_not be_eligible(line_item) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "which is better" do
|
|
66
|
+
before { concurrent.stub(:amount => -200) }
|
|
67
|
+
|
|
68
|
+
context "but not eligible" do
|
|
69
|
+
before { concurrent.stub(:eligible => false) }
|
|
70
|
+
it { expect(action).to be_eligible(line_item) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context "and eligible" do
|
|
74
|
+
before { concurrent.stub(:eligible => true) }
|
|
75
|
+
it { expect(action).to_not be_eligible(line_item) }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context "which is worse" do
|
|
80
|
+
before { concurrent.stub(:amount => -20) }
|
|
81
|
+
it { expect(action).to be_eligible(line_item) }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
context "#compute_amount" do
|
|
87
|
+
before { promotion.promotion_actions = [action] }
|
|
88
|
+
|
|
89
|
+
it "calls compute on the calculator" do
|
|
90
|
+
action.calculator.should_receive(:compute).with(line_item)
|
|
91
|
+
action.compute_amount(line_item)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "calculator returns amount greater than item total" do
|
|
95
|
+
before do
|
|
96
|
+
action.calculator.should_receive(:compute).with(line_item).and_return(300)
|
|
97
|
+
line_item.stub(total: 100)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "does not exceed it" do
|
|
101
|
+
action.compute_amount(line_item).should eql(-100)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
module LineItemDiscount
|
|
5
|
+
describe PromotionPool do
|
|
6
|
+
let(:order) { create(:order) }
|
|
7
|
+
let(:variant) { create(:variant) }
|
|
8
|
+
|
|
9
|
+
let(:rule) { Promotion::Rules::ItemTotal.create(preferred_amount: 30, preferred_operator: "gt") }
|
|
10
|
+
let(:calculator) { LineItemDiscount::Percent.create(preferred_percent: 10) }
|
|
11
|
+
let(:action) { LineItemDiscount::Adjust.new }
|
|
12
|
+
let(:promotion) { create(:promotion) }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
promotion.actions << action
|
|
16
|
+
action.calculator = calculator
|
|
17
|
+
action.save!
|
|
18
|
+
|
|
19
|
+
promotion.rules << rule
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
subject { PromotionPool.new(order) }
|
|
23
|
+
|
|
24
|
+
context "existing promo is not eligible" do
|
|
25
|
+
before { order.contents.add(variant, 1) }
|
|
26
|
+
|
|
27
|
+
it { expect(subject.eligible).to be_empty }
|
|
28
|
+
|
|
29
|
+
it "doesn't return adjustments from that promo" do
|
|
30
|
+
expect(subject.valid_discounts.map(&:originator)).to eq []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "promo becomes eligible" do
|
|
34
|
+
let!(:previous_adjustment) { order.line_items.first.adjustments.first }
|
|
35
|
+
|
|
36
|
+
before { order.contents.add(variant, 8) }
|
|
37
|
+
|
|
38
|
+
it "adjust existing adjustments values" do
|
|
39
|
+
subject.adjust!
|
|
40
|
+
expect(subject.valid_discounts.map(&:amount)).to_not eq [previous_adjustment.amount]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "existing promo is eligible" do
|
|
46
|
+
before { order.contents.add(variant, 8) }
|
|
47
|
+
|
|
48
|
+
it "returns array of eligible actions" do
|
|
49
|
+
expect(subject.eligible).to eq [action]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "returns adjustments from that promo" do
|
|
53
|
+
expect(subject.valid_discounts.map(&:originator)).to eq [action]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
describe LineItem do
|
|
5
|
+
let(:variant) { create(:variant) }
|
|
6
|
+
let(:order) { Order.create }
|
|
7
|
+
|
|
8
|
+
context "completed order" do
|
|
9
|
+
before { Order.any_instance.stub completed?: true }
|
|
10
|
+
|
|
11
|
+
it "doesnt activate discounts" do
|
|
12
|
+
pending "implement something like LineItemDiscount::Adjust.activate"
|
|
13
|
+
expect(LineItemDiscount::Adjust).not_to receive(:all)
|
|
14
|
+
order.contents.add(variant, 1)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
describe Order do
|
|
5
|
+
let(:line_item) { create(:line_item) }
|
|
6
|
+
let(:order) { line_item.order }
|
|
7
|
+
|
|
8
|
+
let(:promotion) { create(:promotion) }
|
|
9
|
+
let(:calculator) { LineItemDiscount::Percent.create(preferred_percent: 10) }
|
|
10
|
+
let(:action) { LineItemDiscount::Adjust.create({promotion: promotion, calculator: calculator}, without_protection: true) }
|
|
11
|
+
|
|
12
|
+
context "finalize" do
|
|
13
|
+
before { action.perform(line_item) }
|
|
14
|
+
|
|
15
|
+
it "locks line item adjustments" do
|
|
16
|
+
order.finalize!
|
|
17
|
+
expect(order.line_item_adjustments.first).to be_closed
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Spree
|
|
4
|
+
describe OrderUpdater do
|
|
5
|
+
let(:order) { create(:order) }
|
|
6
|
+
let(:variant) { create(:variant) }
|
|
7
|
+
|
|
8
|
+
let(:calculator) { LineItemDiscount::Percent.create(preferred_percent: 10) }
|
|
9
|
+
let(:action) { LineItemDiscount::Adjust.new }
|
|
10
|
+
let(:promotion) { create(:promotion) }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
promotion.actions << action
|
|
14
|
+
action.calculator = calculator
|
|
15
|
+
action.save!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context "eligible promo" do
|
|
19
|
+
before { order.contents.add(variant, 1) }
|
|
20
|
+
|
|
21
|
+
it "applies existing line item discounts" do
|
|
22
|
+
expect(order.total.to_f).to_not eql variant.price.to_f
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "not eligible anymore" do
|
|
26
|
+
let(:rule) { Promotion::Rules::ItemTotal.create(preferred_amount: 3000, preferred_operator: "gt") }
|
|
27
|
+
|
|
28
|
+
before do
|
|
29
|
+
promotion.rules << rule
|
|
30
|
+
order.contents.add(variant, 1)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "no longer apply line item adjustment" do
|
|
34
|
+
expect(order.total.to_f).to eql (variant.price * 2).to_f
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context "promo not eligible when item is first created" do
|
|
40
|
+
# Order total needs to be greater than 30
|
|
41
|
+
let(:rule) { Promotion::Rules::ItemTotal.create(preferred_amount: 30, preferred_operator: "gt") }
|
|
42
|
+
|
|
43
|
+
before do
|
|
44
|
+
promotion.rules << rule
|
|
45
|
+
expect(variant.price).to be < rule.preferred_amount
|
|
46
|
+
order.contents.add(variant, 1)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "doesn't apply existing discount to order total" do
|
|
50
|
+
expect(order.total.to_f).to eql variant.price.to_f
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context "but becomes eligible later on" do
|
|
54
|
+
before do
|
|
55
|
+
order.contents.add(variant, 8)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "applies existing discount to order total" do
|
|
59
|
+
expect(order.total.to_f).to_not eql (variant.price * 9).to_f
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
ENV["RAILS_ENV"] = "test"
|
|
2
|
+
|
|
3
|
+
require File.expand_path("../dummy/config/environment.rb", __FILE__)
|
|
4
|
+
|
|
5
|
+
require 'rspec/rails'
|
|
6
|
+
|
|
7
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
|
8
|
+
# in spec/support/ and its subdirectories.
|
|
9
|
+
Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each {|f| require f }
|
|
10
|
+
|
|
11
|
+
require 'spree/testing_support/factories'
|
|
12
|
+
|
|
13
|
+
Dir["#{File.dirname(__FILE__)}/factories/**"].each do |f|
|
|
14
|
+
fp = File.expand_path(f)
|
|
15
|
+
require fp
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
RSpec.configure do |config|
|
|
19
|
+
# == Mock Framework
|
|
20
|
+
#
|
|
21
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
|
22
|
+
#
|
|
23
|
+
# config.mock_with :mocha
|
|
24
|
+
# config.mock_with :flexmock
|
|
25
|
+
# config.mock_with :rr
|
|
26
|
+
config.mock_with :rspec
|
|
27
|
+
|
|
28
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
|
29
|
+
# config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
|
30
|
+
|
|
31
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
|
32
|
+
# examples within a transaction, remove the following line or assign false
|
|
33
|
+
# instead of true.
|
|
34
|
+
config.use_transactional_fixtures = true
|
|
35
|
+
|
|
36
|
+
config.include FactoryGirl::Syntax::Methods
|
|
37
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'spree/line_item_discount/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "spree-line_item_discount"
|
|
8
|
+
gem.version = Spree::LineItemDiscount::VERSION
|
|
9
|
+
gem.authors = ["Washington Luiz"]
|
|
10
|
+
gem.email = ["huoxito@gmail.com"]
|
|
11
|
+
gem.description = "Apply promotion adjustments per items"
|
|
12
|
+
gem.summary = "Apply promotion adjustments per items"
|
|
13
|
+
|
|
14
|
+
gem.files = `git ls-files`.split($/)
|
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
17
|
+
gem.require_paths = ["lib"]
|
|
18
|
+
|
|
19
|
+
gem.add_dependency 'spree_core', '~> 2.0.0'
|
|
20
|
+
|
|
21
|
+
gem.add_development_dependency 'factory_girl', '~> 4.2'
|
|
22
|
+
gem.add_development_dependency 'rspec-rails', '~> 2.14'
|
|
23
|
+
gem.add_development_dependency 'sqlite3'
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: spree-line_item_discount
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Washington Luiz
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2013-08-15 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: spree_core
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ~>
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 2.0.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ~>
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 2.0.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: factory_girl
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ~>
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4.2'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ~>
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '4.2'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec-rails
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ~>
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.14'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ~>
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.14'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: sqlite3
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - '>='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description: Apply promotion adjustments per items
|
|
70
|
+
email:
|
|
71
|
+
- huoxito@gmail.com
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- .gitignore
|
|
77
|
+
- .travis.yml
|
|
78
|
+
- Gemfile
|
|
79
|
+
- LICENSE.txt
|
|
80
|
+
- README.md
|
|
81
|
+
- Rakefile
|
|
82
|
+
- app/controllers/spree/admin/promotions_controller_decorator.rb
|
|
83
|
+
- app/models/spree/adjustment_decorator.rb
|
|
84
|
+
- app/models/spree/line_item_decorator.rb
|
|
85
|
+
- app/models/spree/line_item_discount/adjust.rb
|
|
86
|
+
- app/models/spree/line_item_discount/percent.rb
|
|
87
|
+
- app/models/spree/line_item_discount/promotion_pool.rb
|
|
88
|
+
- app/models/spree/order_decorator.rb
|
|
89
|
+
- app/models/spree/order_updater_decorator.rb
|
|
90
|
+
- app/models/spree/tax_rate_decorator.rb
|
|
91
|
+
- app/views/spree/admin/promotions/actions/_adjust.html.erb
|
|
92
|
+
- config/locales/en.yml
|
|
93
|
+
- lib/spree/line_item_discount.rb
|
|
94
|
+
- lib/spree/line_item_discount/engine.rb
|
|
95
|
+
- lib/spree/line_item_discount/version.rb
|
|
96
|
+
- lib/spree_line_item_discount.rb
|
|
97
|
+
- spec/models/spree/line_item_discount/adjust_spec.rb
|
|
98
|
+
- spec/models/spree/line_item_discount/promotion_pool_spec.rb
|
|
99
|
+
- spec/models/spree/line_item_spec.rb
|
|
100
|
+
- spec/models/spree/order_spec.rb
|
|
101
|
+
- spec/models/spree/order_updater_spec.rb
|
|
102
|
+
- spec/spec_helper.rb
|
|
103
|
+
- spree_line_item_discount.gemspec
|
|
104
|
+
homepage:
|
|
105
|
+
licenses: []
|
|
106
|
+
metadata: {}
|
|
107
|
+
post_install_message:
|
|
108
|
+
rdoc_options: []
|
|
109
|
+
require_paths:
|
|
110
|
+
- lib
|
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - '>='
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '0'
|
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - '>='
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0'
|
|
121
|
+
requirements: []
|
|
122
|
+
rubyforge_project:
|
|
123
|
+
rubygems_version: 2.0.3
|
|
124
|
+
signing_key:
|
|
125
|
+
specification_version: 4
|
|
126
|
+
summary: Apply promotion adjustments per items
|
|
127
|
+
test_files:
|
|
128
|
+
- spec/models/spree/line_item_discount/adjust_spec.rb
|
|
129
|
+
- spec/models/spree/line_item_discount/promotion_pool_spec.rb
|
|
130
|
+
- spec/models/spree/line_item_spec.rb
|
|
131
|
+
- spec/models/spree/order_spec.rb
|
|
132
|
+
- spec/models/spree/order_updater_spec.rb
|
|
133
|
+
- spec/spec_helper.rb
|