decidim-proposals 0.19.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/config/admin/decidim_proposals_manifest.js +1 -0
  3. data/app/assets/images/decidim/gamification/badges/accepted_proposals.svg +1 -234
  4. data/app/assets/images/decidim/gamification/badges/proposal_votes.svg +1 -95
  5. data/app/assets/images/decidim/gamification/badges/proposals.svg +1 -229
  6. data/app/assets/images/decidim/proposals/icon.svg +1 -3
  7. data/app/assets/javascripts/decidim/proposals/admin/proposals.es6 +24 -11
  8. data/app/assets/javascripts/decidim/proposals/admin/proposals_form.js.es6 +0 -5
  9. data/app/assets/javascripts/decidim/proposals/admin/proposals_picker.js.es6 +35 -0
  10. data/app/cells/decidim/proposals/collaborative_draft_link_to_proposal_cell.rb +1 -1
  11. data/app/cells/decidim/proposals/collaborative_draft_m/footer.erb +1 -1
  12. data/app/cells/decidim/proposals/collaborative_draft_m_cell.rb +1 -1
  13. data/app/cells/decidim/proposals/cost_report/show.erb +35 -0
  14. data/app/cells/decidim/proposals/cost_report_cell.rb +42 -0
  15. data/app/cells/decidim/proposals/highlighted_proposals_for_component/show.erb +3 -3
  16. data/app/cells/decidim/proposals/highlighted_proposals_for_component_cell.rb +1 -1
  17. data/app/cells/decidim/proposals/irreversible_action_modal/show.erb +2 -2
  18. data/app/cells/decidim/proposals/irreversible_action_modal_cell.rb +1 -1
  19. data/app/cells/decidim/proposals/participatory_text_proposal/buttons.erb +1 -1
  20. data/app/cells/decidim/proposals/participatory_text_proposal_cell.rb +1 -1
  21. data/app/cells/decidim/proposals/proposal_m/footer.erb +4 -1
  22. data/app/cells/decidim/proposals/proposal_m_cell.rb +37 -9
  23. data/app/cells/decidim/proposals/proposal_tags/show.erb +18 -10
  24. data/app/cells/decidim/proposals/proposal_tags_cell.rb +5 -0
  25. data/app/cells/decidim/proposals/proposals_picker/proposals.erb +12 -0
  26. data/app/cells/decidim/proposals/proposals_picker/show.erb +14 -0
  27. data/app/cells/decidim/proposals/proposals_picker_cell.rb +72 -0
  28. data/app/commands/decidim/proposals/admin/answer_proposal.rb +24 -46
  29. data/app/commands/decidim/proposals/admin/assign_proposals_to_valuator.rb +61 -0
  30. data/app/commands/decidim/proposals/admin/create_proposal.rb +6 -1
  31. data/app/commands/decidim/proposals/admin/create_proposal_note.rb +15 -0
  32. data/app/commands/decidim/proposals/admin/notify_proposal_answer.rb +85 -0
  33. data/app/commands/decidim/proposals/admin/publish_answers.rb +67 -0
  34. data/app/commands/decidim/proposals/admin/unassign_proposals_from_valuator.rb +62 -0
  35. data/app/commands/decidim/proposals/admin/update_proposal.rb +1 -1
  36. data/app/commands/decidim/proposals/admin/update_proposal_scope.rb +75 -0
  37. data/app/commands/decidim/proposals/create_collaborative_draft.rb +1 -1
  38. data/app/commands/decidim/proposals/create_proposal.rb +1 -1
  39. data/app/commands/decidim/proposals/gallery_methods.rb +2 -51
  40. data/app/commands/decidim/proposals/update_proposal.rb +1 -1
  41. data/app/controllers/concerns/decidim/proposals/admin/filterable.rb +82 -0
  42. data/app/controllers/concerns/decidim/proposals/admin/picker.rb +21 -0
  43. data/app/controllers/concerns/decidim/proposals/orderable.rb +13 -2
  44. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +16 -6
  45. data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +8 -9
  46. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +105 -29
  47. data/app/controllers/decidim/proposals/admin/valuation_assignments_controller.rb +58 -0
  48. data/app/controllers/decidim/proposals/collaborative_drafts_controller.rb +19 -3
  49. data/app/controllers/decidim/proposals/proposals_controller.rb +45 -11
  50. data/app/controllers/decidim/proposals/versions_controller.rb +9 -16
  51. data/app/events/decidim/proposals/admin/proposal_note_created_event.rb +27 -0
  52. data/app/events/decidim/proposals/admin/update_proposal_scope_event.rb +11 -0
  53. data/app/forms/decidim/proposals/admin/participatory_text_proposal_form.rb +13 -0
  54. data/app/forms/decidim/proposals/admin/preview_participatory_text_form.rb +2 -2
  55. data/app/forms/decidim/proposals/admin/proposal_answer_form.rb +27 -2
  56. data/app/forms/decidim/proposals/admin/proposal_base_form.rb +129 -0
  57. data/app/forms/decidim/proposals/admin/proposal_form.rb +2 -120
  58. data/app/forms/decidim/proposals/admin/valuation_assignment_form.rb +37 -0
  59. data/app/forms/decidim/proposals/proposal_form.rb +4 -0
  60. data/app/forms/decidim/proposals/proposal_wizard_create_step_form.rb +13 -1
  61. data/app/helpers/decidim/proposals/admin/filterable_helper.rb +17 -0
  62. data/app/helpers/decidim/proposals/admin/proposal_bulk_actions_helper.rb +35 -0
  63. data/app/helpers/decidim/proposals/admin/proposal_rankings_helper.rb +63 -0
  64. data/app/helpers/decidim/proposals/admin/proposals_helper.rb +122 -0
  65. data/app/helpers/decidim/proposals/admin/proposals_picker_helper.rb +30 -0
  66. data/app/helpers/decidim/proposals/application_helper.rb +51 -30
  67. data/app/helpers/decidim/proposals/collaborative_draft_helper.rb +9 -9
  68. data/app/helpers/decidim/proposals/control_version_helper.rb +1 -37
  69. data/app/helpers/decidim/proposals/proposal_cells_helper.rb +1 -1
  70. data/app/helpers/decidim/proposals/proposal_endorsements_helper.rb +0 -145
  71. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +2 -2
  72. data/app/helpers/decidim/proposals/proposal_wizard_helper.rb +24 -7
  73. data/app/helpers/decidim/proposals/proposals_helper.rb +66 -0
  74. data/app/models/decidim/proposals/proposal.rb +167 -29
  75. data/app/models/decidim/proposals/valuation_assignment.rb +24 -0
  76. data/app/permissions/decidim/proposals/admin/permissions.rb +77 -11
  77. data/app/permissions/decidim/proposals/permissions.rb +1 -22
  78. data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +1 -1
  79. data/app/presenters/decidim/proposals/admin_log/valuation_assignment_presenter.rb +51 -0
  80. data/app/presenters/decidim/proposals/admin_log/value_types/valuator_role_user_presenter.rb +19 -0
  81. data/app/presenters/decidim/proposals/collaborative_draft_presenter.rb +2 -28
  82. data/app/presenters/decidim/proposals/log/valuation_assignment_presenter.rb +22 -0
  83. data/app/presenters/decidim/proposals/proposal_presenter.rb +41 -10
  84. data/app/queries/decidim/proposals/metrics/accepted_proposals_metric_manage.rb +1 -2
  85. data/app/queries/decidim/proposals/metrics/endorsements_metric_manage.rb +15 -12
  86. data/app/queries/decidim/proposals/metrics/proposal_participants_metric_measure.rb +5 -4
  87. data/app/queries/decidim/proposals/metrics/proposals_metric_manage.rb +2 -8
  88. data/app/queries/decidim/proposals/metrics/votes_metric_manage.rb +3 -9
  89. data/app/services/decidim/proposals/collaborative_draft_search.rb +18 -10
  90. data/app/services/decidim/proposals/diff_renderer.rb +2 -0
  91. data/app/services/decidim/proposals/proposal_builder.rb +1 -1
  92. data/app/services/decidim/proposals/proposal_search.rb +45 -47
  93. data/app/types/decidim/proposals/proposal_input_filter.rb +29 -0
  94. data/app/types/decidim/proposals/proposal_input_sort.rb +22 -0
  95. data/app/types/decidim/proposals/proposal_type.rb +32 -11
  96. data/app/types/decidim/proposals/proposals_type.rb +22 -15
  97. data/app/validators/proposal_length_validator.rb +38 -0
  98. data/app/views/decidim/proposals/admin/participatory_texts/index.html.erb +9 -1
  99. data/app/views/decidim/proposals/admin/proposal_answers/_form.html.erb +35 -0
  100. data/app/views/decidim/proposals/admin/proposal_notes/_form.html.erb +1 -1
  101. data/app/views/decidim/proposals/admin/proposal_notes/_proposal_notes.html.erb +3 -3
  102. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +8 -2
  103. data/app/views/decidim/proposals/admin/proposals/_form.html.erb +4 -24
  104. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +25 -17
  105. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_assign_to_valuator.html.erb +15 -0
  106. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_dropdown.html.erb +21 -1
  107. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_publish_answers.html.erb +14 -0
  108. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_scope-change.html.erb +25 -0
  109. data/app/views/decidim/proposals/admin/proposals/bulk_actions/_unassign_from_valuator.html.erb +15 -0
  110. data/app/views/decidim/proposals/admin/proposals/index.html.erb +16 -7
  111. data/app/views/decidim/proposals/admin/proposals/publish_answers.js.erb +12 -0
  112. data/app/views/decidim/proposals/admin/proposals/show.html.erb +186 -0
  113. data/app/views/decidim/proposals/admin/proposals/update_category.js.erb +3 -2
  114. data/app/views/decidim/proposals/admin/proposals/update_scope.js.erb +27 -0
  115. data/app/views/decidim/proposals/collaborative_drafts/_edit_form_fields.html.erb +6 -4
  116. data/app/views/decidim/proposals/collaborative_drafts/_filters.html.erb +9 -7
  117. data/app/views/decidim/proposals/collaborative_drafts/_new_collaborative_draft_button.html.erb +4 -4
  118. data/app/views/decidim/proposals/collaborative_drafts/_reject_request_access_form.html.erb +1 -1
  119. data/app/views/decidim/proposals/collaborative_drafts/_wizard_aside.html.erb +4 -2
  120. data/app/views/decidim/proposals/collaborative_drafts/compare.html.erb +2 -0
  121. data/app/views/decidim/proposals/collaborative_drafts/complete.html.erb +2 -0
  122. data/app/views/decidim/proposals/collaborative_drafts/edit.html.erb +3 -1
  123. data/app/views/decidim/proposals/collaborative_drafts/index.html.erb +4 -2
  124. data/app/views/decidim/proposals/collaborative_drafts/new.html.erb +4 -0
  125. data/app/views/decidim/proposals/collaborative_drafts/show.html.erb +29 -30
  126. data/app/views/decidim/proposals/proposals/_edit_form_fields.html.erb +6 -4
  127. data/app/views/decidim/proposals/proposals/_endorsements_card_row.html.erb +0 -16
  128. data/app/views/decidim/proposals/proposals/_filters.html.erb +19 -17
  129. data/app/views/decidim/proposals/proposals/_proposal_badge.html.erb +1 -4
  130. data/app/views/decidim/proposals/proposals/_proposal_preview.html.erb +1 -11
  131. data/app/views/decidim/proposals/proposals/_proposal_similar.html.erb +2 -2
  132. data/app/views/decidim/proposals/proposals/_proposals.html.erb +14 -0
  133. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +14 -7
  134. data/app/views/decidim/proposals/proposals/_wizard_aside.html.erb +4 -2
  135. data/app/views/decidim/proposals/proposals/_wizard_header.html.erb +4 -3
  136. data/app/views/decidim/proposals/proposals/compare.html.erb +2 -0
  137. data/app/views/decidim/proposals/proposals/complete.html.erb +2 -0
  138. data/app/views/decidim/proposals/proposals/edit.html.erb +3 -1
  139. data/app/views/decidim/proposals/proposals/edit_draft.html.erb +2 -0
  140. data/app/views/decidim/proposals/proposals/index.html.erb +7 -12
  141. data/app/views/decidim/proposals/proposals/new.html.erb +6 -2
  142. data/app/views/decidim/proposals/proposals/participatory_texts/_index.html.erb +1 -1
  143. data/app/views/decidim/proposals/proposals/participatory_texts/_proposal_vote_button.html.erb +10 -3
  144. data/app/views/decidim/proposals/proposals/participatory_texts/_view_index.html.erb +1 -1
  145. data/app/views/decidim/proposals/proposals/preview.html.erb +7 -8
  146. data/app/views/decidim/proposals/proposals/show.html.erb +62 -71
  147. data/app/views/decidim/proposals/versions/index.html.erb +14 -32
  148. data/app/views/decidim/proposals/versions/show.html.erb +16 -34
  149. data/config/locales/ar.yml +74 -66
  150. data/config/locales/bg-BG.yml +237 -0
  151. data/config/locales/ca.yml +173 -65
  152. data/config/locales/cs.yml +187 -79
  153. data/config/locales/da-DK.yml +1 -0
  154. data/config/locales/de.yml +266 -162
  155. data/config/locales/el-GR.yml +1 -0
  156. data/config/locales/el.yml +876 -0
  157. data/config/locales/en.yml +179 -71
  158. data/config/locales/es-MX.yml +173 -63
  159. data/config/locales/es-PY.yml +173 -63
  160. data/config/locales/es.yml +173 -65
  161. data/config/locales/et-EE.yml +1 -0
  162. data/config/locales/eu.yml +43 -63
  163. data/config/locales/fi-plain.yml +173 -65
  164. data/config/locales/fi.yml +215 -107
  165. data/config/locales/fr-CA.yml +876 -0
  166. data/config/locales/fr.yml +151 -65
  167. data/config/locales/ga-IE.yml +1 -0
  168. data/config/locales/gl.yml +43 -63
  169. data/config/locales/hr-HR.yml +1 -0
  170. data/config/locales/hu.yml +153 -75
  171. data/config/locales/id-ID.yml +43 -62
  172. data/config/locales/is-IS.yml +274 -0
  173. data/config/locales/it.yml +172 -82
  174. data/config/locales/ja-JP.yml +886 -0
  175. data/config/locales/lt-LT.yml +1 -0
  176. data/config/locales/lv-LV.yml +858 -0
  177. data/config/locales/mt-MT.yml +1 -0
  178. data/config/locales/nl.yml +274 -188
  179. data/config/locales/no.yml +786 -0
  180. data/config/locales/pl.yml +194 -66
  181. data/config/locales/pt-BR.yml +44 -67
  182. data/config/locales/pt.yml +437 -331
  183. data/config/locales/ro-RO.yml +840 -0
  184. data/config/locales/ru.yml +14 -47
  185. data/config/locales/sk-SK.yml +896 -0
  186. data/config/locales/sk.yml +869 -0
  187. data/config/locales/sl.yml +26 -0
  188. data/config/locales/sr-CS.yml +126 -0
  189. data/config/locales/sv.yml +253 -162
  190. data/config/locales/tr-TR.yml +43 -63
  191. data/config/locales/uk.yml +14 -47
  192. data/db/migrate/20181003074440_fix_user_groups_ids_in_proposals_endorsements.rb +4 -0
  193. data/db/migrate/20191206154128_add_endorsements_counter_cache_to_proposals.rb +7 -0
  194. data/db/migrate/20200120215928_move_proposal_endorsements_to_core_endorsements.rb +52 -0
  195. data/db/migrate/20200203111239_add_proposal_valuation_assignments.rb +12 -0
  196. data/db/migrate/20200210135152_add_costs_to_proposals.rb +9 -0
  197. data/db/migrate/20200212120110_sync_proposals_state_with_amendments_state.rb +28 -0
  198. data/db/migrate/20200227175922_add_state_published_at_to_proposals.rb +7 -0
  199. data/db/migrate/20200306123652_publish_existing_proposals_state.rb +15 -0
  200. data/db/migrate/20200730131631_move_proposal_endorsed_event_notifications_to_resource_endorsed_event.rb +20 -0
  201. data/lib/decidim/proposals.rb +1 -0
  202. data/lib/decidim/proposals/admin_engine.rb +7 -3
  203. data/lib/decidim/proposals/component.rb +47 -23
  204. data/lib/decidim/proposals/engine.rb +2 -6
  205. data/lib/decidim/proposals/test/capybara_proposals_picker.rb +49 -0
  206. data/lib/decidim/proposals/test/factories.rb +56 -10
  207. data/lib/decidim/proposals/valuatable.rb +21 -0
  208. data/lib/decidim/proposals/version.rb +1 -1
  209. metadata +94 -53
  210. data/app/assets/javascripts/decidim/proposals/identity_selector_dialog.js.es6 +0 -56
  211. data/app/cells/decidim/proposals/endorsers_list/show.erb +0 -17
  212. data/app/cells/decidim/proposals/endorsers_list_cell.rb +0 -31
  213. data/app/commands/decidim/proposals/attachment_methods.rb +0 -43
  214. data/app/commands/decidim/proposals/endorse_proposal.rb +0 -59
  215. data/app/commands/decidim/proposals/unendorse_proposal.rb +0 -40
  216. data/app/controllers/decidim/proposals/proposal_endorsements_controller.rb +0 -60
  217. data/app/models/decidim/proposals/proposal_endorsement.rb +0 -37
  218. data/app/views/decidim/proposals/admin/proposal_answers/edit.html.erb +0 -22
  219. data/app/views/decidim/proposals/admin/proposal_notes/index.html.erb +0 -3
  220. data/app/views/decidim/proposals/admin/shared/_info_proposal.html.erb +0 -20
  221. data/app/views/decidim/proposals/proposal_endorsements/_identity.html.erb +0 -9
  222. data/app/views/decidim/proposals/proposal_endorsements/identities.html.erb +0 -12
  223. data/app/views/decidim/proposals/proposal_endorsements/update_buttons_and_counters.js.erb +0 -20
  224. data/app/views/decidim/proposals/proposal_widgets/show.html.erb +0 -4
  225. data/app/views/decidim/proposals/proposals/_endorsement_button.html.erb +0 -11
  226. data/app/views/decidim/proposals/proposals/_endorsement_identities_cabin.html.erb +0 -13
  227. data/app/views/decidim/proposals/versions/_version.html.erb +0 -20
