decidim-action_delegator 0.8.2 → 0.9.0

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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +104 -58
  3. data/Rakefile +2 -2
  4. data/app/commands/decidim/action_delegator/admin/create_delegation.rb +1 -3
  5. data/app/commands/decidim/action_delegator/admin/create_setting.rb +5 -2
  6. data/app/commands/decidim/action_delegator/admin/update_setting.rb +4 -2
  7. data/app/controllers/concerns/decidim/action_delegator/devise/sessions_controller_override.rb +1 -1
  8. data/app/controllers/concerns/decidim/action_delegator/election_per_question_votes_controller_override.rb +65 -0
  9. data/app/controllers/concerns/decidim/action_delegator/election_votes_controller_override.rb +38 -0
  10. data/app/controllers/concerns/decidim/action_delegator/votes_controller_methods.rb +46 -0
  11. data/app/controllers/decidim/action_delegator/admin/application_controller.rb +11 -5
  12. data/app/controllers/decidim/action_delegator/admin/delegations_controller.rb +2 -10
  13. data/app/controllers/decidim/action_delegator/admin/invite_participants_controller.rb +0 -8
  14. data/app/controllers/decidim/action_delegator/admin/manage_delegations_controller.rb +13 -20
  15. data/app/controllers/decidim/action_delegator/admin/manage_participants_controller.rb +11 -19
  16. data/app/controllers/decidim/action_delegator/admin/participants_controller.rb +1 -9
  17. data/app/controllers/decidim/action_delegator/admin/permissions_controller.rb +1 -24
  18. data/app/controllers/decidim/action_delegator/admin/ponderations_controller.rb +0 -8
  19. data/app/controllers/decidim/action_delegator/admin/settings_controller.rb +12 -12
  20. data/app/controllers/decidim/action_delegator/application_controller.rb +3 -3
  21. data/app/controllers/decidim/action_delegator/elections/admin/results_controller.rb +58 -0
  22. data/app/controllers/decidim/action_delegator/elections/results_controller.rb +36 -0
  23. data/app/controllers/decidim/action_delegator/user_delegations_controller.rb +1 -1
  24. data/app/controllers/decidim/action_delegator/verifications/delegations_verifier/authorizations_controller.rb +14 -18
  25. data/app/forms/decidim/action_delegator/admin/action_delegator_census_form.rb +44 -0
  26. data/app/forms/decidim/action_delegator/admin/csv_import_form.rb +17 -0
  27. data/app/forms/decidim/action_delegator/admin/ponderation_form.rb +2 -1
  28. data/app/forms/decidim/action_delegator/admin/setting_form.rb +16 -10
  29. data/app/forms/decidim/action_delegator/censuses/internal_users_form.rb +32 -0
  30. data/app/forms/decidim/action_delegator/verifications/delegations_verifier_form.rb +41 -25
  31. data/app/helpers/decidim/action_delegator/admin/{delegation_helper.rb → settings_helper.rb} +3 -16
  32. data/app/helpers/decidim/action_delegator/settings_helper.rb +124 -0
  33. data/app/jobs/decidim/action_delegator/admin/invite_participants_job.rb +1 -1
  34. data/app/jobs/decidim/action_delegator/send_sms_job.rb +2 -2
  35. data/app/jobs/decidim/action_delegator/twilio_send_sms_job.rb +2 -2
  36. data/app/models/decidim/action_delegator/delegation.rb +15 -14
  37. data/app/models/decidim/action_delegator/participant.rb +7 -10
  38. data/app/models/decidim/action_delegator/ponderation.rb +0 -2
  39. data/app/models/decidim/action_delegator/setting.rb +14 -32
  40. data/app/overrides/decidim/elections/admin/dashboard/_results/_add_results_contents.html.erb.deface +10 -0
  41. data/app/overrides/decidim/elections/admin/dashboard/_results/_add_results_tabs.html.erb.deface +3 -0
  42. data/app/overrides/decidim/elections/elections/_election_aside/add_delegation_buttons.html.erb.deface +3 -0
  43. data/app/overrides/decidim/elections/elections/_vote_results/replace_results_div.html.erb.deface +8 -0
  44. data/app/overrides/decidim/elections/per_question_votes/show/add_delegation_hidden_input.html.erb.deface +3 -0
  45. data/app/overrides/decidim/elections/per_question_votes/show/add_delegation_id_to_links.html.erb.deface +3 -0
  46. data/app/overrides/decidim/elections/per_question_votes/waiting/add_delegation_buttons.html.erb.deface +3 -0
  47. data/app/overrides/decidim/elections/votes/receipt/add_delegation_buttons.html.erb.deface +3 -0
  48. data/app/packs/entrypoints/decidim_action_delegator_elections.js +1 -0
  49. data/app/packs/src/decidim/action_delegator/elections_live_results.js +160 -0
  50. data/app/permissions/decidim/action_delegator/admin/permissions.rb +29 -0
  51. data/app/permissions/decidim/action_delegator/permissions.rb +6 -30
  52. data/app/presenters/decidim/action_delegator/setting_presenter.rb +33 -0
  53. data/app/queries/decidim/action_delegator/action_delegator_census_users.rb +51 -0
  54. data/app/queries/decidim/action_delegator/authorized_resources.rb +28 -0
  55. data/app/queries/decidim/action_delegator/election_settings.rb +23 -0
  56. data/app/queries/decidim/action_delegator/{delegated_votes_versions.rb → elections_delegated_votes_versions.rb} +8 -8
  57. data/app/queries/decidim/action_delegator/elections_question_responses_by_type.rb +33 -0
  58. data/app/queries/decidim/action_delegator/elections_question_weighted_responses.rb +28 -0
  59. data/app/queries/decidim/action_delegator/elections_votes_with_ponderations.rb +62 -0
  60. data/app/services/decidim/action_delegator/participants_csv_importer.rb +1 -1
  61. data/app/services/decidim/action_delegator/sms_gateway.rb +2 -2
  62. data/app/views/decidim/action_delegator/admin/censuses/_action_delegator_census_form.html.erb +44 -0
  63. data/app/views/decidim/action_delegator/admin/censuses/_action_delegator_census_options_form.html.erb +25 -0
  64. data/app/views/decidim/action_delegator/admin/delegations/index.html.erb +12 -13
  65. data/app/views/decidim/action_delegator/admin/delegations/new.html.erb +29 -27
  66. data/app/views/decidim/action_delegator/admin/manage_delegations/new.html.erb +36 -24
  67. data/app/views/decidim/action_delegator/admin/manage_participants/new.html.erb +47 -33
  68. data/app/views/decidim/action_delegator/admin/participants/_form.html.erb +14 -8
  69. data/app/views/decidim/action_delegator/admin/participants/edit.html.erb +11 -13
  70. data/app/views/decidim/action_delegator/admin/participants/index.html.erb +20 -25
  71. data/app/views/decidim/action_delegator/admin/participants/new.html.erb +11 -13
  72. data/app/views/decidim/action_delegator/admin/ponderations/_form.html.erb +13 -7
  73. data/app/views/decidim/action_delegator/admin/ponderations/edit.html.erb +11 -13
  74. data/app/views/decidim/action_delegator/admin/ponderations/index.html.erb +8 -11
  75. data/app/views/decidim/action_delegator/admin/ponderations/new.html.erb +11 -13
  76. data/app/views/decidim/action_delegator/admin/settings/_form.html.erb +26 -12
  77. data/app/views/decidim/action_delegator/admin/settings/_participants_sync_check.html.erb +1 -1
  78. data/app/views/decidim/action_delegator/admin/settings/_setting_checks.html.erb +11 -16
  79. data/app/views/decidim/action_delegator/admin/settings/edit.html.erb +11 -12
  80. data/app/views/decidim/action_delegator/admin/settings/index.html.erb +22 -19
  81. data/app/views/decidim/action_delegator/admin/settings/new.html.erb +11 -12
  82. data/app/views/decidim/action_delegator/admin/shared/_tabs_menu.html.erb +15 -0
  83. data/app/views/decidim/action_delegator/censuses/_internal_users_form.html.erb +61 -0
  84. data/app/views/decidim/action_delegator/elections/_delegation_buttons.html.erb +10 -0
  85. data/app/views/decidim/action_delegator/elections/_normal_election_buttons.html.erb +13 -0
  86. data/app/views/decidim/action_delegator/elections/_per_question_buttons.html.erb +9 -0
  87. data/app/views/decidim/action_delegator/elections/_per_question_waiting_buttons.html.erb +19 -0
  88. data/app/views/decidim/action_delegator/elections/_vote_results.html.erb +10 -0
  89. data/app/views/decidim/action_delegator/elections/_vote_results_question.html.erb +13 -0
  90. data/app/views/decidim/action_delegator/elections/admin/dashboard/_by_type_and_weight.html.erb +45 -0
  91. data/app/views/decidim/action_delegator/elections/admin/dashboard/_results_tabs.html.erb +5 -0
  92. data/app/views/decidim/action_delegator/elections/admin/dashboard/_results_type_info.html.erb +4 -0
  93. data/app/views/decidim/action_delegator/elections/admin/dashboard/_sum_of_weights.html.erb +43 -0
  94. data/app/views/decidim/action_delegator/elections/admin/dashboard/_totals.html.erb +27 -0
  95. data/app/views/decidim/action_delegator/user_delegations/index.html.erb +13 -14
  96. data/app/views/decidim/action_delegator/verifications/delegations_verifier/authorizations/edit.html.erb +19 -29
  97. data/app/views/decidim/action_delegator/verifications/delegations_verifier/authorizations/new.html.erb +42 -40
  98. data/config/assets.rb +3 -36
  99. data/config/i18n-tasks.yml +27 -5
  100. data/config/locales/ca.yml +103 -146
  101. data/config/locales/cs.yml +161 -145
  102. data/config/locales/en.yml +110 -75
  103. data/config/locales/es.yml +108 -154
  104. data/db/migrate/20200824113801_create_settings.rb +1 -1
  105. data/db/migrate/20250729104037_add_title_to_action_delegator_settings.rb +31 -0
  106. data/lib/decidim/action_delegator/admin_engine.rb +72 -45
  107. data/lib/decidim/action_delegator/common_rake.rb +13 -0
  108. data/lib/decidim/action_delegator/engine.rb +48 -19
  109. data/lib/decidim/action_delegator/test/delegation_examples.rb +144 -0
  110. data/lib/decidim/action_delegator/test/factories.rb +11 -6
  111. data/lib/decidim/action_delegator/verifications/delegations_authorizer.rb +80 -47
  112. data/lib/decidim/action_delegator/version.rb +3 -3
  113. data/lib/decidim/action_delegator.rb +8 -26
  114. data/lib/tasks/migrate_consultations.rake +382 -0
  115. data/lib/tasks/upgrade_tasks.rake +5 -0
  116. data/package.json +10 -27
  117. metadata +72 -122
  118. data/app/commands/concerns/decidim/action_delegator/consultations/multiple_vote_question_override.rb +0 -31
  119. data/app/commands/concerns/decidim/action_delegator/consultations/vote_question_override.rb +0 -44
  120. data/app/commands/decidim/action_delegator/admin/fix_resource_permissions.rb +0 -46
  121. data/app/commands/decidim/action_delegator/vote_delegation.rb +0 -28
  122. data/app/controllers/concerns/decidim/action_delegator/consultations/consultations_controller_override.rb +0 -16
  123. data/app/controllers/concerns/decidim/action_delegator/consultations/question_multiple_votes_controller_override.rb +0 -29
  124. data/app/controllers/concerns/decidim/action_delegator/consultations/question_votes_controller_override.rb +0 -57
  125. data/app/controllers/concerns/decidim/action_delegator/consultations/questions_controller_override.rb +0 -16
  126. data/app/controllers/concerns/decidim/action_delegator/needs_consultation_styles.rb +0 -24
  127. data/app/controllers/decidim/action_delegator/admin/consultations/exports_controller.rb +0 -27
  128. data/app/controllers/decidim/action_delegator/admin/consultations_controller.rb +0 -47
  129. data/app/controllers/decidim/action_delegator/admin/exports/_sum_of_weights_controller.rb +0 -15
  130. data/app/controllers/decidim/action_delegator/questions_summary_controller.rb +0 -14
  131. data/app/forms/concerns/decidim/action_delegator/consultations/vote_form_override.rb +0 -15
  132. data/app/helpers/decidim/action_delegator/delegation_helper.rb +0 -13
  133. data/app/jobs/decidim/action_delegator/export_consultation_results_job.rb +0 -51
  134. data/app/models/concerns/decidim/action_delegator/consultations/question_override.rb +0 -36
  135. data/app/models/concerns/decidim/action_delegator/consultations/vote_override.rb +0 -15
  136. data/app/models/decidim/action_delegator/unversioned_vote.rb +0 -19
  137. data/app/models/decidim/action_delegator/whodunnit_vote.rb +0 -28
  138. data/app/overrides/decidim/consultations/admin/consultations/results/add_ongoing_warning.html.erb.deface +0 -3
  139. data/app/overrides/decidim/consultations/consultations/_question/add_delegation_link.html.erb.deface +0 -3
  140. data/app/overrides/decidim/consultations/consultations/_question/replace_vote_info.html.erb.deface +0 -4
  141. data/app/overrides/decidim/consultations/consultations/_regular_questions/prevent_empty_questions.html.erb.deface +0 -10
  142. data/app/overrides/decidim/consultations/consultations/_regular_questions/remove_highlighted_scopes.html.erb.deface +0 -5
  143. data/app/overrides/decidim/consultations/question_multiple_votes/_form/add_delegation_notice.html.erb.deface +0 -8
  144. data/app/overrides/decidim/consultations/questions/_results/replace_results.html.erb.deface +0 -3
  145. data/app/overrides/decidim/consultations/questions/_vote_button/add_delegations_link.html.erb.deface +0 -3
  146. data/app/overrides/decidim/consultations/questions/_vote_button/add_modal.html.erb.deface +0 -3
  147. data/app/overrides/decidim/consultations/questions/_vote_button/add_modal_javascript.html.erb.deface +0 -3
  148. data/app/overrides/decidim/consultations/questions/_vote_button/replace_delegation_to_multivote_link.html.erb.deface +0 -8
  149. data/app/overrides/decidim/consultations/questions/_vote_modal/add_delegation_callout.html.erb.deface +0 -3
  150. data/app/overrides/decidim/consultations/questions/_vote_modal_confirm/add_delegation_callout.html.erb.deface +0 -3
  151. data/app/overrides/decidim/consultations/questions/_vote_modal_confirm/add_hidden_field.html.erb.deface +0 -3
  152. data/app/overrides/layouts/decidim/_consultation_header/add_consultation_callout.html.erb.deface +0 -9
  153. data/app/overrides/layouts/decidim/_question_header/add_consultation_callout.html.erb.deface +0 -9
  154. data/app/overrides/layouts/decidim/admin/remove_deprecation.rb +0 -10
  155. data/app/packs/entrypoints/decidim_action_delegator.scss +0 -1
  156. data/app/packs/entrypoints/decidim_action_delegator_questions.js +0 -5
  157. data/app/packs/entrypoints/decidim_action_delegator_questions_summary.js +0 -1
  158. data/app/packs/src/decidim/action_delegator/questions.js +0 -33
  159. data/app/packs/src/decidim/action_delegator/summary.js +0 -24
  160. data/app/packs/stylesheets/decidim/action_delegator/questions.scss +0 -26
  161. data/app/permissions/concerns/decidim/action_delegator/consultations/permissions_override.rb +0 -35
  162. data/app/presenters/decidim/action_delegator/question_with_totals.rb +0 -24
  163. data/app/queries/decidim/action_delegator/consultation_delegations.rb +0 -25
  164. data/app/queries/decidim/action_delegator/delegates_votes_by_consultation.rb +0 -24
  165. data/app/queries/decidim/action_delegator/delegates_votes_by_question.rb +0 -26
  166. data/app/queries/decidim/action_delegator/delegation_votes.rb +0 -30
  167. data/app/queries/decidim/action_delegator/grantee_delegations.rb +0 -24
  168. data/app/queries/decidim/action_delegator/organization_delegations.rb +0 -26
  169. data/app/queries/decidim/action_delegator/organization_settings.rb +0 -31
  170. data/app/queries/decidim/action_delegator/responses.rb +0 -24
  171. data/app/queries/decidim/action_delegator/responses_by_membership.rb +0 -58
  172. data/app/queries/decidim/action_delegator/scrutiny.rb +0 -87
  173. data/app/queries/decidim/action_delegator/setting_delegations.rb +0 -19
  174. data/app/queries/decidim/action_delegator/sum_of_membership_weight.rb +0 -44
  175. data/app/queries/decidim/action_delegator/sum_of_weights.rb +0 -25
  176. data/app/queries/decidim/action_delegator/type_and_weight.rb +0 -26
  177. data/app/queries/decidim/action_delegator/voted_with_ponderations.rb +0 -30
  178. data/app/queries/decidim/action_delegator/votes_count_aggregation.rb +0 -34
  179. data/app/serializers/decidim/action_delegator/consultation_results_serializer.rb +0 -19
  180. data/app/views/decidim/action_delegator/admin/consultations/_ongoing_consultation_warning.html.erb +0 -3
  181. data/app/views/decidim/action_delegator/admin/consultations/results.html.erb +0 -65
  182. data/app/views/decidim/action_delegator/admin/consultations/weighted_results.html.erb +0 -66
  183. data/app/views/decidim/action_delegator/consultations/_link_to_question.html.erb +0 -11
  184. data/app/views/decidim/action_delegator/consultations/_link_with_results.html.erb +0 -11
  185. data/app/views/decidim/action_delegator/consultations/questions/_callout.html.erb +0 -5
  186. data/app/views/decidim/action_delegator/consultations/questions/_delegations_modal.html.erb +0 -34
  187. data/app/views/decidim/action_delegator/consultations/questions/_link_to_delegations.html.erb +0 -15
  188. data/app/views/decidim/action_delegator/consultations/questions/_vote_delegated_active.html.erb +0 -32
  189. data/app/views/decidim/action_delegator/consultations/questions/_vote_delegated_finished.html.erb +0 -9
  190. data/app/views/decidim/action_delegator/consultations/questions/_vote_delegated_upcoming.html.erb +0 -8
  191. data/app/views/decidim/action_delegator/consultations/questions/_weight_results.html.erb +0 -8
  192. data/app/views/decidim/consultations/question_votes/_callout.html.erb +0 -47
  193. data/app/views/decidim/consultations/question_votes/update_vote_button.js.erb +0 -82
  194. data/lib/tasks/import_direct_verification.rake +0 -30
