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
@@ -0,0 +1,382 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :action_delegator do
4
+ desc "Migrate old decidim-consultations data to decidim-elections component"
5
+ task :migrate_consultations, [:component_id, :consultation_id] => :environment do |_task, args|
6
+ component_id = args[:component_id]
7
+ consultation_id = args[:consultation_id]
8
+
9
+ if component_id.blank?
10
+ puts "ERROR: component_id is required"
11
+ puts "Usage: rake action_delegator:migrate_consultations[COMPONENT_ID] or rake action_delegator:migrate_consultations[COMPONENT_ID,CONSULTATION_ID]"
12
+ exit 1
13
+ end
14
+
15
+ component = Decidim::Component.find_by(id: component_id)
16
+ unless component&.manifest_name == "elections"
17
+ puts "ERROR: Component ##{component_id} not found or is not an elections component"
18
+ exit 1
19
+ end
20
+
21
+ # Define inline models for old consultation tables (module no longer exists in Decidim)
22
+ module Legacy
23
+ class Consultation < ApplicationRecord
24
+ self.table_name = "decidim_consultations"
25
+ has_many :questions, class_name: "Legacy::Question", foreign_key: :decidim_consultation_id
26
+ end
27
+
28
+ class Question < ApplicationRecord
29
+ self.table_name = "decidim_consultations_questions"
30
+ belongs_to :consultation, class_name: "Legacy::Consultation", foreign_key: :decidim_consultation_id
31
+ has_many :responses, class_name: "Legacy::Response", foreign_key: :decidim_consultations_questions_id
32
+ has_many :votes, class_name: "Legacy::Vote", foreign_key: :decidim_consultation_question_id
33
+ end
34
+
35
+ class Response < ApplicationRecord
36
+ self.table_name = "decidim_consultations_responses"
37
+ belongs_to :question, class_name: "Legacy::Question", foreign_key: :decidim_consultations_questions_id
38
+ belongs_to :response_group, class_name: "Legacy::ResponseGroup", foreign_key: :decidim_consultations_response_group_id, optional: true
39
+ end
40
+
41
+ class ResponseGroup < ApplicationRecord
42
+ self.table_name = "decidim_consultations_response_groups"
43
+ belongs_to :question, class_name: "Legacy::Question", foreign_key: :decidim_consultations_questions_id
44
+ has_many :responses, class_name: "Legacy::Response", foreign_key: :decidim_consultations_response_group_id
45
+ end
46
+
47
+ class Vote < ApplicationRecord
48
+ self.table_name = "decidim_consultations_votes"
49
+ belongs_to :question, class_name: "Legacy::Question", foreign_key: :decidim_consultation_question_id
50
+ belongs_to :response, class_name: "Legacy::Response", foreign_key: :decidim_consultations_response_id
51
+ belongs_to :author, class_name: "Decidim::User", foreign_key: :decidim_author_id
52
+ end
53
+ end
54
+
55
+ # Check if consultations table exists
56
+ unless ActiveRecord::Base.connection.table_exists?("decidim_consultations")
57
+ puts "ERROR: decidim_consultations table not found. Make sure you have legacy data to migrate."
58
+ exit 1
59
+ end
60
+
61
+ # Fetch consultations to migrate
62
+ consultations = if consultation_id.present?
63
+ Legacy::Consultation.where(id: consultation_id)
64
+ else
65
+ Legacy::Consultation.where(decidim_organization_id: component.organization.id)
66
+ end
67
+
68
+ if consultations.empty?
69
+ puts "No consultations found"
70
+ exit 0
71
+ end
72
+
73
+ # Calculate source data statistics
74
+ source_stats = {
75
+ consultations: consultations.count,
76
+ questions: 0,
77
+ responses: 0,
78
+ votes: 0
79
+ }
80
+
81
+ consultations.each do |c|
82
+ source_stats[:questions] += c.questions.count
83
+ c.questions.each do |q|
84
+ source_stats[:responses] += q.responses.count
85
+ source_stats[:votes] += q.votes.count
86
+ end
87
+ end
88
+
89
+ migrated_stats = {
90
+ elections: 0,
91
+ questions: 0,
92
+ responses: 0,
93
+ votes: 0,
94
+ skipped_votes: 0,
95
+ skipped_consultations: 0,
96
+ errors: []
97
+ }
98
+
99
+ puts "\nStarting migration of #{source_stats[:consultations]} consultations from decidim-consultations to decidim-elections"
100
+ puts "Component: ##{component.id} - #{component.name}"
101
+ puts "Consultations:"
102
+ consultations.each do |c|
103
+ puts " • ##{c.id} - #{c.title}"
104
+ end
105
+ puts "\nSource data summary:"
106
+ puts "-" * 70
107
+ puts "TYPE Elections Questions Responses Votes"
108
+ puts "-" * 70
109
+ puts format("%-12s %12d %12d %12d %12d", "SOURCE", source_stats[:consultations], source_stats[:questions], source_stats[:responses], source_stats[:votes])
110
+ puts "-" * 70
111
+ puts ""
112
+ unless ENV["CI"].present? || ENV["FORCE_MIGRATION"].to_s.downcase == "true"
113
+ print "Continue migration? (y/N): "
114
+ answer = $stdin.gets.chomp.downcase
115
+ unless answer == "y"
116
+ puts "Aborted by user."
117
+ exit 0
118
+ end
119
+ end
120
+
121
+ def determine_census_config(consultation, setting)
122
+ resource_handlers = extract_authorization_handlers(consultation)
123
+ census_manifest = determine_census_manifest(setting, resource_handlers)
124
+ authorization_handlers = determine_authorization_handlers(setting, resource_handlers, census_manifest)
125
+ census_settings = build_census_settings(census_manifest, setting, authorization_handlers)
126
+
127
+ [census_manifest, census_settings]
128
+ end
129
+
130
+ def extract_authorization_handlers(consultation)
131
+ question_ids = consultation.questions.pluck(:id)
132
+ return [] unless ActiveRecord::Base.connection.table_exists?("decidim_resource_permissions") && question_ids.any?
133
+
134
+ handlers = []
135
+ perms = ActiveRecord::Base.connection.execute(<<-SQL.squish)
136
+ SELECT permissions FROM decidim_resource_permissions
137
+ WHERE resource_type = 'Decidim::Consultations::Question'
138
+ AND resource_id IN (#{question_ids.join(",")})
139
+ AND permissions::jsonb -> 'vote' -> 'authorization_handlers' IS NOT NULL
140
+ SQL
141
+
142
+ perms.each do |perm|
143
+ perm_handlers = begin
144
+ JSON.parse(perm["permissions"])["vote"]["authorization_handlers"].keys
145
+ rescue StandardError
146
+ []
147
+ end
148
+ handlers.concat(perm_handlers)
149
+ end
150
+ handlers.uniq
151
+ end
152
+
153
+ def determine_census_manifest(setting, resource_handlers)
154
+ return "action_delegator_census" if setting&.delegations&.any?
155
+ return "internal_users" if setting&.authorization_method.present? || resource_handlers.any?
156
+
157
+ "internal_users"
158
+ end
159
+
160
+ def determine_authorization_handlers(setting, resource_handlers, census_manifest)
161
+ return [] if census_manifest == "action_delegator_census"
162
+ return ["delegations_verifier"] if setting&.authorization_method.present?
163
+
164
+ resource_handlers
165
+ end
166
+
167
+ def build_census_settings(census_manifest, setting, authorization_handlers)
168
+ return { "setting_id" => setting.id } if census_manifest == "action_delegator_census" && setting
169
+
170
+ return {} if authorization_handlers.empty?
171
+
172
+ {
173
+ authorization_handlers: authorization_handlers.index_with do |handler|
174
+ handler == "delegations_verifier" && setting ? { "options" => { "setting" => setting.id.to_s } } : { "options" => {} }
175
+ end
176
+ }
177
+ end
178
+
179
+ def build_question_description(old_question)
180
+ parts = [old_question.subtitle, old_question.what_is_decided].compact.compact_blank
181
+ return {} if parts.empty?
182
+
183
+ parts_by_locale = parts.each_with_object({}) do |part, result|
184
+ part.each do |locale, text|
185
+ result[locale] ||= []
186
+ result[locale] << text if text.present?
187
+ end
188
+ end
189
+ parts_by_locale.transform_values { |arr| arr.join("\n\n") }
190
+ end
191
+
192
+ def migrate_question_votes(old_question, new_question, response_mapping, migrated_stats)
193
+ votes_migrated = 0
194
+ votes_skipped = 0
195
+
196
+ old_question.votes.find_each do |old_vote|
197
+ user = old_vote.author
198
+ unless user
199
+ votes_skipped += 1
200
+ migrated_stats[:skipped_votes] += 1
201
+ next
202
+ end
203
+
204
+ new_response_id = response_mapping[old_vote.decidim_consultations_response_id]
205
+ unless new_response_id
206
+ votes_skipped += 1
207
+ migrated_stats[:skipped_votes] += 1
208
+ next
209
+ end
210
+
211
+ new_vote = Decidim::Elections::Vote.new(
212
+ question: new_question,
213
+ response_option_id: new_response_id,
214
+ voter_uid: user.to_global_id.to_s,
215
+ created_at: old_vote.created_at,
216
+ updated_at: old_vote.updated_at
217
+ )
218
+
219
+ if new_vote.save
220
+ votes_migrated += 1
221
+ migrated_stats[:votes] += 1
222
+ else
223
+ votes_skipped += 1
224
+ migrated_stats[:skipped_votes] += 1
225
+ puts " ✗ Failed to migrate Vote ##{old_vote.id}: #{new_vote.errors.full_messages.join(", ")}"
226
+ end
227
+ end
228
+
229
+ puts " ✓ Migrated #{votes_migrated} votes, skipped #{votes_skipped} votes"
230
+
231
+ [votes_migrated, votes_skipped]
232
+ end
233
+
234
+ consultations.find_each do |consultation|
235
+ ActiveRecord::Base.transaction do
236
+ setting = Decidim::ActionDelegator::Setting.find_by(decidim_consultation_id: consultation.id)
237
+
238
+ # Check if already migrated (idempotency check by matching title)
239
+ existing_election = Decidim::Elections::Election.joins(:component)
240
+ .where(component: component, title: consultation.title)
241
+ .first
242
+
243
+ if existing_election
244
+ cons_title = consultation.title["en"] || consultation.title.values.first
245
+ puts " ✓ Consultation ##{consultation.id} (#{cons_title}) already migrated to Election ##{existing_election.id}"
246
+ migrated_stats[:skipped_consultations] += 1
247
+ next
248
+ end
249
+
250
+ census_manifest, census_settings = determine_census_config(consultation, setting)
251
+
252
+ # Create Election from Consultation
253
+ election = Decidim::Elections::Election.new(
254
+ component: component,
255
+ title: consultation.title,
256
+ description: consultation.description,
257
+ start_at: consultation.start_voting_date&.to_time&.beginning_of_day,
258
+ end_at: consultation.end_voting_date&.to_time&.end_of_day,
259
+ published_at: consultation.published_at,
260
+ published_results_at: consultation.results_published_at,
261
+ results_availability: "after_end",
262
+ census_manifest: census_manifest,
263
+ census_settings: census_settings,
264
+ created_at: consultation.created_at,
265
+ updated_at: consultation.updated_at
266
+ )
267
+
268
+ if election.save
269
+ migrated_stats[:elections] += 1
270
+ puts " ✓ Created Election ##{election.id} from Consultation ##{consultation.id}"
271
+
272
+ consultation.questions.order(:order).each_with_index do |old_question, index|
273
+ new_question = Decidim::Elections::Question.new(
274
+ election: election,
275
+ body: old_question.title.transform_values { |t| ActionController::Base.helpers.strip_tags(t) },
276
+ description: build_question_description(old_question),
277
+ position: old_question.order || index,
278
+ question_type: (old_question.max_votes.to_i > 1 ? "multiple_option" : "single_option"),
279
+ created_at: old_question.created_at,
280
+ updated_at: old_question.updated_at
281
+ )
282
+
283
+ unless new_question.save
284
+ migrated_stats[:errors] << "Question creation failed: #{new_question.errors.full_messages.join(", ")}"
285
+ puts " ✗ Failed to create Question from Old Question ##{old_question.id}: #{new_question.errors.full_messages.join(", ")}"
286
+ next
287
+ end
288
+
289
+ migrated_stats[:questions] += 1
290
+
291
+ response_mapping = {}
292
+ old_question.responses.each do |old_response|
293
+ title = old_response.title
294
+ if old_response.response_group
295
+ title = title.to_h do |locale, text|
296
+ group_text = old_response.response_group.title[locale]
297
+ combined_text = [group_text, text].compact.join(" - ")
298
+ [locale, combined_text]
299
+ end
300
+ end
301
+ new_response = Decidim::Elections::ResponseOption.new(
302
+ question: new_question,
303
+ body: title,
304
+ created_at: old_response.created_at,
305
+ updated_at: old_response.updated_at
306
+ )
307
+
308
+ if new_response.save
309
+ response_mapping[old_response.id] = new_response.id
310
+ migrated_stats[:responses] += 1
311
+ else
312
+ migrated_stats[:errors] << "Response failed: #{new_response.errors.full_messages.join(", ")}"
313
+ puts " ✗ Failed to create Response from Old Response ##{old_response.id}: #{new_response.errors.full_messages.join(", ")}"
314
+ end
315
+ end
316
+
317
+ puts " ✓ Created Question ##{new_question.id} with #{response_mapping.size} Responses"
318
+
319
+ migrate_question_votes(old_question, new_question, response_mapping, migrated_stats)
320
+
321
+ new_question.response_options.each { |ro| Decidim::Elections::ResponseOption.reset_counters(ro.id, :votes) }
322
+ Decidim::Elections::Question.reset_counters(new_question.id, :votes)
323
+ end
324
+
325
+ # Update election votes count
326
+ election.update_votes_count!
327
+ else
328
+ cons_title = consultation.title["en"] || consultation.title.values.first
329
+ migrated_stats[:errors] << "Consultation ##{consultation.id} (#{cons_title}): #{election.errors.full_messages.join(", ")}"
330
+ puts " ✗ Failed to create Election from Consultation ##{consultation.id}: #{election.errors.full_messages.join(", ")}"
331
+ raise ActiveRecord::Rollback
332
+ end
333
+ rescue StandardError => e
334
+ cons_title = begin
335
+ consultation.title["en"] || consultation.title.values.first
336
+ rescue StandardError
337
+ "ID #{consultation.id}"
338
+ end
339
+ migrated_stats[:errors] << "Consultation ##{consultation.id} (#{cons_title}): #{e.message}"
340
+ puts " ✗ Exception migrating Consultation ##{consultation.id} (#{cons_title}): #{e.message}"
341
+ Rails.logger.error e.backtrace.join("\n")
342
+ raise ActiveRecord::Rollback
343
+ end
344
+ end
345
+
346
+ puts "\n#{"-" * 70}"
347
+ puts "Migration results: Consultations → Elections"
348
+ puts "-" * 70
349
+ puts " Elections Questions Responses Votes"
350
+ puts "-" * 70
351
+ puts format("%-12s %12d %12d %12d %12d", "SOURCE", source_stats[:consultations], source_stats[:questions], source_stats[:responses], source_stats[:votes])
352
+ puts format("%-12s %12d %12d %12d %12d", "MIGRATED", migrated_stats[:elections], migrated_stats[:questions], migrated_stats[:responses], migrated_stats[:votes])
353
+
354
+ puts format("%-12s %12d %12s %12s %12s", "EXISTED", migrated_stats[:skipped_consultations], "-", "-", "-") if migrated_stats[:skipped_consultations].positive?
355
+
356
+ puts format("%-12s %12s %12s %12s %12d", "SKIPPED", "-", "-", "-", migrated_stats[:skipped_votes]) if migrated_stats[:skipped_votes].positive?
357
+
358
+ if migrated_stats[:errors].any?
359
+ puts "\nErrors (#{migrated_stats[:errors].size}):"
360
+ migrated_stats[:errors].each { |err| puts " • #{err}" }
361
+ end
362
+
363
+ puts ""
364
+ total_processed = migrated_stats[:elections] + migrated_stats[:skipped_consultations]
365
+ success = total_processed == source_stats[:consultations] &&
366
+ migrated_stats[:questions] == source_stats[:questions] &&
367
+ migrated_stats[:responses] == source_stats[:responses] &&
368
+ migrated_stats[:votes] == source_stats[:votes] &&
369
+ migrated_stats[:errors].empty? &&
370
+ (migrated_stats[:skipped_votes]).zero?
371
+
372
+ if success
373
+ if migrated_stats[:skipped_consultations].positive?
374
+ puts "✓ All data processed (#{migrated_stats[:elections]} migrated, #{migrated_stats[:skipped_consultations]} already existed)"
375
+ else
376
+ puts "✓ All data migrated successfully"
377
+ end
378
+ else
379
+ puts "⚠ Some data was not migrated (see details above)"
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rake::Task["decidim:choose_target_plugins"].enhance do
4
+ ENV["FROM"] = "#{ENV.fetch("FROM", nil)},decidim_action_delegator"
5
+ end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decidim-action_delegator",
3
- "version": "0.6.0",
3
+ "version": "0.9.0",
4
4
  "description": "Delgation actions (votes) for Decidim",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "git+https://github.com/coopdevs/decidim-module-action_delegator.git"
19
+ "url": "git+https://github.com/Openpoke/decidim-module-action_delegator.git"
20
20
  },
