dm_event 4.2.3.2 → 4.2.3.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 073002662f7ff5c6a3f5bda495a7cf1b7fd08555
4
- data.tar.gz: 38ccabbe27f63e82baf49ad276eb1faf47dc2880
3
+ metadata.gz: 166674962a59997e15cc1dd82e97776ea718d6e7
4
+ data.tar.gz: 14dfe6801c934c2d49f64ee532760138d48e2aac
5
5
  SHA512:
6
- metadata.gz: 058292d7c65ec0bf8176323c914df3a68a3411a6395e2c94dfe4d9b407ceb2f4e463d5f3ba9974fb5bec7ed7dca8e37ff8613b1c4b039a743f3dcea421c0c86a
7
- data.tar.gz: 036be6d7ec7b611939d90ec80fab26d79e043e3f882f993d8a538f38c0f97740b70a0581515956536ae3973acc21c5419b1e0d3d062c55a824497119d2186c01
6
+ metadata.gz: 2d4c563433c1131e20a8cad5bc2768e033d9dbf3bc94f7f1457d517511a144e2d878c5b8c6e96d1660ee5d223e4e5b022af140adbea611e66041be3e50ed7ba3
7
+ data.tar.gz: e9f9c5a5fbfac90c1fe75e4363dfedb2acf728f83f080780c9341c27fccec84174fcd84e37f60096871884c7d336788e0bfdde5c13c170cca030ce1a526a4baf
@@ -93,7 +93,9 @@ class DmEvent::Admin::WorkshopsController < DmEvent::Admin::AdminController
93
93
  def financials
94
94
  authorize! :manage_event_finances, @workshop
95
95
  @financials = @workshop.financial_details
96
- @payments = PaymentHistory.where(owner_type: 'Registration', owner_id: @workshop.registrations.pluck(:id)).includes(:user_profile, owner: [:user_profile])
96
+ @payments = PaymentHistory.where(owner_type: 'Registration', owner_id: @workshop.registrations.pluck(:id)).includes(:user_profile, owner: [:user_profile])
97
+ @unpaid = @workshop.registrations.unpaid.includes(:user_profile, :workshop_price)
98
+ @unpaid = @unpaid.to_a.delete_if {|i| i.payment_owed.zero?}.sort_by {|i| i.full_name.downcase}
97
99
  end
98
100
 
99
101
  # Generate a list of all outstanding balances across all workshops
@@ -36,6 +36,7 @@ class Registration < ActiveRecord::Base
36
36
  after_initialize :create_uuid
37
37
  before_create :set_currency
38
38
  after_create :set_receipt_code
39
+ before_save :clear_reminder_sent_on, if: :amount_paid_cents_changed?
39
40
 
40
41
  validates_uniqueness_of :uuid
41
42
  validates_presence_of :workshop_price_id, if: Proc.new { |reg| reg.workshop.workshop_prices.size > 0}
@@ -48,6 +49,12 @@ class Registration < ActiveRecord::Base
48
49
 
49
50
  private
50
51
 
52
+ # when a payment is made, we want reset whether a payment reminder has been sent
53
+ #------------------------------------------------------------------------------
54
+ def clear_reminder_sent_on
55
+ self.payment_reminder_sent_on = nil
56
+ end
57
+
51
58
  # the uuid is used to provide a private url to a customer so that they can access
52
59
  # their registration if not logged in. This is particularly important when
53
60
  # a customer registers without having a user account.
@@ -110,12 +117,13 @@ public
110
117
  end
111
118
 
112
119
  # suggested amount of next payment.
113
- # if a payment is within 20 of the balance_owed, then they should pay the balance
120
+ # when it's recurring, they payment should be whatever is needed to bring their
121
+ # payment plan up to date
114
122
  #------------------------------------------------------------------------------
115
123
  def payment_owed
116
- if workshop_price
117
- amount_20 = Money.new(2000, workshop_price.price.currency)
118
- (workshop_price.payment_price + amount_20) > balance_owed ? balance_owed : workshop_price.payment_price
124
+ if workshop_price && workshop_price.recurring_payments?
125
+ to_pay = recurring_what_should_be_paid_by_now(0) - amount_paid
126
+ to_pay.negative? ? Money.new(0, workshop_price.price.currency) : to_pay
119
127
  else
