decidim-meetings 0.15.2 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/meetings/highlighted_meetings/show.erb +3 -0
  3. data/app/cells/decidim/meetings/highlighted_meetings_cell.rb +25 -0
  4. data/app/cells/decidim/meetings/highlighted_meetings_for_component/show.erb +33 -0
  5. data/app/cells/decidim/meetings/highlighted_meetings_for_component_cell.rb +37 -0
  6. data/app/cells/decidim/meetings/join_meeting_button/show.erb +19 -10
  7. data/app/commands/decidim/meetings/admin/close_meeting.rb +2 -1
  8. data/app/commands/decidim/meetings/admin/copy_meeting.rb +1 -1
  9. data/app/commands/decidim/meetings/admin/create_meeting.rb +3 -2
  10. data/app/commands/decidim/meetings/admin/update_meeting.rb +1 -1
  11. data/app/commands/decidim/meetings/admin/update_registrations.rb +2 -1
  12. data/app/commands/decidim/meetings/admin/validate_registration_code.rb +1 -1
  13. data/app/commands/decidim/meetings/join_meeting.rb +35 -6
  14. data/app/commands/decidim/meetings/leave_meeting.rb +10 -0
  15. data/app/controllers/decidim/meetings/admin/registration_form_controller.rb +30 -0
  16. data/app/controllers/decidim/meetings/calendars_controller.rb +23 -0
  17. data/app/controllers/decidim/meetings/directory/meetings_controller.rb +4 -0
  18. data/app/controllers/decidim/meetings/registrations_controller.rb +43 -0
  19. data/app/events/decidim/meetings/close_meeting_event.rb +7 -0
  20. data/app/events/decidim/meetings/create_meeting_event.rb +3 -0
  21. data/app/events/decidim/meetings/meeting_registration_notification_event.rb +22 -0
  22. data/app/events/decidim/meetings/meeting_registrations_enabled_event.rb +3 -0
  23. data/app/events/decidim/meetings/meeting_registrations_over_percentage_event.rb +4 -0
  24. data/app/events/decidim/meetings/registration_code_validated_event.rb +4 -0
  25. data/app/events/decidim/meetings/upcoming_meeting_event.rb +3 -0
  26. data/app/events/decidim/meetings/update_meeting_event.rb +3 -0
  27. data/app/forms/decidim/meetings/admin/meeting_registrations_form.rb +1 -0
  28. data/app/helpers/decidim/meetings/meetings_helper.rb +18 -0
  29. data/app/jobs/decidim/meetings/upcoming_meeting_notification_job.rb +1 -1
  30. data/app/mailers/decidim/meetings/registration_mailer.rb +1 -9
  31. data/app/models/decidim/meetings/meeting.rb +18 -0
  32. data/app/permissions/decidim/meetings/admin/permissions.rb +14 -0
  33. data/app/presenters/decidim/meetings/admin_log/value_types/meeting_title_description_presenter.rb +1 -1
  34. data/app/presenters/decidim/meetings/meeting_presenter.rb +50 -22
  35. data/app/queries/decidim/meetings/metrics/meeting_followers_metric_measure.rb +31 -0
  36. data/app/serializers/decidim/meetings/registration_serializer.rb +19 -1
  37. data/app/services/decidim/meetings/calendar/base_calendar.rb +61 -0
  38. data/app/services/decidim/meetings/calendar/component_calendar.rb +41 -0
  39. data/app/services/decidim/meetings/calendar/meeting_to_event.rb +70 -0
  40. data/app/services/decidim/meetings/calendar/organization_calendar.rb +33 -0
  41. data/app/services/decidim/meetings/calendar_renderer.rb +16 -0
  42. data/app/views/decidim/meetings/_calendar_modal.html.erb +13 -0
  43. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +2 -2
  44. data/app/views/decidim/meetings/admin/meetings/index.html.erb +4 -1
  45. data/app/views/decidim/meetings/admin/registrations/_form.html.erb +5 -0
  46. data/app/views/decidim/meetings/directory/meetings/_meetings.html.erb +9 -2
  47. data/app/views/decidim/meetings/meetings/_meetings.html.erb +8 -1
  48. data/app/views/decidim/meetings/meetings/show.html.erb +17 -4
  49. data/config/locales/ca.yml +31 -10
  50. data/config/locales/de.yml +30 -10
  51. data/config/locales/en.yml +30 -9
  52. data/config/locales/es-PY.yml +31 -10
  53. data/config/locales/es.yml +30 -9
  54. data/config/locales/eu.yml +31 -10
  55. data/config/locales/fi-pl.yml +31 -10
  56. data/config/locales/fi.yml +31 -10
  57. data/config/locales/fr.yml +30 -9
  58. data/config/locales/gl.yml +30 -9
  59. data/config/locales/hu.yml +31 -10
  60. data/config/locales/id-ID.yml +28 -7
  61. data/config/locales/it.yml +31 -10
  62. data/config/locales/nl.yml +30 -9
  63. data/config/locales/pl.yml +32 -11
  64. data/config/locales/pt-BR.yml +31 -10
  65. data/config/locales/pt.yml +31 -10
  66. data/config/locales/ru.yml +7 -12
  67. data/config/locales/sv.yml +30 -9
  68. data/config/locales/tr-TR.yml +30 -9
  69. data/config/locales/uk.yml +7 -12
  70. data/db/migrate/20180801150031_add_registration_form_enabled_to_decidim_meetings.rb +7 -0
  71. data/db/migrate/20181107175558_add_questionnaire_to_existing_meetings.rb +15 -0
  72. data/lib/decidim/meetings.rb +1 -0
  73. data/lib/decidim/meetings/admin_engine.rb +1 -0
  74. data/lib/decidim/meetings/component.rb +26 -0
  75. data/lib/decidim/meetings/directory_engine.rb +1 -0
  76. data/lib/decidim/meetings/engine.rb +19 -30
  77. data/lib/decidim/meetings/meeting_serializer.rb +77 -0
  78. data/lib/decidim/meetings/test/factories.rb +3 -0
  79. data/lib/decidim/meetings/version.rb +1 -1
  80. metadata +44 -15
  81. data/app/views/decidim/participatory_spaces/_highlighted_meetings.html.erb +0 -19
  82. data/app/views/decidim/participatory_spaces/_meeting.html.erb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b44daa20d879fb3407ca22edb47463ee033f78d64eae5e58c8b269378844b9ac
