decidim-budgets 0.26.10 → 0.27.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/app/cells/decidim/budgets/budgets_header/show.erb +1 -1
  3. data/app/cells/decidim/budgets/order_activity_cell.rb +29 -0
  4. data/app/commands/decidim/budgets/add_line_item.rb +4 -2
  5. data/app/commands/decidim/budgets/admin/create_budget.rb +1 -1
  6. data/app/commands/decidim/budgets/admin/create_order_reminders.rb +66 -0
  7. data/app/commands/decidim/budgets/admin/create_project.rb +5 -2
  8. data/app/commands/decidim/budgets/admin/destroy_budget.rb +1 -1
  9. data/app/commands/decidim/budgets/admin/destroy_project.rb +1 -1
  10. data/app/commands/decidim/budgets/admin/import_proposals_to_budgets.rb +6 -6
  11. data/app/commands/decidim/budgets/admin/update_budget.rb +1 -1
  12. data/app/commands/decidim/budgets/admin/update_project.rb +5 -2
  13. data/app/commands/decidim/budgets/admin/update_project_category.rb +48 -0
  14. data/app/commands/decidim/budgets/admin/update_project_scope.rb +54 -0
  15. data/app/commands/decidim/budgets/admin/update_project_selection.rb +56 -0
  16. data/app/commands/decidim/budgets/cancel_order.rb +1 -1
  17. data/app/commands/decidim/budgets/checkout.rb +10 -3
  18. data/app/commands/decidim/budgets/remove_line_item.rb +1 -1
  19. data/app/controllers/decidim/budgets/admin/application_controller.rb +2 -0
  20. data/app/controllers/decidim/budgets/admin/attachment_collections_controller.rb +1 -3
  21. data/app/controllers/decidim/budgets/admin/attachments_controller.rb +1 -7
  22. data/app/controllers/decidim/budgets/admin/budgets_controller.rb +1 -1
  23. data/app/controllers/decidim/budgets/admin/projects_controller.rb +149 -1
  24. data/app/controllers/decidim/budgets/line_items_controller.rb +2 -2
  25. data/app/controllers/decidim/budgets/orders_controller.rb +1 -1
  26. data/app/controllers/decidim/budgets/projects_controller.rb +15 -19
  27. data/app/forms/decidim/budgets/admin/budget_form.rb +3 -4
  28. data/app/forms/decidim/budgets/admin/order_reminder_form.rb +75 -0
  29. data/app/forms/decidim/budgets/admin/project_form.rb +19 -3
  30. data/app/helpers/decidim/budgets/admin/project_bulk_actions_helper.rb +20 -0
  31. data/app/helpers/decidim/budgets/projects_helper.rb +29 -0
  32. data/app/jobs/decidim/budgets/send_vote_reminder_job.rb +16 -0
  33. data/app/mailers/decidim/budgets/vote_reminder_mailer.rb +39 -0
  34. data/app/models/decidim/budgets/order.rb +2 -4
  35. data/app/models/decidim/budgets/project.rb +14 -10
  36. data/app/packs/entrypoints/decidim_budgets_admin.js +1 -0
  37. data/app/packs/src/decidim/budgets/admin/projects.js +143 -0
  38. data/app/permissions/decidim/budgets/admin/permissions.rb +8 -3
  39. data/app/queries/decidim/budgets/filtered_projects.rb +1 -1
  40. data/app/serializers/decidim/budgets/{data_portability_budgets_order_serializer.rb → download_your_data_budgets_order_serializer.rb} +2 -2
  41. data/app/services/decidim/budgets/order_reminder_generator.rb +85 -0
  42. data/app/views/decidim/budgets/admin/budgets/_form.html.erb +1 -1
  43. data/app/views/decidim/budgets/admin/budgets/edit.html.erb +0 -1
  44. data/app/views/decidim/budgets/admin/budgets/index.html.erb +3 -1
  45. data/app/views/decidim/budgets/admin/budgets/new.html.erb +0 -1
  46. data/app/views/decidim/budgets/admin/projects/_bulk-actions.html.erb +13 -0
  47. data/app/views/decidim/budgets/admin/projects/_form.html.erb +6 -0
  48. data/app/views/decidim/budgets/admin/projects/_project-tr.html.erb +50 -0
  49. data/app/views/decidim/budgets/admin/projects/bulk_actions/_change-selected.html.erb +15 -0
  50. data/app/views/decidim/budgets/admin/projects/bulk_actions/_dropdown.html.erb +36 -0
  51. data/app/views/decidim/budgets/admin/projects/bulk_actions/_recategorize.html.erb +15 -0
  52. data/app/views/decidim/budgets/admin/projects/bulk_actions/_scope-change.html.erb +25 -0
  53. data/app/views/decidim/budgets/admin/projects/edit.html.erb +0 -1
  54. data/app/views/decidim/budgets/admin/projects/index.html.erb +6 -55
  55. data/app/views/decidim/budgets/admin/projects/new.html.erb +0 -1
  56. data/app/views/decidim/budgets/admin/projects/update_attribute.js.erb +26 -0
  57. data/app/views/decidim/budgets/admin/proposals_imports/new.html.erb +0 -1
  58. data/app/views/decidim/budgets/order_summary_mailer/order_summary.html.erb +0 -1
  59. data/app/views/decidim/budgets/projects/_filters.html.erb +4 -4
  60. data/app/views/decidim/budgets/projects/index.html.erb +28 -1
  61. data/app/views/decidim/budgets/projects/show.html.erb +7 -2
  62. data/app/views/decidim/budgets/vote_reminder_mailer/vote_reminder.html.erb +21 -0
  63. data/config/assets.rb +2 -1
  64. data/config/locales/am-ET.yml +1 -0
  65. data/config/locales/ar.yml +5 -1
  66. data/config/locales/bg.yml +1 -0
  67. data/config/locales/ca.yml +48 -5
  68. data/config/locales/cs.yml +53 -8
  69. data/config/locales/da.yml +1 -0
  70. data/config/locales/de.yml +50 -16
  71. data/config/locales/el.yml +3 -33
  72. data/config/locales/en.yml +46 -4
  73. data/config/locales/eo.yml +1 -0
  74. data/config/locales/es-MX.yml +46 -6
  75. data/config/locales/es-PY.yml +49 -6
  76. data/config/locales/es.yml +50 -7
  77. data/config/locales/et.yml +1 -0
  78. data/config/locales/eu.yml +46 -62
  79. data/config/locales/fi-plain.yml +47 -4
  80. data/config/locales/fi.yml +93 -50
  81. data/config/locales/fr-CA.yml +47 -5
  82. data/config/locales/fr.yml +48 -6
  83. data/config/locales/ga-IE.yml +2 -1
  84. data/config/locales/gl.yml +11 -0
  85. data/config/locales/hr.yml +1 -0
  86. data/config/locales/hu.yml +21 -149
  87. data/config/locales/id-ID.yml +2 -1
  88. data/config/locales/is-IS.yml +4 -2
  89. data/config/locales/it.yml +4 -2
  90. data/config/locales/ja.yml +44 -5
  91. data/config/locales/ko.yml +1 -0
  92. data/config/locales/lb.yml +5 -5
  93. data/config/locales/lt.yml +1 -338
  94. data/config/locales/lv.yml +2 -1
  95. data/config/locales/mt.yml +1 -0
  96. data/config/locales/nl.yml +16 -30
  97. data/config/locales/no.yml +18 -1
  98. data/config/locales/om-ET.yml +1 -0
  99. data/config/locales/pl.yml +3 -24
  100. data/config/locales/pt-BR.yml +4 -21
  101. data/config/locales/pt.yml +3 -3
  102. data/config/locales/ro-RO.yml +2 -3
  103. data/config/locales/ru.yml +2 -1
  104. data/config/locales/si-LK.yml +1 -0
  105. data/config/locales/sk.yml +2 -1
  106. data/config/locales/sl.yml +1 -0
  107. data/config/locales/so-SO.yml +1 -0
  108. data/config/locales/sr-CS.yml +1 -0
  109. data/config/locales/sv.yml +26 -7
  110. data/config/locales/sw-KE.yml +1 -0
  111. data/config/locales/ti-ER.yml +1 -0
  112. data/config/locales/tr-TR.yml +3 -3
  113. data/config/locales/uk.yml +3 -2
  114. data/config/locales/val-ES.yml +1 -0
  115. data/config/locales/vi.yml +1 -0
  116. data/config/locales/zh-CN.yml +1 -3
  117. data/config/locales/zh-TW.yml +1 -326
  118. data/db/migrate/20200804175222_votes_enabled_to_votes_choices.rb +2 -2
  119. data/db/migrate/20220428072638_add_geolocalization_fields_to_projects.rb +9 -0
  120. data/lib/decidim/budgets/admin_engine.rb +3 -0
  121. data/lib/decidim/budgets/component.rb +7 -6
  122. data/lib/decidim/budgets/engine.rb +17 -0
  123. data/lib/decidim/budgets/test/factories.rb +11 -0
  124. data/lib/decidim/budgets/version.rb +1 -1
  125. data/lib/decidim/budgets/workflows/all.rb +1 -1
  126. data/lib/decidim/budgets/workflows/base.rb +1 -1
  127. metadata +38 -27
  128. data/app/services/decidim/budgets/project_search.rb +0 -45
  129. data/config/environment.rb +0 -3
  130. data/config/locales/fa-IR.yml +0 -1
  131. data/config/locales/gn-PY.yml +0 -1
  132. data/config/locales/ka-GE.yml +0 -1
  133. data/config/locales/kaa.yml +0 -11
  134. data/config/locales/lo-LA.yml +0 -1
  135. data/config/locales/oc-FR.yml +0 -1
  136. data/config/locales/sq-AL.yml +0 -1
  137. data/config/locales/th-TH.yml +0 -1
