decidim-meetings 0.30.5 → 0.31.0.rc1

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 (205) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb +7 -4
  3. data/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb +1 -1
  4. data/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +36 -0
  5. data/app/cells/decidim/meetings/dates_and_map_cell.rb +1 -1
  6. data/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb +4 -5
  7. data/app/cells/decidim/meetings/join_meeting_button/show.erb +25 -28
  8. data/app/cells/decidim/meetings/join_meeting_button/waitlist_button.erb +22 -0
  9. data/app/cells/decidim/meetings/join_meeting_button_cell.rb +28 -0
  10. data/app/cells/decidim/meetings/meeting_cell.rb +6 -1
  11. data/app/cells/decidim/meetings/meeting_s_cell.rb +15 -0
  12. data/app/cells/decidim/meetings/question_responses/show.erb +7 -7
  13. data/app/cells/decidim/meetings/question_responses_cell.rb +28 -28
  14. data/app/commands/decidim/meetings/admin/copy_meeting.rb +5 -2
  15. data/app/commands/decidim/meetings/admin/create_agenda.rb +6 -2
  16. data/app/commands/decidim/meetings/admin/create_meeting.rb +7 -3
  17. data/app/commands/decidim/meetings/admin/mark_as_attendee.rb +44 -0
  18. data/app/commands/decidim/meetings/admin/publish_meeting.rb +2 -1
  19. data/app/commands/decidim/meetings/admin/update_agenda.rb +6 -2
  20. data/app/commands/decidim/meetings/admin/update_meeting.rb +15 -6
  21. data/app/commands/decidim/meetings/admin/update_questionnaire.rb +4 -4
  22. data/app/commands/decidim/meetings/admin/update_registrations.rb +19 -7
  23. data/app/commands/decidim/meetings/create_meeting.rb +2 -3
  24. data/app/commands/decidim/meetings/{create_answer.rb → create_response.rb} +11 -11
  25. data/app/commands/decidim/meetings/join_meeting.rb +4 -6
  26. data/app/commands/decidim/meetings/join_waitlist.rb +53 -0
  27. data/app/commands/decidim/meetings/leave_meeting.rb +14 -5
  28. data/app/commands/decidim/meetings/update_meeting.rb +1 -2
  29. data/app/controllers/concerns/decidim/meetings/admin/filterable.rb +1 -1
  30. data/app/controllers/decidim/meetings/admin/agenda_controller.rb +2 -2
  31. data/app/controllers/decidim/meetings/admin/invites_controller.rb +1 -1
  32. data/app/controllers/decidim/meetings/admin/meeting_closes_controller.rb +1 -1
  33. data/app/controllers/decidim/meetings/admin/meeting_copies_controller.rb +1 -1
  34. data/app/controllers/decidim/meetings/admin/meetings_controller.rb +4 -4
  35. data/app/controllers/decidim/meetings/admin/meetings_poll_controller.rb +10 -10
  36. data/app/controllers/decidim/meetings/admin/registrations_attendees_controller.rb +79 -0
  37. data/app/controllers/decidim/meetings/admin/registrations_controller.rb +1 -22
  38. data/app/controllers/decidim/meetings/meeting_closes_controller.rb +1 -1
  39. data/app/controllers/decidim/meetings/meetings_controller.rb +11 -44
  40. data/app/controllers/decidim/meetings/polls/{answers_controller.rb → responses_controller.rb} +7 -7
  41. data/app/controllers/decidim/meetings/registrations_controller.rb +39 -12
  42. data/app/events/decidim/meetings/registration_marked_as_attendee_event.rb +9 -0
  43. data/app/events/decidim/meetings/upcoming_meeting_event.rb +41 -0
  44. data/app/events/decidim/meetings/update_meeting_event.rb +25 -0
  45. data/app/forms/decidim/meetings/admin/close_meeting_form.rb +2 -1
  46. data/app/forms/decidim/meetings/admin/meeting_agenda_items_form.rb +4 -0
  47. data/app/forms/decidim/meetings/admin/meeting_form.rb +28 -3
  48. data/app/forms/decidim/meetings/admin/question_form.rb +3 -3
  49. data/app/forms/decidim/meetings/admin/{answer_option_form.rb → response_option_form.rb} +3 -3
  50. data/app/forms/decidim/meetings/admin/validate_registration_code_form.rb +1 -1
  51. data/app/forms/decidim/meetings/base_meeting_form.rb +0 -2
  52. data/app/forms/decidim/meetings/close_meeting_form.rb +2 -1
  53. data/app/forms/decidim/meetings/join_meeting_form.rb +0 -1
  54. data/app/forms/decidim/meetings/meeting_form.rb +3 -2
  55. data/app/forms/decidim/meetings/response_choice_form.rb +14 -0
  56. data/app/forms/decidim/meetings/{answer_form.rb → response_form.rb} +7 -7
  57. data/app/helpers/decidim/meetings/application_helper.rb +0 -1
  58. data/app/helpers/decidim/meetings/meetings_helper.rb +14 -5
  59. data/app/jobs/decidim/meetings/promote_from_waitlist_job.rb +63 -0
  60. data/app/jobs/decidim/meetings/upcoming_meeting_notification_job.rb +1 -1
  61. data/app/mailers/decidim/meetings/registration_mailer.rb +13 -0
  62. data/app/models/decidim/meetings/agenda_item.rb +5 -0
  63. data/app/models/decidim/meetings/meeting.rb +15 -12
  64. data/app/models/decidim/meetings/question.rb +12 -12
  65. data/app/models/decidim/meetings/questionnaire.rb +1 -1
  66. data/app/models/decidim/meetings/registration.rb +19 -7
  67. data/app/models/decidim/meetings/{answer.rb → response.rb} +6 -6
  68. data/app/models/decidim/meetings/response_choice.rb +15 -0
  69. data/app/models/decidim/meetings/{answer_option.rb → response_option.rb} +5 -5
  70. data/app/packs/src/decidim/meetings/admin/destroy_meeting_alert.js +1 -1
  71. data/app/packs/src/decidim/meetings/admin/meetings_components_form.js +1 -8
  72. data/app/packs/src/decidim/meetings/admin/meetings_form.js +1 -1
  73. data/app/packs/src/decidim/meetings/admin/registrations_form.js +1 -1
  74. data/app/packs/src/decidim/meetings/admin/registrations_invite_form.js +1 -1
  75. data/app/packs/src/decidim/meetings/meetings_form.js +1 -1
  76. data/app/packs/src/decidim/meetings/meetings_polls.js +1 -1
  77. data/app/packs/src/decidim/meetings/poll.component.js +5 -5
  78. data/app/packs/stylesheets/decidim/meetings/_item.scss +5 -1
  79. data/app/packs/stylesheets/decidim/meetings/meetings.scss +4 -4
  80. data/app/permissions/decidim/meetings/admin/agenda_permissions.rb +34 -0
  81. data/app/permissions/decidim/meetings/admin/meeting_permissions.rb +44 -0
  82. data/app/permissions/decidim/meetings/admin/permissions.rb +5 -66
  83. data/app/permissions/decidim/meetings/admin/questionnaire_permissions.rb +30 -0
  84. data/app/permissions/decidim/meetings/meeting_permissions.rb +90 -0
  85. data/app/permissions/decidim/meetings/permissions.rb +9 -105
  86. data/app/presenters/decidim/meetings/admin_log/value_types/meeting_title_description_presenter.rb +1 -1
  87. data/app/presenters/decidim/meetings/agenda_item_presenter.rb +29 -0
  88. data/app/presenters/decidim/meetings/meeting_presenter.rb +12 -15
  89. data/app/presenters/decidim/meetings/registration_presenter.rb +24 -0
  90. data/app/queries/decidim/meetings/{questionnaire_user_answers.rb → questionnaire_user_responses.rb} +5 -5
  91. data/app/serializers/decidim/meetings/registration_serializer.rb +5 -6
  92. data/app/services/decidim/meetings/diff_renderer.rb +0 -1
  93. data/app/views/decidim/meetings/_calendar_modal.html.erb +1 -0
  94. data/app/views/decidim/meetings/admin/agenda/_agenda_item_fields.html.erb +1 -1
  95. data/app/views/decidim/meetings/admin/invites/_form.html.erb +1 -1
  96. data/app/views/decidim/meetings/admin/meeting_closes/_form.html.erb +1 -1
  97. data/app/views/decidim/meetings/admin/meetings/_form.html.erb +3 -2
  98. data/app/views/decidim/meetings/admin/meetings/_linked_spaces.html.erb +1 -1
  99. data/app/views/decidim/meetings/admin/meetings/_meeting-tr.html.erb +9 -14
  100. data/app/views/decidim/meetings/admin/meetings/_meeting_actions.html.erb +200 -69
  101. data/app/views/decidim/meetings/admin/meetings/_meetings-thead.html.erb +0 -3
  102. data/app/views/decidim/meetings/admin/meetings/_reminders.html.erb +19 -0
  103. data/app/views/decidim/meetings/admin/meetings/_services.html.erb +1 -1
  104. data/app/views/decidim/meetings/admin/meetings/index.html.erb +7 -5
  105. data/app/views/decidim/meetings/admin/meetings/manage_trash.html.erb +2 -1
  106. data/app/views/decidim/meetings/admin/poll/_form.html.erb +9 -9
  107. data/app/views/decidim/meetings/admin/poll/_question.html.erb +19 -9
  108. data/app/views/decidim/meetings/admin/poll/_response_option.html.erb +35 -0
  109. data/app/views/decidim/meetings/admin/poll/_response_option_template.html.erb +7 -0
  110. data/app/views/decidim/meetings/admin/poll/edit.html.erb +3 -3
  111. data/app/views/decidim/meetings/admin/registration_form/edit_questions.html.erb +5 -5
  112. data/app/views/decidim/meetings/admin/registrations/edit.html.erb +19 -39
  113. data/app/views/decidim/meetings/admin/registrations_attendees/index.html.erb +126 -0
  114. data/app/views/decidim/meetings/layouts/live_event.html.erb +1 -1
  115. data/app/views/decidim/meetings/meeting_closes/_form.html.erb +2 -2
  116. data/app/views/decidim/meetings/meetings/_form.html.erb +2 -11
  117. data/app/views/decidim/meetings/meetings/_meeting.html.erb +2 -2
  118. data/app/views/decidim/meetings/meetings/_meeting_actions.html.erb +3 -3
  119. data/app/views/decidim/meetings/meetings/_meeting_agenda.html.erb +2 -2
  120. data/app/views/decidim/meetings/meetings/_meeting_aside.html.erb +11 -10
  121. data/app/views/decidim/meetings/meetings/_meeting_poll_actions.html.erb +3 -3
  122. data/app/views/decidim/meetings/meetings/_registration_code_modal.html.erb +16 -0
  123. data/app/views/decidim/meetings/polls/questions/_index_admin.html.erb +1 -1
  124. data/app/views/decidim/meetings/polls/questions/_published_question.html.erb +5 -5
  125. data/app/views/decidim/meetings/polls/responses/_multiple_option.html.erb +13 -0
  126. data/app/views/decidim/meetings/polls/responses/_single_option.html.erb +13 -0
  127. data/app/views/decidim/meetings/polls/{answers → responses}/admin.html.erb +4 -4
  128. data/app/views/decidim/meetings/polls/{answers → responses}/index.html.erb +4 -4
  129. data/app/views/decidim/meetings/registration_mailer/confirmation.html.erb +6 -1
  130. data/config/assets.rb +2 -2
  131. data/config/locales/ar.yml +1 -26
  132. data/config/locales/bg.yml +2 -32
  133. data/config/locales/ca-IT.yml +87 -44
  134. data/config/locales/ca.yml +87 -44
  135. data/config/locales/cs.yml +71 -46
  136. data/config/locales/de.yml +89 -46
  137. data/config/locales/el.yml +1 -25
  138. data/config/locales/en.yml +89 -46
  139. data/config/locales/es-MX.yml +87 -44
  140. data/config/locales/es-PY.yml +87 -44
  141. data/config/locales/es.yml +87 -44
  142. data/config/locales/eu.yml +114 -71
  143. data/config/locales/fi-plain.yml +86 -43
  144. data/config/locales/fi.yml +85 -42
  145. data/config/locales/fr-CA.yml +79 -41
  146. data/config/locales/fr.yml +79 -41
  147. data/config/locales/ga-IE.yml +1 -8
  148. data/config/locales/gl.yml +1 -19
  149. data/config/locales/hu.yml +1 -23
  150. data/config/locales/id-ID.yml +0 -16
  151. data/config/locales/is-IS.yml +0 -10
  152. data/config/locales/it.yml +1 -38
  153. data/config/locales/ja.yml +88 -45
  154. data/config/locales/lb.yml +1 -16
  155. data/config/locales/lt.yml +1 -28
  156. data/config/locales/lv.yml +0 -16
  157. data/config/locales/nl.yml +1 -26
  158. data/config/locales/no.yml +1 -29
  159. data/config/locales/pl.yml +2 -32
  160. data/config/locales/pt-BR.yml +13 -205
  161. data/config/locales/pt.yml +1 -29
  162. data/config/locales/ro-RO.yml +56 -32
  163. data/config/locales/ru.yml +0 -16
  164. data/config/locales/sk.yml +0 -16
  165. data/config/locales/sl.yml +0 -4
  166. data/config/locales/sv.yml +89 -46
  167. data/config/locales/tr-TR.yml +0 -20
  168. data/config/locales/uk.yml +0 -12
  169. data/config/locales/zh-CN.yml +0 -19
  170. data/config/locales/zh-TW.yml +1 -26
  171. data/db/migrate/20181107175558_add_questionnaire_to_existing_meetings.rb +8 -2
  172. data/db/migrate/20200827153856_add_commentable_counter_cache_to_meetings.rb +8 -2
  173. data/db/migrate/20201016065302_fix_meetings_registration_terms.rb +8 -2
  174. data/db/migrate/20210310120731_add_followable_counter_cache_to_meetings.rb +8 -2
  175. data/db/migrate/20250317103343_rename_answer_to_response_in_decidim_meetings.rb +18 -0
  176. data/db/migrate/20250403094034_add_reminder_customization_to_decidim_meetings.rb +9 -0
  177. data/db/migrate/20250408071941_add_status_to_registrations_to_decidim_meetings_registrations.rb +8 -0
  178. data/lib/decidim/api/agenda_item_type.rb +6 -2
  179. data/lib/decidim/api/agenda_type.rb +6 -2
  180. data/lib/decidim/api/linked_resources_interface.rb +1 -1
  181. data/lib/decidim/api/meeting_type.rb +20 -10
  182. data/lib/decidim/api/service_type.rb +3 -0
  183. data/lib/decidim/meetings/admin_engine.rb +9 -1
  184. data/lib/decidim/meetings/component.rb +29 -20
  185. data/lib/decidim/meetings/engine.rb +6 -21
  186. data/lib/decidim/meetings/meeting_serializer.rb +1 -2
  187. data/lib/decidim/meetings/schema_org_event_meeting_serializer.rb +0 -10
  188. data/lib/decidim/meetings/seeds.rb +4 -13
  189. data/lib/decidim/meetings/test/factories.rb +10 -16
  190. data/lib/decidim/meetings/user_responses_serializer.rb +47 -0
  191. data/lib/decidim/meetings/version.rb +1 -1
  192. data/lib/decidim/meetings.rb +7 -9
  193. metadata +50 -35
  194. data/app/cells/decidim/meetings/attending_organizations_list_cell.rb +0 -32
  195. data/app/forms/decidim/meetings/answer_choice_form.rb +0 -14
  196. data/app/models/decidim/meetings/answer_choice.rb +0 -15
  197. data/app/queries/decidim/meetings/metrics/meeting_followers_metric_measure.rb +0 -31
  198. data/app/queries/decidim/meetings/metrics/meetings_metric_manage.rb +0 -48
  199. data/app/views/decidim/meetings/admin/poll/_answer_option.html.erb +0 -35
  200. data/app/views/decidim/meetings/admin/poll/_answer_option_template.html.erb +0 -7
  201. data/app/views/decidim/meetings/polls/answers/_multiple_option.html.erb +0 -13
  202. data/app/views/decidim/meetings/polls/answers/_single_option.html.erb +0 -13
  203. data/lib/decidim/meetings/download_your_data_user_answers_serializer.rb +0 -39
  204. data/lib/decidim/meetings/user_answers_serializer.rb +0 -47
  205. /data/app/views/decidim/meetings/polls/{answers → responses}/create.js.erb +0 -0
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rqrcode"
4
+
3
5
  module Decidim
