artfully_ose 1.2.0.pre.19 → 1.2.0.pre.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +28 -0
  3. data/app/assets/javascripts/change-membership.js +66 -16
  4. data/app/assets/javascripts/store/store.js +4 -0
  5. data/app/assets/stylesheets/sass/store.sass +2 -0
  6. data/app/{models/carts → concerns}/cart_finder.rb +0 -0
  7. data/app/{models/actions → concerns}/immutable_action.rb +0 -0
  8. data/app/concerns/pdf_generation.rb +71 -0
  9. data/app/{models/orders → concerns}/unrefundable.rb +0 -0
  10. data/app/controllers/export_controller.rb +1 -5
  11. data/app/controllers/imports_controller.rb +12 -0
  12. data/app/controllers/members/index_controller.rb +1 -1
  13. data/app/controllers/members/members_controller.rb +6 -1
  14. data/app/controllers/members/passwords_controller.rb +10 -0
  15. data/app/controllers/members/people_controller.rb +17 -0
  16. data/app/controllers/membership_cancellations_controller.rb +46 -0
  17. data/app/controllers/membership_comps_controller.rb +97 -0
  18. data/app/controllers/membership_types_controller.rb +6 -2
  19. data/app/controllers/people_controller.rb +15 -3
  20. data/app/controllers/sales_controller.rb +4 -0
  21. data/app/controllers/searches_controller.rb +2 -2
  22. data/app/controllers/sections_controller.rb +1 -1
  23. data/app/controllers/shows_controller.rb +52 -3
  24. data/app/controllers/store/checkouts_controller.rb +2 -7
  25. data/app/controllers/store/memberships_controller.rb +2 -1
  26. data/app/helpers/artfully_ose_helper.rb +4 -3
  27. data/app/mailers/order_mailer.rb +3 -3
  28. data/app/models/actions/comp_action.rb +15 -0
  29. data/app/models/address.rb +7 -0
  30. data/app/models/{carts/cart.rb → cart.rb} +0 -19
  31. data/app/models/checkout.rb +53 -15
  32. data/app/models/comp.rb +21 -11
  33. data/app/models/database_views/item_view.rb +4 -0
  34. data/app/models/event.rb +1 -0
  35. data/app/models/{ext/ext.rb → ext.rb} +14 -1
  36. data/app/models/ext/preprocessor.rb +6 -0
  37. data/app/models/job/order_processor.rb +6 -3
  38. data/app/models/job/show_creator.rb +6 -2
  39. data/app/models/kit.rb +1 -1
  40. data/app/models/kits/membership_kit.rb +8 -1
  41. data/app/models/kits/scannable_tickets_kit.rb +24 -0
  42. data/app/models/member.rb +59 -13
  43. data/app/models/membership.rb +2 -14
  44. data/app/models/membership_cancellation.rb +80 -0
  45. data/app/models/membership_comp.rb +103 -0
  46. data/app/models/membership_comp_job.rb +32 -0
  47. data/app/models/membership_type.rb +3 -3
  48. data/app/models/{orders/order.rb → order.rb} +8 -2
  49. data/app/models/orders/comp_order.rb +4 -0
  50. data/app/models/organization.rb +5 -1
  51. data/app/models/{payments/payment.rb → payment.rb} +12 -1
  52. data/app/models/payments/credit_card_payment.rb +36 -5
  53. data/app/models/person.rb +11 -1
  54. data/app/models/rolling_membership_type.rb +3 -1
  55. data/app/models/sale.rb +1 -1
  56. data/app/models/search.rb +1 -6
  57. data/app/models/seasonal_membership_type.rb +1 -0
  58. data/app/models/ticket.rb +2 -0
  59. data/app/models/ticket/qr_code.rb +1 -1
  60. data/app/models/ticket/transfers.rb +6 -3
  61. data/app/models/ticket_type.rb +6 -0
  62. data/app/models/user.rb +2 -1
  63. data/app/views/addresses/_address.html.haml +7 -6
  64. data/app/views/events/_ticket_type_fields.html.haml +2 -2
  65. data/app/views/imports/index.html.haml +2 -1
  66. data/app/views/layouts/_menu.html.haml +3 -2
  67. data/app/views/layouts/storefront.html.haml +17 -13
  68. data/app/views/members/index/index.html.haml +5 -11
  69. data/app/views/members/invitations/edit.html.haml +17 -0
  70. data/app/views/members/mailer/_invitation_body.html.haml +18 -0
  71. data/app/views/members/mailer/invitation_instructions.html.haml +6 -12
  72. data/app/views/members/mailer/reset_password_instructions.html.erb +8 -0
  73. data/app/views/members/passwords/edit.html.haml +18 -0
  74. data/app/views/members/sessions/new.html.haml +19 -0
  75. data/app/views/membership_cancellations/_form.html.haml +39 -0
  76. data/app/views/membership_cancellations/_modal.html.haml +2 -0
  77. data/app/views/membership_cancellations/_processing.html.haml +14 -0
  78. data/app/views/membership_cancellations/create.js.erb +3 -0
  79. data/app/views/membership_cancellations/new.js.erb +3 -0
  80. data/app/views/membership_comps/confirm.html.haml +85 -0
  81. data/app/views/membership_comps/create.html.haml +18 -0
  82. data/app/views/membership_comps/new.html.haml +82 -0
  83. data/app/views/membership_mailer/cancellation_confirmation.html.haml +9 -0
  84. data/app/views/membership_mailer/cancellation_confirmation.text.haml +6 -0
  85. data/app/views/membership_mailer/refund_problem.html.haml +18 -0
  86. data/app/views/membership_mailer/refund_problem.text.haml +14 -0
  87. data/app/views/membership_types/_form.html.haml +18 -53
  88. data/app/views/membership_types/_membership_type_fees.html.haml +0 -0
  89. data/app/views/membership_types/index.html.haml +1 -5
  90. data/app/views/memberships/_membership_scripts.js.erb +15 -0
  91. data/app/views/memberships/index.html.haml +18 -17
  92. data/app/views/order_mailer/confirmation_for.html.haml +8 -0
  93. data/app/views/orders/_order_sidebar.html.haml +1 -3
  94. data/app/views/organizations/_form.html.haml +6 -0
  95. data/app/views/pdfs/member.html.haml +66 -0
  96. data/app/views/pdfs/order.html.haml +84 -0
  97. data/app/views/people/_comp_membership.html.haml +23 -0
  98. data/app/views/people/_edit_modal.html.haml +6 -3
  99. data/app/views/people/show.html.haml +1 -2
  100. data/app/views/searches/_form.html.haml +34 -33
  101. data/app/views/searches/show.html.haml +4 -1
  102. data/app/views/segments/show.html.haml +4 -1
  103. data/app/views/shows/_ticket_table.html.haml +1 -1
  104. data/app/views/shows/index.html.haml +8 -2
  105. data/app/views/store/checkouts/_membership_info.html.haml +1 -0
  106. data/app/views/store/checkouts/thanks.html.haml +7 -3
  107. data/app/views/store/events/index.html.haml +9 -3
  108. data/app/views/store/events/single_show.html.haml +4 -1
  109. data/app/views/store/memberships/index.html.haml +2 -1
  110. data/app/views/store/orders/show.html.haml +2 -2
  111. data/config/initializers/devise.rb +1 -1
  112. data/config/initializers/paperclip.rb +39 -0
  113. data/config/locales/devise_invitable.en.yml +1 -1
  114. data/config/locales/en.yml +9 -0
  115. data/config/routes.rb +14 -7
  116. data/db/migrate/20131223215615_goodbye_order_view.rb +6 -0
  117. data/db/migrate/20140114011856_add_welcome_message_to_memberships.rb +5 -0
  118. data/db/migrate/20140114212127_add_send_email_to_memberships.rb +5 -0
  119. data/db/migrate/20140125183606_index_memberships_and_types.rb +6 -0
  120. data/db/migrate/20140128213101_add_pdf_to_members.rb +5 -0
  121. data/db/migrate/20140128223140_add_uuid_to_member.rb +6 -0
  122. data/db/migrate/20140128223948_add_qr_code_to_member.rb +9 -0
  123. data/lib/artfully_ose/common_abilities.rb +1 -1
  124. data/lib/artfully_ose/engine.rb +23 -2
  125. data/lib/artfully_ose/version.rb +1 -1
  126. data/spec/factories/action_factories.rb +19 -0
  127. data/spec/factories/address_factories.rb +11 -0
  128. data/spec/factories/cart_factories.rb +26 -0
  129. data/spec/factories/chart_factories.rb +52 -0
  130. data/spec/factories/discount_factories.rb +11 -0
  131. data/spec/factories/donation_factories.rb +14 -0
  132. data/spec/factories/event_factories.rb +29 -0
  133. data/spec/factories/import_factories.rb +61 -0
  134. data/spec/factories/item_factories.rb +51 -0
  135. data/spec/factories/kit_factories.rb +27 -0
  136. data/spec/factories/member_factories.rb +19 -0
  137. data/spec/factories/membership_factories.rb +8 -0
  138. data/spec/factories/membership_type_factories.rb +18 -0
  139. data/spec/factories/order_factories.rb +40 -0
  140. data/spec/factories/organization_factories.rb +44 -0
  141. data/spec/factories/payments_factories.rb +97 -0
  142. data/spec/factories/person_factories.rb +44 -0
  143. data/spec/factories/phone_factories.rb +6 -0
  144. data/spec/factories/section_factories.rb +21 -0
  145. data/spec/factories/segment_factories.rb +6 -0
  146. data/spec/factories/show_factories.rb +33 -0
  147. data/spec/factories/ticket_factories.rb +57 -0
  148. data/spec/factories/ticket_offer_factories.rb +16 -0
  149. data/spec/factories/ticket_type_factories.rb +8 -0
  150. data/spec/factories/user_factories.rb +24 -0
  151. metadata +75 -17
  152. data/app/controllers/membership_awards_controller.rb +0 -60
  153. data/app/models/database_views/order_view.rb +0 -34
  154. data/app/models/membership_award.rb +0 -24
  155. data/app/models/orders/pdf_generation.rb +0 -75
  156. data/app/views/members/invitations/edit.html.erb +0 -14
  157. data/app/views/membership_awards/confirm.html.haml +0 -105
  158. data/app/views/membership_awards/create.html.haml +0 -1
  159. data/app/views/membership_awards/new.html.haml +0 -79