@@ -0,0 +1,160 @@
1
+ /* eslint-disable max-params */
2
+ document.addEventListener("DOMContentLoaded", () => {
3
+ const watchingDiv = document.querySelector("[data-weight-results-live-update]");
4
+ const questionsContainer = document.querySelector("[data-questions-container]");
5
+ const questionTemplate = document.getElementById("question-0");
6
+ const optionTemplate = document.querySelector("[data-option-template]");
7
+ if (!watchingDiv) {
8
+ return;
9
+ }
10
+
11
+ const url = watchingDiv.dataset.weightResultsLiveUpdate;
12
+ const optionVotesPercentTexts = () => document.querySelectorAll("[data-option-votes-percent-text]");
13
+ const optionVotesCountTexts = () => document.querySelectorAll("[data-option-votes-count-text]");
14
+ const optionVotesWidths = () => document.querySelectorAll("[data-option-votes-width]");
15
+ const questionUnweightedCountTexts = () => document.querySelectorAll("[data-question-unweighted-count-text]");
16
+ const questionWeightedCountTexts = () => document.querySelectorAll("[data-question-weighted-count-text]");
17
+ const questionDelegatedCountTexts = () => document.querySelectorAll("[data-question-delegated-count-text]");
18
+ const questionParticipantsCountTexts = () => document.querySelectorAll("[data-question-participants-count-text]");
19
+
20
+ const animateText = (element, value) => {
21
+ if (element.textContent === value) {
22
+ return;
23
+ }
24
+ element.textContent = value;
25
+ element.classList.add("live_results-number_changing");
26
+ setTimeout(() => {
27
+ element.classList.remove("live_results-number_changing");
28
+ }, 1000);
29
+ };
30
+
31
+ const digOptionValue = (data, questionId, optionId, ponderationId, key) => {
32
+ data.questions = data.questions || [];
33
+ const question = data.questions.find((item) => item.id === parseInt(questionId, 10));
34
+ if (!question) {
35
+ return null;
36
+ }
37
+ const responseOptions = question.response_options || [];
38
+ if (!Array.isArray(responseOptions)) {
39
+ return null;
40
+ }
41
+ const option = responseOptions.find((item) => {
42
+ if (ponderationId) {
43
+ return item.id === parseInt(optionId, 10) && item.ponderation_id === parseInt(ponderationId, 10);
44
+ }
45
+ return item.id === parseInt(optionId, 10);
46
+ });
47
+ // console.log("Digging option:", questionId, optionId, ponderationId, option);
48
+ if (!option) {
49
+ return null;
50
+ }
51
+ if (key in option) {
52
+ return option[key];
53
+ }
54
+ return null;
55
+ };
56
+
57
+ const createAdditionalQuestions = (data) => {
58
+ if (!questionTemplate || !questionsContainer || !optionTemplate) {
59
+ return;
60
+ }
61
+ const questions = data.questions || [];
62
+ const additionalQuestions = questions.filter((question) => !document.getElementById(`question-${question.id}`) && question.published_results);
63
+ additionalQuestions.forEach((question) => {
64
+ const questionElement = questionTemplate.cloneNode(true);
65
+ questionElement.id = `question-${question.id}`;
66
+ questionElement.classList.remove("hidden");
67
+ questionElement.querySelector("[data-question-body]").textContent = question.body;
68
+ const optionsContainer = questionElement.querySelector("[data-options-container]");
69
+ question.response_options.forEach((option) => {
70
+ const optionElement = optionTemplate.cloneNode(true);
71
+ optionElement.classList.remove("hidden");
72
+ optionElement.querySelector("[data-option-body]").textContent = option.body;
73
+ optionElement.querySelector("[data-option-votes-count-text").dataset.optionVotesCountText = `${question.id},${option.id}`;
74
+ optionElement.querySelector("[data-option-votes-percent-text").dataset.optionVotesPercentText = `${question.id},${option.id}`;
75
+ optionElement.querySelector("[data-option-votes-width").dataset.optionVotesWidth = `${question.id},${option.id}`;
76
+ optionsContainer.appendChild(optionElement);
77
+ });
78
+ questionsContainer.appendChild(questionElement);
79
+
80
+ });
81
+ };
82
+ const fetchResults = async () => {
83
+ try {
84
+ const response = await fetch(url, {
85
+ method: "GET",
86
+ headers: {
87
+ "Accept": "application/json",
88
+ "Content-Type": "application/json",
89
+ "X-Requested-With": "XMLHttpRequest"
90
+ }
91
+ });
92
+ if (!response.ok) {
93
+ throw new Error("Network response was not ok");
94
+ }
95
+ const data = await response.json();
96
+ // console.log("Fetched data:", data);
97
+
98
+ createAdditionalQuestions(data);
99
+ optionVotesCountTexts().forEach((el) => {
100
+ const [questionId, optionId, ponderationId] = el.dataset.optionVotesCountText.split(",");
101
+ console.log("Updating count text for:", questionId, optionId, ponderationId);
102
+ const val = digOptionValue(data, questionId, optionId, ponderationId, "votes_count_text")
103
+ if (val) {
104
+ animateText(el, val);
105
+ }
106
+ });
107
+ optionVotesPercentTexts().forEach((el) => {
108
+ const [questionId, optionId, ponderationId] = el.dataset.optionVotesPercentText.split(",");
109
+ const val = digOptionValue(data, questionId, optionId, ponderationId, "votes_percent_text")
110
+ if (val) {
111
+ animateText(el, val);
112
+ }
113
+ });
114
+ optionVotesWidths().forEach((el) => {
115
+ const [questionId, optionId, ponderationId] = el.dataset.optionVotesWidth.split(",");
116
+ const val = digOptionValue(data, questionId, optionId, ponderationId, "votes_percent")
117
+ if (val) {
118
+ el.style.width = `${val}%`;
119
+ }
120
+ });
121
+ questionUnweightedCountTexts().forEach((el) => {
122
+ const questionId = el.dataset.questionUnweightedCountText;
123
+ const question = data.questions.find((item) => item.id === parseInt(questionId, 10));
124
+ if (question && question.unweighted_votes_text) {
125
+ animateText(el, question.unweighted_votes_text);
126
+ }
127
+ });
128
+ questionWeightedCountTexts().forEach((el) => {
129
+ const questionId = el.dataset.questionWeightedCountText;
130
+ const question = data.questions.find((item) => item.id === parseInt(questionId, 10));
131
+ if (question && question.weighted_votes_text) {
132
+ animateText(el, question.weighted_votes_text);
133
+ }
134
+ });
135
+ questionDelegatedCountTexts().forEach((el) => {
136
+ const questionId = el.dataset.questionDelegatedCountText;
137
+ const question = data.questions.find((item) => item.id === parseInt(questionId, 10));
138
+ if (question && question.delegated_votes_text) {
139
+ animateText(el, question.delegated_votes_text);
140
+ }
141
+ });
142
+ questionParticipantsCountTexts().forEach((el) => {
143
+ const questionId = el.dataset.questionParticipantsCountText;
144
+ const question = data.questions.find((item) => item.id === parseInt(questionId, 10));
145
+ if (question && question.participants_text) {
146
+ animateText(el, question.participants_text);
147
+ }
148
+ });
149
+
150
+ // repeat for ongoing elections only
151
+ if (data.ongoing) {
152
+ setTimeout(fetchResults, 4000);
153
+ }
154
+ } catch (error) {
155
+ console.error("Error fetching results:", error);
156
+ }
157
+ };
158
+
159
+ fetchResults();
160
+ });
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ module Admin
6
+ class Permissions < Decidim::DefaultPermissions
7
+ def permissions
8
+ return permission_action if permission_action.scope != :admin
9
+ return permission_action unless user && user.admin?
10
+ return permission_action unless [:delegation, :ponderation, :participant, :setting].include?(permission_action.subject)
11
+
12
+ if permission_action.action == :destroy
13
+ toggle_allow(resource.present?)
14
+ else
15
+ allow!
16
+ end
17
+
18
+ permission_action
19
+ end
20
+
21
+ private
22
+
23
+ def resource
24
+ @resource ||= context.fetch(:resource, nil)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -3,16 +3,12 @@
3
3
  module Decidim
