radiant-shop_discounts-extension 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.DS_Store +0 -0
  2. data/.gitignore +1 -0
  3. data/README +3 -0
  4. data/Rakefile +138 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/admin/shop/discounts/discountables_controller.rb +44 -0
  7. data/app/controllers/admin/shop/discounts_controller.rb +64 -0
  8. data/app/models/form_discount.rb +39 -0
  9. data/app/models/shop_discount.rb +37 -0
  10. data/app/models/shop_discountable.rb +64 -0
  11. data/app/views/admin/shop/discounts/edit.html.haml +11 -0
  12. data/app/views/admin/shop/discounts/edit/_foot.html.haml +16 -0
  13. data/app/views/admin/shop/discounts/edit/_form.html.haml +15 -0
  14. data/app/views/admin/shop/discounts/edit/_head.html.haml +4 -0
  15. data/app/views/admin/shop/discounts/edit/_inputs.html.haml +2 -0
  16. data/app/views/admin/shop/discounts/edit/_meta.html.haml +8 -0
  17. data/app/views/admin/shop/discounts/edit/_parts.html.haml +9 -0
  18. data/app/views/admin/shop/discounts/edit/_popups.html.haml +4 -0
  19. data/app/views/admin/shop/discounts/edit/buttons/_browse_categories.html.haml +1 -0
  20. data/app/views/admin/shop/discounts/edit/buttons/_browse_products.html.haml +1 -0
  21. data/app/views/admin/shop/discounts/edit/inputs/_amount.html.haml +3 -0
  22. data/app/views/admin/shop/discounts/edit/inputs/_code.html.haml +3 -0
  23. data/app/views/admin/shop/discounts/edit/inputs/_name.html.haml +3 -0
  24. data/app/views/admin/shop/discounts/edit/meta/_finish.html.haml +5 -0
  25. data/app/views/admin/shop/discounts/edit/meta/_start.html.haml +5 -0
  26. data/app/views/admin/shop/discounts/edit/parts/_categories.html.haml +3 -0
  27. data/app/views/admin/shop/discounts/edit/parts/_products.html.haml +3 -0
  28. data/app/views/admin/shop/discounts/edit/popups/_browse_categories.html.haml +6 -0
  29. data/app/views/admin/shop/discounts/edit/popups/_browse_products.html.haml +6 -0
  30. data/app/views/admin/shop/discounts/edit/shared/_category.html.haml +11 -0
  31. data/app/views/admin/shop/discounts/edit/shared/_product.html.haml +12 -0
  32. data/app/views/admin/shop/discounts/index.html.haml +13 -0
  33. data/app/views/admin/shop/discounts/index/_discount.html.haml +13 -0
  34. data/app/views/admin/shop/discounts/index/_foot.html.haml +5 -0
  35. data/app/views/admin/shop/discounts/index/_head.html.haml +2 -0
  36. data/app/views/admin/shop/discounts/index/buttons/_new_discount.html.haml +1 -0
  37. data/app/views/admin/shop/discounts/new.html.haml +11 -0
  38. data/app/views/admin/shop/discounts/remove.html.haml +12 -0
  39. data/config/locales/en.yml +17 -0
  40. data/config/routes.rb +17 -0
  41. data/cucumber.yml +1 -0
  42. data/db/migrate/20101015162137_setup_shop_discounts.rb +30 -0
  43. data/features/support/env.rb +16 -0
  44. data/features/support/paths.rb +14 -0
  45. data/lib/shop_discounts/models/discountable.rb +14 -0
  46. data/lib/shop_discounts/models/product.rb +20 -0
  47. data/lib/shop_discounts/models/purchaseable.rb +33 -0
  48. data/lib/shop_discounts/tags/cart.rb +18 -0
  49. data/lib/shop_discounts/tags/item.rb +30 -0
  50. data/lib/tasks/shop_discounts_extension_tasks.rake +55 -0
  51. data/public/javascripts/admin/extensions/shop/discounts/edit.js +76 -0
  52. data/public/stylesheets/sass/admin/extensions/shop/discounts/edit.sass +125 -0
  53. data/radiant-shop_discounts-extension.gemspec +124 -0
  54. data/shop_discounts_extension.rb +31 -0
  55. data/spec/controllers/admin/shop/discounts/discountables_controller_spec.rb +72 -0
  56. data/spec/controllers/admin/shop/discounts_controller_spec.rb +81 -0
  57. data/spec/datasets/forms_discount.rb +24 -0
  58. data/spec/datasets/shop_discountables.rb +22 -0
  59. data/spec/datasets/shop_discounts.rb +34 -0
  60. data/spec/lib/shop_discounts/models/discountable_spec.rb +114 -0
  61. data/spec/lib/shop_discounts/models/purchaseable_spec.rb +55 -0
  62. data/spec/lib/shop_discounts/tags/item_spec.rb +96 -0
  63. data/spec/models/form_discount_spec.rb +63 -0
  64. data/spec/models/shop_category_spec.rb +19 -0
  65. data/spec/models/shop_discount_spec.rb +130 -0
  66. data/spec/models/shop_line_item_spec.rb +15 -0
  67. data/spec/models/shop_product_spec.rb +40 -0
  68. data/spec/spec.opts +3 -0
  69. data/spec/spec_helper.rb +23 -0
  70. metadata +161 -0
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
data/README ADDED
@@ -0,0 +1,3 @@
1
+ = Shop Discounts
2
+
3
+ Description goes here
data/Rakefile ADDED
@@ -0,0 +1,138 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "radiant-shop_discounts-extension"
5
+ gem.summary = %Q{Shop Discounts Extension for Radiant CMS}
6
+ gem.description = %Q{RadiantShop: Apply discounts to Products and Categories and have them accessed through codes}
7
+ gem.email = "dk@dirkkelly.com"
8
+ gem.homepage = "http://github.com/thefrontiergroup/shop_discounts"
9
+ gem.authors = ["Dirk Kelly"]
10
+ gem.add_dependency 'radiant-shop-extension'
11
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
12
+ end
13
+ Jeweler::GemcutterTasks.new
14
+ rescue LoadError
15
+ puts "Jeweler (or a dependency) not available. This is only required if you plan to package shop_discounts as a gem."
16
+ end
17
+
18
+ # In rails 1.2, plugins aren't available in the path until they're loaded.
19
+ # Check to see if the rspec plugin is installed first and require
20
+ # it if it is. If not, use the gem version.
21
+
22
+ # Determine where the RSpec plugin is by loading the boot
23
+ unless defined? RADIANT_ROOT
24
+ ENV["RAILS_ENV"] = "test"
25
+ case
26
+ when ENV["RADIANT_ENV_FILE"]
27
+ require File.dirname(ENV["RADIANT_ENV_FILE"]) + "/boot"
28
+ when File.dirname(__FILE__) =~ %r{vendor/radiant/vendor/extensions}
29
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
30
+ else
31
+ require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
32
+ end
33
+ end
34
+
35
+ require 'rake'
36
+ require 'rake/rdoctask'
37
+ require 'rake/testtask'
38
+
39
+ rspec_base = File.expand_path(RADIANT_ROOT + '/vendor/plugins/rspec/lib')
40
+ $LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
41
+ require 'spec/rake/spectask'
42
+ require 'cucumber'
43
+ require 'cucumber/rake/task'
44
+
45
+ # Cleanup the RADIANT_ROOT constant so specs will load the environment
46
+ Object.send(:remove_const, :RADIANT_ROOT)
47
+
48
+ extension_root = File.expand_path(File.dirname(__FILE__))
49
+
50
+ task :default => :spec
51
+ task :stats => "spec:statsetup"
52
+
53
+ desc "Run all specs in spec directory"
54
+ Spec::Rake::SpecTask.new(:spec) do |t|
55
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
56
+ t.spec_files = FileList['spec/**/*_spec.rb']
57
+ end
58
+
59
+ task :features => 'spec:integration'
60
+
61
+ namespace :spec do
62
+ desc "Run all specs in spec directory with RCov"
63
+ Spec::Rake::SpecTask.new(:rcov) do |t|
64
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
65
+ t.spec_files = FileList['spec/**/*_spec.rb']
66
+ t.rcov = true
67
+ t.rcov_opts = ['--exclude', 'spec', '--rails']
68
+ end
69
+
70
+ desc "Print Specdoc for all specs"
71
+ Spec::Rake::SpecTask.new(:doc) do |t|
72
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
73
+ t.spec_files = FileList['spec/**/*_spec.rb']
74
+ end
75
+
76
+ [:models, :controllers, :views, :helpers].each do |sub|
77
+ desc "Run the specs under spec/#{sub}"
78
+ Spec::Rake::SpecTask.new(sub) do |t|
79
+ t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
80
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
81
+ end
82
+ end
83
+
84
+ desc "Run the Cucumber features"
85
+ Cucumber::Rake::Task.new(:integration) do |t|
86
+ t.fork = true
87
+ t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
88
+ # t.feature_pattern = "#{extension_root}/features/**/*.feature"
89
+ t.profile = "default"
90
+ end
91
+
92
+ # Setup specs for stats
93
+ task :statsetup do
94
+ require 'code_statistics'
95
+ ::STATS_DIRECTORIES << %w(Model\ specs spec/models)
96
+ ::STATS_DIRECTORIES << %w(View\ specs spec/views)
97
+ ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
98
+ ::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
99
+ ::CodeStatistics::TEST_TYPES << "Model specs"
100
+ ::CodeStatistics::TEST_TYPES << "View specs"
101
+ ::CodeStatistics::TEST_TYPES << "Controller specs"
102
+ ::CodeStatistics::TEST_TYPES << "Helper specs"
103
+ ::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
104
+ end
105
+
106
+ namespace :db do
107
+ namespace :fixtures do
108
+ desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
109
+ task :load => :environment do
110
+ require 'active_record/fixtures'
111
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
112
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
113
+ Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ desc 'Generate documentation for the shop_discounts extension.'
121
+ Rake::RDocTask.new(:rdoc) do |rdoc|
122
+ rdoc.rdoc_dir = 'rdoc'
123
+ rdoc.title = 'ShopDiscountsExtension'
124
+ rdoc.options << '--line-numbers' << '--inline-source'
125
+ rdoc.rdoc_files.include('README')
126
+ rdoc.rdoc_files.include('lib/**/*.rb')
127
+ end
128
+
129
+ # For extensions that are in transition
130
+ desc 'Test the shop_discounts extension.'
131
+ Rake::TestTask.new(:test) do |t|
132
+ t.libs << 'lib'
133
+ t.pattern = 'test/**/*_test.rb'
134
+ t.verbose = true
135
+ end
136
+
137
+ # Load any custom rakefiles for extension
138
+ Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,44 @@
1
+ class Admin::Shop::Discounts::DiscountablesController < Admin::ResourceController
2
+
3
+ model_class ShopDiscountable
4
+
5
+ def create
6
+ error = 'Could not attach Discount.'
7
+ begin
8
+ @shop_discountable.attributes = {
9
+ :discount_id => params[:discount_id],
10
+ :discounted_id => params[:discounted_id],
11
+ :discounted_type => params[:discounted_type]
12
+ }
13
+ @shop_discountable.save!
14
+
15
+ discounted_type = @shop_discountable.discounted_type.gsub('Shop','').underscore
16
+
17
+ respond_to do |format|
18
+ format.js { render :partial => "admin/shop/discounts/edit/shared/#{discounted_type}", :locals => { discounted_type.to_sym => @shop_discountable } }
19
+ end
20
+ rescue
21
+ respond_to do |format|
22
+ format.js { render :text => error, :status => :unprocessable_entity }
23
+ end
24
+ end
25
+ end
26
+
27
+ def destroy
28
+ error = 'Could not remove Discount.'
29
+ begin
30
+ discounted_type = @shop_discountable.discounted_type.gsub('Shop','').underscore
31
+
32
+ @shop_discountable.destroy
33
+
34
+ respond_to do |format|
35
+ format.js { render :partial => "admin/shop/discounts/edit/shared/#{discounted_type}", :locals => { discounted_type.to_sym => @shop_discountable } }
36
+ end
37
+ rescue
38
+ respond_to do |format|
39
+ format.js { render :text => error, :status => :unprocessable_entity }
40
+ end
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,64 @@
1
+ class Admin::Shop::DiscountsController < Admin::ResourceController
2
+
3
+ model_class ShopDiscount
4
+
5
+ before_filter :config_global
6
+ before_filter :config_index, :only => [ :index ]
7
+ before_filter :config_new, :only => [ :new, :create ]
8
+ before_filter :config_edit, :only => [ :edit, :update ]
9
+ before_filter :assets_global, :except => [ :remove, :destroy ]
10
+ before_filter :assets_edit, :only => [ :edit, :update ]
11
+
12
+ private
13
+
14
+ def config_global
15
+ @inputs ||= []
16
+ @meta ||= []
17
+ @buttons ||= []
18
+ @parts ||= []
19
+ @popups ||= []
20
+ end
21
+
22
+ def config_index
23
+ @buttons << 'new_discount'
24
+ end
25
+
26
+ def config_new
27
+ @inputs << 'name'
28
+ @inputs << 'amount'
29
+ @inputs << 'code'
30
+
31
+ @meta << 'start'
32
+ @meta << 'finish'
33
+ end
34
+
35
+ def config_edit
36
+ @buttons << 'browse_categories'
37
+ @buttons << 'browse_products'
38
+
39
+ @inputs << 'name'
40
+ @inputs << 'amount'
41
+ @inputs << 'code'
42
+
43
+ @meta << 'start'
44
+ @meta << 'finish'
45
+
46
+ @parts << 'categories'
47
+ @parts << 'products'
48
+
49
+ @popups << 'browse_categories'
50
+ @popups << 'browse_products'
51
+ end
52
+
53
+ def assets_global
54
+ include_stylesheet 'admin/extensions/shop/edit'
55
+ include_stylesheet 'admin/extensions/shop/index'
56
+ end
57
+
58
+ def assets_edit
59
+ include_javascript 'admin/extensions/shop/edit'
60
+ include_javascript 'admin/extensions/shop/discounts/edit'
61
+ include_stylesheet 'admin/extensions/shop/discounts/edit'
62
+ end
63
+
64
+ end
@@ -0,0 +1,39 @@
1
+ class FormDiscount
2
+ include Forms::Models::Extension
3
+ include Shop::Models::FormExtension
4
+
5
+ def create
6
+ @result ||= { process.to_sym => false }
7
+
8
+ find_current_order
9
+
10
+ case process
11
+ when 'add'
12
+ if @discount = ShopDiscount.find_by_code(discount_code)
13
+ @discountable = @discount.discountables.create(:discounted_id => @order.id, :discounted_type => @order.class.name)
14
+ @result[process.to_sym] = @discountable.valid?
15
+ end
16
+ when 'remove'
17
+ if @discountable = @order.discountables.find(discountable_id)
18
+ @result[process.to_sym] = @discountable.destroy
19
+ end
20
+ end
21
+
22
+ @result
23
+ end
24
+
25
+ protected
26
+
27
+ def process
28
+ @config[:process]
29
+ end
30
+
31
+ def discount_code
32
+ @data[:discount][:code]
33
+ end
34
+
35
+ def discountable_id
36
+ @data[:discountable][:id]
37
+ end
38
+
39
+ end
@@ -0,0 +1,37 @@
1
+ class ShopDiscount < ActiveRecord::Base
2
+
3
+ # Return all discounts which are valid
4
+ default_scope :conditions => [
5
+ '(starts_at IS NULL and finishes_at IS NULL) OR (starts_at >= :now AND finishes_at <= :now) OR (starts_at IS NULL and finishes_at <= :now)',
6
+ { :now => Time.now }
7
+ ]
8
+
9
+ belongs_to :created_by, :class_name => 'User'
10
+ belongs_to :updated_by, :class_name => 'User'
11
+
12
+ has_many :discountables, :class_name => 'ShopDiscountable', :foreign_key => :discount_id
13
+ has_many :categories, :through => :discountables, :source => :category, :conditions => "shop_discountables.discounted_type = 'ShopCategory'"
14
+ has_many :products, :through => :discountables, :source => :product, :conditions => "shop_discountables.discounted_type = 'ShopProduct'"
15
+ has_many :orders, :through => :discountables, :source => :order, :conditions => "shop_discountables.discounted_type = 'ShopOrder'"
16
+ has_many :line_items, :through => :discountables, :source => :line_item,:conditions => "shop_discountables.discounted_type = 'ShopLineItem'"
17
+
18
+ validates_presence_of :name, :code, :amount
19
+ validates_uniqueness_of :name, :code
20
+ validates_numericality_of :amount
21
+
22
+ # This will override the default scope
23
+ def self.all_including_invalid(*attrs)
24
+ with_exclusive_scope{find(:all,*attrs)}
25
+ end
26
+
27
+ # Return all categories minus its own
28
+ def available_categories
29
+ ShopCategory.all - categories
30
+ end
31
+
32
+ # Returns all products minus its own
33
+ def available_products
34
+ ShopProduct.all - products
35
+ end
36
+
37
+ end
@@ -0,0 +1,64 @@
1
+ class ShopDiscountable < ActiveRecord::Base
2
+
3
+ belongs_to :discount, :class_name => 'ShopDiscount', :foreign_key => :discount_id
4
+ belongs_to :discounted, :foreign_key => :discounted_id, :polymorphic => true
5
+ belongs_to :category, :class_name => 'ShopCategory', :foreign_key => :discounted_id
6
+ belongs_to :product, :class_name => 'ShopProduct', :foreign_key => :discounted_id
7
+ belongs_to :order, :class_name => 'ShopOrder', :foreign_key => :discounted_id
8
+ belongs_to :line_item, :class_name => 'ShopLineItem', :foreign_key => :discounted_id
9
+
10
+ validates_presence_of :discount, :discounted
11
+ validates_uniqueness_of :discounted_id, :scope => [ :discount_id, :discounted_type ]
12
+
13
+ before_validation :create_shop_products_if_shop_category
14
+ before_destroy :destroy_shop_products_if_shop_category
15
+
16
+ before_validation :create_shop_line_items_if_shop_order
17
+ before_destroy :destroy_shop_line_items_if_shop_order
18
+
19
+ # Returns discount of a class
20
+ def self.for(type)
21
+ all(:conditions => { :discounted_type => type.pluralize.classify })
22
+ end
23
+
24
+ # Adds discount to a category's products
25
+ def create_shop_products_if_shop_category
26
+ if discounted_type === 'ShopCategory'
27
+ discounted.products.each do |product|
28
+ # Attach discount to the child product
29
+ ShopDiscountable.create(:discount => discount, :discounted => product)
30
+ end
31
+ end
32
+ end
33
+
34
+ # Removes discount from a category's products
35
+ def destroy_shop_products_if_shop_category
36
+ if discounted_type === 'ShopCategory'
37
+ discount.discountables.for('ShopProduct').each do |discountable|
38
+ discountable.destroy
39
+ end
40
+ end
41
+ end
42
+
43
+ # Adds discount to an order's line_items
44
+ def create_shop_line_items_if_shop_order
45
+ if discounted_type === 'ShopOrder'
46
+ discounted.line_items.each do |line_item|
47
+ if line_item.item.discounts.include?(discount)
48
+ # We're here if the item associated with the line item can be discounted
49
+ discount = ShopDiscountable.create(:discount_id => discount_id, :discounted => line_item)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Removes discount from an order's line_items
56
+ def destroy_shop_line_items_if_shop_order
57
+ if discounted_type === 'ShopOrder'
58
+ discount.discountables.for('ShopLineItem').each do |discountable|
59
+ discountable.destroy
60
+ end
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,11 @@
1
+ - @page_title = @shop_discount.name
2
+
3
+ - render_region :main do |main|
4
+ - main.head do
5
+ = render 'admin/shop/discounts/edit/head'
6
+
7
+ - main.popups do
8
+ = render 'admin/shop/discounts/edit/popups'
9
+
10
+ - main.form do
11
+ = render 'admin/shop/discounts/edit/form'
@@ -0,0 +1,16 @@
1
+ - render_region :foot do |foot|
2
+ - foot.buttons do
3
+ %p.buttons
4
+ = save_model_button(@shop_discount)
5
+ = save_model_and_continue_editing_button(@shop_discount)
6
+ = t('or')
7
+ = link_to t('cancel'), admin_shop_discounts_path
8
+ - foot.timestamp do
9
+ = updated_stamp @shop_discount
10
+
11
+ - unless @shop_discount.new_record?
12
+ :javascript
13
+ if(typeof(ROUTES) === 'undefined') ROUTES = new Array();
14
+
15
+ ROUTES['admin_shop_discount_discountables_path'] = "#{admin_shop_discount_discountables_path(@shop_discount)}";
16
+ ROUTES['admin_shop_discount_discountable_path'] = "#{admin_shop_discount_discountable_path(@shop_discount, ':id')}";
@@ -0,0 +1,15 @@
1
+ - form_for [:admin, @shop_discount], :html => {'data-onsubmit_status'=>"Saving Changes&#8230;"} do |f|
2
+ = hidden_field_tag 'shop_discount_id',@shop_discount.id
3
+
4
+ - render_region :form do |form|
5
+ - form.inputs do
6
+ = render :partial => '/admin/shop/discounts/edit/inputs', :locals => { :f => f }
7
+
8
+ - form.meta do
9
+ = render :partial => '/admin/shop/discounts/edit/meta', :locals => { :f => f }
10
+
11
+ - form.parts do
12
+ = render :partial => '/admin/shop/discounts/edit/parts', :locals => { :f => f }
13
+
14
+ - form.foot do
15
+ = render :partial => 'admin/shop/discounts/edit/foot', :locals => { :f => f }