4
6
  module Meetings
5
7
  # Exposes the meeting resource so users can view them
@@ -16,7 +18,7 @@ module Decidim
16
18
  include Decidim::AttachmentsHelper
17
19
  include Decidim::SanitizeHelper
18
20
 
19
- helper_method :meetings, :meeting, :registration, :search, :tab_panel_items
21
+ helper_method :meetings, :meeting, :registration, :registration_qr_code_image, :search, :tab_panel_items
20
22
 
21
23
  before_action :add_additional_csp_directives, only: [:show]
22
24
 
@@ -39,7 +41,7 @@ module Decidim
39
41
 
40
42
  on(:invalid) do
41
43
  flash.now[:alert] = I18n.t("meetings.create.invalid", scope: "decidim.meetings")
42
- render action: "new"
44
+ render action: "new", status: :unprocessable_entity
43
45
  end
44
46
  end
45
47
  end
@@ -86,7 +88,7 @@ module Decidim
86
88
 
87
89
  on(:invalid) do
88
90
  flash.now[:alert] = I18n.t("meetings.update.invalid", scope: "decidim.meetings")
89
- render :edit
91
+ render :edit, status: :unprocessable_entity
90
92
  end
91
93
  end
92
94
  end
@@ -121,6 +123,12 @@ module Decidim
121
123
  @registration ||= meeting.registrations.find_by(user: current_user)
