decidim-action_delegator 0.8.1 → 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 (186) 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 +3 -11
  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 -21
  15. data/app/controllers/decidim/action_delegator/admin/manage_participants_controller.rb +11 -20
  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/delegation_form.rb +6 -2
  28. data/app/forms/decidim/action_delegator/admin/ponderation_form.rb +2 -1
  29. data/app/forms/decidim/action_delegator/admin/setting_form.rb +16 -10
  30. data/app/forms/decidim/action_delegator/censuses/internal_users_form.rb +32 -0
  31. data/app/forms/decidim/action_delegator/verifications/delegations_verifier_form.rb +41 -25
  32. data/app/helpers/decidim/action_delegator/admin/{delegation_helper.rb → settings_helper.rb} +3 -16
  33. data/app/helpers/decidim/action_delegator/settings_helper.rb +124 -0
  34. data/app/jobs/decidim/action_delegator/admin/invite_participants_job.rb +1 -1
  35. data/app/jobs/decidim/action_delegator/send_sms_job.rb +2 -2
  36. data/app/jobs/decidim/action_delegator/twilio_send_sms_job.rb +2 -2
  37. data/app/models/decidim/action_delegator/delegation.rb +35 -9
  38. data/app/models/decidim/action_delegator/participant.rb +15 -9
  39. data/app/models/decidim/action_delegator/ponderation.rb +0 -2
  40. data/app/models/decidim/action_delegator/setting.rb +14 -32
  41. data/app/overrides/decidim/elections/admin/dashboard/_results/_add_results_contents.html.erb.deface +10 -0
  42. data/app/overrides/decidim/elections/admin/dashboard/_results/_add_results_tabs.html.erb.deface +3 -0
  43. data/app/overrides/decidim/elections/elections/_election_aside/add_delegation_buttons.html.erb.deface +3 -0
  44. data/app/overrides/decidim/elections/elections/_vote_results/replace_results_div.html.erb.deface +8 -0
  45. data/app/overrides/decidim/elections/per_question_votes/show/add_delegation_hidden_input.html.erb.deface +3 -0
  46. data/app/overrides/decidim/elections/per_question_votes/show/add_delegation_id_to_links.html.erb.deface +3 -0
  47. data/app/overrides/decidim/elections/per_question_votes/waiting/add_delegation_buttons.html.erb.deface +3 -0
  48. data/app/overrides/decidim/elections/votes/receipt/add_delegation_buttons.html.erb.deface +3 -0
  49. data/app/packs/entrypoints/decidim_action_delegator_elections.js +1 -0
  50. data/app/packs/src/decidim/action_delegator/elections_live_results.js +160 -0
  51. data/app/permissions/decidim/action_delegator/admin/permissions.rb +29 -0
  52. data/app/permissions/decidim/action_delegator/permissions.rb +6 -30
  53. data/app/presenters/decidim/action_delegator/setting_presenter.rb +33 -0
  54. data/app/queries/decidim/action_delegator/action_delegator_census_users.rb +51 -0
  55. data/app/queries/decidim/action_delegator/authorized_resources.rb +28 -0
  56. data/app/queries/decidim/action_delegator/election_settings.rb +23 -0
  57. data/app/queries/decidim/action_delegator/{delegated_votes_versions.rb → elections_delegated_votes_versions.rb} +8 -8
  58. data/app/queries/decidim/action_delegator/elections_question_responses_by_type.rb +33 -0
  59. data/app/queries/decidim/action_delegator/elections_question_weighted_responses.rb +28 -0
  60. data/app/queries/decidim/action_delegator/elections_votes_with_ponderations.rb +62 -0
  61. data/app/services/decidim/action_delegator/delegations_csv_importer.rb +3 -3
  62. data/app/services/decidim/action_delegator/participants_csv_importer.rb +1 -1
  63. data/app/services/decidim/action_delegator/sms_gateway.rb +2 -2
  64. data/app/views/decidim/action_delegator/admin/censuses/_action_delegator_census_form.html.erb +44 -0
  65. data/app/views/decidim/action_delegator/admin/censuses/_action_delegator_census_options_form.html.erb +25 -0
  66. data/app/views/decidim/action_delegator/admin/delegations/index.html.erb +12 -13
  67. data/app/views/decidim/action_delegator/admin/delegations/new.html.erb +29 -27
  68. data/app/views/decidim/action_delegator/admin/manage_delegations/new.html.erb +36 -24
  69. data/app/views/decidim/action_delegator/admin/manage_participants/new.html.erb +47 -33
  70. data/app/views/decidim/action_delegator/admin/participants/_form.html.erb +14 -8
  71. data/app/views/decidim/action_delegator/admin/participants/edit.html.erb +11 -13
  72. data/app/views/decidim/action_delegator/admin/participants/index.html.erb +20 -25
  73. data/app/views/decidim/action_delegator/admin/participants/new.html.erb +11 -13
  74. data/app/views/decidim/action_delegator/admin/ponderations/_form.html.erb +13 -7
  75. data/app/views/decidim/action_delegator/admin/ponderations/edit.html.erb +11 -13
  76. data/app/views/decidim/action_delegator/admin/ponderations/index.html.erb +8 -11
  77. data/app/views/decidim/action_delegator/admin/ponderations/new.html.erb +11 -13
  78. data/app/views/decidim/action_delegator/admin/settings/_form.html.erb +26 -12
  79. data/app/views/decidim/action_delegator/admin/settings/_participants_sync_check.html.erb +1 -1
  80. data/app/views/decidim/action_delegator/admin/settings/_setting_checks.html.erb +11 -16
  81. data/app/views/decidim/action_delegator/admin/settings/edit.html.erb +11 -12
  82. data/app/views/decidim/action_delegator/admin/settings/index.html.erb +22 -19
  83. data/app/views/decidim/action_delegator/admin/settings/new.html.erb +11 -12
  84. data/app/views/decidim/action_delegator/admin/shared/_tabs_menu.html.erb +15 -0
  85. data/app/views/decidim/action_delegator/censuses/_internal_users_form.html.erb +61 -0
  86. data/app/views/decidim/action_delegator/elections/_delegation_buttons.html.erb +10 -0
  87. data/app/views/decidim/action_delegator/elections/_normal_election_buttons.html.erb +13 -0
  88. data/app/views/decidim/action_delegator/elections/_per_question_buttons.html.erb +9 -0
  89. data/app/views/decidim/action_delegator/elections/_per_question_waiting_buttons.html.erb +19 -0
  90. data/app/views/decidim/action_delegator/elections/_vote_results.html.erb +10 -0
  91. data/app/views/decidim/action_delegator/elections/_vote_results_question.html.erb +13 -0
  92. data/app/views/decidim/action_delegator/elections/admin/dashboard/_by_type_and_weight.html.erb +45 -0
  93. data/app/views/decidim/action_delegator/elections/admin/dashboard/_results_tabs.html.erb +5 -0
  94. data/app/views/decidim/action_delegator/elections/admin/dashboard/_results_type_info.html.erb +4 -0
  95. data/app/views/decidim/action_delegator/elections/admin/dashboard/_sum_of_weights.html.erb +43 -0
  96. data/app/views/decidim/action_delegator/elections/admin/dashboard/_totals.html.erb +27 -0
  97. data/app/views/decidim/action_delegator/user_delegations/index.html.erb +13 -14
  98. data/app/views/decidim/action_delegator/verifications/delegations_verifier/authorizations/edit.html.erb +19 -29
  99. data/app/views/decidim/action_delegator/verifications/delegations_verifier/authorizations/new.html.erb +42 -40
  100. data/config/assets.rb +3 -35
  101. data/config/i18n-tasks.yml +27 -5
  102. data/config/locales/ca.yml +70 -56
  103. data/config/locales/cs.yml +161 -145
  104. data/config/locales/en.yml +110 -67
  105. data/config/locales/es.yml +70 -56
  106. data/db/migrate/20200824113801_create_settings.rb +1 -1
  107. data/db/migrate/20250729104037_add_title_to_action_delegator_settings.rb +31 -0
  108. data/lib/decidim/action_delegator/admin_engine.rb +72 -45
  109. data/lib/decidim/action_delegator/common_rake.rb +13 -0
  110. data/lib/decidim/action_delegator/engine.rb +49 -19
  111. data/lib/decidim/action_delegator/test/delegation_examples.rb +144 -0
  112. data/lib/decidim/action_delegator/test/factories.rb +11 -6
  113. data/lib/decidim/action_delegator/verifications/delegations_authorizer.rb +80 -47
  114. data/lib/decidim/action_delegator/version.rb +3 -3
  115. data/lib/decidim/action_delegator.rb +8 -26
  116. data/lib/tasks/migrate_consultations.rake +382 -0
  117. data/lib/tasks/upgrade_tasks.rake +5 -0
  118. data/package.json +10 -27
  119. metadata +72 -112
  120. data/app/commands/concerns/decidim/action_delegator/consultations/multiple_vote_question_override.rb +0 -31
  121. data/app/commands/concerns/decidim/action_delegator/consultations/vote_question_override.rb +0 -44
  122. data/app/commands/decidim/action_delegator/admin/fix_resource_permissions.rb +0 -46
  123. data/app/commands/decidim/action_delegator/vote_delegation.rb +0 -28
  124. data/app/controllers/concerns/decidim/action_delegator/consultations/consultations_controller_override.rb +0 -16
  125. data/app/controllers/concerns/decidim/action_delegator/consultations/question_multiple_votes_controller_override.rb +0 -29
  126. data/app/controllers/concerns/decidim/action_delegator/consultations/question_votes_controller_override.rb +0 -57
  127. data/app/controllers/concerns/decidim/action_delegator/consultations/questions_controller_override.rb +0 -16
  128. data/app/controllers/concerns/decidim/action_delegator/needs_consultation_styles.rb +0 -24
  129. data/app/controllers/decidim/action_delegator/admin/consultations/exports_controller.rb +0 -27
  130. data/app/controllers/decidim/action_delegator/admin/consultations_controller.rb +0 -47
  131. data/app/controllers/decidim/action_delegator/admin/exports/_sum_of_weights_controller.rb +0 -15
  132. data/app/forms/concerns/decidim/action_delegator/consultations/vote_form_override.rb +0 -15
  133. data/app/helpers/decidim/action_delegator/delegation_helper.rb +0 -13
  134. data/app/jobs/decidim/action_delegator/export_consultation_results_job.rb +0 -51
  135. data/app/models/concerns/decidim/action_delegator/consultations/question_override.rb +0 -18
  136. data/app/models/concerns/decidim/action_delegator/consultations/vote_override.rb +0 -15
  137. data/app/models/decidim/action_delegator/unversioned_vote.rb +0 -19
  138. data/app/models/decidim/action_delegator/whodunnit_vote.rb +0 -28
  139. data/app/overrides/decidim/consultations/admin/consultations/results/add_ongoing_warning.html.erb.deface +0 -3
  140. data/app/overrides/decidim/consultations/consultations/_question/add_delegation_link.html.erb.deface +0 -3
  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/_vote_button/add_delegations_link.html.erb.deface +0 -3
  145. data/app/overrides/decidim/consultations/questions/_vote_button/add_modal.html.erb.deface +0 -3
  146. data/app/overrides/decidim/consultations/questions/_vote_button/add_modal_javascript.html.erb.deface +0 -4
  147. data/app/overrides/decidim/consultations/questions/_vote_button/replace_delegation_to_multivote_link.html.erb.deface +0 -8
  148. data/app/overrides/decidim/consultations/questions/_vote_modal/add_delegation_callout.html.erb.deface +0 -3
  149. data/app/overrides/decidim/consultations/questions/_vote_modal_confirm/add_delegation_callout.html.erb.deface +0 -3
  150. data/app/overrides/decidim/consultations/questions/_vote_modal_confirm/add_hidden_field.html.erb.deface +0 -3
  151. data/app/overrides/layouts/decidim/admin/remove_deprecation.rb +0 -10
  152. data/app/packs/entrypoints/decidim_action_delegator.scss +0 -1
  153. data/app/packs/entrypoints/decidim_action_delegator_questions.js +0 -5
  154. data/app/packs/src/decidim/action_delegator/questions.js +0 -33
  155. data/app/packs/stylesheets/decidim/action_delegator/questions.scss +0 -26
  156. data/app/permissions/concerns/decidim/action_delegator/consultations/permissions_override.rb +0 -35
  157. data/app/presenters/decidim/action_delegator/question_with_totals.rb +0 -24
  158. data/app/queries/decidim/action_delegator/consultation_delegations.rb +0 -25
  159. data/app/queries/decidim/action_delegator/delegates_votes_by_consultation.rb +0 -24
  160. data/app/queries/decidim/action_delegator/delegates_votes_by_question.rb +0 -26
  161. data/app/queries/decidim/action_delegator/delegation_votes.rb +0 -30
  162. data/app/queries/decidim/action_delegator/grantee_delegations.rb +0 -24
  163. data/app/queries/decidim/action_delegator/organization_delegations.rb +0 -26
  164. data/app/queries/decidim/action_delegator/organization_settings.rb +0 -31
  165. data/app/queries/decidim/action_delegator/responses.rb +0 -24
  166. data/app/queries/decidim/action_delegator/responses_by_membership.rb +0 -58
  167. data/app/queries/decidim/action_delegator/scrutiny.rb +0 -87
  168. data/app/queries/decidim/action_delegator/setting_delegations.rb +0 -19
  169. data/app/queries/decidim/action_delegator/sum_of_membership_weight.rb +0 -44
  170. data/app/queries/decidim/action_delegator/sum_of_weights.rb +0 -25
  171. data/app/queries/decidim/action_delegator/type_and_weight.rb +0 -26
  172. data/app/queries/decidim/action_delegator/voted_with_ponderations.rb +0 -30
  173. data/app/queries/decidim/action_delegator/votes_count_aggregation.rb +0 -34
  174. data/app/serializers/decidim/action_delegator/consultation_results_serializer.rb +0 -19
  175. data/app/views/decidim/action_delegator/admin/consultations/_ongoing_consultation_warning.html.erb +0 -3
  176. data/app/views/decidim/action_delegator/admin/consultations/results.html.erb +0 -65
  177. data/app/views/decidim/action_delegator/admin/consultations/weighted_results.html.erb +0 -66
  178. data/app/views/decidim/action_delegator/consultations/_link_to_question.html.erb +0 -11
  179. data/app/views/decidim/action_delegator/consultations/questions/_callout.html.erb +0 -5
  180. data/app/views/decidim/action_delegator/consultations/questions/_delegations_modal.html.erb +0 -34
  181. data/app/views/decidim/action_delegator/consultations/questions/_link_to_delegations.html.erb +0 -11
  182. data/app/views/decidim/action_delegator/consultations/questions/_vote_delegated_active.html.erb +0 -32
  183. data/app/views/decidim/action_delegator/consultations/questions/_vote_delegated_finished.html.erb +0 -9
  184. data/app/views/decidim/action_delegator/consultations/questions/_vote_delegated_upcoming.html.erb +0 -8
  185. data/app/views/decidim/consultations/question_votes/update_vote_button.js.erb +0 -82
  186. 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