@@ -11,6 +11,10 @@ class ItemView < ActiveRecord::Base
11
11
 
12
12
  default_scope order('created_at desc')
13
13
 
14
+ def self.sales_export_filename_for(organization)
15
+ "exports/Artfully-Ticket-Sales-Export-#{organization.id}.csv"
16
+ end
17
+
14
18
  comma :donation do
15
19
  created_at_local_to_organization("Date")
16
20
  email("Email")
data/app/models/event.rb CHANGED
@@ -41,6 +41,7 @@ class Event < ActiveRecord::Base
41
41
  validates_attachment_content_type :image, :content_type => ["image/jpeg", "image/gif", "image/png"]
42
42
 
43
43
  before_create :set_primary_category
44
+ before_create { self.public = true }
44
45
  after_create :create_default_chart
45
46
 
46
47
  serialize :secondary_categories, Array
@@ -26,7 +26,7 @@ module Ext
26
26
 
27
27
  def set_uuid
28
28
  if self.uuid.nil?
29
- self.uuid = SecureRandom.uuid
29
+ self.uuid = PREFIX + SecureRandom.uuid
30
30
  end
31
31
  end
32
32
 
@@ -34,4 +34,17 @@ module Ext
34
34
  PREFIX + SecureRandom.uuid
35
35
  end