@@ -21,9 +21,9 @@ module Decidim
21
21
  #
22
22
  # Returns a string with the value of the css classes.
23
23
  def vote_button_classes(from_proposals_list)
24
- return "card__button button--sc" if from_proposals_list
24
+ return "card__button" if from_proposals_list
25
25
 
26
- "expanded button--sc"
26
+ "expanded"
27
27
  end
28
28
 
29
29
  # Public: Gets the vote limit for each user, if set.
@@ -58,7 +58,15 @@ module Decidim
58
58
  def proposal_wizard_stepper_step(step, current_step)
59
59
  return if step == :step_4 && type_of == :collaborative_drafts
60
60
 
61
- content_tag(:li, proposal_wizard_step_name(step), class: proposal_wizard_step_classes(step, current_step).to_s)
61
+ attributes = { class: proposal_wizard_step_classes(step, current_step).to_s }
62
+ step_title = proposal_wizard_step_name(step)
63
+ if step.to_s.split("_").last.to_i == proposal_wizard_step_number(current_step)
64
+ current_step_title = proposal_wizard_step_name("current_step")
65
+ step_title = content_tag(:span, "#{current_step_title}: ", class: "show-for-sr") + step_title
66
+ attributes["aria-current"] = "step"
67
+ end
68
+
69
+ content_tag(:li, step_title, attributes)
62
70
  end