21
21
  "keywords": [
22
22
  "decidim"
@@ -24,34 +24,17 @@
24
24
  "author": "ivan@pokecode.net",
25
25
  "license": "AGPL-3.0-or-later",
26
26
  "bugs": {
27
- "url": "https://github.com/coopdevs/decidim-module-action_delegator/issues"
27
+ "url": "https://github.com/Openpoke/decidim-module-action_delegator/issues"
28
28
  },
29
- "homepage": "https://github.com/coopdevs/decidim-module-action_delegator#readme",
29
+ "homepage": "https://github.com/Openpoke/decidim-module-action_delegator#readme",
30
30
  "stylelint": {
31
- "extends": "@decidim/stylelint-config",
32
- "rules" : {
33
- "block-opening-brace-space-before": "always"
34
- }
31
+ "extends": "@decidim/stylelint-config"
35
32
  },
36
33
  "devDependencies": {
37
- "@babel/eslint-parser": "^7.19.1",
38
- "@babel/plugin-proposal-class-properties": "^7.18.6",
39
- "@babel/plugin-proposal-private-methods": "^7.18.6",
40
- "@babel/plugin-proposal-private-property-in-object": "^7.20.5",
41
- "@babel/plugin-transform-classes": "^7.20.7",
42
- "@babel/plugin-transform-regenerator": "^7.20.5",
43
- "@babel/plugin-transform-runtime": "^7.19.6",
44
- "@babel/preset-env": "^7.20.2",
45
- "@babel/preset-react": "^7.18.6",
46
- "@decidim/dev": "0.27.1",
47
- "@decidim/eslint-config": "0.27.1",
48
- "@decidim/stylelint-config": "0.27.1",
49
- "eslint-config-prettier": "^8.2.0",
50
- "eslint-config-standard": "^11.0.0",
51
- "eslint-plugin-import": "^2.22.0",
52
- "eslint-plugin-standard": "^3.1.0",
53
- "sass-embedded": "~1.57.1"
54
- },
55
- "dependencies": {
34
+ "@decidim/dev": "0.30",
35
+ "@decidim/eslint-config": "0.30",
36
+ "@decidim/prettier-config": "0.30",
37
+ "@decidim/stylelint-config": "0.30",
38
+ "postcss-scss": "^4.0.9"
56
39
  }
57
40
  }