120
128
  balance_owed
121
129
  end
@@ -167,25 +175,43 @@ public
167
175
  def unpaid?
168
176
  self.accepted? && self.archived_on == nil
169
177
  end
170
-
178
+
171
179
  # Is it time to send a payment reminder?
172
- # Due first 7 days after inital registration. Then every 14 days after that
180
+ # Due first 7 days after inital registration (or a payment period). Then every 14 days after that
173
181
  #------------------------------------------------------------------------------
174
182
  def payment_reminder_due?
175
183
  if preferred_payment_reminder_hold_until.nil? || preferred_payment_reminder_hold_until < Time.now
176
184
  time_period = self.payment_reminder_sent_on.nil? ? (self.created_at + 7.days) : (self.payment_reminder_sent_on + 14.days)
177
- self.balance_owed > Money.new(0, workshop.base_currency) && time_period < Time.now
185
+ past_due?(7) ? time_period < Time.now : false
186
+ else
187
+ false
188
+ end
189
+ end
190
+
191
+ # past due means they haven't paid what they should have paid by now
192
+ #------------------------------------------------------------------------------
193
+ def past_due?(grace_period_in_days = 7)
194
+ return false if !balance_owed.positive?
195
+ if workshop_price.recurring_payments?
196
+ return amount_paid < recurring_what_should_be_paid_by_now(grace_period_in_days)
197
+ else
198
+ return Date.today > (self.created_at + grace_period_in_days.days)
178
199
  end
200
+ end
179
201
 
180
- # if recurring_period since last paymnet
181
- # if 7 days after registration and no payment
182
- # if 14 days since last reminder and no payment
183
- # if resume_reminders is past
184
- # last_payment = payment_histories.order('created_on').last
185
- # if workshop_price.recurring?
186
- # if
202
+ #------------------------------------------------------------------------------
203
+ def recurring_what_should_be_paid_by_now(grace_period_in_days = 7)
204
+ entry = workshop_price.specific_payment_schedule(self.created_at + grace_period_in_days.days, Date.today)
205
+ entry ? entry[:total_due] : 0
187
206
  end
188
-
207
+
208
+ # date when the most recent payment was due
209
+ #------------------------------------------------------------------------------
210
+ def last_payment_due_on
211
+ entry = workshop_price.specific_payment_schedule(self.created_at, Date.today)
212
+ entry ? entry[:due_on] : self.created_at.to_date
213
+ end
214
+
189
215
  # Setup the columns for exporting data as csv.
190
216
  #------------------------------------------------------------------------------
191
217
  def self.csv_columns(workshop)
@@ -202,9 +228,7 @@ public
202
228
  column_definitions << ["State", "item.state.capitalize"]
203
229
  column_definitions << ["Zipcode", "item.zipcode"]
204
230
  column_definitions << ["Country", "item.country.code"]
205
-
206
231
  column_definitions << ['Registered on', 'item.created_at.to_date', 75, {type: 'DateTime', numberformat: 'd mmm, yyyy'}]
207
-
208
232
  column_definitions << ["Price", "item.workshop_price.price.to_f", nil, {type: 'Number', numberformat: '#,##0.00'}]
209
233
  column_definitions << ["Price Description", "item.workshop_price.price_description"]
210
234
  column_definitions << ["Price Sub Descr", "item.workshop_price.sub_description"]
@@ -180,7 +180,7 @@ class Workshop < ActiveRecord::Base
180
180
  success = failed = 0
181
181
  unpaid_list = ( registration_id == 'all' ? registrations.unpaid : registrations.unpaid.where(id: registration_id) )
182
182
  unpaid_list.each do |registration|
183
- if registration.payment_reminder_due? || registration_id != 'all'
183
+ if (registration.payment_reminder_due? && registration.payment_owed.positive?) || registration_id != 'all'
184
184
  email = PaymentReminderMailer.payment_reminder(registration).deliver_now
185
185
  if email
186
186
  registration.update_attribute(:payment_reminder_sent_on, Time.now)