@@ -8,8 +8,9 @@ module Decidim
8
8
  include Decidim::ApplicationHelper
9
9
  include Decidim::Proposals::Admin::Picker if Decidim::Budgets.enable_proposal_linking
10
10
  include Decidim::Budgets::Admin::Filterable
11
+ helper Decidim::Budgets::Admin::ProjectBulkActionsHelper
11
12
 
12
- helper_method :projects, :finished_orders, :pending_orders, :present
13
+ helper_method :projects, :finished_orders, :pending_orders, :present, :project_ids
13
14
 
14
15
  def collection
15
16
  @collection ||= budget.projects.page(params[:page]).per(15)
@@ -74,6 +75,93 @@ module Decidim
74
75
  end
75
76
  end
76
77
 
78
+ def update_category
79
+ enforce_permission_to :update, :project_category
80
+
81
+ ::Decidim::Budgets::Admin::UpdateProjectCategory.call(params[:category][:id], project_ids) do
82
+ on(:invalid_category) do
83
+ flash.now[:error] = I18n.t(
84
+ "projects.update_category.select_a_category",
85
+ scope: "decidim.budgets.admin"
86
+ )
87
+ end
88
+
89
+ on(:invalid_project_ids) do
90
+ flash.now[:alert] = I18n.t(
91
+ "projects.update_category.select_a_project",
92
+ scope: "decidim.budgets.admin"
93
+ )
94
+ end
95
+
96
+ on(:update_projects_category) do
97
+ flash.now[:notice] = update_projects_bulk_response_successful(@response, :category)
98
+ flash.now[:alert] = update_projects_bulk_response_errored(@response, :category)
99
+ end
100
+ end
101
+
102
+ respond_to do |format|
103
+ format.js { render :update_attribute, locals: { form_selector: "#js-form-recategorize-projects", attribute_selector: "#category_id" } }
104
+ end
105
+ end
106
+
107
+ def update_scope
108
+ enforce_permission_to :update, :project_scope
109
+
110
+ ::Decidim::Budgets::Admin::UpdateProjectScope.call(params[:scope_id], project_ids) do
111
+ on(:invalid_scope) do
112
+ flash.now[:error] = t(
113
+ "projects.update_scope.select_a_scope",
114
+ scope: "decidim.budgets.admin"
115
+ )
116
+ end
117
+
118
+ on(:invalid_project_ids) do
119
+ flash.now[:alert] = t(
120
+ "projects.update_scope.select_a_project",
121
+ scope: "decidim.budgets.admin"
122
+ )
123
+ end
124
+
125
+ on(:update_projects_scope) do
126
+ flash.now[:notice] = update_projects_bulk_response_successful(@response, :scope)
127
+ flash.now[:alert] = update_projects_bulk_response_errored(@response, :scope)
128
+ end
129
+ end
130
+
131
+ respond_to do |format|
132
+ format.js { render :update_attribute, locals: { form_selector: "#js-form-scope-change-projects", attribute_selector: "#scope_id" } }
133
+ end
134
+ end
135
+
136
+ def update_selected
137
+ enforce_permission_to :update, :project_selected
138
+
139
+ ::Decidim::Budgets::Admin::UpdateProjectSelection.call(params.dig(:selected, "value"), project_ids) do
140
+ on(:invalid_selection) do
141
+ flash.now[:error] = t(
142
+ "projects.update_selected.select_a_selection",
143
+ scope: "decidim.budgets.admin"
144
+ )
145
+ end
146
+
147
+ on(:invalid_project_ids) do
148
+ flash.now[:alert] = t(
149
+ "projects.update_selected.select_a_project",
150
+ scope: "decidim.budgets.admin"
151
+ )
152
+ end
153
+
154
+ on(:update_projects_selection) do
155
+ flash.now[:notice] = update_projects_bulk_response_successful(@response, :selected)
156
+ flash.now[:alert] = update_projects_bulk_response_errored(@response, :selected)
157
+ end
158
+ end
159
+
160
+ respond_to do |format|
161
+ format.js { render :update_attribute, locals: { form_selector: "#js-form-change-selected-projects", attribute_selector: "#selected_value" } }
162
+ end
163
+ end
164
+
77
165
  private