122
124
  end
123
125
 
126
+ def registration_qr_code_image
127
+ Base64.encode64(
128
+ RQRCode::QRCode.new(registration.validation_code_short_link.short_url).as_png(size: 500).to_s
129
+ ).gsub("\n", "")
130
+ end
131
+
124
132
  def search_collection
125
133
  Meeting
126
134
  .where(component: current_component)
@@ -151,14 +159,6 @@ module Decidim
151
159
  method: :cell,
152
160
  args: ["decidim/meetings/public_participants_list", meeting]
153
161
  },
154
- {
155
- enabled: !meeting.closed? && meeting.user_group_registrations.any?,
156
- id: "organizations",
157
- text: t("attending_organizations", scope: "decidim.meetings.public_participants_list"),
158
- icon: "community-line",
159
- method: :cell,
160
- args: ["decidim/meetings/attending_organizations_list", meeting]
161
- },
162
162
  {
163
163
  enabled: meeting.linked_resources(:proposals, "proposals_from_meeting").present?,
164
164
  id: "included_proposals",
@@ -201,39 +201,6 @@ module Decidim
201
201
  rescue NameError, LoadError
202
202
  nil
203
203
  end
204
-
205
- def conference_context?
206
- return false unless Decidim.module_installed?(:conferences)
207
- return false if current_participatory_space.blank?
208
-
209
- current_participatory_space.is_a?(Decidim::Conference)
210
- end
211
-
212
- def set_component_breadcrumb_item
213
- super
214
- return {} if meeting.blank?
215
-
216
- breadcrumb = {
217
- label: translated_attribute(meeting.title),
218
- url: Decidim::EngineRouter.main_proxy(current_component).meeting_path(meeting),
219
- active: false
220
- }
221
-
222
- # If this meeting is being accessed from within a conference program context,
223
- # add program breadcrumb to maintain proper navigation hierarchy
224
- if conference_context?
225
- program_path = decidim_conferences.conference_conference_program_path(current_participatory_space, current_component)
226
-
227
- context_breadcrumb_items << {
228
- label: t("conference_program.index.title", scope: "decidim"),
229
- url: program_path,
230
- active: false,
231
- resource: current_component
232
- }
233
- end
234
-
235
- context_breadcrumb_items << breadcrumb
236
- end
237
204
  end
238
205
  end
239
206
  end
@@ -3,7 +3,7 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  module Polls
6
- class AnswersController < Decidim::Meetings::ApplicationController
6
+ class ResponsesController < Decidim::Meetings::ApplicationController
7
7
  include Decidim::Meetings::PollsResources
8
8
  include FormFactory
9
9
 
@@ -18,10 +18,10 @@ module Decidim
18
18
  end
19
19
 
20
20
  def create
21
- enforce_permission_to(:create, :answer, question:)
22
- @form = form(AnswerForm).from_params(params.merge(question:, current_user:))
21
+ enforce_permission_to(:create, :response, question:)
22
+ @form = form(ResponseForm).from_params(params.merge(question:, current_user:))
23
23
 
24
- CreateAnswer.call(@form, questionnaire) do
24
+ CreateResponse.call(@form, questionnaire) do
25
25
  # Both :ok and :invalid render the same template, because
26
26
  # validation errors are displayed in the template
27
27
  respond_to do |format|
@@ -33,11 +33,11 @@ module Decidim
33
33
  private
34
34
 
35
35
  def question
36
- @question ||= questionnaire.questions.find(answer_params[:question_id]) if questionnaire
36
+ @question ||= questionnaire.questions.find(response_params[:question_id]) if questionnaire
37
37
  end
38
38
 
39
- def answer_params
40
- params.require(:answer).permit(:question_id, choices: [:body, :answer_option_id])
39
+ def response_params
40
+ params.require(:response).permit(:question_id, choices: [:body, :response_option_id])
41
41
  end
42
42
  end
43
43
  end
@@ -6,25 +6,28 @@ module Decidim
6
6
  class RegistrationsController < Decidim::Meetings::ApplicationController
7
7
  include Decidim::Forms::Concerns::HasQuestionnaire
8
8
 
9
- def answer
9
+ def respond
10
10
  enforce_permission_to(:join, :meeting, meeting:)
11
11
 
12
12
  @form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token:)