4
- data.tar.gz: 5657bacbd1860d53aeb47f1de52856d199c02ddca552c1cfbd98d9ce4f706cc6
3
+ metadata.gz: 0141ff66e04ed850c0ee2064f0c92becffee8f3347fcf6e7af0afb942a58590a
4
+ data.tar.gz: e88e8dfc58316a371b9a0c682660c6e9183dc268dc64f8ac9b63ad8c9ef9d4c2
5
5
  SHA512:
6
- metadata.gz: 99794f0b0d024392a292e79b66f077942ae5e4c3e1d70c4c4f1137ba40b5e991e2733704d0a1685ef8618f8a3526c7bbac660476e2af180991259a53a42ac66a
7
- data.tar.gz: '079896e8fe682cbd49ef7866a6107764a10d62a040d394a07ab7bad8705e6c269a958edaf6c328c1541ddc584da9af6bbf4d4441b00c6d2b420fa4490c796b60'
6
+ metadata.gz: 1157967e5342d9184c9896affdba244cb8e131f4b26f8aa1ade346c9e48f8d6b637dfa98347166f246e9b60b051933a4e781e93f1bd57789e8158c2a0921e5a9
7
+ data.tar.gz: f64250e3bdf35fe0cbc72a83c1712f25ad1696effc9f8ced8c7f4c73bf1048ae32814923ce5b7797208f8e42b8f5547e097ad7322253e8c10c372d7bbf51e31e
@@ -0,0 +1,3 @@
1
+ <% published_components.each do |component| %>
2
+ <%= cell "decidim/meetings/highlighted_meetings_for_component", component %>
3
+ <% end %>
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cell/partial"
4
+
5
+ module Decidim
6
+ module Meetings
7
+ # This cell renders the highlighted meetings for a given participatory
8
+ # space. It is intended to be used in the `participatory_space_highlighted_elements`
9
+ # view hook.
10
+ class HighlightedMeetingsCell < Decidim::ViewModel
11
+ include MeetingCellsHelper
12
+
13
+ private
14
+
15
+ def published_components
16
+ Decidim::Component
17
+ .where(
18
+ participatory_space: model,
19
+ manifest_name: :meetings
20
+ )
21
+ .published
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ <% if upcoming_meetings.any? %>
2
+ <div class="section row collapse upcoming_meetings">
3
+ <h4 class="section-heading">
4
+ <%= translated_attribute(model.name) %> - <%= t("decidim.participatory_spaces.highlighted_meetings.upcoming_meetings") %> <a href="<%= main_component_path(model) %>" class="text-small"><%= t("decidim.participatory_spaces.highlighted_meetings.see_all", count: meetings_count) %></a>
5
+ </h4>
6
+ <div class="card card--list">
7
+ <% upcoming_meetings.each do |meeting| %>
8
+ <%= card_for meeting, size: :list_item %>
9
+ <% end %>
10
+ </div>
11
+ <%= link_to(
12
+ t("decidim.participatory_spaces.highlighted_meetings.see_all", count: meetings_count),
13
+ main_component_path(model),
14
+ class: "button button--sc light secondary button--right"
15
+ ) %>
16
+ </div>
17
+ <% elsif past_meetings.any? %>
18
+ <div class="section row collapse past_meetings">
19
+ <h4 class="section-heading">
20
+ <%= translated_attribute(model.name) %> - <%= t("decidim.participatory_spaces.highlighted_meetings.past_meetings") %> <a href="<%= main_component_path(model) %>" class="text-small"><%= t("decidim.participatory_spaces.highlighted_meetings.see_all", count: meetings_count) %></a>
21
+ </h4>
22
+ <div class="card card--list">
23
+ <% past_meetings.each do |meeting| %>
24
+ <%= card_for meeting, size: :list_item %>
25
+ <% end %>
26
+ </div>
27
+ <%= link_to(
28
+ t("decidim.participatory_spaces.highlighted_meetings.see_all", count: meetings_count),
29
+ main_component_path(model),
30
+ class: "button button--sc light secondary button--right"
31
+ ) %>
32
+ </div>
33
+ <% end %>
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cell/partial"
4
+
5
+ module Decidim
6
+ module Meetings
7
+ # This cell renders the highlighted meetings for a given component.
8
+ # It is intended to be used in the `participatory_space_highlighted_elements`
9
+ # view hook.
10
+ class HighlightedMeetingsForComponentCell < Decidim::ViewModel
11
+ include Decidim::ComponentPathHelper
12
+ include Decidim::CardHelper
13
+
14
+ def show
15
+ render unless meetings_count.zero?
16
+ end
17
+
18
+ private
19
+
20
+ def meetings
21
+ @meetings ||= Decidim::Meetings::Meeting.where(component: model)
22
+ end
23
+
24
+ def past_meetings
25
+ @past_meetings ||= meetings.past.order(end_time: :desc, start_time: :desc).limit(3)
26
+ end
27
+
28
+ def upcoming_meetings
29
+ @upcoming_meetings ||= meetings.upcoming.order(:start_time, :end_time).limit(3)
30
+ end
31
+
32
+ def meetings_count
33
+ @meetings_count ||= meetings.count
34
+ end
35
+ end
36
+ end
37
+ end
@@ -12,16 +12,25 @@
12
12
  <%= t("going", scope: "decidim.meetings.meetings.show") %>