@@ -16,21 +16,47 @@ module Decidim
16
16
  message: I18n.t("delegations.create.error_granter_unique", scope: "decidim.action_delegator.admin")
17
17
  }
18
18
 
19
- delegate :consultation, to: :setting
19
+ validate :grantee_is_not_granter
20
+ validate :granter_and_grantee_belongs_to_same_organization
21
+ validate :granter_is_same_organization_as_context
22
+
23
+ delegate :resource, to: :setting
20
24
 
21
25
  before_destroy { |record| throw(:abort) if record.grantee_voted? }
22
26
 
23
- def self.granted_to?(user, consultation)
24
- GranteeDelegations.for(consultation, user).exists?
27
+ def grantee_voted?
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
+ )
25
34
  end
26
35
 
27
- def grantee_voted?
28
- return false unless consultation.questions.any?
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
40
+ end
41
+
42
+ private
43
+
44
+ def grantee_is_not_granter
45
+ return unless granter == grantee
46
+
47
+ errors.add(:grantee, :invalid)
48
+ end
49
+
50
+ def granter_and_grantee_belongs_to_same_organization
51
+ return unless granter.organization != grantee.organization
52
+
53
+ errors.add(:grantee, :invalid)
54
+ end
55
+
56
+ def granter_is_same_organization_as_context
57
+ return unless setting && granter.organization != setting.organization
29
58
 