13
13
 
14
- JoinMeeting.call(meeting, @form) do
14
+ command = should_join_waitlist? ? JoinWaitlist : JoinMeeting
15
+ joining_waitlist = should_join_waitlist?
16
+
17
+ command.call(meeting, @form) do
15
18
  on(:ok) do
16
- flash[:notice] = I18n.t("registrations.create.success", scope: "decidim.meetings")
17
- redirect_to after_answer_path
19
+ flash[:notice] = I18n.t(joining_waitlist ? "registrations.waitlist.success" : "registrations.create.success", scope: "decidim.meetings")
20
+ redirect_to after_response_path
18
21
  end
19
22
 
20
23
  on(:invalid) do
21
- flash.now[:alert] = I18n.t("registrations.create.invalid", scope: "decidim.meetings")
22
- render template: "decidim/forms/questionnaires/show"
24
+ flash.now[:alert] = I18n.t(joining_waitlist ? "registrations.waitlist.invalid" : "registrations.create.invalid", scope: "decidim.meetings")
25
+ render template: "decidim/forms/questionnaires/show", status: :unprocessable_entity
23
26
  end
24
27
 
25
28
  on(:invalid_form) do
26
- flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope)
27
- render template: "decidim/forms/questionnaires/show"
29
+ flash.now[:alert] = I18n.t("response.invalid", scope: i18n_flashes_scope)
30
+ render template: "decidim/forms/questionnaires/show", status: :unprocessable_entity
28
31
  end