4
4
  module ActionDelegator
5
5
  class Permissions < Decidim::DefaultPermissions
6
- SUBJECTS_WHITELIST = [:delegation, :ponderation, :participant, :setting, :consultation].freeze
7
-
8
6
  def permissions
9
- allowed_delegation_action?
7
+ return permission_action unless user
10
8
 
11
- return permission_action unless user && user.admin?
12
- return permission_action unless permission_action.scope == :admin
13
- return permission_action unless action_delegator_subject?
9
+ return Decidim::ActionDelegator::Admin::Permissions.new(user, permission_action, context).permissions if permission_action.scope == :admin
14
10
 
15
- allow! if can_perform_action?
11
+ allowed_delegation_action?
16
12
 
17
13
  permission_action
18
14
  end
@@ -20,9 +16,9 @@ module Decidim
20
16
  private
21
17
 
22
18
  def allowed_delegation_action?
23
- return unless delegation
19
+ return false unless delegation
24
20
  # Check that the required question verifications are fulfilled
25
- return unless authorized?(:vote, delegation.grantee)
21
+ return false unless authorized?(:vote, delegation.grantee)
26
22
 
27
23
  case permission_action.action
28
24
  when :vote_delegation
@@ -33,7 +29,7 @@ module Decidim
33
29
  end