78
166
 
79
167
  def projects
@@ -84,6 +172,10 @@ module Decidim
84
172
  @orders ||= Order.where(budget: budget)
85
173
  end
86
174
 
175
+ def project_ids
176
+ @project_ids ||= params[:project_ids]
177
+ end
178
+
87
179
  def pending_orders
88
180
  orders.pending
89
181
  end
@@ -95,6 +187,62 @@ module Decidim
95
187
  def project
96
188
  @project ||= projects.find(params[:id])
97
189
  end
190
+
191
+ def update_projects_bulk_response_successful(response, subject)
192
+ return if response[:successful].blank?
193
+
194
+ case subject
195
+ when :category
196
+ t(
197
+ "projects.update_category.success",
198
+ subject_name: response[:subject_name],
199
+ projects: response[:successful].to_sentence,
200
+ scope: "decidim.budgets.admin"
201
+ )
202
+ when :scope
203
+ t(
204
+ "projects.update_scope.success",
205
+ subject_name: response[:subject_name],
206
+ projects: response[:successful].to_sentence,
207
+ scope: "decidim.budgets.admin"
208
+ )
209
+ when :selected
210
+ t(
211
+ "projects.update_selected.success",
212
+ subject_name: response[:subject_name],
213
+ projects: response[:successful].to_sentence,
214
+ scope: "decidim.budgets.admin"
215
+ )
216
+ end
217
+ end
218
+
219
+ def update_projects_bulk_response_errored(response, subject)
220
+ return if response[:errored].blank?
221
+
222
+ case subject
223
+ when :category
224
+ t(
225
+ "projects.update_category.invalid",
226
+ subject_name: response[:subject_name],
227
+ projects: response[:errored].to_sentence,
228
+ scope: "decidim.budgets.admin"
229
+ )
230
+ when :scope
231
+ t(
232
+ "projects.update_scope.invalid",
233
+ subject_name: response[:subject_name],
234
+ projects: response[:errored].to_sentence,
235
+ scope: "decidim.budgets.admin"
236
+ )
237
+ when :selected
238
+ t(
239
+ "projects.update_selected.invalid",
240
+ subject_name: response[:subject_name],
241
+ projects: response[:errored].to_sentence,
242
+ scope: "decidim.budgets.admin"
243
+ )
244
+ end
245
+ end
98
246
  end