29
32
  end
30
33
  end
@@ -47,6 +50,24 @@ module Decidim
47
50
  end
48
51
  end
49
52
 
53
+ def join_waitlist
54
+ enforce_permission_to(:join_waitlist, :meeting, meeting:)
55
+
56
+ @form = JoinMeetingForm.from_params(params).with_context(current_user:)
57
+
58
+ JoinWaitlist.call(meeting, @form) do
59
+ on(:ok) do
60
+ flash[:notice] = I18n.t("registrations.waitlist.success", scope: "decidim.meetings")
61
+ redirect_after_path
62
+ end
63
+
64
+ on(:invalid) do
65
+ flash.now[:alert] = I18n.t("registrations.waitlist.invalid", scope: "decidim.meetings")
66
+ redirect_after_path
67
+ end
68
+ end
69
+ end
70
+
50
71
  def destroy
51
72
  enforce_permission_to(:leave, :meeting, meeting:)
52
73
 
@@ -79,18 +100,24 @@ module Decidim
79
100
  end
80
101
  end
81
102
 
82
- def allow_answers?
83
- meeting.registrations_enabled? && meeting.registration_form_enabled? && meeting.has_available_slots?
103
+ def should_join_waitlist?
104
+ meeting.waitlist_enabled? && !meeting.has_available_slots? && !meeting.has_registration_for?(current_user)
105
+ end
106
+
107
+ def allow_responses?
108
+ return false unless meeting.registrations_enabled? && meeting.registration_form_enabled?
109
+
110
+ meeting.has_available_slots? || should_join_waitlist?
84
111
  end
85
112
 
86
- def after_answer_path
113
+ def after_response_path
87
114
  meeting_path(meeting)
88
115
  end
89
116
 
90
117
  # You can implement this method in your controller to change the URL
91
118
  # where the questionnaire will be submitted.
92
119
  def update_url
93
- answer_meeting_registration_path(meeting_id: meeting.id)
120
+ respond_meeting_registration_path(meeting_id: meeting.id)
94
121
  end
95
122
 
96
123
  def questionnaire_for
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ class RegistrationMarkedAsAttendeeEvent < Decidim::Events::SimpleEvent
6
+ include Decidim::Meetings::MeetingEvent
7
+ end
8
+ end
9
+ end
@@ -4,6 +4,47 @@ module Decidim
4
4
  module Meetings
5
5
  class UpcomingMeetingEvent < Decidim::Events::SimpleEvent
6
6
  include Decidim::Meetings::MeetingEvent