34
30
 
35
31
  def authorized?(permission_action, user, resource: nil)
36
- return unless resource || question
32
+ return false unless resource || question
37
33
 
38
34
  ActionAuthorizer.new(user, permission_action, question, resource).authorize.ok?
39
35
  end
@@ -50,26 +46,6 @@ module Decidim
50
46
  @ponderation ||= context.fetch(:ponderation, nil)
51
47
  end
52
48
 
53
- def consultation_results_exports_action?
54
- permission_action.subject == :consultation && permission_action.action == :export_results
55
- end
56
-
57
- def consultation
58
- @consultation ||= context.fetch(:consultation)
59
- end
60
-
61
- def action_delegator_subject?
62
- SUBJECTS_WHITELIST.include?(permission_action.subject)
63
- end
64
-
65
- def can_perform_action?
66
- if permission_action.action == :destroy
67
- resource.present?
68
- else
69
- true
70
- end
71
- end
72
-
73
49
  def resource
74
50
  @resource ||= context.fetch(:resource, nil)
75
51
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ class SettingPresenter < SimpleDelegator
6
+ include Decidim::TranslationsHelper
7
+
8
+ def initialize(setting)
9
+ @setting = setting
10
+
11
+ super
12
+ end
13
+
14
+ def translated_title
15
+ translated_attribute(__getobj__.title).html_safe
16
+ end
17
+
18
+ def translated_description
19
+ ActionView::Base.full_sanitizer.sanitize(translated_attribute(__getobj__.description)).html_safe
20
+ end
21
+
22
+ def translated_resources_list
23
+ Decidim::ActionDelegator::AuthorizedResources.new(setting: self).query.map(&:presenter)
24
+ end
25
+
26
+ def path_for(resource)
27
+ return nil unless resource
28
+
29
+ Decidim::ResourceLocatorPresenter.new(resource).path
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ # Query to find eligible users for corporate governance census elections
6
+ class ActionDelegatorCensusUsers < Decidim::Query
7
+ def initialize(election)
8
+ @election = election
9
+ @setting_id = election.census_settings["setting_id"]
10
+ @authorization_handlers = election.census_settings["authorization_handlers"]&.keys
11
+ end
12
+
13
+ def query
14
+ return Decidim::User.none unless @setting_id
15
+
16
+ setting = Decidim::ActionDelegator::Setting.find_by(id: @setting_id)
17
+ return Decidim::User.none unless setting
18
+
19
+ if @authorization_handlers.present?
20
+ authorized_participants_and_delegates(setting)
21
+ else
22
+ all_confirmed_users
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :election
29
+
30
+ def authorized_participants_and_delegates(setting)
31
+ participant_ids = setting.participants.pluck(:decidim_user_id)
32
+ delegation_ids = setting.delegations.joins(:grantee).pluck("decidim_action_delegator_delegations.grantee_id")
33
+
34
+ eligible_user_ids = (participant_ids + delegation_ids).compact.uniq
35
+ return @election.organization.users.none if eligible_user_ids.empty?
36
+
37
+ base_scope = @election.organization.users.where(id: eligible_user_ids)
38
+
39
+ Decidim::AuthorizedUsers.new(
40
+ organization: @election.organization,
41
+ handlers: @authorization_handlers,
42
+ strict: true
43
+ ).query.where(id: base_scope.select(:id))
44
+ end
45
+
46
+ def all_confirmed_users
47
+ @election.organization.users.not_deleted.not_blocked.confirmed
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ class AuthorizedResources < Decidim::Query
6
+ # Finds all the resources that have configured the action delegator verifier
7
+ # Note that currently only returns elections (Decidim::Election)
8
+ # In the future components could be returned here as well
9
+ def initialize(setting:)
10
+ @setting = setting
11
+ end
12
+
13
+ def query
14
+ condition = [
15
+ "(census_manifest = 'internal_users' AND census_settings -> 'authorization_handlers' -> 'delegations_verifier' -> 'options' -> 'setting' ? :setting_id) OR " \
16
+ "(census_manifest = 'action_delegator_census' AND census_settings ->> 'setting_id' = :setting_id)",
17
+ { setting_id: @setting.id.to_s }
18
+ ]
19
+
20
+ Decidim::Elections::Election.where(component: components).where(condition)
21
+ end
22
+
23
+ def components
24
+ Decidim::PublicComponents.for(@setting.organization, manifest_name: "elections")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ class ElectionSettings < Decidim::Query
6
+ # Finds all the settings for the given election that have the action delegator verifier configured
7
+ def initialize(election)
8
+ @election = election
9
+ end
10
+
11
+ def query
12
+ setting_id = case @election.census_manifest
13
+ when "action_delegator_census"
14
+ @election.census_settings["setting_id"]
15
+ when "internal_users"
16
+ @election.census_settings.dig("authorization_handlers", "delegations_verifier", "options", "setting")
17
+ end
18
+
19
+ setting_id.present? ? Decidim::ActionDelegator::Setting.where(id: setting_id) : Decidim::ActionDelegator::Setting.none
20
+ end
21
+ end
22
+ end
23
+ end
@@ -2,24 +2,24 @@
2
2
 