30
- @grantee_voted ||= begin
31
- granter_votes = Decidim::Consultations::Vote.where(author: granter, question: consultation.questions)
32
- granter_votes&.detect { |vote| vote.versions.exists?(whodunnit: grantee&.id) } ? true : false
33
- end
59
+ errors.add(:granter, :invalid)
34
60
  end
35
61
  end
36
62
  end
@@ -18,13 +18,14 @@ 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? }
25
24
  validates :email, uniqueness: { scope: :setting }, if: -> { email.present? }
26
25
  validates :phone, uniqueness: { scope: :setting }, if: -> { phone.present? }
27
26
 
27
+ validate :user_belongs_to_organization
28
+
28
29
  # sets the decidim user if found
29
30
  before_save :set_decidim_user
30
31
 
@@ -36,7 +37,7 @@ module Decidim
36
37
  end
37
38
 
38
39
  def user_from_metadata
39
- @user_from_metadata ||= if setting.email_required?
40
+ @user_from_metadata ||= if setting&.email_required?
40
41
  Decidim::User.find_by(email: email, organization: setting.organization)
41
42
  else
42
43
  Decidim::Authorization.find_by(unique_id: uniq_ids)&.user
@@ -48,7 +49,7 @@ module Decidim
48
49
  end
49
50
 
50
51
  def self.verifier_ids(seeds)
51
- 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)}") }
52
53
  end
53
54
 
54
55
  def self.phone_combinations(phones)
@@ -80,15 +81,12 @@ module Decidim
80
81
  ponderation&.title
81
82
  end
82
83
 
83
- # checks if the user has voted in the setting's consultation
84
+ # checks if the user has voted
84
85
  def voted?
85
86
  return false if user.blank?
86
87
 
87
- @voted ||= Decidim::Consultations::Vote
88
- .joins(question: :consultation)
89
- .where(decidim_consultations_questions: {
90
- decidim_consultation_id: setting.consultation.id
91
- }, author: user).any?
88
+ # TODO: Replace vote check once new context is defined
89
+ false
92
90
  end
93
91
 
94
92
  private
@@ -96,6 +94,14 @@ module Decidim
96
94
  def set_decidim_user
97
95
  self.decidim_user = user_from_metadata if decidim_user.blank?
98
96
  end
97
+
98
+ def user_belongs_to_organization
99
+ return if decidim_user.blank? || setting.blank?
100
+
101
+ return if decidim_user.organization == setting.organization
102
+
103
+ errors.add(:decidim_user, :invalid)
104
+ end
99
105
  end
100
106
  end
101
107
  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";