99
247
  end
100
248
  end
@@ -44,11 +44,11 @@ module Decidim
44
44
  private
45
45
 
46
46
  def project
47
- @project ||= budget&.projects&.find_by(id: params[:project_id])
47
+ @project ||= Project.includes(:budget).find_by(id: params[:project_id], decidim_budgets_budget_id: params[:budget_id])
48
48
  end
49
49
 
50
50
  def budget
51
- @budget ||= Budget.find_by(id: params[:budget_id], component: current_component)
51
+ @budget ||= project.budget
52
52
  end
53
53
  end
54
54
  end
@@ -39,7 +39,7 @@ module Decidim
39
39
  private
40
40
 
41
41
  def budget
42
- @budget ||= Budget.find_by(id: params[:budget_id], component: current_component)
42
+ @budget ||= Budget.find_by(id: params[:budget_id])
43
43
  end
44
44
 
45
45
  def redirect_path
@@ -8,7 +8,7 @@ module Decidim
8
8
  include NeedsCurrentOrder
9
9
  include Decidim::Budgets::Orderable
10
10
 
11
- helper_method :projects, :project, :budget
11
+ helper_method :projects, :project, :budget, :all_geocoded_projects
12
12
 
13
13
  def index
14
14
  raise ActionController::RoutingError, "Not Found" unless budget