36
36
  end
37
+
38
+ module S3Link
39
+ def download_link_for(field)
40
+ s3 = AWS::S3.new(
41
+ :access_key_id => ENV['S3_ACCESS_KEY_ID'],
42
+ :secret_access_key => ENV['S3_SECRET_ACCESS_KEY']
43
+ )
44
+ bucket = s3.buckets[ENV['S3_BUCKET']]
45
+ object = bucket.objects[self.send(field).path]
46
+ url = object.url_for(:read, :expires => 10*60)
47
+ url.to_s
48
+ end
49
+ end
37
50
  end
@@ -0,0 +1,6 @@
1
+ module Ext
2
+ module Preprocessor
3
+ def preprocess
4
+ end
5
+ end
6
+ end
@@ -31,7 +31,10 @@ class OrderProcessor < Struct.new(:order, :options)
31
31
  end
32
32
 
33
33
  begin
34
- generate_pdf
34
+ order.tickets.each do |item|
35
+ item.product.generate_qr_code_without_delay
36
+ generate_pdf if order.organization.can? :access, :scannable_tickets
37
+ end
35
38
  rescue Exception => e
36
39
  Exceptional.context(:order_id => order.id)
37
40
  Exceptional.handle(e, "Could not generate PDF for order")
@@ -55,13 +58,13 @@ class OrderProcessor < Struct.new(:order, :options)
55
58
 
