effective_memberships 0.9.6 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/admin/registrar_actions_controller.rb +4 -0
  3. data/app/controllers/effective/membership_directory_controller.rb +9 -0
  4. data/app/datatables/admin/effective_organizations_datatable.rb +1 -1
  5. data/app/datatables/effective_memberships_directory_datatable.rb +12 -12
  6. data/app/models/concerns/effective_memberships_applicant.rb +11 -7
  7. data/app/models/concerns/effective_memberships_category.rb +3 -0
  8. data/app/models/concerns/effective_memberships_directory.rb +44 -18
  9. data/app/models/concerns/effective_memberships_organization.rb +4 -0
  10. data/app/models/concerns/effective_memberships_owner.rb +23 -7
  11. data/app/models/concerns/effective_memberships_registrar.rb +68 -16
  12. data/app/models/effective/membership.rb +32 -9
  13. data/app/models/effective/membership_history.rb +6 -1
  14. data/app/models/effective/registrar_action.rb +9 -1
  15. data/app/views/admin/applicants/_form_approve.html.haml +10 -0
  16. data/app/views/admin/memberships/_form.html.haml +4 -0
  17. data/app/views/admin/registrar_actions/_form.html.haml +3 -0
  18. data/app/views/admin/registrar_actions/_form_reinstatement.html.haml +44 -0
  19. data/app/views/admin/registrar_actions/_form_remove.html.haml +5 -0
  20. data/app/views/effective/applicants/select/_apply_for_reinstatement.html.haml +1 -3
  21. data/app/views/effective/applicants/select.html.haml +7 -11
  22. data/app/views/effective/membership_directory/_form.html.haml +8 -5
  23. data/app/views/effective/membership_directory/_membership.html.haml +20 -0
  24. data/app/views/effective/membership_directory/_membership_directory.html.haml +4 -4
  25. data/app/views/effective/membership_directory/show.html.haml +25 -0
  26. data/app/views/effective/memberships/_dashboard.html.haml +1 -0
  27. data/config/routes.rb +1 -1
  28. data/db/migrate/01_create_effective_memberships.rb.erb +3 -0
  29. data/lib/effective_memberships/version.rb +1 -1
  30. metadata +5 -3
  31. data/app/views/effective/membership_directory/_membership_owner.html.haml +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f42637485b8a72cd2cf2a7f4845e75dbca8d3c379270d4dfe0a607e70610117d
4
- data.tar.gz: 9d41bd14aad6e7e54c76568bd58147d43965fd30060c07525941294b7504d28e
3
+ metadata.gz: 612909b87d53748d9b3488893e419cf6f8bdcb852fdb07d3bd0f015bda89771c
4
+ data.tar.gz: 7c48d16e502036542d4f80e785986134e19466593b44ecce9cef151e76387acc
5
5
  SHA512:
6
- metadata.gz: d456e9bcd40ce78fc83689c5352a85bc156ec50f7149bd169f7af1521d3ec1c58f6ed67b06922b28b36fec1cbb7c880a58e4e6c7ddcf197f3872e37fb5ac247b
7
- data.tar.gz: 2f27824362cf294f83d4f953bb8c283045ef70a03910b17eeff1591f90df0a508bcf7dff1bbca53b49d6663f60d50376e9a8d4fb50da9cc47e6889724d34ef4d
6
+ metadata.gz: 835021e63b547f68e17ae9bd9ca6bbc523217e1f4f7c3eaf100696ef4456fb63b4f384765cad26db941aca67203ddb87e157ac64620e41220f98571a23d9074b
7
+ data.tar.gz: 706ec5a09d03a23f5495ccae6ec1f28fb930a4a1533a41b15f6f9a859f6e1bbb1d19dd9df0f83e0e6a56fb0bd6abe6d378576e5d1d439a2fa9ebaa3adfec2d69
@@ -14,6 +14,10 @@ module Admin
14
14
  success: -> { "#{resource.owner} has been reclassified to #{resource.owner.membership.category}" },
15
15
  redirect: -> { admin_owners_path(resource) }
16
16
 
17
+ submit :reinstate, 'Reinstate',
18
+ success: -> { "#{resource.owner} has been reinstated to #{resource.owner.membership.category}" },
19
+ redirect: -> { admin_owners_path(resource) }
20
+
17
21
  submit :status_assign, 'Assign Status',
18
22
  success: -> { "#{resource.owner} has been assigned #{resource.owner.membership.statuses_sentence}" },
19
23
  redirect: -> { admin_owners_path(resource) }
@@ -16,6 +16,15 @@ module Effective
16
16
  @memberships = @membership_directory.results(page: params[:page])
17
17
  end
18
18
 
19
+ def show
20
+ @page_title = 'Directory'
21
+
22
+ @membership = Effective::Membership.find_by_token(params[:id])
23
+
24
+ EffectiveResources.authorize!(self, :show, @membership)
25
+ EffectiveResources.authorize!(self, :show, @membership.owner)
26
+ end
27
+
19
28
  def build_membership_directory
20
29
  directory = EffectiveMemberships.MembershipDirectory.new(search_params)
21
30
  directory.current_user = current_user
@@ -63,7 +63,7 @@ module Admin
63
63
  end
64
64
 
65
65
  collection do
66
- EffectiveMemberships.Organization.deep.left_joins(:membership).includes(:addresses, membership: :membership_categories)
66
+ EffectiveMemberships.Organization.deep.left_joins(:membership).includes(:addresses, membership: [membership_categories: :category], representatives: :user)
67
67
  end
68
68
 
69
69
  def categories
@@ -3,25 +3,25 @@ class EffectiveMembershipsDirectoryDatatable < Effective::Datatable
3
3
  datatable do
4
4
  length 100
5
5
 
6
- col(:name) { |membership| membership.owner.to_s }
7
-
6
+ col :owner_name, label: 'Name'
8
7
  col :joined_on