@@ -45,7 +45,7 @@ class WorkshopPrice < ActiveRecord::Base
45
45
  # Call this method on the attributes before passing into new() or update_attributes()
46
46
  #------------------------------------------------------------------------------
47
47
  def self.prepare_prices(attributes = {})
48
- attributes['price'] = attributes['price'].to_money(attributes['price_currency']) if attributes['price'].present? && attributes['price_currency'].present?
48
+ attributes['price'] = attributes['price'].to_money(attributes['price_currency']) if attributes['price'].present? && attributes['price_currency'].present?
49
49
  attributes['alt1_price'] = attributes['alt1_price'].to_money(attributes['alt1_price_currency']) if attributes['alt1_price'].present? && attributes['alt1_price_currency'].present?
50
50
  attributes['alt2_price'] = attributes['alt2_price'].to_money(attributes['alt2_price_currency']) if attributes['alt2_price'].present? && attributes['alt2_price_currency'].present?
51
51
  return attributes
@@ -53,14 +53,14 @@ class WorkshopPrice < ActiveRecord::Base
53
53
 
54
54
  #------------------------------------------------------------------------------
55
55
  def visible?
56
- !(disabled? || (!valid_starting_on.nil? && valid_starting_on > Time.now.to_date) || (!valid_until.nil? && valid_until < Time.now.to_date))
56
+ !(disabled? || (!valid_starting_on.nil? && valid_starting_on > Date.today) || (!valid_until.nil? && valid_until < Date.today))
57
57
  end
58
58
 
59
59
  # If the total_available is nil, then there are unlimited tickets to be sold.
60
60
  # Otherwise, check if we have sold out
61
61
  #------------------------------------------------------------------------------
62
62
  def sold_out?(num_sold)
63
- (total_available.blank? or total_available == 0) ? false : (num_sold >= total_available)
63
+ total_available.nil? ? false : (num_sold >= total_available)
64
64
  end
65
65
 
66
66
  #------------------------------------------------------------------------------
@@ -78,12 +78,41 @@ class WorkshopPrice < ActiveRecord::Base
78
78
  def recurring_payments?
79
79
  recurring_number.to_i > 1
80
80
  end
81
-
81
+
82
+ # return array of when payments should be made. if `from_date` is specified,
83
+ # then actual dates are returned. Otherwise number of days
84
+ #------------------------------------------------------------------------------
85
+ def payment_schedule(from_date = nil)
86
+ schedule = []
87
+ if recurring_payments?
88
+ (0...recurring_number).each do |period|
89
+ xdays = period * recurring_period
90
+ schedule << {due_on: (from_date ? from_date.to_date + xdays.days : xdays),
91
+ period_payment: payment_price,
92
+ total_due: (period + 1) * payment_price}
93
+ end
94
+ # adjust the last entry
95
+ schedule.last[:total_due] = price
96
+ schedule.last[:period_payment] = price - (recurring_number - 1) * payment_price
97
+ else
98
+ schedule << {due_on: (from_date ? from_date.to_date : 0), period_payment: payment_price, total_due: payment_price}
99
+ end
100
+ schedule
101
+ end
102
+
103
+ # return the payment schedule entry that is before the specified date
104
+ #------------------------------------------------------------------------------
105
+ def specific_payment_schedule(from_date, on_date = Date.today)
106
+ payment_schedule(from_date).reverse.detect {|item| item[:due_on] <= on_date}
107
+ end
108
+
109
+ # return list of currencies used, in a format for a dropdown list
110
+ # ex: [['USD', 'USD'], ['EUR', 'EUR']]
82
111
  #------------------------------------------------------------------------------
83
112
  def currency_list
84
113
  list = [[price_currency, price_currency]]
85
114
  list << [alt1_price_currency, alt1_price_currency] unless alt1_price_currency.blank?
86
- list << [alt2_price_currency, alt1_price_currency] unless alt1_price_currency.blank?
115
+ list << [alt2_price_currency, alt2_price_currency] unless alt2_price_currency.blank?
87
116
  return list
88
117
  end
89
118
 