13
13
  <% end %>
14
14
  <% else %>
15
- <%= render :registration_confirm %>
16
- <%= action_authorized_button_to(
17
- :join,
18
- i18n_join_text,
19
- "",
20
- resource: model,
21
- class: button_classes,
22
- disabled: !model.has_available_slots?,
23
- data: { open: current_user.present? ? "meeting-registration-confirm-#{model.id}" : "loginModal" }
24
- ) %>
15
+ <% if model.registration_form_enabled? %>
16
+ <%= action_authorized_link_to(
17
+ :join,
18
+ i18n_join_text,
19
+ join_meeting_registration_path(model),
20
+ class: button_classes,
21
+ disabled: !model.has_available_slots?,
22
+ ) %>
23
+ <% else %>
24
+ <%= render :registration_confirm %>
25
+ <%= action_authorized_button_to(
26
+ :join,
27
+ i18n_join_text,
28
+ "",
29
+ class: button_classes,
30
+ disabled: !model.has_available_slots?,
31
+ data: { open: current_user.present? ? "meeting-registration-confirm-#{model.id}" : "loginModal" }
32
+ ) %>
33
+ <% end %>
25
34
  <% end %>
26
35
  <% if shows_remaining_slots? %>
27
36
  <span><%= t("remaining_slots", scope: "decidim.meetings.meetings.show", count: model.remaining_slots) %></span>