7
+
8
+ i18n_attributes :reminders_before_hours
9
+
10
+ def email_intro
11
+ (custom_message.presence || default_email_intro).to_s.html_safe
12
+ end
13
+
14
+ def i18n_options
15
+ {
16
+ resource_title:,
17
+ resource_path:,
18
+ resource_url:,
19
+ participatory_space_url:,
20
+ participatory_space_title:,
21
+ reminders_before_hours: resource.send_reminders_before_hours,
22
+ scope: event_name
23
+ }
24
+ end
25
+
26
+ private
27
+
28
+ def reminder_message
29
+ translated_attribute(resource.reminder_message_custom_content)
30
+ end
31
+
32
+ def default_email_intro
33
+ I18n.t("email_intro", **i18n_options)
34
+ end
35
+
36
+ def custom_message
37
+ template = translated_attribute(resource.reminder_message_custom_content)
38
+ interpolate_custom_message(template).html_safe
39
+ end
40
+
41
+ def interpolate_custom_message(template)
42
+ title = translated_attribute(resource.title).to_s
43
+ hours = resource.send_reminders_before_hours.to_s
44
+ template
45
+ .gsub("{{meeting_title}}", title)
46
+ .gsub("{{before_hours}}", hours)
47
+ end
7
48
  end
8
49
  end
9
50
  end
@@ -4,6 +4,31 @@ module Decidim
4
4
  module Meetings
5
5
  class UpdateMeetingEvent < Decidim::Events::SimpleEvent
6
6
  include Decidim::Meetings::MeetingEvent
7
+
8
+ i18n_attributes :changed_fields
9
+
10
+ def notification_title
11
+ I18n.t(
12
+ "notification_title",
13
+ scope: i18n_scope,
14
+ changed_fields: changed_fields,
15
+ resource_title: translated_attribute(resource.title),
16
+ resource_path: resource_path
17
+ ).html_safe
18
+ end
19
+
20
+ private
21
+
22
+ def changed_field_keys
23
+ extra[:changed_fields] || []
24
+ end
25
+
26
+ def changed_fields
27
+ keys = changed_field_keys
28
+ return "" if keys.empty?
29
+
30
+ keys.map { |key| I18n.t("field_names.#{key}", scope: i18n_scope) }.to_sentence
31
+ end
7
32
  end
8
33
  end
9
34
  end
@@ -11,7 +11,7 @@ module Decidim
11
11
  attribute :video_url, String
12
12
  attribute :audio_url, String
13
13
  attribute :closing_visible, Boolean, default: true
14
- attribute :attendees_count, Integer, default: 0
14
+ attribute :attendees_count, Integer
15
15
  attribute :contributions_count, Integer, default: 0
16
16
  attribute :attending_organizations, String
17
17
  attribute :proposal_ids, Array[Integer]
@@ -27,6 +27,7 @@ module Decidim
27
27
  # Returns nothing.
28
28
  def map_model(model)
29
29
  self.proposal_ids = model.linked_resources(:proposals, "proposals_from_meeting").pluck(:id)
30
+ self.attendees_count = model.attendees_count || model.registrations.where.not(validated_at: nil).count
30
31
  end
31
32
 
32
33
  def proposals
@@ -31,6 +31,10 @@ module Decidim
31
31
 
32
32
  "meeting-agenda-item-child-id"
33
33
  end
34
+
35
+ def map_model(model)
36
+ self.description = model.presenter.editor_description(all_locales: true)
37
+ end
34
38
  end
35
39
  end
36
40
  end
@@ -20,23 +20,28 @@ module Decidim
20
20
  attribute :comments_start_time, Decidim::Attributes::TimeWithZone
21
21
  attribute :comments_end_time, Decidim::Attributes::TimeWithZone
22
22
  attribute :iframe_access_level, String
23
+ attribute :reminder_enabled, Boolean, default: true
24
+ attribute :send_reminders_before_hours, Integer, default: Decidim::Meetings.upcoming_meeting_notification.in_hours.to_i
23
25
 
24
26
  translatable_attribute :title, String
25
27
  translatable_attribute :description, Decidim::Attributes::RichText
26
28
  translatable_attribute :location, String
27
29
  translatable_attribute :location_hints, String
30
+ translatable_attribute :reminder_message_custom_content, String
28
31
 
29
32
  validates :iframe_embed_type, inclusion: { in: Decidim::Meetings::Meeting.iframe_embed_types }
30
- validates :title, :description, translatable_presence: true
31
- validates :title, :description, translated_etiquette: true
33
+ validates :title, :description, translatable_presence: true, translated_etiquette: true
32
34
  validates :registration_type, presence: true
33
35
  validates :registration_url, presence: true, url: true, if: ->(form) { form.on_different_platform? }
34
36
  validates :type_of_meeting, presence: true
35
- validates :location, translatable_presence: true, if: ->(form) { form.in_person_meeting? || form.hybrid_meeting? }
37
+ validates :address, presence: true, if: ->(form) { form.needs_address? && form.location.values.any?(&:present?) && form.address.blank? }
38
+ validates :location, translatable_presence: true, if: ->(form) { form.needs_address? && form.address.present? }
39
+ validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? && form.needs_address? }
36
40
  validates :online_meeting_url, url: true, if: ->(form) { form.online_meeting? || form.hybrid_meeting? }
37
41
  validates :comments_start_time, date: { before: :comments_end_time, allow_blank: true, if: proc { |obj| obj.comments_end_time.present? } }
38
42
  validates :comments_end_time, date: { after: :comments_start_time, allow_blank: true, if: proc { |obj| obj.comments_start_time.present? } }
