decidim-initiatives 0.20.0 → 0.23.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -0
  3. data/app/assets/images/decidim/gamification/badges/initiatives.svg +1 -87
  4. data/app/assets/images/decidim/initiatives/icon.svg +1 -3
  5. data/app/assets/javascripts/decidim/initiatives/admin/initiatives_types.js.es6 +18 -0
  6. data/app/assets/stylesheet/decidim/initiatives/initiatives-votes.css.scss +0 -1
  7. data/app/assets/stylesheet/decidim/initiatives/initiatives.scss +6 -8
  8. data/app/assets/stylesheet/decidim/initiatives/popularity_item.css.scss +0 -1
  9. data/app/assets/stylesheet/decidim/initiatives/print-initiative.css.scss +0 -3
  10. data/app/cells/decidim/initiatives/content_blocks/highlighted_initiatives/show.erb +4 -3
  11. data/app/cells/decidim/initiatives/initiative_m_cell.rb +25 -0
  12. data/app/cells/decidim/initiatives_votes/vote/show.erb +12 -9
  13. data/app/cells/decidim/initiatives_votes/vote_cell.rb +7 -0
  14. data/app/commands/decidim/initiatives/admin/create_initiative_type.rb +6 -1
  15. data/app/commands/decidim/initiatives/admin/send_initiative_to_technical_validation.rb +17 -0
  16. data/app/commands/decidim/initiatives/admin/update_initiative.rb +29 -10
  17. data/app/commands/decidim/initiatives/admin/update_initiative_type.rb +6 -1
  18. data/app/commands/decidim/initiatives/attachment_methods.rb +37 -0
  19. data/app/commands/decidim/initiatives/create_initiative.rb +25 -3
  20. data/app/commands/decidim/initiatives/unvote_initiative.rb +4 -10
  21. data/app/commands/decidim/initiatives/vote_initiative.rb +47 -31
  22. data/app/controllers/concerns/decidim/initiatives/admin/filterable.rb +51 -0
  23. data/app/controllers/concerns/decidim/initiatives/orderable.rb +3 -1
  24. data/app/controllers/concerns/decidim/initiatives/single_initiative_type.rb +26 -0
  25. data/app/controllers/decidim/initiatives/admin/answers_controller.rb +2 -3
  26. data/app/controllers/decidim/initiatives/admin/initiatives_controller.rb +28 -15
  27. data/app/controllers/decidim/initiatives/create_initiative_controller.rb +36 -6
  28. data/app/controllers/decidim/initiatives/initiative_signatures_controller.rb +19 -23
  29. data/app/controllers/decidim/initiatives/initiative_votes_controller.rb +11 -5
  30. data/app/controllers/decidim/initiatives/initiatives_controller.rb +28 -13
  31. data/app/controllers/decidim/initiatives/initiatives_type_scopes_controller.rb +9 -1
  32. data/app/controllers/decidim/initiatives/versions_controller.rb +20 -0
  33. data/app/controllers/decidim/initiatives/{initiative_widgets_controller.rb → widgets_controller.rb} +2 -2
  34. data/app/events/decidim/initiatives/admin/initiative_sent_to_technical_validation_event.rb +21 -0
  35. data/app/events/decidim/initiatives/admin/support_threshold_reached_event.rb +13 -0
  36. data/app/forms/decidim/initiatives/admin/initiative_form.rb +49 -7
  37. data/app/forms/decidim/initiatives/admin/initiative_type_form.rb +8 -1
  38. data/app/forms/decidim/initiatives/initiative_form.rb +56 -1
  39. data/app/forms/decidim/initiatives/vote_form.rb +133 -76
  40. data/app/helpers/decidim/initiatives/application_helper.rb +104 -0
  41. data/app/helpers/decidim/initiatives/initiative_helper.rb +13 -0
  42. data/app/helpers/decidim/initiatives/initiatives_helper.rb +10 -0
  43. data/app/jobs/decidim/initiatives/export_initiatives_job.rb +25 -0
  44. data/app/mailers/decidim/initiatives/initiatives_mailer.rb +0 -21
  45. data/app/models/concerns/decidim/initiatives/has_area.rb +30 -0
  46. data/app/models/decidim/initiative.rb +184 -44
  47. data/app/models/decidim/initiatives_type.rb +5 -2
  48. data/app/models/decidim/initiatives_type_scope.rb +5 -1
  49. data/app/models/decidim/initiatives_vote.rb +19 -23
  50. data/app/permissions/decidim/initiatives/admin/permissions.rb +19 -8
  51. data/app/permissions/decidim/initiatives/permissions.rb +37 -14
  52. data/app/presenters/decidim/initiatives/initiative_stats_presenter.rb +1 -5
  53. data/app/queries/decidim/initiatives/admin/manageable_initiatives.rb +7 -39
  54. data/app/serializers/decidim/initiatives/initiative_serializer.rb +32 -0
  55. data/app/services/decidim/initiatives/diff_renderer.rb +18 -0
  56. data/app/services/decidim/initiatives/initiative_search.rb +59 -15
  57. data/app/services/decidim/initiatives/status_change_notifier.rb +4 -5
  58. data/app/types/decidim/initiatives/initiative_api_type.rb +26 -0
  59. data/app/types/decidim/initiatives/initiative_committee_member_type.rb +18 -0
  60. data/app/types/decidim/initiatives/initiative_type.rb +42 -0
  61. data/app/views/decidim/initiatives/admin/answers/_info_initiative.html.erb +1 -1
  62. data/app/views/decidim/initiatives/admin/exports/_dropdown.html.erb +8 -0
  63. data/app/views/decidim/initiatives/admin/initiatives/_form.html.erb +59 -14
  64. data/app/views/decidim/initiatives/admin/initiatives/_initiative_attachments.erb +43 -0
  65. data/app/views/decidim/initiatives/admin/initiatives/export_pdf_signatures.pdf.erb +12 -9
  66. data/app/views/decidim/initiatives/admin/initiatives/index.html.erb +10 -45
  67. data/app/views/decidim/initiatives/admin/initiatives_types/_form.html.erb +47 -10
  68. data/app/views/decidim/initiatives/admin/initiatives_types/_initiative_type_scopes.html.erb +28 -25
  69. data/app/views/decidim/initiatives/create_initiative/fill_data.html.erb +46 -10
  70. data/app/views/decidim/initiatives/create_initiative/finish.html.erb +17 -10
  71. data/app/views/decidim/initiatives/create_initiative/previous_form.html.erb +2 -1
  72. data/app/views/decidim/initiatives/create_initiative/promotal_committee.html.erb +1 -1
  73. data/app/views/decidim/initiatives/create_initiative/select_initiative_type.html.erb +1 -2
  74. data/app/views/decidim/initiatives/create_initiative/show_similar_initiatives.html.erb +1 -1
  75. data/app/views/decidim/initiatives/initiative_signatures/fill_personal_data.html.erb +1 -1
  76. data/app/views/decidim/initiatives/initiative_signatures/update_buttons_and_counters.js.erb +1 -1
  77. data/app/views/decidim/initiatives/initiative_votes/update_buttons_and_counters.js.erb +1 -1
  78. data/app/views/decidim/initiatives/initiatives/_author.html.erb +1 -1
  79. data/app/views/decidim/initiatives/initiatives/_filters.html.erb +16 -28
  80. data/app/views/decidim/initiatives/initiatives/_index_header.html.erb +39 -5
  81. data/app/views/decidim/initiatives/initiatives/_initiatives.html.erb +11 -1
  82. data/app/views/decidim/initiatives/initiatives/_interactions.html.erb +2 -3
  83. data/app/views/decidim/initiatives/initiatives/_progress_bar.html.erb +24 -9
  84. data/app/views/decidim/initiatives/initiatives/_tags.html.erb +3 -0
  85. data/app/views/decidim/initiatives/initiatives/_vote_cabin.html.erb +1 -13
  86. data/app/views/decidim/initiatives/initiatives/index.html.erb +1 -1
  87. data/app/views/decidim/initiatives/initiatives/show.html.erb +2 -3
  88. data/app/views/decidim/initiatives/versions/index.html.erb +8 -0
  89. data/app/views/decidim/initiatives/versions/show.html.erb +10 -0
  90. data/app/views/layouts/decidim/_initiative_creation_header.html.erb +2 -1
  91. data/app/views/layouts/decidim/_initiative_header.html.erb +2 -1
  92. data/app/views/layouts/decidim/_initiative_signature_creation_header.html.erb +1 -1
  93. data/app/views/layouts/decidim/initiative.html.erb +1 -0
  94. data/app/views/layouts/decidim/initiative_creation.html.erb +1 -2
  95. data/app/views/layouts/decidim/initiative_signature_creation.html.erb +2 -2
  96. data/config/locales/am-ET.yml +1 -0
  97. data/config/locales/ar.yml +17 -21
  98. data/config/locales/bg-BG.yml +13 -0
  99. data/config/locales/bg.yml +13 -0
  100. data/config/locales/ca.yml +100 -22
  101. data/config/locales/cs.yml +110 -32
  102. data/config/locales/da-DK.yml +1 -0
  103. data/config/locales/da.yml +1 -0
  104. data/config/locales/de.yml +75 -21
  105. data/config/locales/el-GR.yml +1 -0
  106. data/config/locales/el.yml +525 -0
  107. data/config/locales/en.yml +102 -24
  108. data/config/locales/eo.yml +1 -0
  109. data/config/locales/es-MX.yml +100 -21
  110. data/config/locales/es-PY.yml +100 -21
  111. data/config/locales/es.yml +104 -25
  112. data/config/locales/et-EE.yml +1 -0
  113. data/config/locales/et.yml +1 -0
  114. data/config/locales/eu.yml +4 -21
  115. data/config/locales/fi-plain.yml +99 -21
  116. data/config/locales/fi.yml +117 -39
  117. data/config/locales/fr-CA.yml +557 -0
  118. data/config/locales/fr.yml +101 -23
  119. data/config/locales/ga-IE.yml +1 -0
  120. data/config/locales/gl.yml +4 -21
  121. data/config/locales/hr-HR.yml +1 -0
  122. data/config/locales/hr.yml +1 -0
  123. data/config/locales/hu.yml +30 -24
  124. data/config/locales/id-ID.yml +4 -21
  125. data/config/locales/is-IS.yml +251 -0
  126. data/config/locales/is.yml +251 -0
  127. data/config/locales/it.yml +116 -61
  128. data/config/locales/ja-JP.yml +529 -0
  129. data/config/locales/ja.yml +549 -0
  130. data/config/locales/ko-KR.yml +1 -0
  131. data/config/locales/ko.yml +1 -0
  132. data/config/locales/lt-LT.yml +1 -0
  133. data/config/locales/lt.yml +1 -0
  134. data/config/locales/lv.yml +525 -0
  135. data/config/locales/mt-MT.yml +1 -0
  136. data/config/locales/mt.yml +1 -0
  137. data/config/locales/nl.yml +76 -21
  138. data/config/locales/no.yml +368 -9
  139. data/config/locales/om-ET.yml +1 -0
  140. data/config/locales/pl.yml +260 -189
  141. data/config/locales/pt-BR.yml +5 -22
  142. data/config/locales/pt.yml +231 -179
  143. data/config/locales/ro-RO.yml +533 -0
  144. data/config/locales/ru.yml +4 -21
  145. data/config/locales/sk-SK.yml +468 -0
  146. data/config/locales/sk.yml +458 -0
  147. data/config/locales/sl.yml +24 -0
  148. data/config/locales/so-SO.yml +1 -0
  149. data/config/locales/sr-CS.yml +8 -0
  150. data/config/locales/sv.yml +102 -31
  151. data/config/locales/ti-ER.yml +1 -0
  152. data/config/locales/tr-TR.yml +4 -21
  153. data/config/locales/uk.yml +4 -21
  154. data/config/locales/vi-VN.yml +1 -0
  155. data/config/locales/vi.yml +1 -0
  156. data/config/locales/zh-CN.yml +549 -0
  157. data/config/locales/zh-TW.yml +1 -0
  158. data/db/migrate/20191106144259_add_settings_to_initiatives_types.rb +8 -0
  159. data/db/migrate/20191107134847_add_scopes_to_initiatives_votes.rb +28 -0
  160. data/db/migrate/20191116170841_allow_multiple_initiative_votes_counter_caches.rb +32 -0
  161. data/db/migrate/20191118105634_allow_multiple_offline_votes.rb +34 -0
  162. data/db/migrate/20200320105920_index_foreign_keys_in_decidim_initiatives.rb +8 -0
  163. data/db/migrate/20200320105921_index_foreign_keys_in_decidim_initiatives_votes.rb +8 -0
  164. data/db/migrate/20200417120551_add_custom_signature_end_time_option.rb +7 -0
  165. data/db/migrate/20200424110930_add_attachments_enabled_option.rb +7 -0
  166. data/db/migrate/20200514085422_add_area_to_initiatives.rb +7 -0
  167. data/db/migrate/20200514102631_add_area_enabled_option_to_initiatives.rb +7 -0
  168. data/db/migrate/20200528151456_remove_user_groups_from_initiative_votes.rb +7 -0
  169. data/db/migrate/20200827154214_add_commentable_counter_cache_to_initiatives.rb +9 -0
  170. data/db/seeds/city.jpeg +0 -0
  171. data/db/seeds/city2.jpeg +0 -0
  172. data/lib/decidim/api/initiative_type_interface.rb +13 -0
  173. data/lib/decidim/initiatives/admin_engine.rb +5 -0
  174. data/lib/decidim/initiatives/api.rb +7 -0
  175. data/lib/decidim/initiatives/engine.rb +11 -2
  176. data/lib/decidim/initiatives/participatory_space.rb +18 -1
  177. data/lib/decidim/initiatives/query_extensions.rb +40 -0
  178. data/lib/decidim/initiatives/test/factories.rb +57 -10
  179. data/lib/decidim/initiatives/version.rb +1 -1
  180. data/lib/tasks/decidim_initiatives.rake +1 -3
  181. metadata +95 -22
  182. data/app/views/decidim/initiatives/initiative_widgets/show.html.erb +0 -4
  183. data/app/views/decidim/initiatives/initiatives/_supports.html.erb +0 -22
  184. data/app/views/decidim/initiatives/initiatives/signature_identities.html.erb +0 -42
  185. data/app/views/decidim/initiatives/initiatives_mailer/notify_validating_request.html.erb +0 -3