63
71
 
64
72
  # Returns the list with all the steps, in html
@@ -77,15 +85,21 @@ module Decidim
77
85
 
78
86
  # Returns a string with the current step number and the total steps number
79
87
  #
80
- # step - A symbol of the target step
81
88
  def proposal_wizard_current_step_of(step)
82
89
  current_step_num = proposal_wizard_step_number(step)
83
- content_tag :span, class: "text-small" do
84
- concat t(:"decidim.proposals.proposals.wizard_steps.step_of", current_step_num: current_step_num, total_steps: total_steps)
90
+ see_steps = content_tag(:span, class: "hide-for-large") do
85
91
  concat " ("
86
92
  concat content_tag :a, t(:"decidim.proposals.proposals.wizard_steps.see_steps"), "data-toggle": "steps"
87
93
  concat ")"
88
94
  end
95
+ content_tag :span, class: "text-small" do
96
+ concat t(:"decidim.proposals.proposals.wizard_steps.step_of", current_step_num: current_step_num, total_steps: total_steps)
97
+ concat see_steps
98
+ end
99
+ end
100
+
101
+ def proposal_wizard_steps_title
102
+ t("title", scope: "decidim.proposals.#{type_of}.wizard_steps")
89
103
  end
90
104
 
91
105
  # Returns a boolean if the step has a help text defined