39
43
  validates :clean_type_of_meeting, presence: true
44
+ validates :send_reminders_before_hours, numericality: { only_integer: true, greater_than: 0 }, if: :reminder_enabled
40
45
  validates(
41
46
  :iframe_access_level,
42
47
  inclusion: { in: Decidim::Meetings::Meeting.iframe_access_levels },
@@ -83,6 +88,26 @@ module Decidim
83
88
  type_of_meeting.presence
84
89
  end
85
90
 
91
+ def reminder_message_custom_content
92
+ return unless reminder_enabled
93
+
94
+ return super if super.respond_to?(:values) && super.values.any?(&:present?)
95
+
96
+ @default_reminder_message ||= if current_organization
97
+ current_organization.available_locales.index_with do |locale|
98
+ I18n.t("decidim.events.meetings.upcoming_meeting.default_body", locale:)
99
+ end
100
+ else
101
+ {}
102
+ end
103
+ end
104
+
105
+ def send_reminders_before_hours
106
+ return nil unless reminder_enabled
107
+
108
+ super.presence&.to_i
109
+ end
110
+
86
111
  def iframe_access_level_select
87
112
  Decidim::Meetings::Meeting.iframe_access_levels.map do |level, _value|
88
113
  [
@@ -10,7 +10,7 @@ module Decidim
10
10
  attribute :position, Integer
11
11
  attribute :mandatory, Boolean, default: false
12
12
  attribute :question_type, String
13
- attribute :answer_options, Array[AnswerOptionForm]
13
+ attribute :response_options, Array[ResponseOptionForm]
14
14
  attribute :max_choices, Integer
15
15
  attribute :deleted, Boolean, default: false
16
16
 
@@ -21,7 +21,7 @@ module Decidim
21
21
  validates :question_type, inclusion: { in: Decidim::Meetings::Question::QUESTION_TYPES }, if: :editable?
22
22
  validates :max_choices, numericality: { only_integer: true, greater_than: 1, less_than_or_equal_to: ->(form) { form.number_of_options } }, allow_blank: true, if: :editable?
23
23
  validates :body, translatable_presence: true, if: :requires_body?
24
- validates :answer_options, presence: true, if: :editable?
24
+ validates :response_options, presence: true, if: :editable?
25
25
 
26
26
  def to_param
27
27
  return id if id.present?
@@ -34,7 +34,7 @@ module Decidim
34
34
  end
35
35
 
36
36
  def number_of_options
37
- answer_options.size
37
+ response_options.size
38
38
  end
39
39
 
40
40
  private
@@ -3,8 +3,8 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  module Admin
6
- # This class holds a Form to update answer options
7
- class AnswerOptionForm < Decidim::Form
6
+ # This class holds a Form to update response options
7
+ class ResponseOptionForm < Decidim::Form
8
8
  include TranslatableAttributes
9
9
 
10
10
  attribute :deleted, Boolean, default: false
@@ -16,7 +16,7 @@ module Decidim
16
16
  def to_param
17
17
  return id if id.present?
18
18
 
19
- "questionnaire-question-answer-option-id"
19
+ "questionnaire-question-response-option-id"
20
20
  end
21
21
  end
22
22
  end
@@ -25,7 +25,7 @@ module Decidim
25
25
 
26
26
  errors.add(
27
27
  :code,
28
- I18n.t("registrations.validate_registration_code.invalid", scope: "decidim.meetings.admin")
28
+ I18n.t("registrations_attendees.validate_registration_code.invalid", scope: "decidim.meetings.admin")
29
29
  )
30
30
  end
31
31
  end
@@ -15,8 +15,6 @@ module Decidim
15
15
 
16
16
  validates :current_component, presence: true
17
17
 
18
- validates :address, presence: true, if: ->(form) { form.needs_address? }
19
- validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? && form.needs_address? }
20
18
  validates :start_time, presence: true, date: { before: :end_time }
21
19
  validates :end_time, presence: true, date: { after: :start_time }
22
20
 
@@ -8,7 +8,7 @@ module Decidim
8
8
  attribute :proposal_ids, Array[Integer]
9
9
  attribute :proposals
10
10
  attribute :closed_at, Decidim::Attributes::TimeWithZone, default: -> { Time.current }
11
- attribute :attendees_count, Integer, default: 0
11
+ attribute :attendees_count, Integer
12
12
 
13
13
  validates :closing_report, presence: true
14
14
  validates :attendees_count,
@@ -22,6 +22,7 @@ module Decidim
22
22
  self.proposal_ids = model.linked_resources(:proposals, "proposals_from_meeting").pluck(:id)
23
23
  presenter = MeetingEditionPresenter.new(model)
24
24
  self.closing_report = presenter.closing_report(all_locales: false)
25
+ self.attendees_count = model.attendees_count || model.registrations.where.not(validated_at: nil).count
25
26
  end
26
27
 
27
28
  def proposals
@@ -3,7 +3,6 @@
3
3
  module Decidim
4
4
  module Meetings
5
5
  class JoinMeetingForm < Decidim::Form
6
- attribute :user_group_id, Integer
7
6
  attribute :public_participation, Boolean, default: false
8
7
  end
9
8
  end
@@ -2,14 +2,13 @@
2
2
 
3
3
  module Decidim
4
4
  module Meetings
5
- # This class holds a Form to create/update meetings for Participants and UserGroups.
5
+ # This class holds a Form to create/update meetings for Participants
6
6
  class MeetingForm < ::Decidim::Meetings::BaseMeetingForm
7
7
  attribute :title, String
8
8
  attribute :description, String
9
9
  attribute :location, String
10
10
  attribute :location_hints, String
11
11
 
12
- attribute :user_group_id, Integer
13
12
  attribute :registration_type, String
14
13
  attribute :registrations_enabled, Boolean, default: false
15
14
  attribute :registration_url, String
@@ -18,6 +17,8 @@ module Decidim
18
17
  attribute :iframe_embed_type, String, default: "none"
19
18
  attribute :iframe_access_level, String
20
19
 
20
+ validates :address, presence: true, if: ->(form) { form.needs_address? }
21
+ validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? && form.needs_address? }
21
22
  validates :iframe_embed_type, inclusion: { in: Decidim::Meetings::Meeting.participants_iframe_embed_types }