@@ -95,21 +124,26 @@ class WorkshopPrice < ActiveRecord::Base
95
124
 
96
125
  # return a bank object filled with the exchange rates, based on the prices.
97
126
  # then you can do: bank.exchange_with(price, 'USD')
127
+ # note: since JPY doesn't have cents (the price doesn't get multiplied by 100)
128
+ # then we need to do that in order to calculate the proper exchange rate
98
129
  #------------------------------------------------------------------------------
99
130
  def bank
100
131
  unless @bank
101
132
  @bank = Money::Bank::VariableExchange.new
133
+ base_cents = price_currency == 'JPY' ? (price_cents.to_f * 100) : price_cents.to_f
102
134
  unless alt1_price_currency.blank?
103
- @bank.add_rate(price_currency, alt1_price_currency, alt1_price_cents.to_f / price_cents.to_f)
104
- @bank.add_rate(alt1_price_currency, price_currency, price_cents.to_f / alt1_price_cents.to_f)
135
+ alt1_cents = alt1_price_currency == 'JPY' ? (alt1_price_cents.to_f * 100) : alt1_price_cents.to_f
136
+ @bank.add_rate(price_currency, alt1_price_currency, alt1_cents / base_cents)
137
+ @bank.add_rate(alt1_price_currency, price_currency, base_cents / alt1_cents)
105
138
  end
106
139
  unless alt2_price_currency.blank?
107
- @bank.add_rate(price_currency, alt2_price_currency, alt2_price_cents.to_f / price_cents.to_f)
108
- @bank.add_rate(alt2_price_currency, price_currency, price_cents.to_f / alt2_price_cents.to_f)
140
+ alt2_cents = alt2_price_currency == 'JPY' ? (alt2_price_cents.to_f * 100) : alt2_price_cents.to_f
141
+ @bank.add_rate(price_currency, alt2_price_currency, alt2_cents / base_cents)
142
+ @bank.add_rate(alt2_price_currency, price_currency, base_cents / alt2_cents)
109
143
  end
110
- unless alt1_price_currency.blank? && alt2_price_currency.blank?
111
- @bank.add_rate(alt1_price_currency, alt2_price_currency, alt2_price_cents.to_f / alt1_price_cents.to_f)
112
- @bank.add_rate(alt2_price_currency, alt1_price_currency, alt1_price_cents.to_f / alt2_price_cents.to_f)
144
+ if !alt1_price_currency.blank? && !alt2_price_currency.blank?
145
+ @bank.add_rate(alt1_price_currency, alt2_price_currency, alt2_cents / alt1_cents)
146
+ @bank.add_rate(alt2_price_currency, alt1_price_currency, alt1_cents / alt2_cents)
113
147
  end
114
148
  end
115
149
  return @bank
@@ -38,6 +38,8 @@
38
38
  <div class="col-md-6 col-sm-6">
39
39
  <% if @registration.workshop_price %>
40
40
  <dl class="block">
41
+ <dt class="text-info">Registered On</dt>
42
+ <dd><%= format_date(@registration.created_at) %></dd>
41
43
  <dt class="text-info">Price</dt>
42
44
  <dd><%= price_details(@registration.workshop_price) %></dd>
43
45
  <% if @registration.payment_comment %>
@@ -106,13 +108,17 @@
106
108
  </tr>
107
109
  <% if @registration.payment_owed.positive? %>
108
110
  <tr class="section">
109
- <th>Next Payment Amount
111
+ <th>Payment Owed
110
112
  <% if @registration.workshop_price.recurring_payments? %>
111
113
  <span style="font-weight: normal"></br>On a payment plan</span>
112
114
  <% end %>
113
115
  </th>
114
116
  <td class="text-info"><%= @registration.payment_owed.format %></td>
115
117
  </tr>
118
+ <tr>
119
+ <th>Payment Due On</th>
120
+ <td class="text-info"><%= format_date(@registration.last_payment_due_on) %></td>
121
+ </tr>
116
122
  <% end %>
117
123
  </table>
118
124
  </div>
@@ -171,39 +177,77 @@
171
177
 
172
178
  <%#--- Payments Tab %>