@@ -5,6 +5,7 @@ module Decidim
5
5
  # Helper method related to initiative object and its internal state.
6
6
  module InitiativeHelper
7
7
  include Decidim::SanitizeHelper
8
+ include Decidim::ResourceVersionsHelper
8
9
 
9
10
  # Public: The css class applied based on the initiative state to
10
11
  # the initiative badge.
@@ -99,6 +100,18 @@ module Decidim
99
100
 
100
101
  send("#{tag}_to", "", html_options, &block)
101
102
  end
103
+
104
+ def can_edit_custom_signature_end_date?(initiative)
105
+ return false unless initiative.custom_signature_end_date_enabled?
106
+
107
+ initiative.created? || initiative.validating?
108
+ end
109
+
110
+ def can_edit_area?(initiative)
111
+ return false unless initiative.area_enabled?
112
+
113
+ initiative.created? || initiative.validating?
114
+ end
102
115
  end
103
116
  end
104
117
  end
@@ -7,6 +7,7 @@ module Decidim
7
7
  def initiatives_filter_form_for(filter)
8
8
  content_tag :div, class: "filters" do
9
9
  form_for filter,
10
+ namespace: filter_form_namespace,
10
11
  builder: Decidim::Initiatives::InitiativesFilterFormBuilder,
11
12
  url: url_for,