9
8
  col :number
10
- col :categories, search: :string, label: 'Category'
11
- end
12
-
13
- collection do
14
- scope = Effective::Membership.directory.all
15
9
 
16
- archived_klasses.each do |klass|
17
- scope = scope.where.not(owner_id: klass.archived.select('id'), owner_type: klass.name)
10
+ col(:categories, search: categories, label: 'Category', sql_column: true).search do |collection, term|
11
+ collection.with_category(EffectiveMemberships.Category.find(term))
18
12
  end
19
13
 
20
- scope
21
14
  end
22
15
 
23
- def archived_klasses
24
- @archived_klasses ||= Effective::Membership.owner_klasses.select { |klass| klass.try(:acts_as_archived?) }
16
+ collection do
17
+ Effective::Membership.deep.sorted
18
+ .in_good_standing
19
+ .without_archived_owners
20
+ .with_category(categories)
21
+ end
22
+
23
+ def categories
24
+ EffectiveMemberships.Category.membership_directory.all
25
25
  end
26
26
 
27
27
  end
@@ -21,6 +21,10 @@ module EffectiveMembershipsApplicant
21
21
  [:start, :select, :summary, :billing, :checkout, :submitted]
22
22
  end
23
23
 
24
+ # Apply to Join - Anyone can do this
25
+ # Apply to Reclassify - Must have an existing membership and category
26
+ # Apply for Reinstatement - Must have a removed membership. Membership history.
27
+
24
28
  def categories
25
29
  ['Apply to Join', 'Apply to Reclassify', 'Apply for Reinstatement']
26
30
  end
@@ -101,7 +105,6 @@ module EffectiveMembershipsApplicant
101
105
  # Required for Apply to Reclassify
102
106
  belongs_to :from_category, polymorphic: true, optional: true
103
107
 
104
- # Required for Apply for Reinstatement
105
108
  belongs_to :from_status, polymorphic: true, optional: true
106
109
 
107
110
  has_many :applicant_reviews, -> { order(:id) }, as: :applicant, inverse_of: :applicant, dependent: :destroy
@@ -202,8 +205,8 @@ module EffectiveMembershipsApplicant
202
205
  self.from_category = owner.membership.categories.first
203
206
  end
204
207
 
205
- if owner.membership.present? && reinstatement?
206
- self.from_status = owner.membership.statuses.find { |status| status.reinstatable? }
208
+ if reinstatement?
209
+ self.from_category = owner.reinstatement_membership_category
207
210
  end
208
211
  end
209
212
 
@@ -215,7 +218,6 @@ module EffectiveMembershipsApplicant
215
218
  validates :user, presence: true
216
219
 
217
220
  validates :from_category, presence: true, if: -> { reclassification? }
218
- validates :from_status, presence: true, if: -> { reinstatement? }
219
221
 
220
222
  validate(if: -> { reclassification? && category_id.present? }) do
221
223
  errors.add(:category_id, "can't reclassify to existing category") if category_id == from_category_id
@@ -603,10 +605,12 @@ module EffectiveMembershipsApplicant
603
605
 
604
606
  # Used by the select step
605
607
  def can_apply_applicant_types_collection
606
- if owner.blank? || owner.membership.blank? || owner.membership.categories.blank?
608
+ if owner.blank?
607
609
  ['Apply to Join']
608
- elsif owner.membership.statuses.any?(&:reinstatable?)
610
+ elsif owner.membership_removed?
609
611
  ['Apply for Reinstatement', 'Apply to Join']
612
+ elsif owner.membership.blank? || owner.membership.categories.blank?
613
+ ['Apply to Join']
610
614
  else
611
615
  ['Apply to Reclassify']
612
616
  end
@@ -861,7 +865,7 @@ module EffectiveMembershipsApplicant
861
865
  elsif reclassification?
862
866
  EffectiveMemberships.Registrar.reclassify!(owner, to: to_category, status: to_status)
863
867
  elsif reinstatement?
864
- EffectiveMemberships.Registrar.reinstate!(owner, from: from_status)
868
+ EffectiveMemberships.Registrar.reinstate!(owner, to: to_category)
865
869
  else
866
870
  raise('unsupported approval applicant_type')
867
871
  end
@@ -114,6 +114,9 @@ module EffectiveMembershipsCategory
114
114
  .or(where(can_apply_restricted: true))
115
115
  }
116
116
 
117
+ # Can be used to limit which memberships are displayed in the directory
118
+ scope :membership_directory, -> { sorted }
119
+
117
120
  validates :title, presence: true, uniqueness: true
118
121
  validates :category_type, presence: true
119
122
  validates :position, presence: true
@@ -19,18 +19,29 @@ module EffectiveMembershipsDirectory
19
19
  attr_accessor :term
20
20
  attr_accessor :category
21
21
 
22
+ # Users
22
23
  attr_accessor :first_name
23
24
  attr_accessor :last_name
24
25
 
26
+ # Organizations
27
+ attr_accessor :title
28
+
25
29
  validates :term, length: { minimum: 3, allow_blank: true }
26
30
  validates :first_name, length: { minimum: 2, allow_blank: true }
27
31
  validates :last_name, length: { minimum: 2, allow_blank: true }
32
+ validates :title, length: { minimum: 2, allow_blank: true }
33
+ end
34
+
35
+ def categories
36
+ EffectiveMemberships.Category.membership_directory.all
28
37
  end
29
38
 
30
39
  # Base collection to search. Can be configured per tenant.
31
40
  def collection
32
- # Example::User.members.membership_in_good_standing.all
33
- raise('to be implemented by app membership_directory')
41
+ Effective::Membership.deep.sorted
42
+ .in_good_standing
43
+ .without_archived_owners
44
+ .with_category(categories)
34
45
  end
35
46
 
36
47
  # Search Users and Organizations for only these fields. Passed into search_any. Return nil for all.
