artfully_ose 1.2.0.pre.16 → 1.2.0.pre.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +8 -8
  2. data/app/assets/javascripts/angular-resource.js +546 -0
  3. data/app/assets/javascripts/angular.js +20311 -0
  4. data/app/assets/javascripts/application.js +13 -2
  5. data/app/assets/javascripts/change-membership.js +98 -0
  6. data/app/assets/stylesheets/application.sass +3 -0
  7. data/app/controllers/artfully_ose_controller.rb +12 -0
  8. data/app/controllers/membership_awards_controller.rb +2 -0
  9. data/app/controllers/membership_changes_controller.rb +39 -0
  10. data/app/controllers/membership_types_controller.rb +2 -0
  11. data/app/controllers/sales_controller.rb +1 -1
  12. data/app/controllers/store/checkouts_controller.rb +4 -4
  13. data/app/controllers/store/orders_controller.rb +1 -1
  14. data/app/helpers/artfully_ose_helper.rb +10 -0
  15. data/app/helpers/membership_types_helper.rb +17 -0
  16. data/app/models/actions/change_action.rb +13 -0
  17. data/app/models/carts/cart.rb +6 -3
  18. data/app/models/donation.rb +4 -0
  19. data/app/models/ext/integrations.rb +1 -3
  20. data/app/models/item.rb +1 -1
  21. data/app/models/kits/membership_kit.rb +1 -1
  22. data/app/models/member.rb +6 -0
  23. data/app/models/membership.rb +10 -2
  24. data/app/models/membership_change.rb +176 -0
  25. data/app/models/membership_type.rb +10 -1
  26. data/app/models/order_handler.rb +2 -2
  27. data/app/models/orders/order.rb +9 -1
  28. data/app/models/section.rb +1 -1
  29. data/app/models/show.rb +2 -2
  30. data/app/models/ticket.rb +4 -0
  31. data/app/models/ticket_type.rb +2 -2
  32. data/app/views/events/_ticket_type_fields.html.haml +18 -17
  33. data/app/views/kits/_list.html.haml +8 -0
  34. data/app/views/layouts/storefront.html.haml +3 -3
  35. data/app/views/members/mailer/invitation_instructions.html.haml +3 -2
  36. data/app/views/membership_kits/edit.html.haml +2 -18
  37. data/app/views/membership_types/_form.html.haml +71 -5
  38. data/app/views/memberships/index.html.haml +45 -45
  39. data/app/views/orders/_item_table.haml +2 -0
  40. data/app/views/organizations/show.html.haml +22 -16
  41. data/app/views/sales/new.html.haml +1 -1
  42. data/app/views/store/checkouts/_membership_info.html.haml +2 -2
  43. data/app/views/store/checkouts/thanks.html.haml +1 -1
  44. data/app/views/store/memberships/index.html.haml +2 -2
  45. data/app/views/store/orders/show.html.haml +3 -3
  46. data/app/views/store/shows/_show.html.haml +1 -1
  47. data/app/views/user_memberships/_list.html.haml +27 -26
  48. data/config/routes.rb +2 -0
  49. data/db/migrate/20131206153323_add_membership_kit.rb +5 -0
  50. data/db/migrate/20131206175325_add_marketing_to_membership_type.rb +6 -0
  51. data/db/migrate/20131210212342_add_show_fee_to_memberhsip_type.rb +5 -0
  52. data/db/migrate/20131210222814_add_renewal_price_to_membership_type.rb +6 -0
  53. data/lib/artfully_ose/version.rb +1 -1
  54. metadata +13 -4
  55. data/app/assets/javascripts/angular-resource.min.js +0 -11
  56. data/app/assets/javascripts/angular.min.js +0 -178
@@ -2,14 +2,15 @@
2
2
  //= require jquery_ujs
3
3
  //= require jquery-lib
4
4
  //= require jquery-migrate-1.2.1
5
- //= require angular.min
6
- //= require angular-resource.min
5
+ //= require angular
6
+ //= require angular-resource
7
7
  //= require locationselector
