Skip to content

Commit 10bc882

Browse files
committed
Port from spree to solidus.
1 parent dc93381 commit 10bc882

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1239
-2
lines changed

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
\#*
2+
*~
3+
.#*
4+
.DS_Store
5+
.idea
6+
.project
7+
tmp
8+
nbproject
9+
*.swp
10+
spec/dummy
11+
.rvmrc
12+
.ruby-version
13+
coverage
14+
Gemfile.lock

.rspec

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--colour

Gemfile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
source 'http://rubygems.org'
2+
3+
gem "solidus", github: 'solidusio/solidus', branch: 'master'
4+
5+
gemspec

LICENSE

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright (c) 2012 Jonathan Dean, 2015 Renuo GmbH
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification,
5+
are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright notice,
8+
this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
* Neither the name Spree nor the names of its contributors may be used to
13+
endorse or promote products derived from this software without specific
14+
prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

100644100755
+158-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,158 @@
1-
# solidus_sale_prices
2-
Completely based on spree_sale_prices
1+
Spree Sale Prices
2+
=================
3+
4+
A Spree Commerce extension (Rails Engine) that lets you set sale prices on products, either by a fixed sale price or a
5+
percentage off of the original price. Sale prices have a start date, end date and enabled flag to allow you to schedule
6+
sales, have a historical record of sale prices and put sales on hold.
7+
8+
Requirements
9+
------------
10+
11+
This Gem has been tested with Spree 2.4-stable and Ruby 2.2.1.
12+
It does not support Ruby versions earlier than 1.9 for sure.
13+
14+
Installing
15+
----------
16+
17+
In your Gemfile add the following for the latest released version:
18+
19+
gem 'spree_sale_prices', github: 'renuo/spree-sale-prices', branch: '2.4-stable'
20+
21+
Install the Gem:
22+
23+
bundle install
24+
25+
Copy the migrations in your app:
26+
27+
bundle exec rake railties:install:migrations
28+
29+
Run database migrations in your app:
30+
31+
bundle exec rake db:migrate
32+
33+
Usage
34+
-----
35+
36+
The following example is assuming that you have a product in your database with the price of $20 and you want to put it on sale immediately for $10:
37+
38+
product = Spree::Product.first
39+
40+
puts product.price.to_f # => 20.0
41+
puts product.on_sale? # => false
42+
43+
product.put_on_sale 10
44+
45+
puts product.price.to_f # => 10.0
46+
puts product.original_price.to_f # => 20.0
47+
puts product.on_sale? # => true
48+
49+
By default it uses the supplied Spree::Calculator::FixedAmountSalePriceCalculator which essentially just returns the
50+
value you give it as the sale price.
51+
52+
You can also give a certain percentage off by specifying that you want to use Spree::Calculator::PercentOffSalePriceCalculator.
53+
Note that the percentage is given as a float between 0 and 1, not the integer amount from 0 to 100.
54+
55+
product.put_on_sale 0.2, { calculator_type: Spree::Calculator::PercentOffSalePriceCalculator.new }
56+
puts product.price.to_f # => 16.0
57+
58+
This extension gives you all of the below methods on both your Products and Variants. If accessed on the Product when reading values,
59+
it will return values from your Master variant. If accessed on the Product when writing values, it will by default update
60+
all variants including the master variants. If you change the all_variants parameter to false, it will only then write to
61+
the master variant and leave the other variants untouched.
62+
63+
**price** Returns the sale price if currently on sale, the original price if not
64+
65+
**sale_price** Returns the sale price if currently on sale, nil if not
66+
67+
**original_price** Always returns the original price
68+
69+
**discount_percent** Return the percentage of discount
70+
71+
**on_sale?** Return a boolean indication if it is currently on sale (enabled is set to true and we are currently within the active date range)
72+
73+
**put\_on\_sale(value[, ...])** Put this item on sale (see below sections for options and more information)
74+
75+
**create_sale** Alias of ```put_on_sale```
76+
77+
**active_sale** Returns the currently active sale (Spree::SalePrice object) that price and sale_price will use. If there is more than one potentially active sale, the one with the latest created_at timestamp is used. See the section on "Multiple active sales" for the reasoning behind that.
78+
79+
**current_sale** Alias of ```active_sale```
80+
81+
**next_active_sale** Currently returns the latest created Spree::SalePrice object (active or not.) The name is kind of misleading so it should probably be changed. We may also want to make this only return the latest created inactive sale, since that's kind of the original intention of it to be used inside of ```enable_sale``` and ```start_sale```. Needs more thought.
82+
83+
**next_current_sale** Alias of ```next_active_sale```
84+
85+
**enable_sale(all_variants = true)** Enable the sale returned by ```next_active_sale``` by setting that Spree::SalePrice object's enabled flag to true. Does not change the start and end dates so it does not necessary mean that the sale will then become active. Therefore, you can enable a sale in this manner and still have it not take effect on the site. (Use ```start_sale``` for that) _Note:_ The all_variants flag is only available on Spree::Product (not on Spree::Variant)
86+
87+
**disable_sale(all_variants = true)** Disable the sale returned by ```active_sale``` by setting that Spree::SalePrice object's enabled flag to false. This always makes the sale inactive, regardless of the date range. _Note:_ The all_variants flag is only available on Spree::Product (not on Spree::Variant)
88+
89+
**start_sale(end_time = nil, all_variants = true)** Start the sale returned by ```next_active_sale``` (and make it active) by setting that Spree::SalePrice object's enabled flag to true and ensuring that the current time is in between the start and end dates. _Note:_ The all_variants flag is only available on Spree::Product (not on Spree::Variant)
90+
91+
**stop_sale(all_variants = true)** Stop the sale returned by ```active_sale``` by setting that Spree::SalePrice object's enabled flag to false and the end date to the current time. _Note:_ The all_variants flag is only available on Spree::Product (not on Spree::Variant)
92+
93+
Since you have these methods available to both your products and variants, it is possible to put the product and all
94+
variants on sale or just particular variants. See the explanation of put\_on\_sale below for more information.
95+
96+
97+
Options for put\_on\_sale (create_sale)
98+
---------------------------------------
99+
100+
put_on_sale(value, { calculator_type: Spree::Calculator::PercentOffSalePriceCalculator.new, all_variants: true, start_at: Time.now, end_at: nil, enabled: true })
101+
102+
**value** (_float_)
103+
104+
This is either the sale price that you want to sell the product for (if using the default FixedAmountSalePriceCalculator)
105+
or the float representation of the percentage off of the original price (between 0 and 1)
106+
107+
**calculator_type** (_string_) - Default: **"Spree::Calculator::FixedAmountSalePriceCalculator"**
108+
109+
Specify which calculator to use for determining the sale price. The default calculator will take the value as is and use it
110+
as the sale price. You can also pass in another calculator value to determine the sale price differently, such as the
111+
provided "Spree::Calculator::PercentOffSalePriceCalculator", which will take a given percentage off of the original
112+
price.
113+
114+
**all_variants** (_boolean_) - Default: **true**
115+
116+
_Only for Spree::Product_. By default it set all of variants (including the master variant) for the product on sale. If you change this value to false
117+
it will only put the master variant on sale. Only change this if you know the implications.
118+
119+
**start_at** (_DateTime or nil_) - Default: **Time.now**
120+
121+
Specify the date and time that the sale takes effect. By default it uses the current time. It can also be nil but it's not
122+
recommended because for future reporting reasons you will probably want to know exactly when the sale started.
123+
124+
**end_at** (_DateTime or nil_) - Default: **nil**
125+
126+
Specify the end date of the sale or nil to keep the sale running indefinitely. For future reporting reasons it's recommended
127+
to set this at the time you decide to deactivate the sale rather than just setting enabled to false.
128+
129+
**enabled** (_boolean_) - Default: **true**
130+
131+
Disable this sale temporarily by setting this to false (overrides the start_at and end_at range). It's not recommended to
132+
use this to stop the sale when you decide to end it because it could impact future reporting needs. It's mainly intended
133+
to keep the sale disabled while you are still working on it and it isn't quite ready, or if you need to disable temporarily
134+
for some reason in the middle of a sale.
135+
136+
Multiple active sales
137+
---------------------
138+
139+
Technically you can have more than one active sale at a time. However, because Spree is going to use product.price or
140+
variant.price throughout (with no additional parameters or means to identify a particular sale), we have to consistently
141+
work with a single sale price so that the customer is always charged the same price as they see on the site. What we do then
142+
is always take the last created sale price. We do this so that if you want to temporarily make a new sale to override a
143+
currently running one, you just add a new active sale. Then when that new sale ends, the old sale will be in effect again
144+
(provided it's still active, of course.) So you can add more than one active sale but only one will actually be used at
145+
a given time.
146+
147+
Testing
148+
-------
149+
150+
Tests are in progress, so there aren't any yet. I know, TDD, blah blah blah.
151+
152+
Be sure to bundle your dependencies and then create a dummy test app for the specs to run against.
153+
154+
$ bundle
155+
$ bundle exec rake test_app
156+
$ bundle exec rspec
157+
158+
Copyright (c) 2012 Jonathan Dean, 2015 Renuo GmbH released under the New BSD License

Rakefile

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'bundler'
2+
Bundler::GemHelper.install_tasks
3+
4+
require 'rspec/core/rake_task'
5+
require 'spree/testing_support/extension_rake'
6+
7+
RSpec::Core::RakeTask.new
8+
9+
task :default do
10+
if Dir["spec/dummy"].empty?
11+
Rake::Task[:test_app].invoke
12+
Dir.chdir("../../")
13+
end
14+
Rake::Task[:spec].invoke
15+
end
16+
17+
desc 'Generates a dummy app for testing'
18+
task :test_app do
19+
ENV['LIB_NAME'] = 'spree_sale_prices'
20+
Rake::Task['extension:test_app'].invoke
21+
end

Versionfile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This file is used to designate compatibilty with different versions of Spree
2+
# Please see http://spreecommerce.com/documentation/extensions.html#versionfile for details
3+
4+
# Examples
5+
#
6+
# "0.70.x" => { :branch => "master"}
7+
# "0.60.x" => { :branch => "0-60-stable" }
8+
# "0.40.x" => { :tag => "v1.0.0", :version => "1.0.0" }
9+
10+
"2.4.x" => { branch: "2-4-stable"}

app/assets/javascripts/spree/backend/spree_sale_prices.js

Whitespace-only changes.

app/assets/javascripts/spree/frontend/spree_sale_prices.js

Whitespace-only changes.

app/assets/stylesheets/spree/backend/spree_sale_prices.css

Whitespace-only changes.

app/assets/stylesheets/spree/frontend/spree_sale_prices.css

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module Spree
2+
module Admin
3+
class SalePricesController < BaseController
4+
5+
before_filter :load_product
6+
7+
respond_to :js, :html
8+
9+
def index
10+
@sale_prices = @product.sale_prices
11+
end
12+
13+
def create
14+
@sale_price = @product.put_on_sale params[:sale_price][:value], sale_price_params
15+
respond_with(@sale_price)
16+
end
17+
18+
def destroy
19+
@sale_price = Spree::SalePrice.find(params[:id])
20+
@sale_price.destroy
21+
respond_with(@sale_price)
22+
end
23+
24+
private
25+
26+
def load_product
27+
@product = Spree::Product.find_by(slug: params[:product_id])
28+
redirect_to request.referer unless @product.present?
29+
end
30+
31+
def sale_price_params
32+
params.require(:sale_price).permit(
33+
:id,
34+
:value,
35+
:currency,
36+
:start_at,
37+
:end_at,
38+
:enabled
39+
)
40+
end
41+
end
42+
end
43+
end
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Spree::BaseHelper.class_eval do
2+
def display_original_price(product_or_variant)
3+
product_or_variant.original_price_in(_current_currency).display_price.to_html
4+
end
5+
6+
def display_discount_percent(product_or_variant, append_text = 'Off')
7+
discount = product_or_variant.discount_percent_in _current_currency
8+
9+
if discount > 0
10+
return "#{number_to_percentage(discount, precision: 0).to_html} #{append_text}"
11+
else
12+
return ''
13+
end
14+
end
15+
16+
# Check if a sale is the current sale for a product, returns true or false
17+
def active_for_sale_price product, sale_price
18+
product.current_sale_in(_current_currency) == sale_price
19+
end
20+
21+
def supported_currencies_for_sale_price
22+
try(:supported_currencies) || [ _current_currency ]
23+
end
24+
25+
private
26+
def _current_currency
27+
try(:current_currency) || Spree::Config[:currency] || 'USD'
28+
end
29+
30+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module Spree
2+
# <b>DEPRECATED:</b> Please use <tt>Calculator::FixedAmountSalePriceCalculator</tt> instead.
3+
class Calculator::DollarAmountSalePriceCalculator < Spree::Calculator
4+
warn '[DEPRECATION] `DollarAmountSalePriceCalculator` is deprecated. Please use `FixedAmountSalePriceCalculator` instead. (%s)' % Kernel.caller.first
5+
6+
def self.description
7+
"Calculates the sale price for a Variant by returning the provided fixed sale price"
8+
end
9+
10+
def compute(sale_price)
11+
sale_price.value
12+
end
13+
end
14+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Spree
2+
class Calculator::FixedAmountSalePriceCalculator < Spree::Calculator
3+
# TODO validate that the sale price is less than the original price
4+
def self.description
5+
"Calculates the sale price for a Variant by returning the provided fixed sale price"
6+
end
7+
8+
def compute(sale_price)
9+
sale_price.value
10+
end
11+
end
12+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module Spree
2+
class Calculator::PercentOffSalePriceCalculator < Spree::Calculator
3+
# TODO validate that the sale price is between 0 and 1
4+
def self.description
5+
"Calculates the sale price for a Variant by taking off a percentage of the original price"
6+
end
7+
8+
def compute(sale_price)
9+
(1.0 - sale_price.value.to_f) * sale_price.variant.original_price.to_f
10+
end
11+
end
12+
end

0 commit comments

Comments
 (0)