effective_memberships 0.9.6 → 0.9.8

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.
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)