8
8
  //= require_directory ./custom
9
9
  //= require bootstrap
10
10
  //= require wysihtml5-0.3.0.min.js
11
11
  //= require bootstrap-wysihtml5.js
12
12
  //= require_self
13
+ //= require change-membership
13
14
 
14
15
  var artfully = angular.module("artfully", ['ngResource']);
15
16
 
@@ -205,6 +206,16 @@ $(document).ready(function() {
205
206
  $('.no-fa').on('keyup', checkForFA);
206
207
  $('.no-fa').on('change', clearFA);
207
208
 
209
+ $('#membership_type_offer_renewal').click(function () {
210
+ $("#renewal-price-group").toggle();
211
+ });
212
+
213
+ if($("#membership_type_offer_renewal").is(':checked')) {
214
+ $("#renewal-price-group").show();
215
+ } else {
216
+ $("#renewal-price-group").hide();
217
+ }
218
+
208
219
  bindMemberTickets()
209
220
 
210
221
  /*********** EXISTING ARTFUL.LY JS ******/
@@ -0,0 +1,98 @@
1
+ //
2
+ // Service for keeping track of which memberships are selected
3
+ //
4
+ angular.module('artfully').factory('membershipSelections', function() {
5
+ // Map of selected membership ids
6
+ var map = {};
7
+
8
+ // Initial values
9
+ $('input[name=membership_ids\\[\\]]:checked').each(function(i,input) { map[input.value] = true });
10
+
11
+ // Watch for changes
12
+ $('body').on('change', 'input[name=membership_ids\\[\\]]', function(change) {
13
+ var e = $(change.target);
14
+ var row = e.closest('tr');
15
+ var type = $(row.find('.item-description')[0]);
16
+ var price = $(row.find('.price')[0]);
17
+ var membership_id = e.val();
18
+
19
+ if ($(e).prop( "checked" )) {
20
+ // Add the membership id if it's not already in the list
21
+ if (map[membership_id] === undefined) {
22
+ map[membership_id] = {
23
+ id: membership_id,
24
+ type: type.text(),
25
+ price: price.text()
26
+ };
27
+ }
28
+
29
+ } else {
30
+ // Remove the membership id
31
+ delete map[membership_id];
32
+ }
33
+ });
34
+
35
+ return map;
36
+ });
37
+
38
+ //
39
+ // Controller
40
+ //
41
+ angular.module('artfully').controller('ChangeMembershipController', ['$scope', 'membershipSelections', function($scope, membershipSelections) {
42
+
43
+ // Template variables
44
+ $scope.payment_method = 'cash';
45
+ $scope.price = 0;
46
+ $scope.total = 0;
47
+ $scope.selected = {};
48
+
49
+ // Helpers
50
+ $scope.anySelected = function() {
51
+ if (Object.keys(membershipSelections).length == 0 ) {
52
+ return false;
53
+ }
54
+ return true;
55
+ }
56
+
57
+ $scope.changeSelected = function(click) {
58
+ click.preventDefault();
59
+
60
+ if (!$scope.anySelected()) {
61
+ alert("Select one or more memberships to change.");
62
+ return false;
63
+ }
64
+
65
+ // Open the modal
66
+ $('#change').modal('show');
67
+ return false;
68
+ }
69
+
70
+ $scope.updateTotal = function() {
71
+ var price = parseFloat($scope.price.substr(1).replace(/,/, ""));
72
+ var total = (Object.keys($scope.selected).length * price);
73
+
74
+
75
+ $scope.total = '$'+total.toFixed(2);
76
+ }
77
+
78
+ // Update the list of selected ids when the modal is shown
79
+ $("#change").on('shown', function(shown) {
80
+ $scope.selected = membershipSelections;
81
+
82
+ // Mask the price field
83
+ touchCurrency();
84
+ });
85
+
86
+ // Clear the list of selected ids when the modal is hidden
87
+ $("#change").on('hidden', function(hidden) {
88
+ $scope.payment_method = 'cash';
89
+ $scope.price = 0;
90
+ $scope.total = 0;
91
+ $scope.selected = {};
92
+
93
+ // Reset the form
94
+ var form = $(this).find('form')[0];
95
+ if (form) { form.reset(); }
96
+ });
97
+ }]);
98
+
@@ -673,3 +673,6 @@ p.alternate_form_link
673
673
  #edit-membership-kit
674
674
  label
675
675
  font-weight: bold
676
+
677
+ .bordered-section
678
+ border-bottom: 1px solid #ACACAE
@@ -17,6 +17,18 @@ class ArtfullyOseController < ActionController::Base
17
17
 
18
18
  protected
19
19
 
20
+ #
21
+ # don't appent "kit" to the name.
22
+ # Example requires_kit :membership
23
+ #
24
+ def self.requires_kit(kit)
25
+ before_filter do
26
+ unless current_user.current_organization.has_kit? (kit)
27
+ raise CanCan::AccessDenied
28
+ end
29
+ end
30
+ end
31
+
20
32
  def to_plural(variable, word)
21
33
  self.class.helpers.pluralize(variable, word)
22
34
  end
@@ -1,4 +1,6 @@
1
1
  class MembershipAwardsController < ArtfullyOseController
2
+ requires_kit :membership
3
+
2
4
  def new
3
5
  Rails.cache.delete(cache_key)
4
6
 
@@ -0,0 +1,39 @@
1
+ class MembershipChangesController < ApplicationController
2
+ before_filter :set_person, :only => [:create]
3
+
4
+ def create
5
+ change = MembershipChange.new(membership_change_params)
6
+ if change.save
7
+ # Success!
8
+ flash[:success] = 'Memberships have been successfully changed.'
9
+ else
10
+ # Error!
11
+ flash[:error] = error_message
12
+ end
13
+ rescue Exception => e
14
+ Rails.logger.info(e.message)
15
+ Rails.logger.info(e.backtrace.join("\n"))
16
+
17
+ flash[:error] = error_message
18
+ ensure
19
+ redirect_to person_memberships_path(@person)
20
+ end
21
+
22
+ private
23
+ def error_message
24
+ "We're sorry but we could not process the change. Please make sure all fields are filled out accurately."
25
+ end
26
+
27
+ def membership_change_params
28
+ permitted = [:membership_ids, :membership_type_id, :price, :payment_method, :credit_card_info, :person_id]
29
+ permitted.reduce({}) do |all,key|
30
+ all[key] = params[key] if params[key].present?
31
+ all
32
+ end
33
+ end
34
+
35
+ def set_person
36
+ @person = Person.find(params[:person_id])
37
+ true
38
+ end
39
+ end
@@ -1,4 +1,6 @@
1
1
  class MembershipTypesController < ArtfullyOseController
2
+ requires_kit :membership
3
+
2
4
  def index
3
5
  @membership_types = current_organization.membership_types.paginate(:page => params[:page], :per_page => 50)
4
6
 
@@ -63,7 +63,7 @@ class SalesController < ArtfullyOseController
63
63
  def tickets_remaining
64
64
  remaining = {}
65
65
  @sale.ticket_types.each do |ticket_type|
66
- remaining[ticket_type.id] = ticket_type.available
66
+ remaining[ticket_type.id] = ticket_type.available("box_office")
67
67
  end
68
68
  remaining
69
69
  end
@@ -29,10 +29,10 @@ class Store::CheckoutsController < Store::StoreController
29
29
  redirect_to store_order_path
30
30
  end
31
31
 
32
- # def dook
33
- # @order = Order.find(157)
34
- # render :thanks
35
- # end
32
+ def dook
33
+ @order = Order.find(157)
34
+ render :thanks
35
+ end
36
36
 
37
37
  def filter(params)
38
38
  filters = Rails.application.config.filter_parameters
@@ -6,7 +6,7 @@ class Store::OrdersController < Store::StoreController
6
6
  handler = OrderHandler.new(current_cart, current_member)
7
7
  handler.handle_tickets(params)
8
8
  handler.handle_donation(params, @store_organization)
9
- handler.handle_memberships(params)
9
+ handler.handle_memberships(params, current_member)
10
10
  handler.handle_discount(params)
11
11
 
12
12
  flash[:alert] = handler.error unless handler.error.blank?
@@ -13,6 +13,16 @@ module ArtfullyOseHelper
13
13
  end
14
14
  end
15
15
 
16
+ #
17
+ # Pass kit name with Kit or _kit. Like this "with_kit(:membership)"
18
+ #
19
+ def with_kit(kit)
20
+ organization = current_user.try(:current_organization) || @store_organization
21
+ if organization.has_kit? kit
22
+ yield
23
+ end
24
+ end
25
+
16
26
  def thanks_message(person)
17
27
  message = "Thanks"
18
28
  message += ", #{person.first_name}" unless person.first_name.blank?
@@ -2,4 +2,21 @@ module MembershipTypesHelper
2
2
  def membership_type_storefront_path(membership_type)
3
3
  url_for(organization_slug: membership_type.organization.cached_slug, controller: 'store/memberships', action: 'show', id: membership_type.id)
4
4
  end
5
+
6
+ def options_for_membership_types(types, opts={})
7
+ default_options = {
8
+ :include_price => false
9
+ }
10
+ options = default_options
11
+ options = options.merge(opts) unless opts.blank?
12
+
13
+ items = types.map do |type|
14
+ name = type.name
15
+ name << (' - $%.2f' % number_to_dollars(type.price)) if options[:include_price]
16
+
17
+ [name, type.id]
18
+ end
19
+
20
+ options_for_select items
21
+ end
5
22
  end
@@ -0,0 +1,13 @@
1
+ class ChangeAction < GetAction
2
+ def subtype
3
+ "Change"
4
+ end
5
+
6
+ def action_type
7
+ "Get"
8
+ end
9
+
10
+ def verb
11
+ "changed"
12
+ end
13
+ end
@@ -2,8 +2,8 @@ class Cart < ActiveRecord::Base
2
2
  include ActiveRecord::Transitions
3
3
 
4
4
  has_many :donations, :dependent => :destroy
5
- has_many :tickets
6
- has_many :memberships
5
+ has_many :tickets, :after_add => :calculate_fees
6
+ has_many :memberships, :after_add => :calculate_fees
7
7
  after_destroy :clear!
8
8
  before_validation :set_token
9
9
  attr_accessor :special_instructions
@@ -74,6 +74,10 @@ class Cart < ActiveRecord::Base
74
74
  items.sum(&:service_fee)
75
75
  end
76
76
 
77
+ def calculate_fees(obj)
78
+ FeeCalculator.apply(FeeStrategy.new).to(self)
79
+ end
80
+
77
81
  def <<(tkts)
78
82
  tkts = Array.wrap(tkts)
79
83
  tkts.each do |t|
@@ -81,7 +85,6 @@ class Cart < ActiveRecord::Base
81
85
  t.cart_price = t.ticket_type.price
82
86
  end
83
87
  self.tickets << tkts.flatten
84
- FeeCalculator.apply(FeeStrategy.new).to(self)
85
88
  end
86
89
 
87
90
  def subtotal
@@ -15,6 +15,10 @@ class Donation < ActiveRecord::Base
15
15
  0
16
16
  end
17
17
 
18
+ def realized_fee
19
+ self.class.realized_fee
20
+ end
21
+
18
22
  def order_summary_description
19
23
  "Donation"
20
24
  end
@@ -24,15 +24,13 @@ module Ext
24
24
  #
25
25
  # Used for rendering widget and api charts. Storefront uses section.ticket_types_for instead
26
26
  #
27
- def chart_for(channel, member, organization_id = nil)
27
+ def chart_for(channel, organization_id = nil)
28
28
  chart = Chart.joins(:show)
29
29
  .joins(:sections => :ticket_types)
30
30
  .includes(:sections => :ticket_types)
31
31
  .where('shows.id = ?', self.id)
32
32
  .where("ticket_types.#{channel} = ?", true)
33
33
 
34
- chart = chart.where(:ticket_types=>{:membership_type_id=>member.try(:current_membership_types)})
35
-
36
34
  chart.first
37
35
  end
38
36
  end
data/app/models/item.rb CHANGED
@@ -263,7 +263,7 @@ class Item < ActiveRecord::Base
263
263
  def set_prices_from(prod)
264
264
  self.original_price = prod.price
265
265
  self.price = (prod.sold_price || prod.cart_price || prod.price)
266
- self.realized_price = self.price - prod.class.realized_fee
266
+ self.realized_price = self.price - prod.realized_fee
267
267
  self.net = (self.realized_price - (per_item_processing_charge || lambda { |item| 0 }).call(self)).floor
268
268
  self.service_fee = prod.service_fee || 0
269
269
  end
@@ -17,7 +17,7 @@ class MembershipKit < Kit
17
17
 
18
18
  before_save :sanitize_accessors
19
19
 
20
- ACCESSORS = [ :marketing_copy_heading, :marketing_copy_sidebar, :thanks_copy, :invitation_email_text_copy ]
20
+ ACCESSORS = [ :marketing_copy_heading, :marketing_copy_sidebar ]
21
21
 
22
22
  ACCESSORS.each do |accessor|
23
23
  attr_accessible accessor
data/app/models/member.rb CHANGED
@@ -79,6 +79,12 @@ class Member < ActiveRecord::Base
79
79
  [CURRENT, LAPSED, PAST, NONE]
80
80
  end
81
81
 
82
+ self.states.each do |st|
83
+ define_method "#{st}?" do
84
+ "#{st}".to_s == self.state.to_s
85
+ end
86
+ end
87
+
82
88
  def self.generate_password
83
89
  Devise.friendly_token
84
90
  end
@@ -19,11 +19,11 @@ class Membership < ActiveRecord::Base
19
19
  scope :lapsed, lambda { |time = Time.now, since = (time - 1.year)| where("ends_at < ?", time).where("ends_at > ?", since.midnight) }
20
20
  scope :past, lambda { |time = Time.now| where("ends_at < ?", time - 1.year) }
21
21
 
22
- def self.for(membership_type)
22
+ def self.for(membership_type, member = nil)
23
23
  new.tap do |membership|
24
24
  membership.membership_type = membership_type
25
25
  membership.organization = membership_type.organization
26
- membership.price = membership_type.price
26
+ membership.price = membership_type.price_for(member)
27
27
  membership.starts_at = membership_type.starts_at
28
28
  membership.ends_at = membership_type.ends_at
29
29
  end
@@ -62,6 +62,14 @@ class Membership < ActiveRecord::Base
62
62
  0
63
63
  end
64
64
 
65
+ def realized_fee
66
+ self.membership_type.hide_fee? ? self.price * MembershipType::SERVICE_FEE : 0
67
+ end
68
+
69
+ def expired?
70
+ ends_at < Time.now
71
+ end
72
+
65
73
  def refundable?
66
74
  false
67
75
  end
@@ -0,0 +1,176 @@
1
+ class MembershipChange
2
+ include ActiveModel::Validations
3
+
4
+ attr_reader :cart
5
+ attr_reader :checkout
6
+ attr_reader :changing_memberships
7
+ attr_reader :new_memberships
8
+ attr_reader :payment
9
+
10
+ attr_accessor :credit_card_info
11
+ attr_accessor :membership_ids
12
+ attr_accessor :membership_type_id
13
+ attr_accessor :payment_method
14
+ attr_accessor :person_id
15
+ attr_accessor :price
16
+
17
+ validates :person_id, :presence => true
18
+ validates :membership_ids, :presence => true
19
+ validates :membership_type_id, :presence => true
20
+ validates :payment_method, :presence => true
21
+ validates :price, :presence => true
22
+ validates :credit_card_info, :presence => {:if => :credit?}
23
+
24
+ # This is purely to assuage shoulda and it's validation matchers
25
+ def self.reflect_on_association(a)
26
+ return false
27
+ end
28
+
29
+ def initialize(params = nil)
30
+ @price = 0
31
+ assign_params(params) if params
32
+ end
33
+
34
+ def assign_params(new_params)
35
+ return if new_params.blank?
36
+
37
+ params = new_params.stringify_keys
38
+ params.each do |k,v|
39
+ if respond_to?("#{k}=")
40
+ send("#{k}=", v)
41
+ else
42
+ raise(ArgumentError, "unknown parameter: #{k}")
43
+ end
44
+ end
45
+ end
46
+
47
+ def cart
48
+ unless @cart
49
+ @cart = Cart.create
50
+
51
+ # Add the new memberships
52
+ new_memberships.each do |membership|
53
+ cart.memberships << membership
54
+ end
55
+ end
56
+ @cart
57
+ end
58
+
59
+ def changing_memberships
60
+ @changing_memberships ||= Membership.where(id: membership_ids)
61
+ end
62
+
63
+ def checkout
64
+ @checkout ||= MembershipChange::Checkout.new(cart, payment)
65
+ end
66
+
67
+ def comp?
68
+ 'comp' == payment_method
69
+ end
70
+
71
+ def credit?
72
+ !!(payment_method =~ /credit|cc/)
73
+ end
74
+
75
+ def membership_type
76
+ @membership_type ||= MembershipType.find(membership_type_id)
77
+ end
78
+
79
+ def new_memberships
80
+ if @new_memberships.blank?
81
+ @new_memberships = []
82
+
83
+ unless membership_ids.blank?
84
+ # Grab the old memberships and hash on id
85
+ old_memberships = changing_memberships.reduce({}) do |all, o|
86
+ all[o.id.to_s] = o
87
+ all
88
+ end
89
+
90
+ # Make new memberships
91
+ @new_memberships = membership_ids.map do |old_membership_id|
92
+ old = old_memberships[old_membership_id]
93
+
94
+ # Create the new membership, setting sold price, and the start/end dates from the old one
95
+ membership = Membership.for(membership_type)
96
+ membership.starts_at = old.starts_at
97
+ membership.ends_at = old.ends_at
98
+ membership.price = price
99
+ membership.member = person.member
100
+
101
+ membership
102
+ end
103
+ end
104
+ end
105
+ @new_memberships
106
+ end
107
+
108
+ def payment
109
+ unless @payment
110
+ @payment = Payment.create(payment_method, {credit_card: credit_card_info})
111
+ @payment.customer = person
112
+ end
113
+ @payment
114
+ end
115
+
116
+ def person
117
+ @person ||= Person.find(person_id)
118
+ end
119
+
120
+ def price
121
+ return nil unless @price
122
+ return 0 if comp?
123
+ @price
124
+ end
125
+
126
+ def price=(new_price)
127
+ new_price = new_price.to_i unless new_price.blank?
128
+ @price = new_price
129
+ end
130
+
131
+ def save
132
+ return false unless valid?
133
+
134
+ Order.transaction do
135
+ expiration = 1.second.ago
136
+
137
+ # Complete checkout
138
+ success = checkout.finish
139
+
140
+ # Failure?
141
+ raise MembershipChange::Error unless success
142
+
143
+ # Expire old memberships
144
+ changing_memberships.each do |old|
145
+ old.adjust_expiration_to(expiration)
146
+ end
147
+ end
148
+ true
149
+ rescue MembershipChange::Error => e
150
+ payment.errors[:base].each do |msg|
151
+ errors.add(:base, msg)
152
+ end
153
+ false
154
+ end
155
+
156
+ def valid?
157
+ super && checkout.valid?
158
+ end
159
+
160
+ class Error < StandardError; end
161
+
162
+ class Cart < ::Cart
163
+ end
164
+
165
+ class Checkout < ::Checkout
166
+ def order_class
167
+ MembershipChange::Order
168
+ end
169
+ end
170
+
171
+ class Order < ::Order
172
+ def membership_action_class
173
+ ChangeAction
174
+ end
175
+ end
176
+ end