@@ -28,37 +28,33 @@ module Decidim
28
28
  def projects
29
29
  return @projects if @projects
30
30
 
31
- @projects = reorder(search.results)
32
- @projects = @projects.page(params[:page]).per(current_component.settings.projects_per_page)
31
+ @projects = search.result.page(params[:page]).per(current_component.settings.projects_per_page)
32
+ @projects = reorder(@projects)
33
+ end
34
+
35
+ def all_geocoded_projects
36
+ @all_geocoded_projects ||= projects.geocoded
33
37
  end
34
38
 
35
39
  def project
36
- @project ||= budget&.projects&.find_by(id: params[:id])
40
+ @project ||= Project.find_by(id: params[:id])
37
41
  end
38
42
 
39
- def search_klass
40
- ProjectSearch
43
+ def search_collection
44
+ Project.where(budget: budget).includes([:scope, :component, :attachments, :category])
41
45
  end
42
46
 
43
47
  def default_filter_params
44
48
  {
45
- search_text: "",
46
- status: default_filter_status_params,
47
- scope_id: default_filter_scope_params,
48
- category_id: default_filter_category_params
49
+ search_text_cont: "",
50
+ with_any_status: default_filter_status_params,
51
+ with_any_scope: default_filter_scope_params,
52
+ with_any_category: default_filter_category_params
49
53
  }
50
54
  end
51
55
 
52
56
  def default_filter_status_params
53
- show_selected_budgets? ? %w(selected) : %w(all)
54
- end
55
-
56
- def show_selected_budgets?
57
- voting_finished? && budget.projects.selected.any?
58
- end
59
-
60
- def context_params
61
- { budget: budget, component: current_component, organization: current_organization }
57
+ voting_finished? ? %w(selected) : %w(all)
62
58
  end
63
59
  end
64
60
  end
@@ -16,8 +16,7 @@ module Decidim
16
16
  attribute :decidim_scope_id, Integer
17
17
 
18
18
  validates :title, translatable_presence: true
19
- validates :weight, numericality: { greater_than_or_equal_to: 0 }
20
- validates :total_budget, numericality: { greater_than: 0 }
19
+ validates :weight, :total_budget, numericality: { greater_than_or_equal_to: 0 }
21
20
  validates :scope, presence: true, if: ->(form) { form.decidim_scope_id.present? }
22
21
  validates :decidim_scope_id, scope_belongs_to_component: true, if: ->(form) { form.decidim_scope_id.present? }
23
22
 
@@ -25,14 +24,14 @@ module Decidim
25
24
  #
26
25
  # Returns a Decidim::Scope
27
26
  def scope
28
- @scope ||= @decidim_scope_id ? current_component.scopes.find_by(id: @decidim_scope_id) : current_component.scope
27
+ @scope ||= @attributes["decidim_scope_id"].value ? current_component.scopes.find_by(id: @attributes["decidim_scope_id"].value) : current_component.scope
29
28
  end
30
29
 
31
30
  # Scope identifier
32
31
  #
33
32
  # Returns the scope identifier related to the meeting
34
33
  def decidim_scope_id
35
- @decidim_scope_id || scope&.id
34
+ super || scope&.id
36
35
  end
37
36
  end