@@ -52,7 +52,8 @@ module Decidim
52
52
  event: "decidim.events.meetings.meeting_closed",
53
53
  event_class: Decidim::Meetings::CloseMeetingEvent,
54
54
  resource: meeting,
55
- recipient_ids: meeting.followers.pluck(:id)
55
+ affected_users: [meeting.organizer],
56
+ followers: meeting.followers - [meeting.organizer]
56
57
  )
57
58
  end
58
59
 
@@ -76,7 +76,7 @@ module Decidim
76
76
  event: "decidim.events.meetings.meeting_created",
77
77
  event_class: Decidim::Meetings::CreateMeetingEvent,
78
78
  resource: @copied_meeting,
79
- recipient_ids: @copied_meeting.participatory_space.followers.pluck(:id)
79
+ followers: @copied_meeting.participatory_space.followers
80
80
  )
81
81
  end
82
82
  end
@@ -47,7 +47,8 @@ module Decidim
47
47
  transparent: @form.transparent,
48
48
  organizer: @form.organizer,
49
49
  registration_terms: @form.current_component.settings.default_registration_terms,
50
- component: @form.current_component
50
+ component: @form.current_component,
51
+ questionnaire: Decidim::Forms::Questionnaire.new
51
52
  }
52
53
 
53
54
  @meeting = Decidim.traceability.create!(
@@ -71,7 +72,7 @@ module Decidim
71
72
  event: "decidim.events.meetings.meeting_created",
72
73
  event_class: Decidim::Meetings::CreateMeetingEvent,
73
74
  resource: @meeting,
74
- recipient_ids: @meeting.participatory_space.followers.pluck(:id)
75
+ followers: @meeting.participatory_space.followers
75
76
  )
76
77
  end
77
78
  end
@@ -64,7 +64,7 @@ module Decidim
64
64
  event: "decidim.events.meetings.meeting_updated",
65
65
  event_class: Decidim::Meetings::UpdateMeetingEvent,
66
66
  resource: meeting,
67
- recipient_ids: meeting.followers.pluck(:id)
67
+ followers: meeting.followers
68
68
  )
69
69
  end
70
70
 
@@ -33,6 +33,7 @@ module Decidim
33
33
 
34
34
  def update_meeting_registrations
35
35
  meeting.registrations_enabled = form.registrations_enabled
36
+ meeting.registration_form_enabled = form.registration_form_enabled
36
37
 
37
38
  if form.registrations_enabled
38
39
  meeting.available_slots = form.available_slots
@@ -48,7 +49,7 @@ module Decidim
48
49
  event: "decidim.events.meetings.registrations_enabled",
49
50
  event_class: Decidim::Meetings::MeetingRegistrationsEnabledEvent,
50
51
  resource: meeting,
51
- recipient_ids: meeting.followers.pluck(:id)
52
+ followers: meeting.followers
52
53
  )
53
54
  end
54
55
 
@@ -39,7 +39,7 @@ module Decidim
39
39
  event: "decidim.events.meetings.registration_code_validated",
40
40
  event_class: Decidim::Meetings::RegistrationCodeValidatedEvent,
41
41
  resource: meeting,