173
179
  <div class="tab-pane fade" id="payment_tab">
174
- <%= subsection title: 'Recorded Payments' do %>
175
- <table class="table table-condensed table-gradient table-hover" style="margin-bottom: 20px;">
176
- <thead>
180
+ <div class="row">
181
+ <div class="col-md-9 col-sm-9">
182
+ <%= subsection title: 'Recorded Payments' do %>
183
+ <table class="table table-condensed table-gradient table-hover" style="margin-bottom: 20px;">
184
+ <thead>
185
+ <tr>
186
+ <th width="100">Date</th>
187
+ <th>Description</th>
188
+ <th width="75">Paid</th>
189
+ <th width="100">Method</th>
190
+ <th width="18"><%= icons(:edit) %></th>
191
+ </tr>
192
+ </thead>
193
+ <tbody>
194
+ <% @registration.payment_histories.order('created_on ASC').each do |payment| %>
195
+ <tr>
196
+ <td><%= format_date(payment.payment_date) %></td>
197
+ <td><%= payment.item_ref %></td>
198
+ <td><%= payment.total.format(no_cents_if_whole: true, symbol: true) %></td>
199
+ <td><%= payment.payment_method %></td>
200
+ <td>
201
+ <% if payment.user_profile #--- only allow editing of manual payments %>
202
+ <%= link_to(icons('icon-pencil3'), '#', data: { toggle: "modal", target: "#edit_payment_#{payment.id}"}, title: 'Edit Payment') %>
203
+ <% end %>
204
+ </td>
205
+ </tr>
206
+ <% end %>
207
+ </tbody>
208
+ </table>
209
+ <%= link_to 'Add Payment', '#', data: { toggle: "modal", target: "#enter_new_payment"}, title: 'Add Payment', class: "btn btn-xs btn-primary" %>
210
+ <%= link_to 'Customer Payment Page', @registration.payment_url, target: '_blank', class: 'btn btn-xs btn-default' %>
211
+ <% if @registration.payment_owed.positive? %>
212
+ &nbsp;&nbsp;Next payment should be: <span class="text-info"><%= @registration.payment_owed.format %></span>
213
+ <% end %>
214
+ <% end %>
215
+ </div>
216
+ <div class="col-md-3 col-sm-3">
217
+ <table class="accounting" style="width: 100%">
177
218
  <tr>
178
- <th width="100">Date</th>
179
- <th>Description</th>
180
- <th width="75">Paid</th>
181
- <th width="100">Method</th>
182
- <th width="18"><%= icons(:edit) %></th>
219
+ <th>Price</th>
220
+ <td><%= @registration.price.format %></td>
183
221
  </tr>
184
- </thead>
185
- <tbody>
186
- <% @registration.payment_histories.order('created_on ASC').each do |payment| %>
187
- <tr>
188
- <td><%= format_date(payment.payment_date) %></td>
189
- <td><%= payment.item_ref %></td>
190
- <td><%= payment.total.format(no_cents_if_whole: true, symbol: true) %></td>
191
- <td><%= payment.payment_method %></td>
192
- <td>
193
- <% if payment.user_profile #--- only allow editing of manual payments %>
194
- <%= link_to(icons('icon-pencil3'), '#', data: { toggle: "modal", target: "#edit_payment_#{payment.id}"}, title: 'Edit Payment') %>
222
+ <tr>
223
+ <th>Discount</th>
224
+ <td class="text-danger"> - <%= @registration.discount.format %></td>
225
+ </tr>
226
+ <tr class="section">
227
+ <th>Total</th>
228
+ <td><strong><%= @registration.discounted_price.format %></strong></td>
229
+ </tr>
230
+ <tr>
231
+ <th>Paid</th>
232
+ <td class="text-success"><%= @registration.amount_paid.format %></td>
233
+ </tr>
234
+ <% if @registration.payment_owed.positive? %>
235
+ <tr class="section">
236
+ <th>Payment Owed
237
+ <% if @registration.workshop_price.recurring_payments? %>
238
+ <span style="font-weight: normal"></br>On a payment plan</span>
195
239
  <% end %>