38
37
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ module Admin
6
+ class OrderReminderForm < Decidim::Form
7
+ def reminder_amount
8
+ @reminder_amount ||= if !voting_enabled? || voting_ends_soon?
9
+ 0
10
+ else
11
+ user_ids = []
12
+ unfinished_orders.each do |order|
13
+ reminder = Decidim::Reminder.find_by(component: current_component, user: order.user)
14
+ if !reminder || (reminder.deliveries.present? && reminder.deliveries.last.created_at < minimum_interval_between_reminders.ago)
15
+ user_ids << order.user.id
16
+ end
17
+ end
18
+ user_ids.uniq.count
19
+ end
20
+ end
21
+
22
+ def voting_enabled?
23
+ current_component.current_settings.votes == "enabled"
24
+ end
25
+
26
+ def voting_ends_soon?
27
+ return false unless participatory_space.respond_to? :active_step
28
+ return false if participatory_space.active_step.blank?
29
+
30
+ time_zone = current_organization.time_zone
31
+ return false if time_zone.blank?
32
+
33
+ end_time = current_component.participatory_space.active_step[:end_date].in_time_zone(time_zone).end_of_day
34
+
35
+ 6.hours.from_now >= end_time
36
+ end
37
+
38
+ def minimum_interval_between_reminders
39
+ 24.hours
40
+ end
41
+
42
+ private
43
+
44
+ def minimum_time_before_first_reminder
45
+ @minimum_time_before_first_reminder ||= begin
46
+ reminder_manifest = Decidim.reminders_registry.for(:orders)
47
+ if reminder_manifest.blank?
48
+ minimum_interval_between_reminders
49
+ else
50
+ Array(reminder_manifest.settings.attributes[:reminder_times].default).first
51
+ end
52
+ end
53
+ end
54
+
55
+ def participatory_space
56
+ @participatory_space ||= current_component.participatory_space
57
+ end
58
+
59
+ def unfinished_orders
60
+ @unfinished_orders ||= Decidim::Budgets::Order.where(
61
+ budget: budgets,
62
+ checked_out_at: nil,
63
+ created_at: Time.zone.at(0)..minimum_time_before_first_reminder.ago
64
+ ).select do |order|
65
+ order.user.email.present?
66
+ end
67
+ end
68
+
69
+ def budgets
70
+ @budgets ||= Decidim::Budgets::Budget.where(component: current_component)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -13,6 +13,9 @@ module Decidim
13
13
  translatable_attribute :title, String
14
14
  translatable_attribute :description, String
15
15
 
16
+ attribute :address, String
17
+ attribute :latitude, Float
18
+ attribute :longitude, Float
16
19
  attribute :budget_amount, Integer
17
20
  attribute :decidim_scope_id, Integer
18
21
  attribute :decidim_category_id, Integer
@@ -25,7 +28,7 @@ module Decidim
25
28
  validates :title, translatable_presence: true
26
29
  validates :description, translatable_presence: true
27
30
  validates :budget_amount, presence: true, numericality: { greater_than: 0 }
28
-
31
+ validates :address, geocoding: true, if: ->(form) { form.has_address? && !form.geocoded? }
29
32
  validates :category, presence: true, if: ->(form) { form.decidim_category_id.present? }
30
33
  validates :scope, presence: true, if: ->(form) { form.decidim_scope_id.present? }
31
34
  validates :decidim_scope_id, scope_belongs_to_component: true, if: ->(form) { form.decidim_scope_id.present? }
@@ -33,6 +36,7 @@ module Decidim
33
36
  validate :notify_missing_attachment_if_errored
34
37
 
35
38
  delegate :categories, to: :current_component
39
+ alias component current_component
36
40
 
37
41
  def map_model(model)
38
42
  self.proposal_ids = model.linked_resources(:proposals, "included_proposals").pluck(:id)
@@ -49,6 +53,18 @@ module Decidim
49
53
  &.order(title: :asc)
50
54
  end
51
55
 
56
+ def geocoding_enabled?
57
+ Decidim::Map.available?(:geocoding) && current_component.settings.geocoding_enabled?
58
+ end
59
+
60
+ def has_address?
61
+ geocoding_enabled? && address.present?
62
+ end
63
+
64
+ def geocoded?
65
+ latitude.present? && longitude.present?
66
+ end
67
+
52
68
  # Finds the Budget from the decidim_budgets_budget_id.
53
69
  #
54
70
  # Returns a Decidim::Budgets:Budget
@@ -67,14 +83,14 @@ module Decidim
67
83
  #