@@ -146,12 +160,15 @@ module Decidim
146
160
  url
147
161
  end
148
162
 
149
- def wizard_aside_back_text
163
+ def wizard_aside_back_text(from = nil)
164
+ key = "back"
165
+ key = "back_from_#{from}" if from
166
+
150
167
  case type_of
151
168
  when :collaborative_drafts
152
- t("back", scope: "decidim.proposals.collaborative_drafts.wizard_aside").html_safe
169
+ t(key, scope: "decidim.proposals.collaborative_drafts.wizard_aside").html_safe
153
170
  else
154
- t("back", scope: "decidim.proposals.proposals.wizard_aside").html_safe
171
+ t(key, scope: "decidim.proposals.proposals.wizard_aside").html_safe
155
172
  end
156
173
  end
157
174
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # Simple helpers to handle markup variations for proposals
6
+ module ProposalsHelper
7
+ def proposal_reason_callout_args
8
+ {
9
+ announcement: {
10
+ title: proposal_reason_callout_title,
11
+ body: decidim_sanitize(translated_attribute(@proposal.answer))
12
+ },
13
+ callout_class: proposal_reason_callout_class
14
+ }
15
+ end
16
+
17
+ def proposal_reason_callout_class
18
+ case @proposal.state
19
+ when "accepted"
20
+ "success"
21
+ when "evaluating"
22
+ "warning"
23
+ when "rejected"
24
+ "alert"
25
+ else
26
+ ""
27
+ end
28
+ end
29
+
30
+ def proposal_reason_callout_title
31
+ i18n_key = case @proposal.state
32
+ when "evaluating"
33
+ "proposal_in_evaluation_reason"
34
+ else
35
+ "proposal_#{@proposal.state}_reason"
36
+ end
37
+
38
+ t(i18n_key, scope: "decidim.proposals.proposals.show")
39
+ end
40
+
41
+ def filter_proposals_state_values
42
+ Decidim::CheckBoxesTreeHelper::TreeNode.new(
43
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("", t("decidim.proposals.application_helper.filter_state_values.all")),
44
+ [
45
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("accepted", t("decidim.proposals.application_helper.filter_state_values.accepted")),
46
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("evaluating", t("decidim.proposals.application_helper.filter_state_values.evaluating")),
47
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("not_answered", t("decidim.proposals.application_helper.filter_state_values.not_answered")),
48
+ Decidim::CheckBoxesTreeHelper::TreePoint.new("rejected", t("decidim.proposals.application_helper.filter_state_values.rejected"))
49
+ ]
50
+ )
51
+ end
52
+
53
+ def proposal_has_costs?
54
+ @proposal.cost.present? &&
55
+ translated_attribute(@proposal.cost_report).present? &&
56
+ translated_attribute(@proposal.execution_period).present?
57
+ end
58
+
59
+ def resource_version(resource, options = {})
60
+ return unless resource.respond_to?(:amendable?) && resource.amendable?
61
+
62
+ super
63
+ end
64
+ end
65
+ end
66
+ end
@@ -24,6 +24,10 @@ module Decidim
24
24
  include Decidim::Amendable