12
13
  as: :filter,
@@ -17,6 +18,15 @@ module Decidim
17
18
  end
18
19
  end
19
20
  end
21
+
22
+ private
23
+
24
+ # Creates a unique namespace for a filter form to prevent dupliacte IDs in
25
+ # the DOM when multiple filter forms are rendered with the same fields (e.g.
26
+ # for desktop and mobile).
27
+ def filter_form_namespace
28
+ "filters_#{SecureRandom.uuid}"
29
+ end
20
30
  end
21
31
  end
22
32
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Initiatives
5
+ class ExportInitiativesJob < ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(user, format)
9
+ export_data = Decidim::Exporters.find_exporter(format).new(collection, serializer).export
10
+
11
+ ExportMailer.export(user, "initiatives", export_data).deliver_now
12
+ end
13
+
14
+ private
15
+
16
+ def collection
17
+ Decidim::Initiative.all
18
+ end
19
+
20
+ def serializer
21
+ Decidim::Initiatives::InitiativeSerializer
22
+ end
23
+ end
24
+ end
25
+ end
@@ -51,27 +51,6 @@ module Decidim
51
51
  end
52
52
  end
53
53
 
54
- # Notify an initiative requesting technical validation
55
- def notify_validating_request(initiative, user)
56
- return if user.email.blank?
57
-
58
- @organization = initiative.organization
59
- @link = decidim_admin_initiatives.edit_initiative_url(initiative, host: @organization.host)
60
-
61
- with_user(user) do
62
- @subject = I18n.t(
63
- "decidim.initiatives.initiatives_mailer.technical_validation_for",
64
- title: translated_attribute(initiative.title)
65
- )
66
- @body = I18n.t(
67
- "decidim.initiatives.initiatives_mailer.technical_validation_body_for",
68
- title: translated_attribute(initiative.title)
69
- )
70
-
71
- mail(to: "#{user.name} <#{user.email}>", subject: @subject)
72
- end
73
- end
74
-
75
54
  # Notify progress to all initiative subscribers.
