piggybak_coupons 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.
Files changed (49) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +5 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/piggybak_coupons/piggybak_coupons.js +58 -0
  5. data/app/assets/stylesheets/piggybak_coupons/application.css +13 -0
  6. data/app/controllers/piggybak_coupons/coupon_controller.rb +14 -0
  7. data/app/models/piggybak_coupons/coupon.rb +85 -0
  8. data/app/models/piggybak_coupons/coupon_application.rb +24 -0
  9. data/app/views/layouts/piggybak_coupons/application.html.erb +14 -0
  10. data/app/views/piggybak_coupons/_apply_coupon.html.erb +11 -0
  11. data/config/routes.rb +3 -0
  12. data/db/migrate/20121012220537_piggybak_coupons_setup.rb +21 -0
  13. data/lib/piggybak_coupons/engine.rb +72 -0
  14. data/lib/piggybak_coupons/line_item_decorator.rb +21 -0
  15. data/lib/piggybak_coupons/version.rb +3 -0
  16. data/lib/piggybak_coupons.rb +4 -0
  17. data/lib/tasks/piggybak_coupons_tasks.rake +4 -0
  18. data/test/dummy/README.rdoc +261 -0
  19. data/test/dummy/Rakefile +7 -0
  20. data/test/dummy/app/assets/javascripts/application.js +15 -0
  21. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/test/dummy/app/controllers/application_controller.rb +3 -0
  23. data/test/dummy/app/helpers/application_helper.rb +2 -0
  24. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  25. data/test/dummy/config/application.rb +59 -0
  26. data/test/dummy/config/boot.rb +10 -0
  27. data/test/dummy/config/database.yml +25 -0
  28. data/test/dummy/config/environment.rb +5 -0
  29. data/test/dummy/config/environments/development.rb +37 -0
  30. data/test/dummy/config/environments/production.rb +67 -0
  31. data/test/dummy/config/environments/test.rb +37 -0
  32. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/test/dummy/config/initializers/inflections.rb +15 -0
  34. data/test/dummy/config/initializers/mime_types.rb +5 -0
  35. data/test/dummy/config/initializers/secret_token.rb +7 -0
  36. data/test/dummy/config/initializers/session_store.rb +8 -0
  37. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  38. data/test/dummy/config/locales/en.yml +5 -0
  39. data/test/dummy/config/routes.rb +4 -0
  40. data/test/dummy/config.ru +4 -0
  41. data/test/dummy/public/404.html +26 -0
  42. data/test/dummy/public/422.html +26 -0
  43. data/test/dummy/public/500.html +25 -0
  44. data/test/dummy/public/favicon.ico +0 -0
  45. data/test/dummy/script/rails +6 -0
  46. data/test/integration/navigation_test.rb +10 -0
  47. data/test/piggybak_coupons_test.rb +7 -0
  48. data/test/test_helper.rb +15 -0
  49. metadata +146 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ = PiggybakCoupons