@@ -44,21 +55,21 @@ module EffectiveMembershipsDirectory
44
55
  end
45
56
 
46
57
  def present?
47
- term.present? || first_name.present? || last_name.present? || category.present?
58
+ term.present? || first_name.present? || last_name.present? || title.present? || category.present?
48
59
  end
49
60
 
50
61
  # Search and assigns the collection
51
62
  # Assigns the entire collection() if there are no search terms
52
63
  # Otherwise validate the search terms
53
64
  def search!
54
- @membership_owners = build_collection()
55
- @membership_owners = @membership_owners.none if present? && !valid?
56
- @membership_owners
65
+ @memberships = build_collection()
66
+ @memberships = @memberships.none if present? && !valid?
67
+ @memberships
57
68
  end
58
69
 
59
70
  # The unpaginated results of the search
60
- def membership_owners
61
- @membership_owners || collection
71
+ def memberships
72
+ @memberships || collection
62
73
  end
63
74
 
64
75
  # The paginated results
@@ -66,38 +77,53 @@ module EffectiveMembershipsDirectory
66
77
  page = (page || 1).to_i
67
78
  offset = [(page - 1), 0].max * per_page
68
79
 
69
- membership_owners.limit(per_page).offset(offset)
80
+ memberships.limit(per_page).offset(offset)
70
81
  end
71
82
 
72
83
  protected
73
84
 
74
85
  def build_collection
75
- owners = collection()
76
- raise('expected an ActiveRecord collection') unless owners.kind_of?(ActiveRecord::Relation)
77
- raise('expected a collection of membership_owners') unless owners.klass.respond_to?(:effective_memberships_owner?)
86
+ memberships = collection()
87
+ raise('expected an ActiveRecord collection') unless memberships.kind_of?(ActiveRecord::Relation)
88
+ raise('expected an ActiveRecord collection of Effective::Memberships') unless memberships.klass.respond_to?(:effective_membership?)
78
89
 
79
90
  # Filter by term
80
91
  if term.present?
81
- owners = Effective::Resource.new(owners).search_any(term, columns: search_any_columns)
92
+ owners = owner_klasses.map { |klass| Effective::Resource.new(klass).search_any(term, columns: search_any_columns) }
93
+ memberships = memberships.where(owner: owners)
82
94
  end
83
95
 
84
96
  # Filter by first name
85
97
  if first_name.present?
86
- owners = Effective::Resource.new(owners).search_any(first_name, columns: :first_name)
98
+ owners = owner_klasses.map { |klass| Effective::Resource.new(klass).search_any(first_name, columns: :first_name) }
99
+ memberships = memberships.where(owner: owners)
87
100
  end
88
101
 
89
102
  # Filter by last name
90
103
  if last_name.present?
91
- owners = Effective::Resource.new(owners).search_any(last_name, columns: :last_name)
104
+ owners = owner_klasses.map { |klass| Effective::Resource.new(klass).search_any(last_name, columns: :last_name) }
105
+ memberships = memberships.where(owner: owners)
106
+ end
107
+
108
+ # Filter by title
109
+ if title.present?
110
+ owners = owner_klasses.map { |klass| Effective::Resource.new(klass).search_any(last_name, columns: :title) }
111
+ memberships = memberships.where(owner: owners)
92
112
  end
93
113
 
94
114
  # Filter by category
95
115
  if category.present?
96
- cat = EffectiveMemberships.Category.where(id: category)
97
- owners = owners.members_with_category(cat) if cat.present?
116
+ memberships = memberships.with_category(EffectiveMemberships.Category.where(id: category))
98
117
  end
99
118
 
100
- owners
119
+ # Return an ActiveRecord::Relation of Effective::Memberships
120
+ memberships
121
+ end
122
+
123
+ private
124
+
125
+ def owner_klasses
126
+ Effective::Membership.owner_klasses
101
127
  end
102
128
 
103
129
  end
@@ -97,4 +97,8 @@ module EffectiveMembershipsOrganization
97
97
  representatives.reject(&:marked_for_destruction?).map(&:user)
98
98
  end
99
99
 
100
+ def membership_users
101
+ users.select { |user| user.is?(:member) && !user.archived? }
102
+ end
103
+
100
104
  end
@@ -130,8 +130,25 @@ module EffectiveMembershipsOwner
130
130
  end
131
131
 
132
132
  def membership_removed_on
133
- return nil unless membership_removed?
134
- membership_histories.find { |history| history.removed? }.start_on
133
+ removed_membership_history.start_on if membership_removed?
134
+ end
135
+
136
+ # The membership history that is removed. Has exit statuses that may affect reinstatement fees.
137
+ def removed_membership_history
138
+ membership_histories.reverse.find { |history| history.removed? } if membership_removed?
139
+ end
140
+
141
+ # The membership history we would reinstate to. Should be the one just before removed.
142
+ def reinstatement_membership_history
143
+ membership_histories.reverse.find { |history| !history.removed? } if membership_removed?
144
+ end
145
+
146
+ def reinstatement_membership_category
147
+ reinstatement_membership_history.membership_categories.first if membership_removed?
148
+ end
149
+
150
+ def reinstatement_membership_statuses
151
+ reinstatement_membership_history.membership_statuses if membership_removed?
135
152
  end
136
153
 
137
154
  def registrar_action_categories(action)
@@ -139,7 +156,7 @@ module EffectiveMembershipsOwner
139
156
  end
140
157
 
141
158
  def registrar_action_statuses(action)
142
- EffectiveMemberships.Status.sorted.all.where.not(id: EffectiveMemberships.Registrar.not_in_good_standing_status)
159
+ EffectiveMemberships.Status.sorted.all
143
160
  end
144
161
 
145
162
  def after_build_fee(fee)
@@ -311,9 +328,7 @@ module EffectiveMembershipsOwner
311
328
  EffectiveMemberships.Registrar.in_good_standing!(self)