56
59
  def process_memberships
57
60
  self.order.memberships.each do |membership_item|
58
- Member.for(membership_item.product)
61
+ Member.for(membership_item.product, self.order.person)
59
62
  end
60
63
  self.order.create_membership_action
61
64
  end
62
65
 
63
66
  def generate_pdf
64
- pdf_generator = Orders::PdfGeneration.new(order)
67
+ pdf_generator = PdfGeneration.new(order)
65
68
  download_url = pdf_generator.generate
66
69
  order.pdf = URI.parse(download_url)
67
70
  order.save
@@ -15,7 +15,7 @@ class ShowCreator < Struct.new(:datetimes, :show_params, :chart_params, :event,
15
15
 
16
16
  def perform
17
17
  ActiveRecord::Base.transaction do
18
- datetimes.each do |f|
18
+ datetimes.each do |datetime_string|
19
19
  @show = self.event.next_show
20
20
 
21
21
  #clear the sections and replace them with whatever they entered
@@ -25,10 +25,14 @@ class ShowCreator < Struct.new(:datetimes, :show_params, :chart_params, :event,
25
25
  @show.organization = organization
26
26
  @show.chart_id = @show.chart.id
27
27
 
28
- @show.datetime = ActiveSupport::TimeZone.create(self.event.time_zone).parse(DateTime.parse(f).to_s)
28
+ @show.datetime = DateTime.parse(datetime_string).change(:offset => offset(datetime_string, event.time_zone))
29
29
  @show.go!(publish)
30
30
  end
31
31
  end
32
32
  end
33
33
 
34
+ def offset(datetime_string, time_zone)
35
+ ActiveSupport::TimeZone.create(event.time_zone).parse(datetime_string).formatted_offset
36
+ end
37
+
34
38
  end
data/app/models/kit.rb CHANGED
@@ -61,7 +61,7 @@ class Kit < ActiveRecord::Base
61
61
  end
62
62
 
63
63
  def self.subklasses
64
- @subklasses ||= [ TicketingKit, RegularDonationKit, SponsoredDonationKit, ResellerKit, MailchimpKit, MembershipKit ].freeze
64
+ @subklasses ||= [ TicketingKit, RegularDonationKit, SponsoredDonationKit, ResellerKit, MailchimpKit, MembershipKit, ScannableTicketsKit ].freeze
65
65
  end
66
66
 
67
67
  def self.pad_with_new_kits(kits = [])
@@ -11,10 +11,11 @@ class MembershipKit < Kit
11
11
  end
12
12
 
13
13
  when_active do |organization|
14
- organization.can :access, :reselling
14
+ organization.can :access, :membership
15
15
  end
16
16
  end
17
17
 
18
+ before_save :initialize_accessors
18
19
  before_save :sanitize_accessors
19
20
 
20
21
  ACCESSORS = [ :marketing_copy_heading, :marketing_copy_sidebar ]
@@ -47,6 +48,12 @@ class MembershipKit < Kit
47
48
  save
48
49
  end
49
50
 
51
+ def initialize_accessors
52
+ ACCESSORS.each do |accessor|
53
+ self.send("#{accessor}=", "") if self.send("#{accessor}").nil?
54
+ end
55
+ end
56
+
50
57
  def sanitize_accessors
51
58
  ACCESSORS.each do |accessor|
52
59
  self.send("#{accessor}=", (sanitize self.send(accessor)))
@@ -0,0 +1,24 @@
1
+ class ScannableTicketsKit < Kit
2
+
3
+ acts_as_kit :admin_only => true do
4
+ approve :unless => :no_bank_account?
5
+
6
+ self.configurable = false
7
+
8
+ state_machine do
9
+ state :cancelled, :enter => :kit_cancelled
10
+ end
11
+
12
+ when_active do |organization|
13
+ organization.can :access, :scannable_tickets
14
+ end
15
+ end
16
+
17
+ def friendly_name
18
+ "Scannable Tickets"
19
+ end
20
+
21
+ def pitch
22
+ "Your patrons will receive barcoded tickets that you can scan at the door."
23
+ end
24
+ end
data/app/models/member.rb CHANGED
@@ -2,6 +2,8 @@ class Member < ActiveRecord::Base
2
2
 
3
3
  include Ext::DeviseConfiguration
4
4
  include Ext::Integrations::Member
5
+ include Ext::Uuid
6
+ include Ext::S3Link
5
7
 
6
8
  has_many :memberships
7
9
  belongs_to :organization
@@ -10,7 +12,7 @@ class Member < ActiveRecord::Base
10
12
  validates :organization, :presence => true
11
13
  validates :member_number, :presence => true
12
14
 
13
- before_create :set_member_number
15
+ before_validation :set_member_number, :unless => :persisted?
14
16
 
15
17
  CURRENT = :current
16
18
  LAPSED = :lapsed
@@ -21,6 +23,10 @@ class Member < ActiveRecord::Base
21
23
  scope :lapsed, where("lapsed_memberships_count > 0").where("current_memberships_count = 0")
22
24
  scope :past, where("past_memberships_count > 0").where("lapsed_memberships_count = 0").where("current_memberships_count = 0")
23
25
 
26
+ has_attached_file :pdf, MEMBER_PDF_STORAGE
27
+ has_attached_file :qr_code, MEMBER_QR_STORAGE
28
+ delegate :url, :to => :qr_code, :prefix => true
29
+
24
30
  #
25
31
  # devise_invitable needs this otherwise it can't set the :from param in an email
26
32
  #
@@ -100,26 +106,66 @@ class Member < ActiveRecord::Base
100
106
  super && !suspended?
101
107
  end
102
108
 
103
- def self.find_by_membership(membership)
104
- Member.find_by_email_and_organization_id(membership.item.order.person.email, membership.organization) #TODO: Biggest join ever
109
+ def self.find_by_membership(membership, person)
110
+ Member.find_by_email_and_organization_id(person.email, membership.organization) #TODO: Biggest join ever
111
+ end
112
+
113
+ def member_through
114
+ self.memberships.map{|membership| membership.ends_at}.max
105
115
  end
106
116
 
107
117
  #
108
- # * Creates (and invites) a member for this membership if one does not already exist
118
+ # * Creates a member for this membership if one does not already exist
109
119
  # * Attaches that member to the applicable person
120
+ # * Invites that member if membership.send_email is true
121
+ # * Generates a new qr code if this is a new member
122
+ # * Generates a membership card if this is a new member
110
123
  #
111
- def self.for(membership)
112
- member = Member.find_by_membership(membership)
124
+ # This method CANNOT be run in any synchronous flow
125
+ #
126
+ def self.for(membership, person)
127
+ member = Member.find_by_membership(membership, person)
128
+
113
129
  if member.nil?
114
- member = Member.new({:email => membership.item.order.person.email})
115
- member.organization = membership.item.order.organization
116
- member.person = membership.item.order.person
117
- member.invite!
118
- end
130
+ member = Member.create!({:email => person.email,
131
+ :organization => membership.organization,
132
+ :person => person,
133
+ :password => SecureRandom.hex(32)}, :without_protection => true)
134
+
135
+ member.person.create_address if member.person.address.nil?
136
+
137
+ membership.member = member
138
+ membership.save
139
+
140
+ member.generate_qr_code
141
+ member.generate_pdf
119
142
 
120
- membership.member = member
121
- membership.save
143
+ member.invite! if membership.send_email
144
+ else
145
+ membership.member = member
146
+ membership.save
147
+ end
122
148
 
123
149
  member
124
150
  end
151
+
152
+ def generate_pdf
153
+ pdf_generator = PdfGeneration.new(self)
154
+ download_url = pdf_generator.generate
155
+ self.pdf = URI.parse(download_url)
156
+ self.save
157
+ end
158
+
159
+ def generate_qr_code
160
+ file = Tempfile.new(['qr-code', '.png'])
161
+ Ticket::QRCode.new(self, nil).render(file)
162
+ self.qr_code = file
163
+
164
+ # If we don't save the ticket here, paperclip will leak a file handle.
165
+ # Even though we close our Tempfile below, paperclip copies that into
166
+ # another Tempfile, and only closes the handle when the ticket is saved.
167
+ save
168
+
169
+ file.close
170
+ end
125
171
  end
@@ -1,5 +1,4 @@
1
1
  class Membership < ActiveRecord::Base
2
- include Itemable
3
2
  belongs_to :organization
4
3
  belongs_to :member
5
4
  belongs_to :membership_type
@@ -24,6 +23,7 @@ class Membership < ActiveRecord::Base
24
23
  membership.membership_type = membership_type
25
24
  membership.organization = membership_type.organization
26
25
  membership.price = membership_type.price_for(member)
26
+ membership.sold_price = membership.price
27
27
  membership.starts_at = membership_type.starts_at
28
28
  membership.ends_at = membership_type.ends_at
29
29
  end
@@ -33,18 +33,6 @@ class Membership < ActiveRecord::Base
33
33
  self.member.try(:count_memberships)
34
34
  end
35
35
 
36
- # TODO: DJ this. Could be thousands of people.
37
- def self.for_award(membership_award)
38
- membership_award.people.each do |people|
39
- membership = Membership.for(membership_award.membership_type)
40
- membership.ends_at = membership_award.ends_at
41
- membership.sold_price = membership_award.sold_price
42
-
43
- # create order
44
- # Member.for(membership)
45
- end
46
- end
47
-
48
36
  def adjust_expiration_to(new_ends_at)
49
37
  self.ends_at = new_ends_at
50
38
  self.save
@@ -71,7 +59,7 @@ class Membership < ActiveRecord::Base
71
59
  end
72
60
 
73
61
  def refundable?
74
- false
62
+ true
75
63
  end
76
64
 
77
65
  def exchangeable?
@@ -0,0 +1,80 @@
1
+ class MembershipCancellation
2
+ attr_reader :cancelled_memberships
3
+ attr_reader :membership_ids
4
+
5
+ def initialize(membership_ids=nil)
6
+ @cancelled_memberships = []
7
+ @membership_ids = Array.wrap(membership_ids) unless membership_ids.nil?
8
+ end
9
+
10
+ def self.enqueue(membership_ids)
11
+ membership_ids = Array.wrap(membership_ids)
12
+ Delayed::Job.enqueue(MembershipCancellation.new(membership_ids))
13
+ end
14
+
15
+ def expired_at
16
+ @expired_at ||= 1.second.ago
17
+ end
18
+
19
+ def memberships
20
+ @memberships ||= Membership.where(id: membership_ids)
21
+ end
22
+
23
+ def non_refundables
24
+ @non_refundables ||= memberships.select { |m| !m.item.refundable? || !m.item.order.credit?}
25
+ end
26
+
27
+ def refund_available?
28
+ !refundables.blank?
29
+ end
30
+
31
+ def refund_amount
32
+ @refund_amount ||= refundables.map(&:price).sum
33
+ end
34
+
35
+ def refundables
36
+ @refundables ||= memberships.select {|m| m.item.refundable? && m.item.order.credit? }
37
+ end
38
+
39
+ def refundables_for(order)
40
+ refundables.select { |r| r.item.order.id == order.id }
41
+ end
42
+
43
+ def refundable_orders
44
+ @refundable_orders ||= refundables.map(&:item).map(&:order).uniq
45
+ end
46
+
47
+ def perform
48
+ Order.transaction do
49
+
50
+ # Handle refunds
51
+ refundable_orders.each do |order|
52
+ # Find refundable memberships for this order
53
+ refundables = refundables_for(order)
54
+
55
+ # Submit the refund
56
+ refund = Refund.new(order, refundables.map(&:item))
57
+ refund.submit(:send_email_confirmation => true)
58
+
59
+
60
+ cancel_memberships!(refundables) if refund.successful?
61
+ end
62
+
63
+ # Cancel non-refundable memberships
64
+ cancel_memberships!(non_refundables)
65
+ end
66
+ end
67
+
68
+ def owner
69
+ @owner ||= memberships.first.item.order.person
70
+ end
71
+
72
+ private
73
+
74
+ def cancel_memberships!(memberships)
75
+ memberships.map do |m|
76
+ m.adjust_expiration_to expired_at
77
+ @cancelled_memberships << m
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,103 @@
1
+ class MembershipComp
2
+ QUEUE = "comp"
3
+
4
+ include ActiveModel::Conversion
5
+ include ActiveModel::Validations
6
+ extend ActiveModel::Naming
7
+
8
+ validate :people_belong_to_this_org,
9
+ :expiration_date_cannot_be_in_the_past,
10
+ :we_have_some_people
11
+ validates :number_of_memberships, numericality: true
12
+
13
+ attr_accessor :people,
14
+ :person_id,
15
+ :segment_id,
16
+ :search_id,
17
+ :organization,
18
+ :membership_type,
19
+ :number_of_memberships,
20
+ :ends_at,
21
+ :welcome_message,
22
+ :send_email,
23
+ :notes,
24
+ :sold_price,
25
+ :benefactor
26
+
27
+ def initialize
28
+ self.people = []
29
+ end
30
+
31
+ def persisted?
32
+ false
33
+ end
34
+
35
+ def people_ids
36
+ people.nil? ? [] : people.collect(&:id)
37
+ end
38
+
39
+ def people_belong_to_this_org
40
+ people.each do |person|
41
+ Rails.logger.info "FLARG #{person.organization.id} #{organization}"
42
+ if person.organization != organization
43
+ errors.add(:base, "Person does not belong to this organization. Please contact Artful.ly support if this problem persists.")
44
+ end
45
+ end
46
+ end
47
+
48
+ #
49
+ # Weird Rails/DJ bug where calling validate on membership_comp caused the errors
50
+ # to hang around. DJ would choke when Syck tried to deserialize the errors attr
51
+ # The error message was horrible and misleading (uninitialized constant Syck::Syck)
52
+ #
53
+ # It's probably because we're including ActiveModel::Validations on MembershipComp
54
+ #
55
+ def clear_errors
56
+ @errors = nil
57
+ end
58
+
59
+ def we_have_some_people
60
+ if people.blank?
61
+ errors.add(:base, "You haven't selected any people to receive memberships")
62
+ end
63
+ end
64
+
65
+ def expiration_date_cannot_be_in_the_past
66
+ if !ends_at.present?
67
+ errors.add(:base, "Please enter a membership expiration date")
68
+ end
69
+
70
+ if DateTime.parse(ends_at) < Date.today
71
+ errors.add(:base, "Membership expiration date can't be in the past")
72
+ end
73
+
74
+ rescue
75
+ errors.add(:base, "Please enter a membership expiration date")
76
+ end
77
+
78
+ def award
79
+ return false unless valid?
80
+ MembershipCompJob.enqueue self
81
+ end
82
+
83
+ def perform
84
+ self.people.each do |person|
85
+ next if person.email.blank?
86
+ next if person.company?
87
+ memberships = []
88
+ self.number_of_memberships.to_i.times do
89
+ membership = Membership.for(self.membership_type)
90
+ membership.ends_at = self.ends_at
91
+ membership.sold_price = 0
92
+ membership.welcome_message = self.welcome_message
93
+ membership.send_email = self.send_email
94
+ membership.save
95
+ memberships << membership
96
+ end
97
+
98
+ comp = Comp.new(nil, [], memberships, person, self.benefactor)
99
+ comp.notes = self.notes
100
+ comp.submit
101
+ end
102
+ end
103
+ end