76
55
  def notify_progress(initiative, user)
77
56
  return if user.email.blank?
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Decidim
6
+ module Initiatives
7
+ module HasArea
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ belongs_to :area,
12
+ foreign_key: "decidim_area_id",
13
+ class_name: "Decidim::Area",
14
+ optional: true
15
+
16
+ delegate :areas, to: :organization
17
+
18
+ validate :area_belongs_to_organization
19
+ end
20
+
21
+ private
22
+
23
+ def area_belongs_to_organization
24
+ return unless area && organization
25
+
26
+ errors.add(:area, :invalid) unless areas.where(id: area.id).exists?
27
+ end
28
+ end
29
+ end
30
+ end
@@ -7,7 +7,7 @@ module Decidim
7
7
  include Decidim::Authorable
8
8
  include Decidim::Participable
9
9
  include Decidim::Publicable
10
- include Decidim::Scopable
10
+ include Decidim::ScopableParticipatorySpace
11
11
  include Decidim::Comments::Commentable
12
12
  include Decidim::Followable
13
13
  include Decidim::HasAttachments
@@ -19,6 +19,10 @@ module Decidim
19
19
  include Decidim::HasReference
20
20
  include Decidim::Randomable
21
21
  include Decidim::Searchable
22
+ include Decidim::Initiatives::HasArea
23
+ include Decidim::TranslatableResource
24
+
25
+ translatable_fields :title, :description, :answer
22
26
 
