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
@@ -5,15 +5,23 @@ require "securerandom"
5
5
  module Decidim
6
6
  module ActionDelegator
7
7
  module Verifications
8
- # A form object to be used when public users want to get verified using their phone.
8
+ # This verifier checks if there is some setting in which the participant is required
9
+ # to verify it's phone (the first active setting will be used for that).
10
+ # If no setting requires phone verification, it will check if there is some setting
11
+ # in which the participant is required to verify it's email.
12
+ # If no setting requires email verification, the user won't be able to proceed.
13
+ # If there are multiple active settings, the user will be verified for the first one
14
+ #
15
+ # Note that the ActionAuthorizer associated with this handler will check the current status
16
+ # of the settings and delegations regardless of this verification metadata
9
17
  class DelegationsVerifierForm < AuthorizationHandler
10
18
  attribute :email, String
11
19
  attribute :phone, String
12
20
 
13
- validates :verification_code, :sms_gateway, presence: true
21
+ validates :verification_code, :sms_gateway, presence: true, if: ->(form) { form.setting&.phone_required? }
14
22
  validates :phone, presence: true, if: ->(form) { form.setting&.phone_required? }
15
23
  validates :email, presence: true, if: ->(form) { form.setting&.email_required? }
16
- validate :setting_exists
24
+
17
25
  validate :user_in_census
18
26
 
19
27
  alias user current_user
@@ -24,7 +32,7 @@ module Decidim
24
32
 
25
33
  def unique_id
26
34
  Digest::MD5.hexdigest(
27
- "#{setting&.phone_required? ? phone : email}-#{setting&.organization&.id}-#{Digest::MD5.hexdigest(Rails.application.secrets.secret_key_base)}"
35
+ "#{setting&.phone_required? ? phone : email}-#{setting&.organization&.id}-#{Digest::MD5.hexdigest(Rails.application.secret_key_base)}"
28
36
  )
29
37
  end
30
38
 
@@ -41,10 +49,17 @@ module Decidim
41
49
 
42
50
  def metadata
43
51
  {
44
- phone: phone
52
+ phone:,
53
+ setting_ids:
45
54
  }
46
55
  end
47
56
 
57
+ def setting_ids
58
+ return [] unless current_user
59
+
60
+ valid_participants&.map(&:decidim_action_delegator_setting_id)&.uniq || []
61
+ end
62
+
48
63
  # The verification metadata to validate in the next step.
49
64
  def verification_metadata