196
- </td>
240
+ </th>
241
+ <td class="text-info"><%= @registration.payment_owed.format %></td>
242
+ </tr>
243
+ <tr>
244
+ <th>Payment Due On</th>
245
+ <td class="text-info"><%= format_date(@registration.last_payment_due_on) %></td>
197
246
  </tr>
198
247
  <% end %>
199
- </tbody>
200
- </table>
201
- <%= link_to 'Add Payment', '#', data: { toggle: "modal", target: "#enter_new_payment"}, title: 'Add Payment', class: "btn btn-xs btn-primary" %>
202
- <%= link_to 'Customer Payment Page', @registration.payment_url, target: '_blank', class: 'btn btn-xs btn-default' %>
203
- <% if @registration.payment_owed.positive? %>
204
- &nbsp;&nbsp;Next payment should be: <span class="text-info"><%= @registration.payment_owed.format %></span>
205
- <% end %>
206
- <% end %>
248
+ </table>
249
+ </div>
250
+ </div>
207
251
 
208
252
  <%= subsection title: 'Payment Reminders' do %>
209
253
  <%= simple_form_for @registration, url: admin_registration_path,
@@ -85,27 +85,34 @@
85
85
  </div>
86
86
  </div>
87
87
 
88
- <% unpaid = @workshop.registrations.unpaid.order('payment_reminder_sent_on ASC') %>
89
88
  <% toolbar = toolbar_btn('Send Payment Reminders', send_payment_reminder_emails_admin_workshop_path(@workshop, registration_id: :all), class: 'btn btn-xs btn-info', method: :patch, data: {confirm: "Are you sure you wish to send a reminder email to those eligible?"}) %>
90
- <%= panel body: false, title: "#{unpaid.count} Unpaid Participants", toolbar: toolbar do %>
89
+ <%= panel body: false, title: "#{@unpaid.count} Unpaid Participants", toolbar: toolbar do %>
90
+ <div class="panel-body">
91
+ <p>Shows those that have missed their payment (either a full payment, or a recurring payment). If they have made all the payments
92
+ up to this point in time, they are not listed.</p>
93
+ <p>You can send them a payment reminder email at any time using the <em>Send</em> button. A line is marked in blue if we have detected that
94
+ they should be sent a payment reminder. <em>Send Payment Reminders</em> will send the email to all persons marked in blue.</p>
95
+ </div>
91
96
  <table class="table table-bordered table-condensed table-striped">
92
97
  <thead>
93
98
  <tr>
94
99
  <th class="receipt_code">Receipt</th>
95
100
  <th class="name">Name</th>
96
- <th class="cost">Balance</th>
97
- <th class="date">Registered</th>
101
+ <th class="cost">Balance Owed</th>
102
+ <th class="cost">Payment Due</th>
103
+ <th class="date">Payment Was Due On</th>
98
104
  <th class="date">Reminded</th>
99
105
  <th></th>
100
106
  </tr>
101
107
  </thead>
102
108
  <tbody>
103
- <% unpaid.each do |registration| -%>
109
+ <% @unpaid.each do |registration| -%>
104
110
  <tr>
105
111
  <td class="receipt_code" style="white-space:nowrap"><%= registration.receipt_code %></td>
106
112
  <td class="name"><%= link_to registration.full_name, dm_event.edit_admin_registration_path(registration) %> </td>
107
113
  <td class="cost"><%= registration.balance_owed.format %><%= content_tag(:span, icons('icon-bubble2 inside-content', size: 12), class: 'pull-right') if registration.private_comments.count > 0 %></td>
108
- <td class="date" style="white-space:nowrap"><%= format_date(registration.created_at) %></td>
114
+ <td class="cost"><%= registration.payment_owed.format %><%= " <sup>R</sup>".html_safe if registration.try(:workshop_price).try(:recurring_payments?) %></td>
115
+ <td class="date" style="white-space:nowrap"><%= format_date(registration.last_payment_due_on) %></td>
109
116
  <td class="date" <%= "style='background-color: #B3D4FD;'".html_safe if registration.payment_reminder_due? %>>
110
117
  <% if registration.payment_reminder_sent_on.nil? %>
111
118
  not yet