23
27
  belongs_to :organization,
24
28
  foreign_key: "decidim_organization_id",
@@ -29,8 +33,9 @@ module Decidim
29
33
  class_name: "Decidim::InitiativesTypeScope",
30
34
  inverse_of: :initiatives
31
35
 
32
- delegate :type, :scope, :scope_name, to: :scoped_type, allow_nil: true
33
- delegate :promoting_committee_enabled?, to: :type
36
+ delegate :type, :scope, :scope_name, :supports_required, to: :scoped_type, allow_nil: true
37
+ delegate :attachments_enabled?, :promoting_committee_enabled?, :custom_signature_end_date_enabled?, :area_enabled?, to: :type
38
+ delegate :name, to: :area, prefix: true, allow_nil: true
34
39
 
35
40
  has_many :votes,
36
41
  foreign_key: "decidim_initiative_id",
@@ -59,41 +64,51 @@ module Decidim
59
64
 
60
65
  validates :title, :description, :state, presence: true
61
66
  validates :signature_type, presence: true
62
- validate :signature_type_allowed
63
-
64
67
  validates :hashtag,
65
68
  uniqueness: true,
66
69
  allow_blank: true,
67
70
  case_sensitive: false
71
+ validate :signature_type_allowed
68
72
 
69
73
  scope :open, lambda {
70
- published
71
- .where.not(state: [:discarded, :rejected, :accepted])
72
- .where("signature_start_date <= ?", Date.current)
73
- .where("signature_end_date >= ?", Date.current)
74
+ where.not(state: [:discarded, :rejected, :accepted, :created])
75
+ .currently_signable
74
76
  }
