decidim-meetings 0.30.2 → 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.
- checksums.yaml +4 -4
- data/app/cells/decidim/meetings/cancel_registration_meeting_button/cancelation_modal.erb +7 -4
- data/app/cells/decidim/meetings/cancel_registration_meeting_button/show.erb +1 -1
- data/app/cells/decidim/meetings/cancel_registration_meeting_button_cell.rb +36 -0
- data/app/cells/decidim/meetings/dates_and_map_cell.rb +1 -1
- data/app/cells/decidim/meetings/join_meeting_button/registration_modal.erb +4 -5
- data/app/cells/decidim/meetings/join_meeting_button/show.erb +25 -28
- data/app/cells/decidim/meetings/join_meeting_button/waitlist_button.erb +22 -0
- data/app/cells/decidim/meetings/join_meeting_button_cell.rb +28 -0
- data/app/cells/decidim/meetings/question_responses/show.erb +7 -7
- data/app/cells/decidim/meetings/question_responses_cell.rb +28 -28
- data/app/commands/decidim/meetings/admin/copy_meeting.rb +5 -2
- data/app/commands/decidim/meetings/admin/create_agenda.rb +6 -2
- data/app/commands/decidim/meetings/admin/create_meeting.rb +7 -3
- data/app/commands/decidim/meetings/admin/mark_as_attendee.rb +44 -0
- data/app/commands/decidim/meetings/admin/publish_meeting.rb +2 -1
- data/app/commands/decidim/meetings/admin/update_agenda.rb +6 -2
- data/app/commands/decidim/meetings/admin/update_meeting.rb +15 -6
- data/app/commands/decidim/meetings/admin/update_questionnaire.rb +4 -4
- data/app/commands/decidim/meetings/admin/update_registrations.rb +19 -7
- data/app/commands/decidim/meetings/create_meeting.rb +2 -3
- data/app/commands/decidim/meetings/{create_answer.rb → create_response.rb} +11 -11
- data/app/commands/decidim/meetings/join_meeting.rb +4 -6
- data/app/commands/decidim/meetings/join_waitlist.rb +53 -0
- data/app/commands/decidim/meetings/leave_meeting.rb +14 -5
- data/app/commands/decidim/meetings/update_meeting.rb +1 -2
- data/app/controllers/concerns/decidim/meetings/admin/filterable.rb +1 -1
- data/app/controllers/decidim/meetings/admin/agenda_controller.rb +2 -2
- data/app/controllers/decidim/meetings/admin/invites_controller.rb +1 -1
- data/app/controllers/decidim/meetings/admin/meeting_closes_controller.rb +1 -1
- data/app/controllers/decidim/meetings/admin/meeting_copies_controller.rb +1 -1
- data/app/controllers/decidim/meetings/admin/meetings_controller.rb +4 -4
- data/app/controllers/decidim/meetings/admin/meetings_poll_controller.rb +10 -10
- data/app/controllers/decidim/meetings/admin/registrations_attendees_controller.rb +79 -0
- data/app/controllers/decidim/meetings/admin/registrations_controller.rb +1 -22
- data/app/controllers/decidim/meetings/meeting_closes_controller.rb +1 -1
- data/app/controllers/decidim/meetings/meetings_controller.rb +11 -11
- data/app/controllers/decidim/meetings/polls/{answers_controller.rb → responses_controller.rb} +7 -7
- data/app/controllers/decidim/meetings/registrations_controller.rb +39 -12
- data/app/events/decidim/meetings/registration_marked_as_attendee_event.rb +9 -0
- data/app/events/decidim/meetings/upcoming_meeting_event.rb +41 -0
- data/app/events/decidim/meetings/update_meeting_event.rb +25 -0
- data/app/forms/decidim/meetings/admin/close_meeting_form.rb +2 -1
- data/app/forms/decidim/meetings/admin/meeting_agenda_items_form.rb +4 -0
- data/app/forms/decidim/meetings/admin/meeting_form.rb +28 -3
- data/app/forms/decidim/meetings/admin/question_form.rb +3 -3
- data/app/forms/decidim/meetings/admin/{answer_option_form.rb → response_option_form.rb} +3 -3
- data/app/forms/decidim/meetings/admin/validate_registration_code_form.rb +1 -1
- data/app/forms/decidim/meetings/base_meeting_form.rb +0 -2
- data/app/forms/decidim/meetings/close_meeting_form.rb +2 -1
- data/app/forms/decidim/meetings/join_meeting_form.rb +0 -1
- data/app/forms/decidim/meetings/meeting_form.rb +3 -2
- data/app/forms/decidim/meetings/response_choice_form.rb +14 -0
- data/app/forms/decidim/meetings/{answer_form.rb → response_form.rb} +7 -7
- data/app/helpers/decidim/meetings/application_helper.rb +0 -1
- data/app/helpers/decidim/meetings/meetings_helper.rb +14 -5
- data/app/jobs/decidim/meetings/promote_from_waitlist_job.rb +63 -0
- data/app/jobs/decidim/meetings/upcoming_meeting_notification_job.rb +1 -1
- data/app/mailers/decidim/meetings/registration_mailer.rb +13 -0
- data/app/models/decidim/meetings/agenda_item.rb +5 -0
- data/app/models/decidim/meetings/meeting.rb +15 -12
- data/app/models/decidim/meetings/question.rb +12 -12
- data/app/models/decidim/meetings/questionnaire.rb +1 -1
- data/app/models/decidim/meetings/registration.rb +19 -7
- data/app/models/decidim/meetings/{answer.rb → response.rb} +6 -6
- data/app/models/decidim/meetings/response_choice.rb +15 -0
- data/app/models/decidim/meetings/{answer_option.rb → response_option.rb} +5 -5
- data/app/packs/src/decidim/meetings/admin/destroy_meeting_alert.js +1 -1
- data/app/packs/src/decidim/meetings/admin/meetings_components_form.js +1 -8
- data/app/packs/src/decidim/meetings/admin/meetings_form.js +1 -1
- data/app/packs/src/decidim/meetings/admin/registrations_form.js +1 -1
- data/app/packs/src/decidim/meetings/admin/registrations_invite_form.js +1 -1
- data/app/packs/src/decidim/meetings/meetings_form.js +1 -1
- data/app/packs/src/decidim/meetings/meetings_polls.js +1 -1
- data/app/packs/src/decidim/meetings/poll.component.js +5 -5
- data/app/packs/stylesheets/decidim/meetings/_item.scss +5 -1
- data/app/packs/stylesheets/decidim/meetings/meetings.scss +4 -4
- data/app/permissions/decidim/meetings/admin/agenda_permissions.rb +34 -0
- data/app/permissions/decidim/meetings/admin/meeting_permissions.rb +44 -0
- data/app/permissions/decidim/meetings/admin/permissions.rb +5 -66
- data/app/permissions/decidim/meetings/admin/questionnaire_permissions.rb +30 -0
- data/app/permissions/decidim/meetings/meeting_permissions.rb +90 -0
- data/app/permissions/decidim/meetings/permissions.rb +9 -105
- data/app/presenters/decidim/meetings/admin_log/value_types/meeting_title_description_presenter.rb +1 -1
- data/app/presenters/decidim/meetings/agenda_item_presenter.rb +29 -0
- data/app/presenters/decidim/meetings/meeting_presenter.rb +11 -15
- data/app/presenters/decidim/meetings/registration_presenter.rb +24 -0
- data/app/queries/decidim/meetings/{questionnaire_user_answers.rb → questionnaire_user_responses.rb} +5 -5
- data/app/serializers/decidim/meetings/registration_serializer.rb +5 -6
- data/app/services/decidim/meetings/diff_renderer.rb +0 -1
- data/app/views/decidim/meetings/_calendar_modal.html.erb +1 -0
- data/app/views/decidim/meetings/admin/agenda/_agenda_item_fields.html.erb +1 -1
- data/app/views/decidim/meetings/admin/invites/_form.html.erb +1 -1
- data/app/views/decidim/meetings/admin/meeting_closes/_form.html.erb +1 -1
- data/app/views/decidim/meetings/admin/meetings/_form.html.erb +3 -2
- data/app/views/decidim/meetings/admin/meetings/_linked_spaces.html.erb +1 -1
- data/app/views/decidim/meetings/admin/meetings/_meeting-tr.html.erb +11 -8
- data/app/views/decidim/meetings/admin/meetings/_meeting_actions.html.erb +200 -69
- data/app/views/decidim/meetings/admin/meetings/_reminders.html.erb +19 -0
- data/app/views/decidim/meetings/admin/meetings/_services.html.erb +1 -1
- data/app/views/decidim/meetings/admin/meetings/index.html.erb +7 -5
- data/app/views/decidim/meetings/admin/meetings/manage_trash.html.erb +2 -1
- data/app/views/decidim/meetings/admin/poll/_form.html.erb +6 -6
- data/app/views/decidim/meetings/admin/poll/_question.html.erb +13 -13
- data/app/views/decidim/meetings/admin/poll/_response_option.html.erb +35 -0
- data/app/views/decidim/meetings/admin/poll/_response_option_template.html.erb +7 -0
- data/app/views/decidim/meetings/admin/poll/edit.html.erb +3 -3
- data/app/views/decidim/meetings/admin/registration_form/edit_questions.html.erb +5 -5
- data/app/views/decidim/meetings/admin/registrations/edit.html.erb +19 -39
- data/app/views/decidim/meetings/admin/registrations_attendees/index.html.erb +126 -0
- data/app/views/decidim/meetings/layouts/live_event.html.erb +1 -1
- data/app/views/decidim/meetings/meeting_closes/_form.html.erb +2 -2
- data/app/views/decidim/meetings/meetings/_form.html.erb +2 -11
- data/app/views/decidim/meetings/meetings/_meeting.html.erb +2 -2
- data/app/views/decidim/meetings/meetings/_meeting_actions.html.erb +3 -3
- data/app/views/decidim/meetings/meetings/_meeting_agenda.html.erb +2 -2
- data/app/views/decidim/meetings/meetings/_meeting_aside.html.erb +11 -10
- data/app/views/decidim/meetings/meetings/_meeting_poll_actions.html.erb +3 -3
- data/app/views/decidim/meetings/meetings/_registration_code_modal.html.erb +16 -0
- data/app/views/decidim/meetings/polls/questions/_index_admin.html.erb +1 -1
- data/app/views/decidim/meetings/polls/questions/_published_question.html.erb +5 -5
- data/app/views/decidim/meetings/polls/responses/_multiple_option.html.erb +13 -0
- data/app/views/decidim/meetings/polls/responses/_single_option.html.erb +13 -0
- data/app/views/decidim/meetings/polls/{answers → responses}/admin.html.erb +4 -4
- data/app/views/decidim/meetings/polls/{answers → responses}/index.html.erb +4 -4
- data/app/views/decidim/meetings/registration_mailer/confirmation.html.erb +6 -1
- data/config/assets.rb +2 -2
- data/config/locales/ar.yml +1 -26
- data/config/locales/bg.yml +2 -32
- data/config/locales/ca-IT.yml +86 -40
- data/config/locales/ca.yml +86 -40
- data/config/locales/cs.yml +71 -43
- data/config/locales/de.yml +87 -41
- data/config/locales/el.yml +1 -25
- data/config/locales/en.yml +89 -43
- data/config/locales/es-MX.yml +87 -41
- data/config/locales/es-PY.yml +87 -41
- data/config/locales/es.yml +87 -41
- data/config/locales/eu.yml +86 -40
- data/config/locales/fi-plain.yml +86 -40
- data/config/locales/fi.yml +85 -39
- data/config/locales/fr-CA.yml +79 -38
- data/config/locales/fr.yml +79 -38
- data/config/locales/ga-IE.yml +1 -7
- data/config/locales/gl.yml +1 -19
- data/config/locales/hu.yml +1 -22
- data/config/locales/id-ID.yml +0 -16
- data/config/locales/is-IS.yml +0 -10
- data/config/locales/it.yml +1 -29
- data/config/locales/ja.yml +88 -42
- data/config/locales/lb.yml +1 -15
- data/config/locales/lt.yml +1 -28
- data/config/locales/lv.yml +0 -16
- data/config/locales/nl.yml +1 -26
- data/config/locales/no.yml +1 -28
- data/config/locales/pl.yml +2 -32
- data/config/locales/pt-BR.yml +1 -28
- data/config/locales/pt.yml +1 -28
- data/config/locales/ro-RO.yml +56 -29
- data/config/locales/ru.yml +0 -16
- data/config/locales/sk.yml +0 -16
- data/config/locales/sl.yml +0 -4
- data/config/locales/sv.yml +85 -39
- data/config/locales/tr-TR.yml +0 -20
- data/config/locales/uk.yml +0 -12
- data/config/locales/zh-CN.yml +0 -19
- data/config/locales/zh-TW.yml +1 -26
- data/db/migrate/20181107175558_add_questionnaire_to_existing_meetings.rb +8 -2
- data/db/migrate/20200827153856_add_commentable_counter_cache_to_meetings.rb +8 -2
- data/db/migrate/20201016065302_fix_meetings_registration_terms.rb +8 -2
- data/db/migrate/20210310120731_add_followable_counter_cache_to_meetings.rb +8 -2
- data/db/migrate/20250317103343_rename_answer_to_response_in_decidim_meetings.rb +18 -0
- data/db/migrate/20250403094034_add_reminder_customization_to_decidim_meetings.rb +9 -0
- data/db/migrate/20250408071941_add_status_to_registrations_to_decidim_meetings_registrations.rb +8 -0
- data/lib/decidim/api/agenda_item_type.rb +6 -2
- data/lib/decidim/api/agenda_type.rb +6 -2
- data/lib/decidim/api/linked_resources_interface.rb +1 -1
- data/lib/decidim/api/meeting_type.rb +20 -10
- data/lib/decidim/api/service_type.rb +3 -0
- data/lib/decidim/meetings/admin_engine.rb +9 -1
- data/lib/decidim/meetings/component.rb +29 -8
- data/lib/decidim/meetings/engine.rb +6 -21
- data/lib/decidim/meetings/meeting_serializer.rb +1 -2
- data/lib/decidim/meetings/schema_org_event_meeting_serializer.rb +0 -10
- data/lib/decidim/meetings/seeds.rb +4 -13
- data/lib/decidim/meetings/test/factories.rb +10 -16
- data/lib/decidim/meetings/user_responses_serializer.rb +47 -0
- data/lib/decidim/meetings/version.rb +1 -1
- data/lib/decidim/meetings.rb +7 -9
- metadata +49 -35
- data/app/cells/decidim/meetings/attending_organizations_list_cell.rb +0 -32
- data/app/forms/decidim/meetings/answer_choice_form.rb +0 -14
- data/app/models/decidim/meetings/answer_choice.rb +0 -15
- data/app/queries/decidim/meetings/metrics/meeting_followers_metric_measure.rb +0 -31
- data/app/queries/decidim/meetings/metrics/meetings_metric_manage.rb +0 -48
- data/app/views/decidim/meetings/admin/poll/_answer_option.html.erb +0 -35
- data/app/views/decidim/meetings/admin/poll/_answer_option_template.html.erb +0 -7
- data/app/views/decidim/meetings/polls/answers/_multiple_option.html.erb +0 -13
- data/app/views/decidim/meetings/polls/answers/_single_option.html.erb +0 -13
- data/lib/decidim/meetings/download_your_data_user_answers_serializer.rb +0 -39
- data/lib/decidim/meetings/user_answers_serializer.rb +0 -47
- /data/app/views/decidim/meetings/polls/{answers → responses}/create.js.erb +0 -0
data/app/controllers/decidim/meetings/polls/{answers_controller.rb → responses_controller.rb}
RENAMED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Decidim
|
4
4
|
module Meetings
|
5
5
|
module Polls
|
6
|
-
class
|
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, :
|
22
|
-
@form = form(
|
21
|
+
enforce_permission_to(:create, :response, question:)
|
22
|
+
@form = form(ResponseForm).from_params(params.merge(question:, current_user:))
|
23
23
|
|
24
|
-
|
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(
|
36
|
+
@question ||= questionnaire.questions.find(response_params[:question_id]) if questionnaire
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
40
|
-
params.require(:
|
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
|
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
|
-
|
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
|
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("
|
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
|
83
|
-
meeting.
|
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
|
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
|
-
|
120
|
+
respond_meeting_registration_path(meeting_id: meeting.id)
|
94
121
|
end
|
95
122
|
|
96
123
|
def questionnaire_for
|
@@ -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
|
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
|
@@ -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 :
|
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 :
|
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 :
|
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
|
-
|
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
|
7
|
-
class
|
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-
|
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("
|
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
|
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
|
@@ -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
|
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
|
6
|
-
class
|
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[
|
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
|
24
|
-
@
|
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
|
-
|
41
|
+
ResponseChoiceForm.from_model(choice)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
def selected_choices
|
46
|
-
choices.select(&:
|
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::
|
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
|
#
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Meetings
|
5
|
+
# Background job that promotes users from the waitlist to registered status,
|
6
|
+
# depending on available slots in the meeting.
|
7
|
+
class PromoteFromWaitlistJob < ApplicationJob
|
8
|
+
queue_as :default
|
9
|
+
|
10
|
+
def perform(meeting_id)
|
11
|
+
@meeting = Decidim::Meetings::Meeting.find_by(id: meeting_id)
|
12
|
+
return unless @meeting
|
13
|
+
|
14
|
+
promote_users_from_waitlist
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :meeting
|
20
|
+
|
21
|
+
def promote_users_from_waitlist
|
22
|
+
meeting.with_lock do
|
23
|
+
loop do
|
24
|
+
break unless meeting.remaining_slots.positive?
|
25
|
+
|
26
|
+
next_in_waitlist = meeting.registrations.on_waiting_list.first
|
27
|
+
break unless next_in_waitlist
|
28
|
+
|
29
|
+
promote(next_in_waitlist)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def promote(registration)
|
35
|
+
return unless registration.waiting_list?
|
36
|
+
|
37
|
+
registration.update!(status: :registered)
|
38
|
+
notify_user(registration)
|
39
|
+
end
|
40
|
+
|
41
|
+
def notify_user(registration)
|
42
|
+
send_email_confirmation(registration)
|
43
|
+
send_internal_notification(registration)
|
44
|
+
end
|
45
|
+
|
46
|
+
def send_email_confirmation(registration)
|
47
|
+
Decidim::Meetings::RegistrationMailer.confirmation(registration.user, meeting, registration).deliver_later
|
48
|
+
end
|
49
|
+
|
50
|
+
def send_internal_notification(registration)
|
51
|
+
Decidim::EventsManager.publish(
|
52
|
+
event: "decidim.events.meetings.meeting_registration_confirmed",
|
53
|
+
event_class: Decidim::Meetings::MeetingRegistrationNotificationEvent,
|
54
|
+
resource: meeting,
|
55
|
+
affected_users: [registration.user],
|
56
|
+
extra: {
|
57
|
+
registration_code: registration.code
|
58
|
+
}
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -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
|
# A custom mailer for sending notifications to users when
|
@@ -20,8 +22,11 @@ module Decidim
|
|
20
22
|
@registration = registration
|
21
23
|
@organization = @meeting.organization
|
22
24
|
@locator = Decidim::ResourceLocatorPresenter.new(@meeting)
|
25
|
+
@registration_code_enabled = meeting.component.settings.registration_code_enabled
|
26
|
+
@qr_filename = "qr-#{@registration.code.parameterize}.png"
|
23
27
|
|
24
28
|
add_calendar_attachment
|
29
|
+
add_qr_code_attachment if @registration_code_enabled
|
25
30
|
|
26
31
|
subject = I18n.t("confirmation.subject", scope: "decidim.meetings.mailer.registration_mailer")
|
27
32
|
mail(to: user.email, subject:)
|
@@ -30,12 +35,20 @@ module Decidim
|
|
30
35
|
|
31
36
|
private
|
32
37
|
|
38
|
+
def qr_code
|
39
|
+
@qr_code ||= RQRCode::QRCode.new(@registration.validation_code_short_link.short_url)
|
40
|
+
end
|
41
|
+
|
33
42
|
def add_calendar_attachment
|
34
43
|
calendar = Icalendar::Calendar.new
|
35
44
|
calendar.add_event(Calendar::MeetingToEvent.new(@meeting).event)
|
36
45
|
|
37
46
|
attachments["meeting-calendar-info.ics"] = calendar.to_ical
|
38
47
|
end
|
48
|
+
|
49
|
+
def add_qr_code_attachment
|
50
|
+
attachments[@qr_filename] = qr_code.as_png(size: 500).to_blob
|
51
|
+
end
|
39
52
|
end
|
40
53
|
end
|
41
54
|
end
|
@@ -33,6 +33,11 @@ module Decidim
|
|
33
33
|
def self.log_presenter_class_for(_log)
|
34
34
|
Decidim::Meetings::AdminLog::AgendaItemPresenter
|
35
35
|
end
|
36
|
+
|
37
|
+
# Returns the presenter for this model
|
38
|
+
def presenter
|
39
|
+
Decidim::Meetings::AgendaItemPresenter.new(self)
|
40
|
+
end
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|