50
65
  {
@@ -55,34 +70,43 @@ module Decidim
55
70
 
56
71
  # currently, we rely on the last setting.
57
72
  # This could be improved by allowing the user to select the setting (or related phone).
58
- def setting
59
- @setting ||= context[:setting]
73
+ def active_settings
74
+ @active_settings ||= context[:active_settings]
60
75
  end
61
76
 
62
- def participants
63
- @participants ||= Decidim::ActionDelegator::Participant.where(setting: setting)
77
+ # find the participant in any of the active settings
78
+ # If phone is required, just find the first participant and validate the phone
79
+ # if not, find by email in any of the active settings
80
+ def participant
81
+ valid_participants&.first
64
82
  end
65
83
 
66
- def participant
67
- return unless setting
84
+ def valid_participants
85
+ return [] unless setting
68
86
 
69
- @participant ||= begin
87
+ @valid_participants ||= begin
70
88
  params = {}
71
89
  params[:email] = email if setting.email_required?
72
90
  if setting.phone_required?
73
91
  if phone.blank?
74
- @participant = setting.participants.none
92
+ @valid_participants = setting.participants.none
75
93
  else
76
- params[:phone] = phone
77
94
  params[:phone] = phone_prefixes.map { |prefix| "#{prefix}#{phone}" }
78
95
  params[:phone] += phone_prefixes.map { |prefix| phone.delete_prefix(prefix).to_s }
79
96
  end
80
97
  end
81
98
 
82
- setting.participants.find_by(params)
99
+ setting.participants.where(params)
83
100
  end
84
101
  end
85
102
 
103
+ # find the first setting where phone is required or, if not, the first setting where email is required
104
+ # This works because the email is unique per user so it does not matter which setting we use to find the participant
105
+ # If the setting requires phone, only one active setting with phone verification is allowed to exist at a time
106
+ def setting
107
+ @setting ||= active_settings&.phone_required&.first || active_settings&.email_required&.first
108
+ end
109
+
86
110
  private
87
111
 
88
112
  def phone_prefixes
@@ -95,16 +119,8 @@ module Decidim
95
119
  return if errors.any?
96
120
  return if participant
97
121
 
98
- errors.add(:phone, :phone_not_found) if setting.phone_required?
99
- errors.add(:email, :email_not_found) if setting.email_required?
100
- end
101
-
102
- def setting_exists
103
- return if errors.any?
104
- return if setting
105
-
106
- errors.add(:phone, :invalid)
107
- errors.add(:email, :invalid)
122
+ errors.add(:phone, :phone_not_found) if setting&.phone_required?
123
+ errors.add(:email, :email_not_found) if setting&.email_required?
108
124
  end
109
125
 
110
126
  def verification_code
@@ -3,7 +3,7 @@
3
3
  module Decidim
4
4
  module ActionDelegator
5
5
  module Admin
6
- module DelegationHelper
6
+ module SettingsHelper
7
7
  def granters_for_select
8
8
  current_organization.users
9
9
  end
@@ -12,31 +12,18 @@ module Decidim
12
12
  current_organization.users
13
13
  end
14
14
 
15
- def consultations_for_select
16
- organization_consultations.map { |consultation| [translated_attribute(consultation.title), consultation.id] }
17
- end
18
-
19
15
  def ponderations_for_select(setting)
20
16
  setting.ponderations.map { |ponderation| [ponderation.title, ponderation.id] }
21
17
  end
22
18
 
23
- def organization_consultations
24
- Decidim::Consultations::OrganizationConsultations.new(current_organization).query
25
- end
26
-
27
- def missing_verifications_for(resources, action)
28
- resources.where.not(id: Decidim::ResourcePermission.select(:resource_id)
29
- .where(resource: resources)
30
- .where(Arel.sql("permissions->'#{action}'->'authorization_handlers'->>'delegations_verifier' IS NOT NULL")))
31
- end
32
-
33
19
  def missing_decidim_users(participants)
34
20
  participants.where(decidim_user: nil).or(participants.where.not(decidim_user: current_organization.users)).where.not(id: missing_registered_users(participants))
35
21
  end
36
22
 
23
+ # TODO: need to check
37
24
  def missing_registered_users(participants)
38
25
  participants.where.not(email: current_organization.users.select(:email))
39
- .where.not("MD5(CONCAT(phone,'-',?,'-',?)) IN (?)",
26
+ .where.not("MD5(CONCAT(phone, '-', CAST(? AS text), '-', CAST(? AS text))) IN (?)",
40
27
  current_organization.id,
41
28
  Digest::MD5.hexdigest(Rails.application.secret_key_base),
42
29
  Authorization.select(:unique_id).where.not(unique_id: nil))
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module ActionDelegator
5
+ module SettingsHelper
6
+ include ActionView::Helpers::NumberHelper
7
+
8
+ def current_resource_settings
9
+ @current_resource_settings ||= if defined?(election) && election.present?
10
+ settings_for(election)
11
+ elsif @election.present?
12
+ settings_for(@election)
13
+ else
14
+ Decidim::ActionDelegator::Setting.none
15
+ end
16
+ end
17
+
18
+ def settings_for(resource)
19
+ case resource
20
+ when Decidim::Elections::Election
21
+ Decidim::ActionDelegator::ElectionSettings.new(resource).query
22
+ else
23
+ Decidim::ActionDelegator::Setting.none
24
+ end
25
+ end
26
+
27
+ def delegations_for(resource, user)
28
+ case resource
29
+ when Decidim::Elections::Election
30
+ Decidim::ActionDelegator::Delegation.where(
31
+ setting: settings_for(resource),
32
+ grantee: user
33
+ )
34
+ else
35
+ Decidim::ActionDelegator::Delegation.none
36
+ end
37
+ end
38
+
39
+ def participant_voted?(resource, user)
40
+ case resource
41
+ when Decidim::Elections::Election
42
+ resource.votes.exists?(voter_uid: user.to_global_id.to_s)
43
+ else
44
+ false
45
+ end
46
+ end
47
+
48
+ def elections_question_responses_by_type(question)
49
+ # Use size instead of count since votes are preloaded
50
+ total_votes = question.votes.size
51
+
52
+ ElectionsQuestionResponsesByType.new(question, current_resource_settings).query.map do |option|
53
+ ponderation ||= Decidim::ActionDelegator::Ponderation.find_by(id: option.ponderation_id)
54
+ votes_count = option.votes_total || 0
55
+ votes_count_text = I18n.t("votes_count", scope: "decidim.elections.admin.dashboard.questions_table", count: votes_count)
56
+ votes_percent = total_votes.positive? ? (option.votes_total.to_f / total_votes) * 100 : 0
57
+ {
58
+ id: option.id,
59
+ body: translated_attribute(option.body),
60
+ votes_count: votes_count,
61
+ votes_count_text: votes_count_text,
62
+ votes_percent: votes_percent,
63
+ votes_percent_text: number_to_percentage(votes_percent, precision: 1),
64
+ ponderation_id: ponderation&.id,
65
+ ponderation_title: ponderation&.title || "-"
66
+ }
67
+ end
68
+ end
69
+
70
+ def elections_question_weighted_responses(question)
71
+ # Use size instead of count since votes are preloaded
72
+ total_votes = question.votes.size
73
+
74
+ question_totals = {}
75
+ responses = ElectionsQuestionWeightedResponses.new(question, current_resource_settings).query.map do |option|
76
+ question_totals[question.id] ||= 0.0
77
+ question_totals[question.id] += option.weighted_votes_total.to_f
78
+ option
79
+ end
80
+
81
+ responses.map do |option|
82
+ votes_count = option.weighted_votes_total || 0
83
+ votes_percent = total_votes.positive? ? (option.weighted_votes_total.to_f / question_totals[question.id]) * 100 : 0
84
+ {
85
+ id: option.id,
86
+ question_id: question.id,
87
+ body: translated_attribute(option.body),
88
+ votes_count: votes_count,
89
+ votes_count_text: I18n.t("votes_count", scope: "decidim.elections.admin.dashboard.questions_table", count: votes_count),
90
+ votes_percent: votes_percent,
91
+ votes_percent_text: number_to_percentage(votes_percent, precision: 1)
92
+ }
93
+ end
94
+ end
95
+
96
+ def elections_question_stats(question)
97
+ question_totals = {}
98
+ ElectionsQuestionWeightedResponses.new(question, current_resource_settings).query.each do |option|
99
+ question_totals[question.id] ||= 0.0
100
+ question_totals[question.id] += option.weighted_votes_total.to_f
101
+ end
102
+
103
+ # Use preloaded votes association
104
+ votes = question.votes
105
+ unweighted_votes = votes.size
106
+ weighted_votes = question_totals[question.id].to_f.round
107
+ # Note that this works because votes cannot be edited, only created or destroyed. So only one version will exist per vote (the creation event)
108
+ delegated_votes = votes.select { |vote| vote.versions.any? { |v| v.decidim_action_delegator_delegation_id.present? } }.size
109
+ participants = votes.map(&:voter_uid).uniq.size
110
+
111
+ {
112
+ participants: participants,
113
+ participants_text: I18n.t("participants_count", scope: "decidim.action_delegator.elections.admin.dashboard.questions_table", count: participants),
114
+ unweighted_votes: unweighted_votes,
115
+ unweighted_votes_text: I18n.t("votes_count", scope: "decidim.elections.admin.dashboard.questions_table", count: unweighted_votes),
116
+ weighted_votes: weighted_votes,
117
+ weighted_votes_text: I18n.t("votes_count", scope: "decidim.elections.admin.dashboard.questions_table", count: weighted_votes),
118
+ delegated_votes: delegated_votes,
119
+ delegated_votes_text: I18n.t("votes_count", scope: "decidim.elections.admin.dashboard.questions_table", count: delegated_votes)
120
+ }
121
+ end
122
+ end
123
+ end
124
+ end
@@ -27,7 +27,7 @@ module Decidim
27
27
  def users_list_to_invite
28
28
  @users_list_to_invite ||= @current_setting.participants.where(decidim_user: nil)
29
29
  .where.not(email: @organization.users.select(:email))
30
- .where.not("MD5(CONCAT(phone,'-',?,'-',?)) IN (?)",
30
+ .where.not("MD5(CONCAT(phone::text, '-', CAST(? AS text), '-', CAST(? AS text))) IN (?)",
31
31
  @organization.id,
32
32
  Digest::MD5.hexdigest(Rails.application.secret_key_base),
33
33
  Authorization.select(:unique_id)
@@ -28,8 +28,8 @@ module Decidim
28
28
  def send_sms!
29
29
  @response = client.call(:send_sms,
30
30
  message: {
31
- user: ENV["SMS_USER"],
32
- pass: ENV["SMS_PASS"],
31
+ user: ENV.fetch("SMS_USER", nil),
32
+ pass: ENV.fetch("SMS_PASS", nil),
33
33
  src: sender,
34
34
  dst: mobile_phone_number,
35
35
  msg: message
@@ -32,11 +32,11 @@ module Decidim
32
32
  end
33
33
 
34
34
  def twilio_account_sid
35
- ENV["TWILIO_ACCOUNT_SID"]
35
+ ENV.fetch("TWILIO_ACCOUNT_SID", nil)
36
36
  end
37
37
 
38
38
  def twilio_auth_token
39
- ENV["TWILIO_AUTH_TOKEN"]
39
+ ENV.fetch("TWILIO_AUTH_TOKEN", nil)
40
40
  end
41
41
  end
42
42
  end
@@ -18,23 +18,25 @@ module Decidim
18
18
 
19
19
  validate :grantee_is_not_granter
20
20
  validate :granter_and_grantee_belongs_to_same_organization
21
- validate :granter_is_same_organization_as_consultation
21
+ validate :granter_is_same_organization_as_context
22
22
 
23
- delegate :consultation, to: :setting
23
+ delegate :resource, to: :setting
24
24
 
25
25
  before_destroy { |record| throw(:abort) if record.grantee_voted? }
26
26
 
27
- def self.granted_to?(user, consultation)
28
- GranteeDelegations.for(consultation, user).exists?
29
- end
30
-
31
27
  def grantee_voted?
32
- return false unless consultation.questions.any?
28
+ return false unless grantee && setting
29
+
30
+ @grantee_voted ||= PaperTrail::Version.exists?(
31
+ whodunnit: grantee.id,
32
+ object_changes: { decidim_action_delegator_delegation_id: id }
33
+ )
34
+ end
33
35
 
34
- @grantee_voted ||= begin
35
- granter_votes = Decidim::Consultations::Vote.where(author: granter, question: consultation.questions)
36
- granter_votes&.detect { |vote| vote.versions.exists?(whodunnit: grantee&.id) } ? true : false
37
- end
36
+ # a safe way to get the user that represents the granter in this setting
37
+ # it might not exist if the granter is not in the census
38
+ def user
39
+ @user ||= setting.participants.find_by(decidim_user: granter)&.decidim_user
38
40
  end
39
41
 
40
42
  private
@@ -51,9 +53,8 @@ module Decidim
51
53
  errors.add(:grantee, :invalid)
52
54
  end
53
55
 
54
- def granter_is_same_organization_as_consultation
55
- return unless setting && setting.consultation
56
- return unless consultation.organization != granter.organization
56
+ def granter_is_same_organization_as_context
57
+ return unless setting && granter.organization != setting.organization
57
58
 
58
59
  errors.add(:granter, :invalid)
59
60
  end
@@ -18,7 +18,6 @@ module Decidim
18
18
  class_name: "Decidim::User",
19
19
  optional: true
20
20
 
21
- delegate :consultation, to: :setting
22
21
  delegate :organization, to: :setting
23
22
 
24
23
  validates :decidim_user, uniqueness: { scope: :setting }, if: -> { decidim_user.present? }
@@ -50,7 +49,7 @@ module Decidim
50
49
  end
51
50
 
52
51
  def self.verifier_ids(seeds)
53
- seeds.map { |seed| Digest::MD5.hexdigest("#{seed}-#{Digest::MD5.hexdigest(Rails.application.secrets.secret_key_base)}") }
52
+ seeds.map { |seed| Digest::MD5.hexdigest("#{seed}-#{Digest::MD5.hexdigest(Rails.application.secret_key_base)}") }
54
53
  end
55
54
 
56
55
  def self.phone_combinations(phones)
@@ -82,15 +81,12 @@ module Decidim
82
81
  ponderation&.title
83
82
  end
84
83
 
85
- # checks if the user has voted in the setting's consultation
84
+ # checks if the user has voted
86
85
  def voted?
87
86
  return false if user.blank?
88
87
 
89
- @voted ||= Decidim::Consultations::Vote
90
- .joins(question: :consultation)
91
- .where(decidim_consultations_questions: {
92
- decidim_consultation_id: setting.consultation.id
93
- }, author: user).any?
88
+ # TODO: Replace vote check once new context is defined
89
+ false
94
90
  end
95
91
 
96
92
  private
@@ -100,8 +96,9 @@ module Decidim
100
96
  end
101
97
 
102
98
  def user_belongs_to_organization
103
- return unless decidim_user && setting && setting.consultation
104
- return if decidim_user.organization == organization
99
+ return if decidim_user.blank? || setting.blank?
100
+
101
+ return if decidim_user.organization == setting.organization
105
102
 
106
103
  errors.add(:decidim_user, :invalid)
107
104
  end
@@ -14,8 +14,6 @@ module Decidim
14
14
  class_name: "Decidim::ActionDelegator::Participant",
15
15
  dependent: :restrict_with_error
16
16
 
17
- delegate :consultation, to: :setting
18
-
19
17
  def title
20
18
  @title ||= "#{name} (x#{weight})"
21
19
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Decidim
4
4
  module ActionDelegator
5
- # Contains the delegation settings of a consultation. Rather than a single attribute here
5
+ # Contains the delegation settings of an election. Rather than a single attribute here
6
6
  # a setting is the record itself: a bunch of configuration values.
7
7
  class Setting < ApplicationRecord
8
8
  self.table_name = "decidim_action_delegator_settings"
9
9
 
10
- belongs_to :consultation,
11
- foreign_key: "decidim_consultation_id",
12
- class_name: "Decidim::Consultation"
10
+ belongs_to :organization,
11
+ foreign_key: "decidim_organization_id",
12
+ class_name: "Decidim::Organization"
13
13
  has_many :delegations,
14
14
  inverse_of: :setting,
15
15
  foreign_key: "decidim_action_delegator_setting_id",
@@ -28,43 +28,25 @@ module Decidim
28
28
 
29
29
  validates :max_grants, presence: true
30
30
  validates :max_grants, numericality: { greater_than: 0 }
31
- validates :consultation, uniqueness: true
32
31
 
33
- enum authorization_method: { phone: 0, email: 1, both: 2 }, _prefix: :verify_with
32
+ enum :authorization_method, [:phone, :email, :both], prefix: :verify_with
34
33
 
35
- delegate :title, to: :consultation
36
- delegate :organization, to: :consultation
34
+ scope :active, -> { where(active: true) }
35
+ scope :phone_required, -> { where(authorization_method: [:phone, :both]) }
36
+ scope :email_required, -> { where(authorization_method: [:email, :both]) }
37
37
 
38
38
  default_scope { order(created_at: :desc) }
39
39
 
40
- def state
41
- @state ||= if consultation.end_voting_date < Time.zone.now
42
- :closed
43
- elsif consultation.start_voting_date <= Time.zone.now
44
- :ongoing
45
- else
46
- :pending
47
- end
48
- end
49
-
50
- def ongoing?
51
- state == :ongoing
52
- end
40
+ def editable? = active?
53
41
 
54
- def editable?
55
- state != :closed
56
- end
42
+ def destroyable? = participants.empty? && ponderations.empty? && delegations.empty?
57
43
 
58
- def destroyable?
59
- participants.empty? && ponderations.empty? && delegations.empty?
60
- end
44
+ def phone_required? = verify_with_phone? || verify_with_both?
61
45
 
62
- def phone_required?
63
- verify_with_phone? || verify_with_both?
64
- end
46
+ def email_required? = verify_with_email? || verify_with_both?
65
47
 
66
- def email_required?
67
- verify_with_email? || verify_with_both?
48
+ def presenter
49
+ Decidim::ActionDelegator::SettingPresenter.new(self)
68
50
  end
69
51
  end
70
52
  end
@@ -0,0 +1,10 @@
1
+ <!-- replace "erb[loud]:contains('decidim/elections/admin/dashboard/questions_with_results')" -->
2
+
3
+ <%= render "decidim/action_delegator/elections/admin/dashboard/results_type_info" if current_resource_settings.present? %>
4
+
5
+ <% if current_resource_settings.present? && lookup_context.exists?("decidim/action_delegator/elections/admin/dashboard/#{params[:results]}", [], true) %>
6
+ <%= render "decidim/action_delegator/elections/admin/dashboard/#{params[:results]}" %>
7
+ <% append_javascript_pack_tag("decidim_action_delegator_elections") %>
8
+ <% else %>
9
+ <%= render "decidim/elections/admin/dashboard/questions_with_results" %>
10
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_top ".row.column" -->
2
+
3
+ <%= render "decidim/action_delegator/elections/admin/dashboard/results_tabs" if settings_for(election).present? %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_bottom ".election__aside-vote" -->
2
+
3
+ <%= render "decidim/action_delegator/elections/delegation_buttons" if election.ongoing? %>
@@ -0,0 +1,8 @@
1
+ <!-- surround "div[data-erb-data-results-live-update]" -->
2
+
3
+ <% if current_resource_settings.present? %>
4
+ <%= render "decidim/action_delegator/elections/vote_results", questions: %>
5
+ <% append_javascript_pack_tag("decidim_action_delegator_elections") %>
6
+ <% else %>
7
+ <%= render_original %>
8
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_after "erb[loud]:contains('question_title')" -->
2
+
3
+ <input type="hidden" name="delegation" value="<%= @delegation&.id %>">
@@ -0,0 +1,3 @@
1
+ <!-- replace "erb[loud]:contains('link_to url_for(action: :show, id: question.previous_question)')" -->
2
+
3
+ <%= link_to url_for(action: :show, id: question.previous_question, delegation: @delegation&.id), class: "button button__lg button__transparent-secondary" do %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_after ".vote-navigation" -->
2
+
3
+ <%= render "decidim/action_delegator/elections/per_question_waiting_buttons" %>
@@ -0,0 +1,3 @@
1
+ <!-- insert_bottom ".vote-submitted" -->
2
+
3
+ <%= render "decidim/action_delegator/elections/per_question_waiting_buttons" if election.per_question? %>
@@ -0,0 +1 @@
1
+ import "src/decidim/action_delegator/elections_live_results.js";