75
77
  scope :closed, lambda {
76
- published
77
- .where(state: [:discarded, :rejected, :accepted])
78
- .or(where("signature_start_date > ?", Date.current))
79
- .or(where("signature_end_date < ?", Date.current))
78
+ where(state: [:discarded, :rejected, :accepted])
79
+ .or(currently_unsignable)
80
80
  }
81
81
  scope :published, -> { where.not(published_at: nil) }
82
82
  scope :with_state, ->(state) { where(state: state) if state.present? }
83
83
 
84
+ scope :currently_signable, lambda {
85
+ where("signature_start_date <= ?", Date.current)
86
+ .where("signature_end_date >= ?", Date.current)
87
+ }
88
+ scope :currently_unsignable, lambda {
89
+ where("signature_start_date > ?", Date.current)
90
+ .or(where("signature_end_date < ?", Date.current))
91
+ }
92
+
93
+ scope :answered, -> { where.not(answered_at: nil) }
94
+
84
95
  scope :public_spaces, -> { published }
85
96
  scope :signature_type_updatable, -> { created }
86
97
 
87
98
  scope :order_by_most_recent, -> { order(created_at: :desc) }
88
- scope :order_by_supports, -> { order(Arel.sql("initiative_votes_count + coalesce(offline_votes, 0) desc")) }
99
+ scope :order_by_supports, -> { order(Arel.sql("(coalesce((online_votes->>'total')::int,0) + coalesce((offline_votes->>'total')::int,0)) DESC")) }
100
+ scope :order_by_most_recently_published, -> { order(published_at: :desc) }
89
101
  scope :order_by_most_commented, lambda {
90
102
  select("decidim_initiatives.*")
91
103
  .left_joins(:comments)
92
104
  .group("decidim_initiatives.id")
93
105
  .order(Arel.sql("count(decidim_comments_comments.id) desc"))
94
106
  }
107
+ scope :future_spaces, -> { none }
108
+ scope :past_spaces, -> { closed }
95
109
 
96
- after_save :notify_state_change
110
+ before_update :set_offline_votes_total
111
+ after_commit :notify_state_change
97
112
  after_create :notify_creation
98
113
 
99
114
  searchable_fields({
@@ -106,18 +121,20 @@ module Decidim
106
121
  # is Resourceable instead of ParticipatorySpaceResourceable so we can't use `visible?`
107
122
  index_on_update: ->(initiative) { initiative.published? })
108
123
 
109
- def self.future_spaces
110
- none
111
- end
112
-
113
- def self.past_spaces
114
- closed
115
- end
116
-
117
124
  def self.log_presenter_class_for(_log)
118
125
  Decidim::Initiatives::AdminLog::InitiativePresenter
119
126
  end
120
127
 
128
+ # PUBLIC banner image
129
+ #
130
+ # Overrides participatory space's banner image with the banner image defined
131
+ # for the initiative type.
132
+ #
133
+ # RETURNS string
134
+ delegate :banner_image, to: :type
135
+ delegate :document_number_authorization_handler, :promoting_committee_enabled?, to: :type
136
+ delegate :type, :scope, :scope_name, to: :scoped_type, allow_nil: true
137
+
121
138
  # PUBLIC
122
139
  #
123
140
  # Returns true when an initiative has been created by an individual person.
@@ -172,17 +189,6 @@ module Decidim
172
189
  ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
173
190
  end
174
191
 
175
- # PUBLIC banner image
176
- #
177
- # Overrides participatory space's banner image with the banner image defined
178
- # for the initiative type.
179
- #
180
- # RETURNS string
181
- delegate :banner_image, to: :type
182
-
183
- delegate :document_number_authorization_handler, to: :type
184
- delegate :supports_required, to: :scoped_type
185
-
186
192
  def votes_enabled?
187
193
  published? &&
188
194
  signature_start_date <= Date.current &&
@@ -227,11 +233,10 @@ module Decidim
227
233
  published_at: Time.current,
228
234
  state: "published",
229
235
  signature_start_date: Date.current,
230
- signature_end_date: Date.current + Decidim::Initiatives.default_signature_time_period_length
236
+ signature_end_date: signature_end_date || Date.current + Decidim::Initiatives.default_signature_time_period_length
231
237
  )