42
- recipient_ids: [form.registration.user.id],
42
+ affected_users: [form.registration.user],
43
43
  extra: {
44
44
  registration: form.registration
45
45
  }
@@ -8,9 +8,11 @@ module Decidim
8
8
  #
9
9
  # meeting - The current instance of the meeting to be joined.
10
10
  # user - The user joining the meeting.
11
- def initialize(meeting, user)
11
+ # registration_form - The questionnaire filled by the attendee
12
+ def initialize(meeting, user, registration_form = nil)
12
13
  @meeting = meeting
13
14
  @user = user
15
+ @registration_form = registration_form
14
16
  end
15
17
 
16
18
  # Creates a meeting registration if the meeting has registrations enabled
@@ -20,10 +22,17 @@ module Decidim
20
22
  def call
21
23
  meeting.with_lock do
22
24
  return broadcast(:invalid) unless can_join_meeting?
25
+
26
+ if registration_form
27
+ return broadcast(:invalid_form) unless registration_form.valid?
28
+ save_registration_form
29
+ end
30
+
23
31
  create_registration
24
32
  accept_invitation
25
33
  send_email_confirmation
26
- send_notification
34
+ send_notification_confirmation
35
+ notify_admin_over_percentage
27
36
  increment_score
28
37
  end
29
38
  broadcast(:ok)
@@ -31,12 +40,16 @@ module Decidim
31
40
 
32
41
  private
33
42
 
34
- attr_reader :meeting, :user, :registration
43
+ attr_reader :meeting, :user, :registration, :registration_form
35
44
 
36
45
  def accept_invitation
37
46
  meeting.invites.find_by(user: user)&.accept!
38
47
  end
39
48
 
49
+ def save_registration_form
50
+ Decidim::Forms::AnswerQuestionnaire.call(registration_form, user, meeting.questionnaire)
51
+ end
52
+
40
53
  def create_registration
41
54
  @registration = Decidim::Meetings::Registration.create!(meeting: meeting, user: user)
42
55
  end
@@ -46,14 +59,30 @@ module Decidim
46
59
  end
47
60
 
48
61
  def send_email_confirmation
49
- Decidim::Meetings::RegistrationMailer.confirmation(user, meeting, registration).deliver_later
62
+ Decidim::Meetings::RegistrationMailer.confirmation(
63
+ @user,
64
+ @meeting,
65
+ @registration
66
+ ).deliver_now
67
+ end
68
+
69
+ def send_notification_confirmation
70
+ Decidim::EventsManager.publish(
71
+ event: "decidim.events.meetings.meeting_registration_confirmed",
72
+ event_class: Decidim::Meetings::MeetingRegistrationNotificationEvent,
73
+ resource: @meeting,
74
+ affected_users: [@user],
75
+ extra: {
76
+ registration_code: @registration.code
77
+ }
78
+ )
50
79
  end
51
80
 
52
81
  def participatory_space_admins
53
82
  @meeting.component.participatory_space.admins
54
83
  end
55
84
 
56
- def send_notification
85
+ def notify_admin_over_percentage
57
86
  return send_notification_over(0.5) if occupied_slots_over?(0.5)
58
87
  return send_notification_over(0.8) if occupied_slots_over?(0.8)
59
88
  send_notification_over(1.0) if occupied_slots_over?(1.0)
@@ -64,7 +93,7 @@ module Decidim
64
93
  event: "decidim.events.meetings.meeting_registrations_over_percentage",
65
94
  event_class: Decidim::Meetings::MeetingRegistrationsOverPercentageEvent,
66
95
  resource: @meeting,
67
- recipient_ids: participatory_space_admins.pluck(:id),
96
+ affected_users: participatory_space_admins,
68
97
  extra: {
69
98
  percentage: percentage
70
99
  }
@@ -21,6 +21,7 @@ module Decidim
21
21
  @meeting.with_lock do
22
22
  return broadcast(:invalid) unless registration
23
23
  destroy_registration
24
+ destroy_questionnaire_answers
24
25
  decrement_score
25
26
  end
26
27
  broadcast(:ok)
@@ -36,6 +37,15 @@ module Decidim
36
37
  registration.destroy!