25
25
  include Decidim::NewsletterParticipant
26
26
  include Decidim::Randomable
27
+ include Decidim::Endorsable
28
+ include Decidim::Proposals::Valuatable
29
+
30
+ POSSIBLE_STATES = %w(not_answered evaluating accepted rejected withdrawn).freeze
27
31
 
28
32
  fingerprint fields: [:title, :body]
29
33
 
@@ -34,8 +38,6 @@ module Decidim
34
38
 
35
39
  component_manifest_name "proposals"
36
40
 
37
- has_many :endorsements, foreign_key: "decidim_proposal_id", class_name: "ProposalEndorsement", dependent: :destroy, counter_cache: "proposal_endorsements_count"
38
-
39
41
  has_many :votes,
40
42
  -> { final },
41
43
  foreign_key: "decidim_proposal_id",
@@ -49,14 +51,56 @@ module Decidim
49
51
 
50
52
  geocoded_by :address, http_headers: ->(proposal) { { "Referer" => proposal.component.organization.host } }
51
53
 
52
- scope :accepted, -> { where(state: "accepted") }
53
- scope :rejected, -> { where(state: "rejected") }
54
- scope :evaluating, -> { where(state: "evaluating") }
54
+ scope :answered, -> { where.not(answered_at: nil) }
55
+ scope :not_answered, -> { where(answered_at: nil) }
56
+
57
+ scope :state_not_published, -> { where(state_published_at: nil) }
58
+ scope :state_published, -> { where.not(state_published_at: nil).where.not(state: nil) }
59
+
60
+ scope :accepted, -> { state_published.where(state: "accepted") }
61
+ scope :rejected, -> { state_published.where(state: "rejected") }
62
+ scope :evaluating, -> { state_published.where(state: "evaluating") }
55
63
  scope :withdrawn, -> { where(state: "withdrawn") }
56
- scope :except_rejected, -> { where.not(state: "rejected").or(where(state: nil)) }
64
+ scope :except_rejected, -> { where.not(state: "rejected").or(state_not_published) }
57
65
  scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
58
66
  scope :drafts, -> { where(published_at: nil) }