232
238
  end
233
239
 
234
- #
235
240
  # Public: Unpublishes this initiative
236
241
  #
237
242
  # Returns true if the record was properly saved, false otherwise.
@@ -251,10 +256,25 @@ module Decidim
251
256
  attributes["hashtag"].to_s.delete("#")
252
257
  end
253
258
 
259
+ # Public: Calculates the number of total current supports.
260
+ #
261
+ # Returns an Integer.
254
262
  def supports_count
255
- face_to_face_votes = offline_votes.nil? || online_signature_type? ? 0 : offline_votes
256
- digital_votes = offline_signature_type? ? 0 : (initiative_votes_count + initiative_supports_count)
257
- digital_votes + face_to_face_votes
263
+ online_votes_count + offline_votes_count
264
+ end
265
+
266
+ # Public: Calculates the number of current supports for a scope.
267
+ #
268
+ # Returns an Integer.
269
+ def supports_count_for(scope)
270
+ online_votes_count_for(scope) + offline_votes_count_for(scope)
271
+ end
272
+
273
+ # Public: Calculates the number of supports required to accept the initiative for a scope.
274
+ #
275
+ # Returns an Integer.
276
+ def supports_required_for(scope)
277
+ initiative_type_scopes.find_by(decidim_scopes_id: scope&.id).supports_required
258
278
  end
259
279
 
260
280
  # Public: Returns the percentage of required supports reached
@@ -266,7 +286,75 @@ module Decidim
266
286
 
267
287
  # Public: Whether the supports required objective has been reached
268
288
  def supports_goal_reached?
269
- supports_count >= supports_required
289
+ initiative_type_scopes.map(&:scope).all? { |scope| supports_goal_reached_for?(scope) }
290
+ end
291
+
292
+ # Public: Whether the supports required objective has been reached for a scope
293
+ def supports_goal_reached_for?(scope)
294
+ supports_count_for(scope) >= supports_required_for(scope)
295
+ end
296
+
297
+ # Public: Calculates all the votes across all the scopes.
298
+ #
299
+ # Returns an Integer.
300
+ def online_votes_count
301
+ return 0 if offline_signature_type?
302
+
303
+ online_votes["total"].to_i
304
+ end
305
+
306
+ def offline_votes_count
307
+ return 0 if online_signature_type?
308
+
309
+ offline_votes["total"].to_i
310
+ end
311
+
312
+ def online_votes_count_for(scope)
313
+ return 0 if offline_signature_type?
314
+
315
+ scope_key = (scope&.id || "global").to_s
316
+
317
+ (online_votes || {}).fetch(scope_key, 0).to_i
318
+ end
319
+
320
+ def offline_votes_count_for(scope)
321
+ return 0 if online_signature_type?
322
+
323
+ scope_key = (scope&.id || "global").to_s
324
+
325
+ (offline_votes || {}).fetch(scope_key, 0).to_i
326
+ end
327
+
328
+ def update_online_votes_counters
329
+ online_votes = votes.group(:scope).count.each_with_object({}) do |(scope, count), counters|
330
+ counters[scope&.id || "global"] = count
331
+ counters["total"] ||= 0
332
+ counters["total"] += count
333
+ end
334
+
335
+ # rubocop:disable Rails/SkipsModelValidations
336
+ update_column("online_votes", online_votes)
337
+ # rubocop:enable Rails/SkipsModelValidations
338
+ end
339
+
340
+ def set_offline_votes_total
341
+ return if offline_votes.blank? || scope.nil?
342
+
343
+ offline_votes["total"] = offline_votes[scope.id.to_s]
344
+ end
345
+
346
+ # Public: Finds all the InitiativeTypeScopes that are eligible to be voted by a user.
347
+ # Usually this is only the `scoped_type` but voting on children of the scoped type is
348
+ # enabled we have to filter all the available scopes in the initiative type to select
349
+ # the ones that are a descendant of the initiative type.
350
+ #
351
+ # Returns an Array of Decidim::InitiativesScopeType.
352
+ def votable_initiative_type_scopes
353
+ return Array(scoped_type) unless type.child_scope_threshold_enabled?
354
+
355
+ initiative_type_scopes.select do |initiative_type_scope|
356
+ initiative_type_scope.scope.present? && (scoped_type.global_scope? || scoped_type.scope.ancestor_of?(initiative_type_scope.scope))
357
+ end.prepend(scoped_type).uniq
270
358
  end
