dm_event 4.2.3.2 → 4.2.3.3

Sign up to get free protection for your applications and to get access to all the features.
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