3
3
  module Decidim
4
4
  module ActionDelegator
5
- # Returns all PaperTrail versions of a consultation's delegated votes for auditing purposes.
5
+ # Returns all PaperTrail versions of a election's delegated votes for auditing purposes.
6
6
  # It is intended to be used to easily fetch this data when a judge ask us so.
7
- class DelegatedVotesVersions
8
- def initialize(consultation)
9
- @consultation = consultation
7
+ class ElectionsDelegatedVotesVersions
8
+ def initialize(election)
9
+ @election = election
10
+ @settings = ElectionSettings.new(election).query
10
11
  end
11
12
 
12
13
  def query
13
14
  PaperTrail::Version
14
15
  .joins("INNER JOIN decidim_action_delegator_delegations ON decidim_action_delegator_delegations.id = versions.decidim_action_delegator_delegation_id")
15
16
  .joins("INNER JOIN decidim_action_delegator_settings ON decidim_action_delegator_settings.id = decidim_action_delegator_delegations.decidim_action_delegator_setting_id")
16
- .where(decidim_action_delegator_settings: { decidim_consultation_id: consultation.id })
17
+ .where(item_type: "Decidim::Elections::Vote")
18
+ .where(decidim_action_delegator_settings: { id: @settings.select(:id) })
17
19
  .order("versions.created_at ASC")