312
329
  end
313
330
 
314
- if membership_histories.blank?
315
- build_membership_history()
316
- end
331
+ build_membership_history() if membership_histories.blank?
317
332
 
318
333
  save!
319
334
  end
@@ -361,9 +376,10 @@ module EffectiveMembershipsOwner
361
376
  errors = []
362
377
  history = membership_histories.first
363
378
  last_history = membership_histories.last
379
+ was_removed = membership_histories.any? { |history| history.removed? }
364
380
 
365
381
  # Check membership joined on date matches first history start date
366
- if membership.joined_on != history.start_on
382
+ if membership.joined_on != history.start_on && !was_removed
367
383
  errors << "The joined date #{membership.joined_on.strftime('%F')} does not match the first history start date of #{history.start_on.strftime('%F')}. Please change the first history start date to #{membership.joined_on.strftime('%F')} or update the joined date above."
368
384
  end
369
385
 
@@ -131,9 +131,12 @@ module EffectiveMembershipsRegistrar
131
131
  membership.registration_on = date # Always new registration_on
132
132
 
133
133
  # Assign Number (this could be nil)
134
- number = next_membership_number(owner, to: to) if number.blank?
135
- membership.number = number
136
- membership.number_as_integer = (Integer(number) rescue nil)
134
+ number = next_membership_number(owner, to: to) if number.nil?
135
+
136
+ if number.present?
137
+ membership.number = number
138
+ membership.number_as_integer = (Integer(number) rescue nil)
139
+ end
137
140
 
138
141
  # Assign Category
139
142
  membership.build_membership_category(category: to)
@@ -173,11 +176,13 @@ module EffectiveMembershipsRegistrar
173
176
 
174
177
  date ||= Time.zone.now
175
178
 
179
+ # Existing Membership
176
180
  membership = owner.membership
177
181
 
178
- # Assign Category
182
+ # Last Changed Date
179
183
  membership.registration_on = date
180
184
 
185
+ # Assign Category
181
186
  membership.build_membership_category(category: to)
182
187
  membership.membership_category(category: from).mark_for_destruction
183
188
 
@@ -195,24 +200,60 @@ module EffectiveMembershipsRegistrar
195
200
  save!(owner, date: date)
196
201
  end
197
202
 
198
- def reinstate!(owner, from:, date: nil, skip_fees: false)
203
+ def reinstate!(owner, to: nil, date: nil, skip_fees: false)
199
204
  raise('expecting a memberships owner') unless owner.class.respond_to?(:effective_memberships_owner?)
200
- raise('owner must have an existing membership. use register! instead') if owner.membership.blank?
205
+ raise('owner must have a removed membership.') unless owner.membership_removed?
206
+
207
+ history = owner.reinstatement_membership_history
208
+ raise('owner must have a reinstatement membership history.') if history.blank?
201
209
 
202
- raise('expecting a from') if from.blank?
203
- raise('expected a from status or category') unless from.class.respond_to?(:effective_memberships_status?) || from.class.respond_to?(:effective_memberships_category?)
210
+ to ||= owner.reinstatement_membership_category
211
+ raise('expecting a to memberships category') unless to.class.respond_to?(:effective_memberships_category?)
204
212
 
213
+ statuses = owner.reinstatement_membership_statuses
214
+
215
+ # Default Date
205
216
  date ||= Time.zone.now
217
+ period = period(date: date)
218
+ period_end_on = period_end_on(date: date)
206
219
 
207
- membership = owner.membership
208
- membership.membership_status(status: from).mark_for_destruction if from.class.respond_to?(:effective_memberships_status?)
209
- membership.membership_category(category: from).mark_for_destruction if from.class.respond_to?(:effective_memberships_category?)
220
+ # Build a membership
221
+ membership = owner.build_membership
222
+
223
+ # Assign Dates
224
+ membership.joined_on = date
225
+ membership.registration_on = date
226
+
227
+ # Assign Number (this could be nil)
228
+ if history.number.present?
229
+ membership.number = history.number
230
+ membership.number_as_integer = (Integer(history.number) rescue nil)
231
+ end
232
+
233
+ # Assign Category
234
+ membership.build_membership_category(category: to)
235
+
236
+ # Assign Statuses
237
+ statuses.each do |status|
238
+ membership.build_membership_status(status: status)
239
+ end
210
240
 
241
+ # Assign fees paid through period
242
+ if skip_fees
243
+ membership.fees_paid_period = period
244
+ membership.fees_paid_through_period = period_end_on
245
+ end
246
+
247
+ # Or, Build Fees
211
248
  unless skip_fees
212
249
  fee = owner.build_prorated_fee(date: date)
213
250
  # This might already be present and purchased if joined and reinstated in the same period
214
251
  end
215
252
 
253
+ # Assign member role
254
+ add_member_role(owner)
255
+
256
+ # Save owner
216
257
  save!(owner, date: date)
217
258
  end
218
259
 
@@ -267,16 +308,17 @@ module EffectiveMembershipsRegistrar
267
308
  true
268
309
  end
269
310
 
270
- def remove!(owner, date: nil)
311
+ # When you remove, you can set a status(es) which may affect reinstatement applications
312
+ def remove!(owner, statuses: nil, date: nil, notes: nil)
313
+ statuses = Array(statuses)
314
+
271
315
  raise('expecting a memberships owner') unless owner.class.respond_to?(:effective_memberships_owner?)
272
316
  raise('expected a member with a membership') unless owner.membership.present?
317
+ raise('expecting an membership status (optional)') if statuses.present? && statuses.any? { |status| !status.class.respond_to?(:effective_memberships_status?) }
273
318
 
274
319
  # Date
275
320
  date ||= Time.zone.now
276
321
 
277
- # Remove Membership
278
- owner.membership.mark_for_destruction
279
-
280
322
  # Delete unpurchased fees and orders