37
38
  end
38
39
 
40
+ def questionnaire_answers
41
+ questionnaire = Decidim::Forms::Questionnaire.find_by(questionnaire_for_id: @meeting)
42
+ questionnaire.answers.where(user: @user)
43
+ end
44
+
45
+ def destroy_questionnaire_answers
46
+ questionnaire_answers.destroy_all
47
+ end
48
+
39
49
  def decrement_score
40
50
  Decidim::Gamification.decrement_score(@user, :attended_meetings)
41
51
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ module Admin
6
+ # This controller allows an admin to manage the form to be filled when an user joins the meeting
7
+ class RegistrationFormController < Admin::ApplicationController
8
+ include Decidim::Forms::Admin::Concerns::HasQuestionnaire
9
+
10
+ def questionnaire_for
11
+ meeting
12
+ end
13
+
14
+ def update_url
15
+ meeting_registrations_form_path(meeting_id: meeting.id)
16
+ end
17
+
18
+ def after_update_url
19
+ edit_meeting_registrations_path(meeting_id: meeting.id)
20
+ end
21
+
22
+ private
23
+
24
+ def meeting
25
+ @meeting ||= Meeting.where(component: current_component).find(params[:meeting_id])
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ # Exposes the meeting resources as an .ics file so users can import them
6
+ # to their favorite calendar app
7
+ class CalendarsController < Decidim::Meetings::ApplicationController
8
+ layout false
9
+ helper_method :meetings
10
+ before_action :set_default_request_format
11
+
12
+ def show
13
+ render plain: CalendarRenderer.for(current_component), content_type: "type/calendar"
14
+ end
15
+
16
+ private
17
+
18
+ def set_default_request_format
19
+ request.format = :text
20
+ end
21
+ end
22
+ end
23
+ end
@@ -28,6 +28,10 @@ module Decidim
28
28
  @meeting_spaces = @meeting_spaces.prepend(["all", t(".all")])
29
29
  end
30
30
 
31
+ def calendar
32
+ render plain: CalendarRenderer.for(current_organization), content_type: "type/calendar"
33
+ end
34
+
31
35
  private
32
36
 
33
37
  def meetings
@@ -4,6 +4,31 @@ module Decidim
4
4
  module Meetings
5
5
  # Exposes the registration resource so users can join and leave meetings.
6
6
  class RegistrationsController < Decidim::Meetings::ApplicationController
7
+ include Decidim::Forms::Concerns::HasQuestionnaire
8
+
9
+ def answer
10
+ enforce_permission_to :join, :meeting, meeting: meeting
11
+
12
+ @form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
13
+
14
+ JoinMeeting.call(meeting, current_user, @form) do
15
+ on(:ok) do
16
+ flash[:notice] = I18n.t("registrations.create.success", scope: "decidim.meetings")
17
+ redirect_to after_answer_path
18
+ end
19
+
20
+ on(:invalid) do
21
+ flash.now[:alert] = I18n.t("registrations.create.invalid", scope: "decidim.meetings")
22
+ render template: "decidim/forms/questionnaires/show"
23
+ end
24
+
25
+ on(:invalid_form) do
26
+ flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope)
27
+ render template: "decidim/forms/questionnaires/show"
28
+ end
29
+ end
30
+ end
31
+
7
32
  def create
8
33
  enforce_permission_to :join, :meeting, meeting: meeting
9
34
 
@@ -52,6 +77,24 @@ module Decidim
52
77
  end
53
78
  end
54
79
 
80
+ def allow_answers?
81
+ meeting.registrations_enabled? && meeting.registration_form_enabled? && meeting.has_available_slots?
82
+ end
83
+
84
+ def after_answer_path
85
+ meeting_path(meeting)
86
+ end
87
+
88
+ # You can implement this method in your controller to change the URL
89
+ # where the questionnaire will be submitted.
90
+ def update_url
91
+ answer_meeting_registration_path(meeting_id: meeting.id)
92
+ end
93
+
94
+ def questionnaire_for
95
+ meeting
96
+ end
97
+
55
98
  private
56
99
 
57
100
  def meeting