67
+ scope :except_drafts, -> { where.not(published_at: nil) }
59
68
  scope :published, -> { where.not(published_at: nil) }
69
+ scope :official_origin, lambda {
70
+ where.not(coauthorships_count: 0)
71
+ .joins(:coauthorships)
72
+ .where(decidim_coauthorships: { decidim_author_type: "Decidim::Organization" })
73
+ }
74
+ scope :citizens_origin, lambda {
75
+ where.not(coauthorships_count: 0)
76
+ .joins(:coauthorships)
77
+ .where.not(decidim_coauthorships: { decidim_author_type: "Decidim::Organization" })
78
+ }
79
+ scope :user_group_origin, lambda {
80
+ where.not(coauthorships_count: 0)
81
+ .joins(:coauthorships)
82
+ .where(decidim_coauthorships: { decidim_author_type: "Decidim::UserBaseEntity" })
83
+ .where.not(decidim_coauthorships: { decidim_user_group_id: nil })
84
+ }
85
+ scope :meeting_origin, lambda {
86
+ where.not(coauthorships_count: 0)
87
+ .joins(:coauthorships)
88
+ .where(decidim_coauthorships: { decidim_author_type: "Decidim::Meetings::Meeting" })
89
+ }
90
+ scope :sort_by_valuation_assignments_count_asc, lambda {
91
+ order(sort_by_valuation_assignments_count_nulls_last_query + "ASC NULLS FIRST")
92
+ }
93
+
94
+ scope :sort_by_valuation_assignments_count_desc, lambda {
95
+ order(sort_by_valuation_assignments_count_nulls_last_query + "DESC NULLS LAST")
96
+ }
97
+
98
+ def self.with_valuation_assigned_to(user, space)
99
+ valuator_roles = space.user_roles(:valuator).where(user: user)
100
+
101
+ includes(:valuation_assignments)
102
+ .where(decidim_proposals_valuation_assignments: { valuator_role_id: valuator_roles })
103
+ end
60
104
 
61
105
  acts_as_list scope: :decidim_component_id
62
106
 
@@ -100,10 +144,9 @@ module Decidim
100
144
 
101
145
  participants_has_voted_ids = Decidim::Proposals::ProposalVote.joins(:proposal).where(proposal: proposals).joins(:author).map(&:decidim_author_id).flatten.compact.uniq
102
146
 
103
- endorsements_participants_ids = Decidim::Proposals::ProposalEndorsement.joins(:proposal)
104
- .where(proposal: proposals)
105
- .where(decidim_author_type: "Decidim::UserBaseEntity")
106
- .map(&:decidim_author_id).flatten.compact.uniq
147
+ endorsements_participants_ids = Decidim::Endorsement.where(resource: proposals)
148
+ .where(decidim_author_type: "Decidim::UserBaseEntity")
149
+ .map(&:decidim_author_id).flatten.compact.uniq
107
150
 
108
151
  (endorsements_participants_ids + participants_has_voted_ids + coauthors_recipients_ids).flatten.compact.uniq
109
152
  end
@@ -124,54 +167,75 @@ module Decidim
124
167
  ProposalVote.where(proposal: self, author: user).any?
125
168
  end
126
169
 
127
- # Public: Check if the user has endorsed the proposal.
128
- # - user_group: may be nil if user is not representing any user_group.
170
+ # Public: Checks if the proposal has been published or not.
129
171
  #
130
172
  # Returns Boolean.
131
- def endorsed_by?(user, user_group = nil)
132
- endorsements.where(author: user, user_group: user_group).any?
173
+ def published?
174
+ published_at.present?
133
175
  end
134
176
 
135
- # Public: Checks if the proposal has been published or not.
177
+ # Public: Returns the published state of the proposal.
136
178
  #
137
179
  # Returns Boolean.
138
- def published?
139
- published_at.present?
180
+ def state
181
+ return amendment.state if emendation?
182
+ return nil unless published_state? || withdrawn?
183
+
184
+ super
185
+ end
186
+
187
+ # This is only used to define the setter, as the getter will be overriden below.
188
+ alias_attribute :internal_state, :state
189
+
190
+ # Public: Returns the internal state of the proposal.
191
+ #
192
+ # Returns Boolean.
193
+ def internal_state
194
+ return amendment.state if emendation?
195
+
196
+ self[:state]
197
+ end
198
+
199
+ # Public: Checks if the organization has published the state for the proposal.
200
+ #
201
+ # Returns Boolean.
202
+ def published_state?
203
+ emendation? || state_published_at.present?
140
204
  end
141
205
 
142
206
  # Public: Checks if the organization has given an answer for the proposal.
143
207
  #
144
208
  # Returns Boolean.
145
209
  def answered?
146
- answered_at.present? && state.present?
210
+ answered_at.present?
211
+ end
212
+
213
+ # Public: Checks if the author has withdrawn the proposal.
214
+ #
215
+ # Returns Boolean.
216
+ def withdrawn?
217
+ internal_state == "withdrawn"
147
218
  end
148
219
 
149
220
  # Public: Checks if the organization has accepted a proposal.
150
221
  #
151
222
  # Returns Boolean.
152
223
  def accepted?
153
- answered? && state == "accepted"
224
+ state == "accepted"
154
225
  end
155
226
 
156
227
  # Public: Checks if the organization has rejected a proposal.
157
228
  #
158
229
  # Returns Boolean.