281
323
  owner.outstanding_fee_payment_fees.each { |fee| fee.mark_for_destruction }
282
324
  owner.outstanding_fee_payment_orders.each { |order| order.mark_for_destruction }
@@ -284,7 +326,17 @@ module EffectiveMembershipsRegistrar
284
326
  # Remove member role
285
327
  remove_member_role(owner)
286
328
 
287
- save!(owner, date: date)
329
+ # Remove Membership
330
+ owner.membership.mark_for_destruction
331
+
332
+ # We do the save a bit differently here because we want to set the removed statuses
333
+ history = owner.build_membership_history(start_on: date, notes: notes)
334
+
335
+ if statuses.present?
336
+ history.assign_attributes(statuses: statuses.map(&:to_s), status_ids: statuses.map(&:id))
337
+ end
338
+
339
+ owner.save!
288
340
  end
289
341
 
290
342
  def not_in_good_standing!(owner, date: nil, notes: nil)
@@ -1,6 +1,7 @@
1
1
  module Effective
2
2
  class Membership < ActiveRecord::Base
3
3
  belongs_to :owner, polymorphic: true
4
+ has_secure_token
4
5
 
5
6
  attr_accessor :current_action
6
7
 
@@ -24,30 +25,34 @@ module Effective
24
25
  fees_paid_period :date # The most recent period they have paid in. Start date of period.
25
26
  fees_paid_through_period :date # The most recent period they have paid in. End date of period. Kind of an expires.
26
27
 
28
+ # Membership Directory
29
+ owner_name :string
30
+ token :string
31
+
27
32
  timestamps
28
33
  end
29
34
 
30
35
  scope :deep, -> { includes(:owner, membership_categories: :category, membership_statuses: :status) }
31
- scope :sorted, -> { order(:id) }
36
+ scope :sorted, -> { order('lower(owner_name)') }
32
37
 
33
38
  scope :with_status, -> (statuses) {
34
- raise('expected an EffectiveMemberships.Status') unless Array(statuses).all? { |status| status.kind_of?(EffectiveMemberships.Status) }
35
- where(id: MembershipStatus.where(status: statuses).select(:membership_id))
39
+ raise('expected an EffectiveMemberships.Status') unless statuses.class.respond_to?(:effective_memberships_status?) || Array(statuses).all? { |status| status.kind_of?(EffectiveMemberships.Status) }
40
+ where(id: MembershipStatus.where(status_id: statuses).select(:membership_id))
36
41
  }
37
42
 
38
43
  scope :without_status, -> (statuses) {
39
- raise('expected an EffectiveMemberships.Status') unless Array(statuses).all? { |status| status.kind_of?(EffectiveMemberships.Status) }
40
- where.not(id: MembershipStatus.where(status: statuses).select(:membership_id))
44
+ raise('expected an EffectiveMemberships.Status') unless statuses.class.respond_to?(:effective_memberships_status?) || Array(statuses).all? { |status| status.kind_of?(EffectiveMemberships.Status) }
45
+ where.not(id: MembershipStatus.where(status_id: statuses).select(:membership_id))
41
46
  }
42
47
 
43
48
  scope :with_category, -> (categories) {
44
- raise('expected an EffectiveMemberships.Category') unless Array(categories).all? { |cat| cat.kind_of?(EffectiveMemberships.Category) }
45
- where(id: MembershipCategory.where(category: categories).select(:membership_id))
49
+ raise('expected an EffectiveMemberships.Category') unless categories.class.respond_to?(:effective_memberships_category?) || Array(categories).all? { |cat| cat.kind_of?(EffectiveMemberships.Category) }
50
+ where(id: MembershipCategory.where(category_id: categories).select(:membership_id))
46
51
  }
47
52
 
48
53
  scope :without_category, -> (categories) {
49
- raise('expected an EffectiveMemberships.Category') unless Array(categories).all? { |cat| cat.kind_of?(EffectiveMemberships.Category) }
50
- where.not(id: MembershipCategory.where(category: categories).select(:membership_id))
54
+ raise('expected an EffectiveMemberships.Category') unless categories.class.respond_to?(:effective_memberships_category?) || Array(categories).all? { |cat| cat.kind_of?(EffectiveMemberships.Category) }
55
+ where.not(id: MembershipCategory.where(category_id: categories).select(:membership_id))
51
56
  }
52
57
 