18
20
  end
19
21
 
20
- private
21
-
22
- attr_reader :consultation
22
+ attr_reader :election, :settings
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ class ElectionsQuestionResponsesByType
6
+ # Add your query logic here
7
+
8
+ def initialize(question, settings)
9
+ @question = question
10
+ @settings = settings
11
+ end
12
+
13
+ def query
14
+ return Decidim::Elections::ResponseOption.none if question.blank? || settings.blank?
15
+
16
+ ElectionsVotesWithPonderations
17
+ .new(question.response_options, settings)
18
+ .query
19
+ .select("#{Decidim::Elections::ResponseOption.table_name}.*,
20
+ COALESCE(decidim_action_delegator_ponderation_id, 0) AS ponderation_id,
21
+ COALESCE(CAST(decidim_action_delegator_ponderations.weight AS FLOAT), 1.0) AS ponderation_weight,
22
+ COUNT(decidim_elections_votes.id) AS votes_total")
23
+ .group("#{Decidim::Elections::ResponseOption.table_name}.id,
24
+ COALESCE(decidim_action_delegator_ponderation_id, 0),
25
+ COALESCE(CAST(decidim_action_delegator_ponderations.weight AS FLOAT), 1.0)")
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :question, :settings
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ class ElectionsQuestionWeightedResponses
6
+ def initialize(question, settings)
7
+ @question = question
8
+ @settings = settings
9
+ end
10
+
11
+ def query
12
+ return Decidim::Elections::ResponseOption.none if question.blank? || settings.blank?
13
+
14
+ subquery = ElectionsQuestionResponsesByType.new(question, settings).query
15
+ Decidim::Elections::ResponseOption.unscoped
16
+ .joins("INNER JOIN (#{subquery.to_sql}) AS subquery ON subquery.id = #{Decidim::Elections::ResponseOption.table_name}.id")
17
+ .select(
18
+ "#{Decidim::Elections::ResponseOption.table_name}.*, SUM(subquery.votes_total * subquery.ponderation_weight) AS weighted_votes_total"
19
+ )
20
+ .group("#{Decidim::Elections::ResponseOption.table_name}.id")
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :question, :settings
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ class ElectionsVotesWithPonderations < Decidim::Query
6
+ def initialize(relation, settings)
7
+ @relation = relation
8
+ @settings = settings
9
+
10
+ @participants = Arel::Table.new("decidim_action_delegator_participants")
11
+ @ponderations = Arel::Table.new("decidim_action_delegator_ponderations")
12
+ @votes = Arel::Table.new("decidim_elections_votes")
13
+ @user_global_id_prefix = Decidim::User.new(id: 0).to_global_id.to_s.sub(%r{/0\z}, "/")
14
+ end
15
+
16
+ def query
17
+ return relation.none if settings.blank?
18
+
19
+ relation
20
+ .left_joins(:votes)
21
+ .joins(participants_join)
22
+ .joins(ponderations_join)
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :relation, :settings, :participants, :ponderations, :votes, :user_global_id_prefix
28
+
29
+ # LEFT OUTER JOIN "decidim_action_delegator_participants"
30
+ # ON "decidim_action_delegator_participants"."decidim_action_delegator_setting_id" IN (1, 2, 3)
31
+ # AND CONCAT(#{user_global_id_prefix}, "decidim_action_delegator_participants"."decidim_user_id") = "decidim_election_votes"."voter_uid"
32
+ # LEFT OUTER JOIN "decidim_action_delegator_ponderations"
33
+ # ON "decidim_action_delegator_ponderations"."id" = "decidim_action_delegator_participants"."decidim_action_delegator_ponderation_id"
34
+ def participants_join
35
+ Arel::Nodes::OuterJoin.new(
36
+ participants,
37
+ Arel::Nodes::On.new(
38
+ participants[:decidim_action_delegator_setting_id].in(settings.pluck(:id))
39
+ .and(
40
+ Arel::Nodes::NamedFunction.new(
41
+ "CONCAT",
42
+ [
43
+ Arel::Nodes.build_quoted(user_global_id_prefix),
44
+ participants[:decidim_user_id]
45
+ ]
46
+ ).eq(votes[:voter_uid])
47
+ )
48
+ )
49
+ )
50
+ end
51
+
52
+ def ponderations_join
53
+ Arel::Nodes::OuterJoin.new(
54
+ ponderations,
55
+ Arel::Nodes::On.new(
56
+ ponderations[:id].eq(participants[:decidim_action_delegator_ponderation_id])
57
+ )
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -83,7 +83,7 @@ module Decidim
83
83
  end
84
84
 
85
85
  def check_exists?(field, form)
86
- @participant = Decidim::ActionDelegator::Participant.find_by(field => form.send(field), setting: @current_setting) if form.send(field).present?
86
+ @participant = Decidim::ActionDelegator::Participant.find_by(field => form.send(field), :setting => @current_setting) if form.send(field).present?
87
87
  @participant.present?
88
88
  end
89
89
 
@@ -30,11 +30,11 @@ module Decidim
30
30
  end
31
31
 
32
32
  def sender
33
- ENV["SMS_SENDER"]
33
+ ENV.fetch("SMS_SENDER", nil)
34
34
  end
35
35
 
36
36
  def sms_gateway_provider
37
- ENV["SMS_GATEWAY_PROVIDER"]
37
+ ENV.fetch("SMS_GATEWAY_PROVIDER", nil)
38
38
  end
39
39
 
40
40
  def sms_gateway_provider_valid?
@@ -0,0 +1,44 @@
1
+ <div class="row column form__wrapper pb-4" id="corporate-governance-census-settings">
2
+ <%= form.select(:setting_id,
3
+ options_for_select(form.object.settings_for_select, form.object.setting_id),
4
+ { label: t("setting_group", scope: "decidim.action_delegator.admin.census"), prompt: false },
5
+ { id: "action_delegator_census_setting_id" }) %>
6
+ </div>
7
+
8
+ <div class="row column font-semibold pb-4"><%= t("decidim.elections.censuses.internal_users.internal_type_title") %></div>
9
+ <div class="row column">
10
+ <div class="callout">
11
+ <%= t("decidim.elections.censuses.action_delegator_census.authorizer_handler_admin_callout_html") %>
12
+ </div>
13
+
14
+ </div>
15
+ <div class="mt-4 form__wrapper gap-1">
16
+ <%= form.collection_check_boxes :authorization_handlers_names,
17
+ @form.available_authorizations,
18
+ :name,
19
+ :description do |builder| %>
20
+ <div class="row">
21
+ <%= builder.label { builder.check_box + builder.text } %>
22
+ <div id="authorization-handler-<%= builder.value %>" class="authorization-handler my-4 ml-6" style="display: <%= form.object.authorization_handlers_names.include?(builder.value) ? "block" : "none" %>;">
23
+ <%= render "decidim/action_delegator/admin/censuses/action_delegator_census_options_form", form: form, handler_name: builder.value %>
24
+ </div>
25
+ </div>
26
+ <% end %>
27
+ </div>
28
+
29
+ <script>
30
+ document.addEventListener("turbo:load", function() {
31
+ const checkboxes = document.querySelectorAll("input[type='checkbox'][id^='action_delegator_census_authorization_handlers_names_']");
32
+ checkboxes.forEach(checkbox => {
33
+ checkbox.addEventListener("change", function() {
34
+ const handlerId = `authorization-handler-${this.value}`;
35
+ const handlerElement = document.getElementById(handlerId);
36
+ if (this.checked) {
37
+ handlerElement.style.display = "block";
38
+ } else {
39
+ handlerElement.style.display = "none";
40
+ }
41
+ });
42
+ });
43
+ });
44
+ </script>
@@ -0,0 +1,25 @@
1
+ <%
2
+ handler_name = local_assigns[:handler_name]
3
+ schema = form.object.options_schema(handler_name)
4
+ attributes = form.object.options_attributes(handler_name)
5
+ # this variable is send as a context in the settings_attribute_input helper
6
+ @component = current_component
7
+ %>
8
+
9
+ <%= form.fields_for(:authorization_handlers_options) do |options_form| %>
10
+ <%= options_form.fields_for(handler_name, schema) do |attribute_options_form| %>
11
+ <% attributes.each do |name, attribute| %>
12
+ <%# Skip the 'setting' field for delegations_verifier when using action_delegator_census %>
13
+ <%# because the setting is already selected at the census level %>
14
+ <% next if handler_name == "delegations_verifier" && name.to_s == "setting" %>
15
+
16
+ <%= settings_attribute_input(
17
+ attribute_options_form,
18
+ attribute,
19
+ name,
20
+ "decidim.authorization_handlers.#{handler_name}.fields",
21
+ tabs_prefix: "#{name}_tabs_prefix"
22
+ ) %>
23
+ <% end %>
24
+ <% end %>
25
+ <% end %>