2
+
3
+ Coupon support for Piggybak. Includes types %, $, and freeshipping. Coupons have a specified amount (tied to % and $ use), a minimum cart total, expiration date, and number of uses.
4
+
5
+ CanCan access must be updated to allow admin access to view coupons.
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'PiggybakCoupons'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,58 @@
1
+ $(function() {
2
+ $('#shipping select').change(function() {
3
+ piggybak_coupons.apply_coupon(false);
4
+ return false;
5
+ });
6
+ $('#apply_coupon').click(function() {
7
+ piggybak_coupons.apply_coupon(false);
8
+ return false;
9
+ });
10
+ $('#submit input').click(function() {
11
+ piggybak_coupons.apply_coupon(true);
12
+ return false;
13
+ });
14
+ setTimeout(function() {
15
+ if($('#coupon_code').val() != '') {
16
+ piggybak_coupons.apply_coupon(false);
17
+ }
18
+ }, 500);
19
+ });
20
+
21
+ var piggybak_coupons = {
22
+ apply_coupon: function(on_submit) {
23
+ $('#coupon input[type=hidden]').remove();
24
+ $('#coupon_response').hide();
25
+ $.ajax({
26
+ url: coupon_lookup,
27
+ cached: false,
28
+ data: {
29
+ code: $('#coupon_code').val(),
30
+ shipcost: $('#shipping_total').html().replace(/^\$/, '')
31
+ },
32
+ dataType: "JSON",
33
+ success: function(data) {
34
+ if(data.valid_coupon) {
35
+ var el1 = $('<input>').attr('type', 'hidden').attr('name', 'piggybak_order[line_items_attributes][2][line_item_type]').val('coupon_application');
36
+ var el2 = $('<input>').attr('type', 'hidden').attr('name', 'piggybak_order[line_items_attributes][2][coupon_application_attributes][code]').val($('#coupon_code').val());
37
+ $('#coupon').append(el1);
38
+ $('#coupon').append(el2);
39
+ $('#coupon_response').html('Coupon successfully applied to order.');
40
+ $('#coupon_application_total').html('-$' + (-1*parseFloat(data.amount)).toFixed(2));
41
+ $('#coupon_application_row').show();
42
+ piggybak.update_totals();
43
+ } else {
44
+ $('#coupon_response').html(data.message).show();
45
+ $('#coupon_application_total').html('$0.00');
46
+ $('#coupon_application_row').hide();
47
+ piggybak.update_totals();
48
+ }
49
+ if(on_submit) {
50
+ $('#new_piggybak_order').submit();
51
+ }
52
+ },
53
+ error: function() {
54
+ //do nothing right now
55
+ }
56
+ });
57
+ }
58
+ };
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,14 @@
1
+ module PiggybakCoupons
2
+ class CouponController < ApplicationController
3
+ def apply
4
+ cart = Piggybak::Cart.new(request.cookies["cart"])
5
+ valid_coupon = ::PiggybakCoupons::Coupon.valid_coupon(params[:code], cart, true)
6
+ if valid_coupon.is_a?(::PiggybakCoupons::Coupon)
7
+ amount = ::PiggybakCoupons::Coupon.apply_discount(params[:code], cart, params[:shipcost])
8
+ render :json => { :valid_coupon => true, :amount => amount }
9
+ else
10
+ render :json => { :valid_coupon => false, :message => valid_coupon }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,85 @@
1
+ module PiggybakCoupons
2
+ class Coupon < ActiveRecord::Base
3
+ self.table_name = 'coupons'
4
+
5
+ has_many :coupon_applications
6
+
7
+ attr_accessor :coupon_type, :application_detail
8
+ attr_accessible :code, :amount, :discount_type, :min_cart_total, :expiration_date, :allowed_applications
9
+
10
+ validates_presence_of :code, :amount, :discount_type, :min_cart_total, :expiration_date, :allowed_applications
11
+ validates_uniqueness_of :code
12
+ validates_numericality_of :amount, :greater_than_or_equal_to => 0
13
+ validates_numericality_of :min_cart_total, :greater_than_or_equal_to => 0
14
+ validates_numericality_of :allowed_applications, :greater_than_or_equal_to => 0
15
+ validate :validate_dollar_discount
16
+
17
+ def validate_dollar_discount
18
+ if self.discount_type == "$" && self.amount > self.min_cart_total
19
+ self.errors.add(:min_cart_total, "Minimum cart total must be greater than amount for dollar discount.")
20
+ end
21
+ end
22
+
23
+ def coupon_type
24
+ if self.discount_type == "ship"
25
+ return "free shipping"
26
+ elsif self.discount_type == "%"
27
+ return "#{self.amount}#{self.discount_type}"
28
+ elsif self.discount_type == "$"
29
+ return "#{self.discount_type}#{sprintf("%.2f", self.amount)}"
30
+ end
31
+ end
32
+
33
+ def discount_type_enum
34
+ [['Percent', '%'], ['Dollar', '$'], ['Free Shipping', 'ship']]
35
+ end
36
+
37
+ def application_detail
38
+ "#{self.coupon_applications.size} of #{self.allowed_applications} allowed uses applied"
39
+ end
40
+
41
+ def self.valid_coupon(code, object, already_applied)
42
+ # First check
43
+ coupon = Coupon.find_by_code(code)
44
+ return "Invalid coupon code." if coupon.nil?
45
+
46
+ # Expiration date check
47
+ return "Expired coupon." if coupon.expiration_date < Date.today
48
+
49
+ # Min cart total check
50
+ return "Order does not meet minimum total for coupon." if object.subtotal < coupon.min_cart_total.to_f
51
+
52
+ # Allowed applications check
53
+ return "Coupon has already been used #{coupon.allowed_applications} times." if !already_applied && (coupon.coupon_applications.size >= coupon.allowed_applications)
54
+
55
+ if object.is_a?(Piggybak::Order) && coupon.discount_type == "ship"
56
+ ship_line_item = object.line_items.detect { |li| li.line_item_type == "shipment" }
57
+ return "No shipping on this order." if !ship_line_item
58
+ end
59
+ coupon
60
+ end
61
+
62
+ def self.apply_discount(code, object, shipcost = 0.0)
63
+ coupon = Coupon.find_by_code(code)
64
+ return 0 if coupon.nil?
65
+
66
+ # $ or % discount_type discount
67
+ if coupon.discount_type == "$"
68
+ return -1*coupon.amount
69
+ elsif coupon.discount_type == "%"
70
+ return (-1.to_f*(coupon.amount/100)*object.subtotal).to_c
71
+ elsif coupon.discount_type == "ship"
72
+ if object.is_a?(Piggybak::Order)
73
+ ship_line_item = object.line_items.detect { |li| li.line_item_type == "shipment" }
74
+ if ship_line_item
75
+ return -1*ship_line_item.price
76
+ else
77
+ return 0.00
78
+ end
79
+ elsif object.is_a?(Piggybak::Cart)
80
+ return -1*shipcost.to_f
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,24 @@
1
+ module PiggybakCoupons
2
+ class CouponApplication < ActiveRecord::Base
3
+ # TODO: figure out how to avoid this
4
+ set_table_name 'coupon_applications'
5
+
6
+ belongs_to :coupon
7
+ belongs_to :line_item, :class_name => "::Piggybak::LineItem", :dependent => :destroy
8
+
9
+ attr_accessor :code, :order
10
+ attr_accessible :line_item_id, :coupon_id, :code
11
+
12
+ validate :validate_coupon
13
+ def validate_coupon
14
+ if self.new_record?
15
+ valid_coupon = Coupon.valid_coupon(self.code, self.order, false)
16
+ if valid_coupon.is_a?(::PiggybakCoupons::Coupon)
17
+ self.coupon_id = valid_coupon.id
18
+ else
19
+ self.errors.add(:code, valid_coupon)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>PiggybakCoupons</title>
5
+ <%= stylesheet_link_tag "piggybak_coupons/application", :media => "all" %>
6
+ <%= javascript_include_tag "piggybak_coupons/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,11 @@
1
+ <%= javascript_include_tag "piggybak_coupons/piggybak_coupons" %>
2
+
3
+ <div id="piggybak_coupon_apply_box">
4
+ <div id="coupon_response" style="display:none;"></div>
5
+ <input type="text" name="piggybak_coupon_code" id="coupon_code" value="<%= params.has_key?(:piggybak_coupon_code) ? params[:piggybak_coupon_code] : '' %>" />
6
+ <input type="submit" value="Apply Coupon" id="apply_coupon" />
7
+ </div>
8
+
9
+ <script type="text/javascript">
10
+ var coupon_lookup = "<%= piggybak_coupons.apply_coupon_url %>";
11
+ </script>
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ PiggybakCoupons::Engine.routes.draw do
2
+ match "/apply_coupon" => "coupon#apply"
3
+ end
@@ -0,0 +1,21 @@
1
+ class PiggybakCouponsSetup < ActiveRecord::Migration
2
+ def up
3
+ create_table :coupons do |t|
4
+ t.string :code, :null => false
5
+ t.decimal :amount, :null => false
6
+ t.string :discount_type, :null => false
7
+ t.decimal :min_cart_total, :null => false
8
+ t.date :expiration_date
9
+ t.integer :allowed_applications
10
+ end
11
+ create_table :coupon_applications do |t|
12
+ t.references :line_item
13
+ t.references :coupon
14
+ end
15
+ end
16
+
17
+ def down
18
+ drop_table :coupons
19
+ drop_table :coupon_applications
20
+ end
21
+ end
@@ -0,0 +1,72 @@
1
+ require 'piggybak_coupons/line_item_decorator'
2
+
3
+ module PiggybakCoupons
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace PiggybakCoupons
6
+
7
+ config.to_prepare do
8
+ Piggybak::LineItem.send(:include, ::PiggybakCoupons::LineItemDecorator)
9
+ end
10
+
11
+ config.before_initialize do
12
+ Piggybak.config do |config|
13
+ config.line_item_types[:coupon_application] = { :visible => true,
14
+ :nested_attrs => true,
15
+ :fields => ["coupon_application"],
16
+ :allow_destroy => true,
17
+ :class_name => "::PiggybakCoupons::CouponApplication",
18
+ :display_in_cart => "Discount"
19
+ }
20
+ end
21
+ end
22
+
23
+ initializer "piggybak_coupons.precompile_hook" do |app|
24
+ app.config.assets.precompile += ['piggybak_coupons/piggybak_coupons.js']
25
+ end
26
+
27
+ initializer "piggybak_coupons.rails_admin_config" do |app|
28
+ RailsAdmin.config do |config|
29
+ config.model PiggybakCoupons::Coupon do
30
+ navigation_label "Orders"
31
+ label "Coupon"
32
+
33
+ list do
34
+ field :code
35
+ field :coupon_type
36
+ field :min_cart_total do
37
+ formatted_value do
38
+ "$#{sprintf("%.2f", value)}"
39
+ end
40
+ end
41
+ field :expiration_date
42
+ field :application_detail
43
+ end
44
+ edit do
45
+ field :code
46
+ field :amount
47
+ field :discount_type, :enum
48
+ field :min_cart_total
49
+ field :expiration_date
50
+ field :allowed_applications
51
+ end
52
+ end
53
+
54
+ config.model PiggybakCoupons::CouponApplication do
55
+ label "Coupon"
56
+ visible false
57
+
58
+ edit do
59
+ field :code do
60
+ read_only do
61
+ !bindings[:object].new_record?
62
+ end
63
+ pretty_value do
64
+ bindings[:object].coupon ? bindings[:object].coupon.code : ""
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,21 @@
1
+ module PiggybakCoupons
2
+ module LineItemDecorator
3
+ extend ActiveSupport::Concern
4
+
5
+ def preprocess_coupon_application
6
+ self.description = "Coupon"
7
+ self.coupon_application.order = self.order
8
+ if !self.new_record?
9
+ valid_coupon = ::PiggybakCoupons::Coupon.valid_coupon(self.coupon_application.coupon.code, self.order, true)
10
+ if !valid_coupon.is_a?(::PiggybakCoupons::Coupon)
11
+ self.mark_for_destruction
12
+ end
13
+ end
14
+ end
15
+
16
+ def postprocess_coupon_application
17
+ self.price = ::PiggybakCoupons::Coupon.apply_discount(self.coupon_application.coupon.code, self.order)
18
+ true
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module PiggybakCoupons
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ require "piggybak_coupons/engine"
2
+
3
+ module PiggybakCoupons
4
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :piggybak_coupons do
3
+ # # Task goes here
4
+ # end