271
359
 
272
360
  # Public: Overrides slug attribute from participatory processes.
@@ -289,18 +377,20 @@ module Decidim
289
377
  true
290
378
  end
291
379
 
292
- # PUBLIC
293
- #
294
- # Checks if user is the author or is part of the promotal committee
380
+ # Public: Checks if user is the author or is part of the promotal committee
295
381
  # of the initiative.
296
382
  #
297
- # RETURNS boolean
383
+ # Returns a Boolean.
298
384
  def has_authorship?(user)
299
385
  return true if author.id == user.id
300
386
 
301
387
  committee_members.approved.where(decidim_users_id: user.id).any?
302
388
  end
303
389
 
390
+ def author_users
391
+ [author].concat(committee_members.excluding_author.map(&:user))
392
+ end
393
+
304
394
  def accepts_offline_votes?
305
395
  published? && (offline_signature_type? || any_signature_type?)
306
396
  end
@@ -332,8 +422,27 @@ module Decidim
332
422
  organization.available_authorizations.include?("sms") && type.validate_sms_code_on_votes?
333
423
  end
334
424
 
425
+ # Public: Returns an empty object. This method should be implemented by
426
+ # `ParticipatorySpaceResourceable`, but for some reason this model does not
427
+ # implement this interface.
428
+ def user_role_config_for(_user, _role_name)
429
+ Decidim::ParticipatorySpaceRoleConfig::Base.new(:empty_role_name)
430
+ end
431
+
335
432
  private
336
433
 
434
+ # Private: This is just an alias because the naming on InitiativeTypeScope
435
+ # is very confusing. The `scopes` method doesn't return Decidim::Scope but
436
+ # Decidim::InitiativeTypeScopes.
437
+ #
438
+ # ¯\_(ツ)_/¯
439
+ #
440
+ # Returns an Array of Decidim::InitiativesScopeType.
441
+ def initiative_type_scopes
442
+ type.scopes
443
+ end
444
+
445
+ # Private: A validator that verifies the signaature type is allowed by the InitiativeType.
337
446
  def signature_type_allowed
338
447
  return if published?
339
448
 
@@ -351,5 +460,36 @@ module Decidim
351
460
  notifier = Decidim::Initiatives::StatusChangeNotifier.new(initiative: self)
352
461
  notifier.notify
353
462
  end
463
+
464
+ # Allow ransacker to search for a key in a hstore column (`title`.`en`)
465
+ [:title, :description].each do |column|
466
+ ransacker column do |parent|
467
+ Arel::Nodes::InfixOperation.new("->>", parent.table[column], Arel::Nodes.build_quoted(I18n.locale.to_s))
468
+ end
469
+ end
470
+
471
+ # Allow ransacker to search on an Enum Field
472
+ ransacker :state, formatter: proc { |int| states[int] }
473
+
474
+ ransacker :type_id do
475
+ Arel.sql("decidim_initiatives_type_scopes.decidim_initiatives_types_id")
476
+ end
477
+
478
+ # method for sort_link by number of supports
479
+ ransacker :supports_count do
480
+ Arel.sql("(coalesce((online_votes->>'total')::int,0) + coalesce((offline_votes->>'total')::int,0))")
481
+ end
482
+
483
+ ransacker :id_string do
484
+ Arel.sql(%{cast("decidim_initiatives"."id" as text)})
485
+ end
486
+
487
+ ransacker :author_name do
488
+ Arel.sql("decidim_users.name")
489
+ end
490
+
491
+ ransacker :author_nickname do
492
+ Arel.sql("decidim_users.nickname")
493
+ end
354
494
  end
355
495
  end