dup_spree_promo 1.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/LICENSE +26 -0
  2. data/app/assets/javascripts/admin/promotions.js +98 -0
  3. data/app/assets/javascripts/admin/spree_promo.js +32 -0
  4. data/app/assets/javascripts/store/spree_promo.js +1 -0
  5. data/app/assets/stylesheets/admin/spree_promo.css +3 -0
  6. data/app/assets/stylesheets/store/spree_promo.css +17 -0
  7. data/app/controllers/spree/admin/promotion_actions_controller.rb +27 -0
  8. data/app/controllers/spree/admin/promotion_rules_controller.rb +32 -0
  9. data/app/controllers/spree/admin/promotions_controller.rb +30 -0
  10. data/app/controllers/spree/checkout_controller_decorator.rb +30 -0
  11. data/app/controllers/spree/content_controller_decorator.rb +13 -0
  12. data/app/controllers/spree/orders_controller_decorator.rb +24 -0
  13. data/app/controllers/spree/store_controller_decorator.rb +55 -0
  14. data/app/helpers/spree/promotion_rules_helper.rb +13 -0
  15. data/app/models/spree/adjustment_decorator.rb +7 -0
  16. data/app/models/spree/calculator/free_shipping.rb +18 -0
  17. data/app/models/spree/calculator/percent_per_item.rb +48 -0
  18. data/app/models/spree/order_decorator.rb +19 -0
  19. data/app/models/spree/order_updater_decorator.rb +14 -0
  20. data/app/models/spree/payment_decorator.rb +5 -0
  21. data/app/models/spree/product_decorator.rb +8 -0
  22. data/app/models/spree/promotion.rb +105 -0
  23. data/app/models/spree/promotion/actions/create_adjustment.rb +48 -0
  24. data/app/models/spree/promotion/actions/create_line_items.rb +23 -0
  25. data/app/models/spree/promotion/rules/first_order.rb +12 -0
  26. data/app/models/spree/promotion/rules/item_total.rb +21 -0
  27. data/app/models/spree/promotion/rules/product.rb +46 -0
  28. data/app/models/spree/promotion/rules/user.rb +24 -0
  29. data/app/models/spree/promotion/rules/user_logged_in.rb +20 -0
  30. data/app/models/spree/promotion_action.rb +19 -0
  31. data/app/models/spree/promotion_action_line_item.rb +8 -0
  32. data/app/models/spree/promotion_rule.rb +25 -0
  33. data/app/overrides/promo_admin_tabs.rb +6 -0
  34. data/app/overrides/promo_cart_coupon_code_field.rb +6 -0
  35. data/app/overrides/promo_coupon_code_field.rb +6 -0
  36. data/app/overrides/promo_product_properties.rb +6 -0
  37. data/app/views/spree/admin/promotion_actions/create.js.erb +12 -0
  38. data/app/views/spree/admin/promotion_actions/destroy.js.erb +1 -0
  39. data/app/views/spree/admin/promotion_rules/create.js.erb +13 -0
  40. data/app/views/spree/admin/promotion_rules/destroy.js.erb +3 -0
  41. data/app/views/spree/admin/promotions/_actions.html.erb +32 -0
  42. data/app/views/spree/admin/promotions/_form.html.erb +56 -0
  43. data/app/views/spree/admin/promotions/_promotion_action.html.erb +11 -0
  44. data/app/views/spree/admin/promotions/_promotion_rule.html.erb +9 -0
  45. data/app/views/spree/admin/promotions/_rules.html.erb +45 -0
  46. data/app/views/spree/admin/promotions/_tab.html.erb +1 -0
  47. data/app/views/spree/admin/promotions/actions/_create_adjustment.html.erb +26 -0
  48. data/app/views/spree/admin/promotions/actions/_create_line_items.html.erb +22 -0
  49. data/app/views/spree/admin/promotions/edit.html.erb +28 -0
  50. data/app/views/spree/admin/promotions/index.html.erb +52 -0
  51. data/app/views/spree/admin/promotions/new.html.erb +16 -0
  52. data/app/views/spree/admin/promotions/rules/_first_order.html.erb +0 -0
  53. data/app/views/spree/admin/promotions/rules/_item_total.html.erb +6 -0
  54. data/app/views/spree/admin/promotions/rules/_landing_page.html.erb +5 -0
  55. data/app/views/spree/admin/promotions/rules/_product.html.erb +9 -0
  56. data/app/views/spree/admin/promotions/rules/_user.html.erb +4 -0
  57. data/app/views/spree/admin/promotions/rules/_user_logged_in.html.erb +0 -0
  58. data/app/views/spree/checkout/_coupon_code_field.html.erb +6 -0
  59. data/app/views/spree/orders/_coupon_code_field.html.erb +7 -0
  60. data/app/views/spree/products/_promotions.html.erb +23 -0
  61. data/config/locales/en.yml +98 -0
  62. data/config/routes.rb +8 -0
  63. data/db/migrate/20120831092359_spree_promo_one_two.rb +45 -0
  64. data/lib/spree/promo.rb +9 -0
  65. data/lib/spree/promo/engine.rb +56 -0
  66. data/lib/spree/promo/environment.rb +9 -0
  67. data/lib/spree_promo.rb +1 -0
  68. metadata +129 -0
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2007-2012, Spree Commerce, Inc. and other contributors
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.
@@ -0,0 +1,98 @@
1
+ var initProductActions = function(){
2
+
3
+ // Add classes on promotion items for design
4
+ $('a.delete').live('mouseover mouseout', function(event) {
5
+ if (event.type == 'mouseover') {
6
+ $(this).parent().addClass('action-remove');
7
+ } else {
8
+ $(this).parent().removeClass('action-remove');
9
+ }
10
+ });
11
+
12
+ $(".variant_autocomplete").variantAutocomplete();
13
+
14
+ $('.calculator-fields').each(function(){
15
+ var $fields_container = $(this);
16
+ var $type_select = $fields_container.find('.type-select');
17
+ var $settings = $fields_container.find('.settings');
18
+ var $warning = $fields_container.find('.warning');
19
+ var originalType = $type_select.val();
20
+
21
+ $warning.hide();
22
+ $type_select.change(function(){
23
+ if( $(this).val() == originalType ){
24
+ $warning.hide();
25
+ $settings.show();
26
+ $settings.find('input').removeAttr('disabled');
27
+ } else {
28
+ $warning.show();
29
+ $settings.hide();
30
+ $settings.find('input').attr('disabled', 'disabled');
31
+ }
32
+ });
33
+ });
34
+
35
+ //
36
+ // CreateLineItems Promotion Action
37
+ //
38
+ ( function(){
39
+ var hideOrShowItemTables = function(){
40
+ $('.promotion_action table').each(function(){
41
+ if($(this).find('td').length == 0){
42
+ $(this).hide();
43
+ } else {
44
+ $(this).show();
45
+ }
46
+ });
47
+ };
48
+ hideOrShowItemTables();
49
+
50
+ // Remove line item
51
+ var setupRemoveLineItems = function(){
52
+ $(".remove_promotion_line_item").click(function(){
53
+ line_items_el = $($('.line_items_string')[0])
54
+ finder = RegExp($(this).data("variant-id") + "x\\d+")
55
+ line_items_el.val(line_items_el.val().replace(finder, ""))
56
+ $(this).parents('tr').remove();
57
+ hideOrShowItemTables();
58
+ });
59
+ };
60
+
61
+ setupRemoveLineItems();
62
+ // Add line item to list
63
+ $(".promotion_action.create_line_items button.add").unbind('click').click(function(e){
64
+ var $container = $(this).parents('.promotion_action');
65
+ var product_name = $container.find("input[name='add_product_name']").val();
66
+ var variant_id = $container.find("input[name='add_variant_id']").val();
67
+ var quantity = $container.find("input[name='add_quantity']").val();
68
+ if(variant_id){
69
+ // Add to the table
70
+ var newRow = "<tr><td>" + product_name + "</td><td>" + quantity + "</td><td><img src='/assets/admin/icons/cross.png' /></td></tr>";
71
+ $container.find('table').append(newRow);
72
+ // Add to serialized string in hidden text field
73
+ var $hiddenField = $container.find(".line_items_string");
74
+ $hiddenField.val($hiddenField.val() + "," + variant_id + "x" + quantity);
75
+ setupRemoveLineItems();
76
+ hideOrShowItemTables();
77
+ }
78
+ return false;
79
+ });
80
+
81
+ } )();
82
+
83
+ }
84
+
85
+ $(document).ready(function() {
86
+ initProductActions();
87
+
88
+ // toggle fields for specific events
89
+ $('#promotion_event_name').change(function() {
90
+ $('#promotion_code_field').toggle($('#promotion_event_name').val() == 'spree.checkout.coupon_code_added');
91
+ $('#promotion_path_field').toggle($('#promotion_event_name').val() == 'spree.content.visited');
92
+ });
93
+ $('#promotion_event_name').change();
94
+
95
+ });
96
+
97
+
98
+
@@ -0,0 +1,32 @@
1
+ //= require admin/spree_core
2
+ //= require_tree .
3
+
4
+ $(document).ready(function() {
5
+ if ($('.user_picker').length > 0) {
6
+ $('.user_picker').select2({
7
+ minimumInputLength: 1,
8
+ multiple: true,
9
+ initSelection: function(element, callback) {
10
+ $.get(Spree.routes.user_search, { ids: element.val() }, function(data) {
11
+ callback(data)
12
+ })
13
+ },
14
+ ajax: {
15
+ url: Spree.routes.user_search,
16
+ datatype: 'json',
17
+ data: function(term, page) {
18
+ return { q: term }
19
+ },
20
+ results: function(data, page) {
21
+ return { results: data }
22
+ }
23
+ },
24
+ formatResult: function(user) {
25
+ return user.email;
26
+ },
27
+ formatSelection: function(user) {
28
+ return user.email;
29
+ }
30
+ });
31
+ }
32
+ })
@@ -0,0 +1 @@
1
+ //= require store/spree_core
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require admin/spree_core
3
+ */
@@ -0,0 +1,17 @@
1
+ /*
2
+ *= require store/spree_core
3
+ *= require_self
4
+ */
5
+
6
+ #update-cart .coupon-code-field {
7
+ /* yes, this is ugly... */
8
+ margin-top: -42px !important;
9
+ }
10
+
11
+ @media only screen and (max-width: 767px) {
12
+ #empty-cart,
13
+ #update-cart .coupon-code-field {
14
+ /* yes, this is ugly... */
15
+ margin-top: 0 !important;
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ class Spree::Admin::PromotionActionsController < Spree::Admin::BaseController
2
+ def create
3
+ @calculators = Spree::Promotion::Actions::CreateAdjustment.calculators
4
+ @promotion = Spree::Promotion.find(params[:promotion_id])
5
+ @promotion_action = params[:action_type].constantize.new(params[:promotion_action])
6
+ @promotion_action.promotion = @promotion
7
+ if @promotion_action.save
8
+ flash[:success] = I18n.t(:successfully_created, :resource => I18n.t(:promotion_action))
9
+ end
10
+ respond_to do |format|
11
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
12
+ format.js { render :layout => false }
13
+ end
14
+ end
15
+
16
+ def destroy
17
+ @promotion = Spree::Promotion.find(params[:promotion_id])
18
+ @promotion_action = @promotion.promotion_actions.find(params[:id])
19
+ if @promotion_action.destroy
20
+ flash[:success] = I18n.t(:successfully_removed, :resource => I18n.t(:promotion_action))
21
+ end
22
+ respond_to do |format|
23
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
24
+ format.js { render :layout => false }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ class Spree::Admin::PromotionRulesController < Spree::Admin::BaseController
2
+ helper 'spree/promotion_rules'
3
+
4
+ def create
5
+ @promotion = Spree::Promotion.find(params[:promotion_id])
6
+ # Remove type key from this hash so that we don't attempt
7
+ # to set it when creating a new record, as this is raises
8
+ # an error in ActiveRecord 3.2.
9
+ promotion_rule_type = params[:promotion_rule].delete(:type)
10
+ @promotion_rule = promotion_rule_type.constantize.new(params[:promotion_rule])
11
+ @promotion_rule.promotion = @promotion
12
+ if @promotion_rule.save
13
+ flash[:success] = I18n.t(:successfully_created, :resource => I18n.t(:promotion_rule))
14
+ end
15
+ respond_to do |format|
16
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
17
+ format.js { render :layout => false }
18
+ end
19
+ end
20
+
21
+ def destroy
22
+ @promotion = Spree::Promotion.find(params[:promotion_id])
23
+ @promotion_rule = @promotion.promotion_rules.find(params[:id])
24
+ if @promotion_rule.destroy
25
+ flash[:success] = I18n.t(:successfully_removed, :resource => I18n.t(:promotion_rule))
26
+ end
27
+ respond_to do |format|
28
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion)}
29
+ format.js { render :layout => false }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module Spree
2
+ module Admin
3
+ class PromotionsController < ResourceController
4
+ before_filter :load_data
5
+ helper 'spree/promotion_rules'
6
+
7
+ protected
8
+ def build_resource
9
+ if params[:promotion]
10
+ calculator_type = params[:promotion].delete(:calculator_type)
11
+ @promotion = Promotion.new(params[:promotion])
12
+ if calculator_type
13
+ @promotion.calculator = calculator_type.constantize.new
14
+ end
15
+ else
16
+ @promotion = Promotion.new
17
+ end
18
+ @promotion
19
+ end
20
+
21
+ def location_after_save
22
+ spree.edit_admin_promotion_url(@promotion)
23
+ end
24
+
25
+ def load_data
26
+ @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ Spree::CheckoutController.class_eval do
2
+
3
+ #TODO 90% of this method is duplicated code. DRY
4
+ def update
5
+ if @order.update_attributes(object_params)
6
+
7
+ fire_event('spree.checkout.update')
8
+ render :edit and return unless apply_coupon_code
9
+
10
+ if @order.next
11
+ state_callback(:after)
12
+ else
13
+ flash[:error] = t(:payment_processing_failed)
14
+ redirect_to checkout_state_path(@order.state)
15
+ return
16
+ end
17
+
18
+ if @order.state == 'complete' || @order.completed?
19
+ flash.notice = t(:order_processed_successfully)
20
+ flash[:commerce_tracking] = 'nothing special'
21
+ redirect_to completion_route
22
+ else
23
+ redirect_to checkout_state_path(@order.state)
24
+ end
25
+ else
26
+ render :edit
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,13 @@
1
+ Spree::ContentController.class_eval do
2
+ after_filter :fire_visited_path, :only => :show
3
+ after_filter :fire_visited_action, :except => :show
4
+
5
+ def fire_visited_path
6
+ fire_event('spree.content.visited', :path => "content/#{params[:path]}")
7
+ end
8
+
9
+ def fire_visited_action
10
+ fire_event('spree.content.visited', :path => "content/#{params[:action]}")
11
+ end
12
+
13
+ end
@@ -0,0 +1,24 @@
1
+ Spree::OrdersController.class_eval do
2
+
3
+ def update
4
+ @order = current_order
5
+ if @order.update_attributes(params[:order])
6
+ render :edit and return unless apply_coupon_code
7
+
8
+ @order.line_items = @order.line_items.select {|li| li.quantity > 0 }
9
+ fire_event('spree.order.contents_changed')
10
+ respond_with(@order) do |format|
11
+ format.html do
12
+ if params.has_key?(:checkout)
13
+ redirect_to checkout_state_path(@order.checkout_steps.first)
14
+ else
15
+ redirect_to cart_path
16
+ end
17
+ end
18
+ end
19
+ else
20
+ respond_with(@order)
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,55 @@
1
+ Spree::StoreController.class_eval do
2
+
3
+ protected
4
+ def apply_coupon_code
5
+ if @order.coupon_code.present?
6
+ # check if coupon code is already applied
7
+ if @order.adjustments.promotion.eligible.detect { |p| p.originator.promotion.code == @order.coupon_code }.present?
8
+ flash[:notice] = t(:coupon_code_already_applied)
9
+ true
10
+ else
11
+ event_name = "spree.checkout.coupon_code_added"
12
+
13
+ # TODO should restrict to payload's event name?
14
+ promotion = Spree::Promotion.find_by_code(@order.coupon_code)
15
+
16
+ if promotion.present?
17
+ if promotion.expired?
18
+ flash[:error] = t(:coupon_code_expired)
19
+ return false
20
+ end
21
+
22
+ if promotion.usage_limit_exceeded?
23
+ flash[:error] = t(:coupon_code_max_usage)
24
+ return false
25
+ end
26
+
27
+ previous_promo = @order.adjustments.promotion.eligible.first
28
+ fire_event(event_name, :coupon_code => @order.coupon_code)
29
+ promo = @order.adjustments.promotion.detect { |p| p.originator.promotion.code == @order.coupon_code }
30
+
31
+ if promo.present? and promo.eligible
32
+ flash[:success] = t(:coupon_code_applied)
33
+ true
34
+ elsif previous_promo.present? and promo.present?
35
+ flash[:error] = t(:coupon_code_better_exists)
36
+ false
37
+ elsif promo.present?
38
+ flash[:error] = t(:coupon_code_not_eligible)
39
+ false
40
+ else
41
+ # if the promotion was created after the order
42
+ flash[:error] = t(:coupon_code_not_found)
43
+ false
44
+ end
45
+ else
46
+ flash[:error] = t(:coupon_code_not_found)
47
+ false
48
+ end
49
+ end
50
+ else
51
+ true
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ module PromotionRulesHelper
3
+
4
+ def options_for_promotion_rule_types(promotion)
5
+ existing = promotion.rules.map { |rule| rule.class.name }
6
+ rule_names = Rails.application.config.spree.promotions.rules.map(&:name).reject{ |r| existing.include? r }
7
+ options = rule_names.map { |name| [ t("promotion_rule_types.#{name.demodulize.underscore}.name"), name] }
8
+ options_for_select(options)
9
+ end
10
+
11
+ end
12
+ end
13
+
@@ -0,0 +1,7 @@
1
+ Spree::Adjustment.class_eval do
2
+ class << self
3
+ def promotion
4
+ where(:originator_type => 'Spree::PromotionAction')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ class Calculator::FreeShipping < Calculator
3
+ def self.description
4
+ I18n.t(:free_shipping)
5
+ end
6
+
7
+ def compute(object)
8
+ if object.is_a?(Array)
9
+ return if object.empty?
10
+ order = object.first.order
11
+ else
12
+ order = object
13
+ end
14
+
15
+ order.ship_total
16
+ end
17
+ end
18
+ end