68
84
  # Returns a Decidim::Scope
69
85
  def scope
70
- @scope ||= @decidim_scope_id ? current_component.scopes.find_by(id: @decidim_scope_id) : current_component.scope
86
+ @scope ||= @attributes["decidim_scope_id"].value ? current_component.scopes.find_by(id: @attributes["decidim_scope_id"].value) : current_component.scope
71
87
  end
72
88
 
73
89
  # Scope identifier
74
90
  #
75
91
  # Returns the scope identifier related to the project
76
92
  def decidim_scope_id
77
- @decidim_scope_id || scope&.id
93
+ super || scope&.id
78
94
  end
79
95
 
80
96
  private
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ module Admin
6
+ module ProjectBulkActionsHelper
7
+ def bulk_selections
8
+ select(
9
+ :selected,
10
+ :value,
11
+ [
12
+ [t("projects.index.select_for_implementation", scope: "decidim.budgets.admin"), true],
13
+ [t("projects.index.deselect_implementation", scope: "decidim.budgets.admin"), false]
14
+ ]
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -4,6 +4,9 @@ module Decidim
4
4
  module Budgets
5
5
  # A helper to render order and budgets actions
6
6
  module ProjectsHelper
7
+ include Decidim::ApplicationHelper
8
+ include Decidim::MapHelper
9
+
7
10
  # Render a budget as a currency
8
11
  #
9
12
  # budget - A integer to represent a budget
@@ -80,6 +83,32 @@ module Decidim
80
83
  t(".vote_threshold_percent_rule.description", minimum_budget: budget_to_currency(current_order.minimum_budget))
81
84
  end
82
85
  end
86
+
87
+ # Serialize a collection of geocoded projects to be used by the dynamic map component
88
+ #
89
+ # geocoded_projects - A collection of geocoded projects
90
+ def projects_data_for_map(geocoded_projects)
91
+ geocoded_projects.map do |project|
92
+ project_data_for_map(project)
93
+ end
94
+ end
95
+
96
+ def project_data_for_map(project)
97
+ project
98
+ .slice(:latitude, :longitude, :address)
99
+ .merge(
100
+ title: decidim_html_escape(translated_attribute(project.title)),
101
+ description: html_truncate(decidim_sanitize_editor(translated_attribute(project.description)), length: 100),
102
+ icon: icon("project", width: 40, height: 70, remove_icon_class: true),
103
+ link: ::Decidim::ResourceLocatorPresenter.new([project.budget, project]).path
104
+ )
105
+ end
106
+
107
+ def has_position?(project)
108
+ return if project.address.blank?
109
+
110
+ project.latitude.present? && project.longitude.present?
111
+ end
83
112
  end
84
113
  end
85
114
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ class SendVoteReminderJob < ApplicationJob
6
+ queue_as :vote_reminder
7
+
8
+ def perform(reminder)
9
+ return if reminder.records.active.blank?
10
+
11
+ ::Decidim::ReminderDelivery.create(reminder: reminder)
12
+ ::Decidim::Budgets::VoteReminderMailer.vote_reminder(reminder).deliver_now
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ class VoteReminderMailer < Decidim::ApplicationMailer
6
+ include Decidim::TranslationsHelper
7
+ include Decidim::SanitizeHelper
8
+
9
+ helper Decidim::TranslationsHelper
10
+
11
+ helper_method :routes
12
+
13
+ # Send the user an email reminder to finish voting
14
+ #
15
+ # reminder - the reminder to send.
16
+ def vote_reminder(reminder)
17
+ @reminder = reminder
18
+ @user = reminder.user
19
+ with_user(@user) do
20
+ @orders = reminder.records.active.map(&:remindable)
21
+ @organization = @user.organization
22
+
23
+ subject = I18n.t(
24
+ "decidim.budgets.vote_reminder_mailer.vote_reminder.email_subject",
25
+ count: @orders.count
26
+ )
27
+
28
+ mail(to: @user.email, subject: subject)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def routes
35
+ @routes ||= Decidim::EngineRouter.main_proxy(@reminder.component)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,7 +5,7 @@ module Decidim
5
5
  # The data store for a Order in the Decidim::Budgets component. It is unique for each
6
6
  # user and component and contains a collection of projects
7
7
  class Order < Budgets::ApplicationRecord
8
- include Decidim::DataPortability
8
+ include Decidim::DownloadYourData
9
9
  include Decidim::NewsletterParticipant
10
10
 
11
11
  belongs_to :user, class_name: "Decidim::User", foreign_key: "decidim_user_id"
@@ -15,7 +15,6 @@ module Decidim
15
15
  has_many :projects, through: :line_items, class_name: "Decidim::Budgets::Project", foreign_key: "decidim_project_id"
16
16
 
17
17
  validates :user, uniqueness: { scope: :budget }
18
- validates :budget, presence: true
19
18
  validate :user_belongs_to_organization
20
19
 
21
20
  # Rules active for the budget threshold and minimum budgets rules.
@@ -109,7 +108,6 @@ module Decidim
109
108
  # enabled
110
109
  def budget_percent
111
110
  return (total_projects.to_f / maximum_projects) * 100 if projects_rule?
112
- return 0 if budget.total_budget.zero?
113
111
 
114
112
  (total_budget.to_f / budget.total_budget) * 100
115
113
  end
@@ -172,7 +170,7 @@ module Decidim
172
170
  end
173
171
 
174
172
  def self.export_serializer
175
- Decidim::Budgets::DataPortabilityBudgetsOrderSerializer
173
+ Decidim::Budgets::DownloadYourDataBudgetsOrderSerializer
176
174
  end
177
175
 
178
176
  def self.newsletter_participant_ids(component)
@@ -18,6 +18,7 @@ module Decidim
18
18
  include Decidim::Randomable
19
19
  include Decidim::Searchable
20
20
  include Decidim::TranslatableResource
21
+ include Decidim::FilterableResource
21
22
 
22
23
  translatable_fields :title, :description
23
24
 
@@ -33,6 +34,10 @@ module Decidim
33
34
  scope :selected, -> { where.not(selected_at: nil) }
34
35
  scope :not_selected, -> { where(selected_at: nil) }
35
36
 
37
+ geocoded_by :address
38
+
39
+ scope_search_multi :with_any_status, [:selected, :not_selected]
40
+
36
41
  searchable_fields(
37
42
  scope_id: :decidim_scope_id,
38
43
  participatory_space: { component: :participatory_space },
@@ -54,16 +59,12 @@ module Decidim
54
59
  Decidim::Budgets::AdminLog::ProjectPresenter
55
60
  end
56
61
 
57
- def resource_locator
58
- ::Decidim::ResourceLocatorPresenter.new([budget, self])
59
- end
60
-
61
62
  def polymorphic_resource_path(url_params)
62
- resource_locator.path(url_params)
63
+ ::Decidim::ResourceLocatorPresenter.new([budget, self]).path(url_params)
63
64
  end
64
65
 
65
66
  def polymorphic_resource_url(url_params)
66
- resource_locator.url(url_params)
67
+ ::Decidim::ResourceLocatorPresenter.new([budget, self]).url(url_params)
67
68
  end
68
69
 
69
70
  # Public: Overrides the `comments_have_votes?` Commentable concern method.
@@ -102,10 +103,9 @@ module Decidim
102
103
  Arel.sql(%{cast("decidim_budgets_projects"."id" as text)})
103
104
  end
104
105
 
105
- # Allow ransacker to search for a key in a hstore column (`title`.`en`)
106
- ransacker :title do |parent|
107
- Arel::Nodes::InfixOperation.new("->>", parent.table[:title], Arel::Nodes.build_quoted(I18n.locale.to_s))
108
- end
106
+ # Create i18n ransackers for :title and :description.
107
+ # Create the :search_text ransacker alias for searching from both of these.
108
+ ransacker_i18n_multi :search_text, [:title, :description]
109
109
 
110
110
  ransacker :selected do
111
111
  Arel.sql(%{("decidim_budgets_projects"."selected_at")::text})
@@ -123,6 +123,10 @@ module Decidim
123
123
  SQL
124
124
  Arel.sql(query)
125
125
  end
126
+
127
+ def self.ransackable_scopes(_auth_object = nil)
128
+ [:with_any_status, :with_any_scope, :with_any_category]
129
+ end
126
130
  end
127
131
  end
128
132
  end
@@ -0,0 +1 @@
1
+ import "src/decidim/budgets/admin/projects"