53
58
  scope :joined_before, -> (date) {
@@ -73,14 +78,24 @@ module Effective
73
78
  scope :not_in_good_standing, -> { with_status(EffectiveMemberships.Registrar.not_in_good_standing_status) }
74
79
  scope :in_good_standing, -> { without_status(EffectiveMemberships.Registrar.not_in_good_standing_status) }
75
80
 
81
+ scope :without_archived_owners, -> {
82
+ where.not(owner: klass.acts_as_archived_owner_klasses.map { |klass| klass.archived })
83
+ }
84
+
76
85
  before_validation do
77
86
  self.registration_on ||= joined_on
78
87
  end
79
88
 
89
+ before_validation(if: -> { owner.present? }) do
90
+ self.owner_name = owner.to_s
91
+ end
92
+
80
93
  before_validation(if: -> { number_changed? }) do
81
94
  self.number_as_integer = (number.present? ? (Integer(number) rescue nil) : nil)
82
95
  end
83
96
 
97
+ validates :owner_name, presence: true
98
+
84
99
  validates :number, uniqueness: { allow_blank: true }
85
100
  validates :number, presence: true, if: -> { categories.any?(:membership_number_required?) }
86
101
 
@@ -100,11 +115,19 @@ module Effective
100
115
  maximum('number_as_integer') || 0
101
116
  end
102
117
 
118
+ def self.effective_membership?
119
+ true
120
+ end
121
+
103
122
  def self.owner_klasses
104
123
  klasses = Effective::Membership.distinct(:owner_type).pluck(:owner_type)
105
124
  klasses.select { |klass| klass.safe_constantize }.map { |klass| klass.constantize }
106
125
  end
107
126
 
127
+ def self.acts_as_archived_owner_klasses
128
+ owner_klasses.select { |klass| klass.try(:acts_as_archived?) }
129
+ end
130
+
108
131
  def to_s
109
132
  return 'membership' if owner.blank?
110
133
 
@@ -25,13 +25,18 @@ module Effective
25
25
  serialize :status_ids, Array
26
26
 
27
27
  scope :deep, -> { includes(:owner) }
28
- scope :sorted, -> { order(:start_on) }
28
+ scope :sorted, -> { order(:start_on).order(:id) }
29
29
 
30
30
  scope :removed, -> { where(removed: true) }
31
31
 
32
32
  validates :owner, presence: true
33
33
  validates :start_on, presence: true
34
34
 
35
+ with_options(if: -> { removed? }) do
36
+ validates :number, absence: { message: 'must be blank when removed' }
37
+ validates :category_ids, absence: { message: 'must be blank when removed' }
38
+ end
39
+
35
40
  def to_s
36
41
  'membership history'
37
42
  end
@@ -17,6 +17,9 @@ module Effective
17
17
  attr_accessor :membership_number
18
18
  attr_accessor :skip_fees
19
19
 
20
+ # Reinstate
21
+ attr_accessor :skip_fees
22
+
20
23
  # Status Change
21
24
  attr_accessor :status_ids
22
25
  attr_accessor :status_id
@@ -64,6 +67,11 @@ module Effective
64
67
  EffectiveMemberships.Registrar.reclassify!(owner, to: category, skip_fees: skip_fees?)
65
68
  end
66
69
 
70
+ def reinstate!
71
+ update!(current_action: :reinstate)
72
+ EffectiveMemberships.Registrar.reinstate!(owner, skip_fees: skip_fees?)
73
+ end
74
+
67
75
  def status_assign!
68
76
  update!(current_action: :status_assign)
69
77
  EffectiveMemberships.Registrar.status_assign!(owner, status: statuses)
@@ -86,7 +94,7 @@ module Effective
86
94
 
87
95
  def remove!
88
96
  update!(current_action: :remove)
89
- EffectiveMemberships.Registrar.remove!(owner)
97
+ EffectiveMemberships.Registrar.remove!(owner, statuses: statuses)
90
98
  end
91
99
 
92
100
  def assign_attributes(atts)
@@ -4,9 +4,13 @@
4
4
  - categories = EffectiveMemberships.Category.all
5
5
  = f.select :category_id, categories, label: 'Approve to'
6
6
 
7
+ - reinstatement = applicant.owner.reinstatement_membership_history if applicant.reinstatement?
8
+
7
9
  %p
8
10
  - if applicant.owner.membership&.number_was.present?
9
11
  The member will keep their existing membership number: #{applicant.owner.membership.number}.
12
+ - elsif applicant.reinstatement? && reinstatement.number.present?
13
+ The member will keep their previous membership number: #{reinstatement.number}.
10
14
  - else
11
15
  - number = EffectiveMemberships.Registrar.next_membership_number(applicant.owner, to: applicant.category)
12
16
 
@@ -15,6 +19,9 @@
15
19
  - else
16
20
  No membership number will be assigned.
17
21
 
22
+ - if applicant.reinstatement?
23
+ %p The member will be reinstated with their previous status: #{badges(reinstatement.membership_statuses) || 'None'}.
24
+
18
25
  %h3 Fees
19
26
  %p The following fees will be created:
20
27
  - month = Time.zone.now.strftime('%B')
@@ -27,6 +34,9 @@
27
34
  %li A #{month} prorated fee to the new category
28
35
  %li A #{month} discount fee from their old category
29
36
 
37
+ - if applicant.reinstatement?
38
+ %li A #{month} prorated fee to their reinstatement category
39
+
30
40
  %p The following unpurchased fees will be deleted:
31
41
  - outstanding_fee_payment_fees = applicant.owner.outstanding_fee_payment_fees
32
42
 
@@ -15,3 +15,7 @@
15
15
  - if membership.blank? && owner.membership_histories.present?
16
16
  = card('History') do
17
17
  = render_datatable(Admin::EffectiveMembershipHistoriesDatatable.new(owner: owner), inline: true, simple: true)
18
+
19
+ - if owner.membership_removed?
20
+ - registrar_action = Effective::RegistrarAction.new(current_user: current_user, owner: owner)
21
+ = render 'admin/registrar_actions/form_reinstatement', registrar_action: registrar_action
@@ -15,3 +15,6 @@
15
15
  .mb-4= render 'admin/registrar_actions/form_not_in_good_standing', registrar_action: registrar_action
16
16
  .mb-4= render 'admin/registrar_actions/form_fees_paid', registrar_action: registrar_action
17
17
  .mb-4= render 'admin/registrar_actions/form_remove', registrar_action: registrar_action
18
+
19
+
20
+ - # The reinstatement form is added by memberships/form and not here
@@ -0,0 +1,44 @@
1
+ .card
2
+ .card-body
3
+ %h5.card-title Reinstate
4
+
5
+ = effective_form_with(model: [:admin, registrar_action], url: effective_memberships.admin_registrar_actions_path) do |f|
6
+ = f.hidden_field :owner_id
7
+ = f.hidden_field :owner_type
8
+
9
+ - removed = f.object.owner.removed_membership_history
10
+ - reinstatement = f.object.owner.reinstatement_membership_history
11
+
12
+ - raise('expected a removed membership history') unless removed.present?
13
+ - raise('expected a reinstatement membership history') unless reinstatement.present?
14
+
15
+ - period = EffectiveMemberships.Registrar.current_period
16
+
17
+ %p.text-muted
18
+ Reinstate to previous membership and optionally create fees.
19
+
20
+ %p
21
+ This member was removed on #{removed.start_on.strftime('%F')} from
22
+ = reinstatement.membership_categories.first
23
+ = badges(reinstatement.membership_statuses)
24
+ - if reinstatement.number.present?
25
+ with number #{reinstatement.number}.
26
+
27
+ = f.check_box :current_action, label: 'Yes, reinstate this member to their previous category, status and number'
28
+
29
+ = f.show_if :current_action, true do
30
+ = f.check_box :skip_fees, label: 'Yes, skip creating fees and just set the category'
31
+
32
+ = f.hide_if :skip_fees, true do
33
+ %p The following fee(s) will be created:
34
+ - month = Time.zone.now.strftime('%B')
35
+
36
+ %ul
37
+ %li A #{month} prorated fee for the #{reinstatement.membership_categories.first} category
38
+
39
+ %p The member will be required to return to the website and make a fee payment
40
+
41
+ = f.show_if :skip_fees, true do
42
+ %p No fees will be created
43
+
44
+ = f.submit 'Reinstate', border: false, center: true, 'data-confirm': "Really reinstate #{f.object.owner}?"
@@ -13,5 +13,10 @@
13
13
  = f.check_box :current_action, label: 'Yes, remove this member'
14
14
 
15
15
  = f.show_if :current_action, true do
16
+ - statuses = f.object.owner.registrar_action_statuses(:remove)
17
+
18
+ - if statuses.present?
19
+ = f.select :status_ids, statuses, label: 'Remove with status', multiple: true, hint: 'Will be assigned to the membership history and may affect fees generated for reinstatement applicants'
20
+
16
21
  = f.submit 'Remove', border: false, center: true,
17
22
  'data-confirm': "Really remove #{f.object.owner}?"
@@ -1,3 +1 @@
1
- - membership = f.object.owner.membership
2
-
3
- %p Apply for reinstatement from #{badges(f.object.from_status || f.object.from_category)}.
1
+ %p Apply for reinstatement to #{resource.owner.reinstatement_membership_category}.
@@ -3,7 +3,8 @@
3
3
 
4
4
  - categories = resource.can_apply_categories_collection()
5
5
  - applicant_types = resource.can_apply_applicant_types_collection()
6
- - existing_category = resource.owner&.membership&.category
6
+ - existing_category = resource.owner.membership&.category
7
+ - reinstatement_category = resource.owner.reinstatement_membership_category
7
8
 
8
9
  = card do
9
10
  = effective_form_with(model: resource, url: wizard_path(step), method: :put) do |f|
@@ -20,11 +21,6 @@
20
21
  = render('effective/applicants/select/apply_to_join', f: f)
21
22
  = render('effective/applicants/select/categories', f: f, categories: categories)
22
23
 
23
- - elsif applicant_types == ['Apply for Reinstatement']
24
- = f.hidden_field :applicant_type, value: applicant_types.first
25
- = f.hidden_field :category_id, value: existing_category.id
26
- = render('effective/applicants/select/apply_for_reinstatement', f: f)
27
-
28
24
  - elsif applicant_types == ['Apply to Reclassify']
29
25
  = render('effective/applicants/select/apply_to_reclassify', f: f)
30
26
  = render('effective/applicants/select/categories', f: f, categories: categories - [existing_category])
@@ -32,6 +28,11 @@
32
28
  - else
33
29
  = f.select :applicant_type, applicant_types, label: 'Apply to...'
34
30
 
31
+ - if applicant_types.include?('Apply for Reinstatement')
32
+ = f.show_if :applicant_type, 'Apply for Reinstatement' do
33
+ = f.hidden_field :category_id, value: reinstatement_category.id
34
+ = render('effective/applicants/select/apply_for_reinstatement', f: f)
35
+
35
36
  - if applicant_types.include?('Apply to Join')
36
37
  = f.show_if :applicant_type, 'Apply to Join' do
37
38
  = render('effective/applicants/select/apply_to_join', f: f)
@@ -42,9 +43,4 @@
42
43
  = render('effective/applicants/select/apply_to_reclassify', f: f)
43
44
  = render('effective/applicants/select/categories', f: f, categories: categories - [existing_category])
44
45
 
45
- - if applicant_types.include?('Apply for Reinstatement')
46
- = f.show_if :applicant_type, 'Apply for Reinstatement' do
47
- = f.hidden_field :category_id, value: existing_category.id
48
- = render('effective/applicants/select/apply_for_reinstatement', f: f)
49
-
50
46
  = f.save 'Save and Continue'
@@ -1,11 +1,14 @@
1
1
  = effective_form_with(scope: :q, model: membership_directory, method: :get, url: request.path) do |f|
2
- - if f.object.term.present?
3
- = f.search_field :term, label: 'Name'
2
+ = f.search_field :term, label: 'Name'
4
3
 
5
- = f.search_field :first_name
6
- = f.search_field :last_name
4
+ -# Users only
5
+ /= f.search_field :first_name
6
+ /= f.search_field :last_name
7
7
 
8
- = f.select :category, EffectiveMemberships.Category.all
8
+ - # Organizations only
9
+ /= f.search_field :title
10
+
11
+ = f.select :category, membership_directory.categories
9
12
 
10
13
  = f.save('Search', class: 'btn btn-primary btn-search mr-3', name: nil)
11
14
  = link_to 'Reset filters', request.path
@@ -0,0 +1,20 @@
1
+ = card do
2
+ %h6= membership.owner
3
+ %p= membership.categories.map(&:membership_directory_title).to_sentence
4
+ %p= membership.statuses.map(&:membership_directory_title).to_sentence
5
+
6
+ - if membership.owner.try(:email).present?
7
+ %p= reveal_mail_to(membership.owner.email)
8
+
9
+ - if (organizations = membership.owner.try(:membership_organizations)).present?
10
+ .mb-4.membership-directory-organizations
11
+ - organizations.each do |organization|
12
+ %div= organization
13
+
14
+ - if (users = membership.owner.try(:membership_users)).present?
15
+ .mb-4.membership-directory-users
16
+ - users.each do |user|
17
+ %div= user
18
+
19
+ - if EffectiveResources.authorized?(self, :show, membership) && EffectiveResources.authorized?(self, :show, membership.owner)
20
+ = link_to 'Show more', effective_memberships.membership_directory_path(membership.token)
@@ -4,14 +4,14 @@
4
4
  - results = membership_directory.results(page: params[:page])
5
5
 
6
6
  - if membership_directory.present? && results.length == 0
7
- .alert.alert-info There are no results for your search. Please try again.
7
+ .mt-4.alert.alert-info There are no results for your search. Please try again.
8
8
 
9
9
  - results.in_groups_of(3).each do |group|
10
10
  .row.mt-4
11
- - group.each do |owner|
12
- - next unless owner
11
+ - group.each do |membership|
13
12
  .col-md
14
- = render('effective/membership_directory/membership_owner', membership: owner.membership, owner: owner)
13
+ - next unless membership
14
+ = render('effective/membership_directory/membership', membership: membership)
15
15
 
16
16
  %nav.d-flex.justify-content-center
17
17
  = bootstrap_paginate(results, per_page: membership_directory.per_page)
@@ -0,0 +1,25 @@
1
+ = render 'layout' do
2
+ .effective-membership
3
+
4
+ %h6= @membership.owner
5
+ %p= @membership.categories.map(&:membership_directory_title).to_sentence
6
+ %p= @membership.statuses.map(&:membership_directory_title).to_sentence
7
+
8
+ - if @membership.owner.respond_to?(:rich_text_body)
9
+ .mb-4= @membership.owner.rich_text_body.to_s
10
+
11
+ - if @membership.owner.try(:email).present?
12
+ %p= reveal_mail_to(@membership.owner.email)
13
+
14
+ - if (organizations = @membership.owner.try(:membership_organizations)).present?
15
+ .mb-4.membership-directory-organizations
16
+ - organizations.each do |organization|
17
+ %div= organization
18
+
19
+ - if (users = @membership.owner.try(:membership_users)).present?
20
+ .mb-4.membership-directory-users
21
+ - users.each do |user|
22
+ %div= user
23
+
24
+ %hr
25
+ = link_to 'Return to Directory', effective_memberships.membership_directory_index_path, class: 'btn btn-primary'
@@ -35,6 +35,7 @@
35
35
 
36
36
  - if current_user.membership_removed?
37
37
  %p Your membership was removed on #{current_user.membership_removed_on.strftime('%F')}.
38
+ %p You may Apply for Reinstatement to #{current_user.reinstatement_membership_category} by clicking the Apply to Join button below and then complete the wizard.
38
39
 
39
40
  - if membership_organizations.present?
40
41
  %p You are a representative for #{pluralize(membership_organizations.length, 'member organization')}.
data/config/routes.rb CHANGED
@@ -26,7 +26,7 @@ EffectiveMemberships::Engine.routes.draw do
26
26
  resources :build, controller: :fee_payments, only: [:show, :update]
27
27
  end
28
28
 
29
- resources :membership_directory, only: :index
29
+ resources :membership_directory, only: [:index, :show]
30
30
  get '/directory', to: 'membership_directory#index'
31
31
 
32
32
  resources :membership_cards, only: :index
@@ -95,6 +95,9 @@ class CreateEffectiveMemberships < ActiveRecord::Migration[6.0]
95
95
  t.date :fees_paid_period
96
96
  t.date :fees_paid_through_period
97
97
 
98
+ t.string :owner_name
99
+ t.string :token
100
+
98
101
  t.datetime :updated_at
99
102
  t.datetime :created_at
100
103
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveMemberships
2
- VERSION = '0.9.6'
2
+ VERSION = '0.9.8'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_memberships
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ version: 0.9.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-28 00:00:00.000000000 Z
11
+ date: 2022-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -383,6 +383,7 @@ files:
383
383
  - app/views/admin/registrar_actions/_form_not_in_good_standing.html.haml
384
384
  - app/views/admin/registrar_actions/_form_reclassify.html.haml
385
385
  - app/views/admin/registrar_actions/_form_register.html.haml
386
+ - app/views/admin/registrar_actions/_form_reinstatement.html.haml
386
387
  - app/views/admin/registrar_actions/_form_remove.html.haml
387
388
  - app/views/admin/registrar_actions/_form_status_change.html.haml
388
389
  - app/views/admin/representatives/_form.html.haml
@@ -482,9 +483,10 @@ files:
482
483
  - app/views/effective/membership_cards/index.html.haml
483
484
  - app/views/effective/membership_directory/_form.html.haml
484
485
  - app/views/effective/membership_directory/_layout.html.haml
486
+ - app/views/effective/membership_directory/_membership.html.haml
485
487
  - app/views/effective/membership_directory/_membership_directory.html.haml
486
- - app/views/effective/membership_directory/_membership_owner.html.haml
487
488
  - app/views/effective/membership_directory/index.html.haml
489
+ - app/views/effective/membership_directory/show.html.haml
488
490
  - app/views/effective/memberships/_dashboard.html.haml
489
491
  - app/views/effective/memberships/_membership.html.haml
490
492
  - app/views/effective/memberships_mailer/applicant_approved.liquid
@@ -1,7 +0,0 @@
1
- = card do
2
- %h6= owner
3
- %p= membership.categories.map(&:membership_directory_title).to_sentence
4
- %p= membership.statuses.map(&:membership_directory_title).to_sentence
5
-
6
- - if owner.try(:email).present?
7
- %p= reveal_mail_to(owner.email)