159
230
  def rejected?
160
- answered? && state == "rejected"
231
+ state == "rejected"
161
232
  end
162
233
 
163
234
  # Public: Checks if the organization has marked the proposal as evaluating it.
164
235
  #
165
236
  # Returns Boolean.
166
237
  def evaluating?
167
- answered? && state == "evaluating"
168
- end
169
-
170
- # Public: Checks if the author has withdrawn the proposal.
171
- #
172
- # Returns Boolean.
173
- def withdrawn?
174
- state == "withdrawn"
238
+ state == "evaluating"
175
239
  end
176
240
 
177
241
  # Public: Overrides the `reported_content_url` Reportable concern method.
@@ -221,7 +285,7 @@ module Decidim
221
285
  def editable_by?(user)
222
286
  return true if draft?
223
287
 
224
- !answered? && within_edit_time_limit? && !copied_from_other_component? && created_by?(user)
288
+ !published_state? && within_edit_time_limit? && !copied_from_other_component? && created_by?(user)
225
289
  end
226
290
 
227
291
  # Checks whether the user can withdraw the given proposal.
@@ -249,6 +313,69 @@ module Decidim
249
313
  Arel.sql(query)
250
314
  end
251
315
 
316
+ # Defines the base query so that ransack can actually sort by this value
317
+ def self.sort_by_valuation_assignments_count_nulls_last_query
318
+ <<-SQL
319
+ (
320
+ SELECT COUNT(decidim_proposals_valuation_assignments.id)
321
+ FROM decidim_proposals_valuation_assignments
322
+ WHERE decidim_proposals_valuation_assignments.decidim_proposal_id = decidim_proposals_proposals.id
323
+ GROUP BY decidim_proposals_valuation_assignments.decidim_proposal_id
324
+ )
325
+ SQL
326
+ end
327
+
328
+ # method to filter by assigned valuator role ID
329
+ def self.valuator_role_ids_has(value)
330
+ query = <<-SQL
331
+ :value = any(
332
+ (SELECT decidim_proposals_valuation_assignments.valuator_role_id
333
+ FROM decidim_proposals_valuation_assignments
334
+ WHERE decidim_proposals_valuation_assignments.decidim_proposal_id = decidim_proposals_proposals.id
335
+ )
336
+ )
337
+ SQL
338
+ where(query, value: value)
339
+ end
340
+
341
+ def self.ransackable_scopes(_auth = nil)
342
+ [:valuator_role_ids_has]
343
+ end
344
+
345
+ ransacker :state_published do
346
+ Arel.sql("CASE
347
+ WHEN EXISTS (
348
+ SELECT 1 FROM decidim_amendments
349
+ WHERE decidim_amendments.decidim_emendation_type = 'Decidim::Proposals::Proposal'
350
+ AND decidim_amendments.decidim_emendation_id = decidim_proposals_proposals.id
351
+ ) THEN 0
352
+ WHEN state_published_at IS NULL AND answered_at IS NOT NULL THEN 2
353
+ WHEN state_published_at IS NOT NULL THEN 1
354
+ ELSE 0 END
355
+ ")
356
+ end
357
+
358
+ ransacker :state do
359
+ Arel.sql("CASE WHEN state = 'withdrawn' THEN 'withdrawn' WHEN state_published_at IS NULL THEN NULL ELSE state END")
360
+ end
361
+
362
+ ransacker :id_string do
363
+ Arel.sql(%{cast("decidim_proposals_proposals"."id" as text)})
364
+ end
365
+
366
+ ransacker :is_emendation do |_parent|
367
+ query = <<-SQL
368
+ (
369
+ SELECT EXISTS (
370
+ SELECT 1 FROM decidim_amendments
371
+ WHERE decidim_amendments.decidim_emendation_type = 'Decidim::Proposals::Proposal'
372
+ AND decidim_amendments.decidim_emendation_id = decidim_proposals_proposals.id
373
+ )
374
+ )
375
+ SQL
376
+ Arel.sql(query)
377
+ end
378
+
252
379
  def self.export_serializer
253
380
  Decidim::Proposals::ProposalSerializer
254
381
  end
@@ -270,6 +397,17 @@ module Decidim
270
397
  Time.current < limit
271
398
  end
272
399
 
400
+ def process_amendment_state_change!
401
+ return unless %w(accepted rejected evaluating withdrawn).member?(amendment.state)
402
+
403
+ PaperTrail.request(enabled: false) do
404
+ update!(
405
+ state: amendment.state,
406
+ state_published_at: Time.current
407
+ )
408
+ end
409
+ end
410
+
273
411
  private
274
412
 
275
413
  def copied_from_other_component?
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # A valuation assignment links a proposal and a Valuator user role.
6
+ # Valuators will be users in charge of defining the monetary cost of a
7
+ # proposal.
8
+ class ValuationAssignment < ApplicationRecord
9
+ include Decidim::Traceable
10
+ include Decidim::Loggable
11
+
12
+ belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal"
13
+ belongs_to :valuator_role, polymorphic: true
14
+
15
+ def self.log_presenter_class_for(_log)
16
+ Decidim::Proposals::AdminLog::ValuationAssignmentPresenter
17
+ end
18
+
19
+ def valuator
20
+ valuator_role.user
21
+ end
22
+ end
23
+ end
24
+ end
@@ -8,18 +8,22 @@ module Decidim
8
8
  # The public part needs to be implemented yet
9
9
  return permission_action if permission_action.scope != :admin
10
10
 
11
- if create_permission_action?
12
- # There's no special condition to create proposal notes, only
13
- # users with access to the admin section can do it.
14
- allow! if permission_action.subject == :proposal_note
15
-
16
- # Proposals can only be created from the admin when the
17
- # corresponding setting is enabled.
18
- toggle_allow(admin_creation_is_enabled?) if permission_action.subject == :proposal
11
+ # Valuators can only perform these actions
12
+ if user_is_valuator?
13
+ if valuator_assigned_to_proposal?
14
+ can_create_proposal_note?
15
+ can_create_proposal_answer?
16
+ end
17
+ can_export_proposals?
18
+ valuator_can_unassign_valuator_from_proposals?
19
+
20
+ return permission_action
21
+ end
19
22
 
20
- # Proposals can only be answered from the admin when the
21
- # corresponding setting is enabled.
22
- toggle_allow(admin_proposal_answering_is_enabled?) if permission_action.subject == :proposal_answer
23
+ if create_permission_action?
24
+ can_create_proposal_note?
25
+ can_create_proposal_from_admin?
26
+ can_create_proposal_answer?
23
27
  end
24
28
 
25
29
  # Admins can only edit official proposals if they are within the
@@ -29,15 +33,30 @@ module Decidim
29
33
  # Every user allowed by the space can update the category of the proposal
30
34
  allow! if permission_action.subject == :proposal_category && permission_action.action == :update
31
35
 
36
+ # Every user allowed by the space can update the scope of the proposal
37
+ allow! if permission_action.subject == :proposal_scope && permission_action.action == :update
38
+
32
39
  # Every user allowed by the space can import proposals from another_component
33
40
  allow! if permission_action.subject == :proposals && permission_action.action == :import
34
41
 
42
+ # Every user allowed by the space can export proposals
43
+ can_export_proposals?
44
+
35
45
  # Every user allowed by the space can merge proposals to another component
36
46
  allow! if permission_action.subject == :proposals && permission_action.action == :merge
37
47
 
38
48
  # Every user allowed by the space can split proposals to another component
39
49
  allow! if permission_action.subject == :proposals && permission_action.action == :split
40
50
 
51
+ # Every user allowed by the space can assign proposals to a valuator
52
+ allow! if permission_action.subject == :proposals && permission_action.action == :assign_to_valuator
53
+
54
+ # Every user allowed by the space can unassign a valuator from proposals
55
+ can_unassign_valuator_from_proposals?
56
+
57
+ # Only admin users can publish many answers at once
58
+ toggle_allow(user.admin?) if permission_action.subject == :proposals && permission_action.action == :publish_answers
59
+
41
60
  if permission_action.subject == :participatory_texts && participatory_texts_are_enabled?
42
61
  # Every user allowed by the space can manage (import, update and publish) participatory texts to proposals
43
62
  allow! if permission_action.action == :manage
@@ -52,6 +71,23 @@ module Decidim
52
71
  @proposal ||= context.fetch(:proposal, nil)
53
72
  end
54
73
 
74
+ def user_valuator_role
75
+ @user_valuator_role ||= space.user_roles(:valuator).find_by(user: user)
76
+ end
77
+
78
+ def user_is_valuator?
79
+ return if user.admin?
80
+
81
+ user_valuator_role.present?
82
+ end
83
+
84
+ def valuator_assigned_to_proposal?
85
+ @valuator_assigned_to_proposal ||=
86
+ Decidim::Proposals::ValuationAssignment
87
+ .where(proposal: proposal, valuator_role: user_valuator_role)
88
+ .any?
89
+ end
90
+
55
91
  def admin_creation_is_enabled?
56
92
  current_settings.try(:creation_enabled?) &&
57
93
  component_settings.try(:official_proposals_enabled)
@@ -75,6 +111,36 @@ module Decidim
75
111
  def participatory_texts_are_enabled?
76
112
  component_settings.participatory_texts_enabled?
77
113
  end
114
+
115
+ # There's no special condition to create proposal notes, only
116
+ # users with access to the admin section can do it.
117
+ def can_create_proposal_note?
118
+ allow! if permission_action.subject == :proposal_note
119
+ end
120
+
121
+ # Proposals can only be created from the admin when the
122
+ # corresponding setting is enabled.
123
+ def can_create_proposal_from_admin?
124
+ toggle_allow(admin_creation_is_enabled?) if permission_action.subject == :proposal
125
+ end
126
+
127
+ # Proposals can only be answered from the admin when the
128
+ # corresponding setting is enabled.
129
+ def can_create_proposal_answer?
130
+ toggle_allow(admin_proposal_answering_is_enabled?) if permission_action.subject == :proposal_answer
131
+ end
132
+
133
+ def can_unassign_valuator_from_proposals?
134
+ allow! if permission_action.subject == :proposals && permission_action.action == :unassign_from_valuator
135
+ end
136
+
137
+ def valuator_can_unassign_valuator_from_proposals?
138
+ can_unassign_valuator_from_proposals? if user == context.fetch(:valuator, nil)
139
+ end
140
+
141
+ def can_export_proposals?
142
+ allow! if permission_action.subject == :proposals && permission_action.action == :export
143
+ end
78
144
  end
79
145
  end
80
146
  end