22
23
  validates :title, presence: true, etiquette: true
23
24
  validates :description, presence: true, etiquette: true
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Meetings
5
+ # This class holds a Form to save the chosen option for an response
6
+ class ResponseChoiceForm < Decidim::Form
7
+ attribute :body, String
8
+ attribute :position, Integer
9
+ attribute :response_option_id, Integer
10
+
11
+ validates :response_option_id, presence: true
12
+ end
13
+ end
14
+ end
@@ -2,13 +2,13 @@
2
2
 
3
3
  module Decidim
4
4
  module Meetings
5
- # This class holds a Form to save the questionnaire answers from Decidim's public page
6
- class AnswerForm < Decidim::Form
5
+ # This class holds a Form to save the questionnaire responses from Decidim's public page
6
+ class ResponseForm < Decidim::Form
7
7
  include Decidim::TranslationsHelper
8
8
 
9
9
  attribute :question_id, String
10
10
  attribute :body, String
11
- attribute :choices, Array[AnswerChoiceForm]
11
+ attribute :choices, Array[ResponseChoiceForm]
12
12
  attribute :current_user, Decidim::User
13
13
 
14
14
  validates :selected_choices, presence: true
@@ -20,8 +20,8 @@ module Decidim
20
20
  @question ||= Decidim::Meetings::Question.find(question_id)
21
21
  end
22
22
 
23
- def answer
24
- @answer ||= Decidim::Meetings::Answer.find_by(decidim_user_id: current_user.id, decidim_question_id: question_id) if current_user
23
+ def response
24
+ @response ||= Decidim::Meetings::Response.find_by(decidim_user_id: current_user.id, decidim_question_id: question_id) if current_user
25
25
  end
26
26
 
27
27
  def label
@@ -38,12 +38,12 @@ module Decidim
38
38
  self.question = model.question
39
39
 
40
40
  self.choices = model.choices.map do |choice|
41
- AnswerChoiceForm.from_model(choice)
41
+ ResponseChoiceForm.from_model(choice)
42
42
  end
43
43
  end
44
44
 
45
45
  def selected_choices
46
- choices.select(&:answer_option_id)
46
+ choices.select(&:response_option_id)
47
47
  end
48
48
 
49
49
  private
@@ -15,7 +15,6 @@ module Decidim
15
15
 
16
16
  def filter_origin_values
17
17
  origin_keys = %w(official participants)
18
- origin_keys << "user_group" if current_organization.user_groups_enabled?
19
18
 
20
19
  origin_values = flat_filter_values(*origin_keys, scope: "decidim.meetings.meetings.filters.origin_values")
21
20
  origin_values.prepend(["", t("all", scope: "decidim.meetings.meetings.filters.origin_values")])
@@ -8,7 +8,7 @@ module Decidim
8
8
  include Decidim::Meetings::ApplicationHelper
9
9
  include Decidim::TranslationsHelper
10
10
  include Decidim::ResourceHelper
11
- include Decidim::EndorsableHelper
11
+ include Decidim::LikeableHelper
12
12
 
13
13
  # Public: truncates the meeting description
14
14
  #
@@ -23,6 +23,19 @@ module Decidim
23
23
  CGI.unescapeHTML html_truncate(description, max_length:, tail:)
24
24
  end
25
25
 
26
+ def waitlist_status_block(registration)
27
+ return unless registration.waiting_list?
28
+
29
+ render layout: "decidim/meetings/layouts/aside_block", locals: { emoji: "ticket-line" } do
30
+ content_tag(:div) do
31
+ safe_join([
32
+ content_tag(:h3, t("waitlist.status", scope: "decidim.meetings.meetings.show"), class: "meeting__aside-block__title"),
33
+ content_tag(:p, t("waitlist.description", scope: "decidim.meetings.meetings.show"), class: "text-sm")
34
+ ])
35
+ end
36
+ end
37
+ end
38
+
26
39
  # Public: The css class applied based on the meeting type to
27
40
  # the css class.
28
41
  #
@@ -118,10 +131,6 @@ module Decidim
118
131
  end
119
132
  end
120
133
 
121
- def current_user_groups?
122
- current_organization.user_groups_enabled? && Decidim::UserGroups::ManageableUserGroups.for(current_user).verified.any?
123
- end
124
-
125
134
  # Public: URL to create an event in Google Calendars based on meeting
126
135
  # data.
127
136
  #