decidim-budgets 0.21.0 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/images/decidim/budgets/icon.svg +1 -11
- data/app/assets/javascripts/decidim/budgets/projects.js.es6 +26 -4
- data/app/assets/stylesheets/decidim/budgets/budget/_budget-list.scss +147 -24
- data/app/cells/decidim/budgets/project_list_item/project_data.erb +5 -0
- data/app/cells/decidim/budgets/project_list_item/project_data_number.erb +3 -0
- data/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb +15 -0
- data/app/cells/decidim/budgets/project_list_item/project_data_votes.erb +7 -0
- data/app/cells/decidim/budgets/project_list_item/project_image.erb +5 -0
- data/app/cells/decidim/budgets/project_list_item/project_text.erb +17 -0
- data/app/cells/decidim/budgets/project_list_item/show.erb +5 -0
- data/app/cells/decidim/budgets/project_list_item_cell.rb +86 -0
- data/app/cells/decidim/budgets/project_m/data.erb +2 -2
- data/app/cells/decidim/budgets/project_m/footer.erb +1 -1
- data/app/commands/decidim/budgets/admin/create_project.rb +19 -9
- data/app/commands/decidim/budgets/admin/update_project.rb +12 -1
- data/app/commands/decidim/budgets/checkout.rb +2 -1
- data/app/controllers/concerns/decidim/budgets/needs_current_order.rb +5 -1
- data/app/controllers/concerns/decidim/budgets/orderable.rb +61 -0
- data/app/controllers/decidim/budgets/admin/projects_controller.rb +8 -5
- data/app/controllers/decidim/budgets/line_items_controller.rb +1 -1
- data/app/controllers/decidim/budgets/projects_controller.rb +5 -1
- data/app/forms/decidim/budgets/admin/component_form.rb +44 -0
- data/app/forms/decidim/budgets/admin/project_form.rb +14 -2
- data/app/helpers/decidim/budgets/projects_helper.rb +17 -0
- data/app/jobs/decidim/budgets/send_order_summary_job.rb +15 -0
- data/app/mailers/decidim/budgets/order_summary_mailer.rb +34 -0
- data/app/models/decidim/budgets/order.rb +28 -1
- data/app/models/decidim/budgets/project.rb +4 -0
- data/app/views/decidim/budgets/admin/projects/_form.html.erb +3 -6
- data/app/views/decidim/budgets/admin/projects/proposals_picker.html.erb +1 -0
- data/app/views/decidim/budgets/order_summary_mailer/order_summary.html.erb +21 -0
- data/app/views/decidim/budgets/projects/_budget_confirm.html.erb +1 -1
- data/app/views/decidim/budgets/projects/_budget_summary.html.erb +8 -2
- data/app/views/decidim/budgets/projects/_filters.html.erb +10 -6
- data/app/views/decidim/budgets/projects/_linked_projects.html.erb +1 -1
- data/app/views/decidim/budgets/projects/_order_progress.html.erb +8 -16
- data/app/views/decidim/budgets/projects/_project.html.erb +1 -57
- data/app/views/decidim/budgets/projects/_project_budget_button.html.erb +3 -3
- data/app/views/decidim/budgets/projects/_projects.html.erb +8 -1
- data/app/views/decidim/budgets/projects/index.html.erb +3 -3
- data/app/views/decidim/budgets/projects/index.js.erb +8 -0
- data/app/views/decidim/budgets/projects/show.html.erb +1 -1
- data/config/locales/ar.yml +0 -5
- data/config/locales/bg-BG.yml +7 -0
- data/config/locales/ca.yml +26 -4
- data/config/locales/cs.yml +43 -21
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/de.yml +26 -4
- data/config/locales/el.yml +174 -0
- data/config/locales/en.yml +26 -4
- data/config/locales/es-MX.yml +26 -4
- data/config/locales/es-PY.yml +26 -4
- data/config/locales/es.yml +26 -4
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/eu.yml +0 -5
- data/config/locales/fi-plain.yml +26 -4
- data/config/locales/fi.yml +37 -15
- data/config/locales/fr-CA.yml +175 -0
- data/config/locales/fr.yml +26 -4
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +0 -5
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hu.yml +16 -5
- data/config/locales/id-ID.yml +0 -5
- data/config/locales/is-IS.yml +0 -5
- data/config/locales/it.yml +27 -5
- data/config/locales/ja-JP.yml +171 -0
- data/config/locales/lt-LT.yml +1 -0
- data/config/locales/lv-LV.yml +172 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/nl.yml +26 -4
- data/config/locales/no.yml +20 -7
- data/config/locales/pl.yml +73 -51
- data/config/locales/pt-BR.yml +1 -6
- data/config/locales/pt.yml +63 -41
- data/config/locales/ro-RO.yml +179 -0
- data/config/locales/ru.yml +0 -5
- data/config/locales/sk-SK.yml +180 -0
- data/config/locales/sk.yml +175 -0
- data/config/locales/sl.yml +5 -0
- data/config/locales/sr-CS.yml +29 -0
- data/config/locales/sv.yml +26 -4
- data/config/locales/tr-TR.yml +0 -5
- data/config/locales/uk.yml +0 -5
- data/lib/decidim/budgets/admin_engine.rb +2 -0
- data/lib/decidim/budgets/component.rb +4 -0
- data/lib/decidim/budgets/seeds/city.jpeg +0 -0
- data/lib/decidim/budgets/test/factories.rb +33 -0
- data/lib/decidim/budgets/version.rb +1 -1
- metadata +49 -14
@@ -6,6 +6,9 @@ module Decidim
|
|
6
6
|
# This command is executed when the user changes a Project from the admin
|
7
7
|
# panel.
|
8
8
|
class UpdateProject < Rectify::Command
|
9
|
+
include ::Decidim::AttachmentMethods
|
10
|
+
include ::Decidim::GalleryMethods
|
11
|
+
|
9
12
|
# Initializes an UpdateProject Command.
|
10
13
|
#
|
11
14
|
# form - The form from which to get the data.
|
@@ -13,6 +16,7 @@ module Decidim
|
|
13
16
|
def initialize(form, project)
|
14
17
|
@form = form
|
15
18
|
@project = project
|
19
|
+
@attached_to = project
|
16
20
|
end
|
17
21
|
|
18
22
|
# Updates the project if valid.
|
@@ -21,9 +25,16 @@ module Decidim
|
|
21
25
|
def call
|
22
26
|
return broadcast(:invalid) if form.invalid?
|
23
27
|
|
28
|
+
if process_gallery?
|
29
|
+
build_gallery
|
30
|
+
return broadcast(:invalid) if gallery_invalid?
|
31
|
+
end
|
32
|
+
|
24
33
|
transaction do
|
25
34
|
update_project
|
26
35
|
link_proposals
|
36
|
+
create_gallery if process_gallery?
|
37
|
+
photo_cleanup!
|
27
38
|
end
|
28
39
|
|
29
40
|
broadcast(:ok)
|
@@ -31,7 +42,7 @@ module Decidim
|
|
31
42
|
|
32
43
|
private
|
33
44
|
|
34
|
-
attr_reader :project, :form
|
45
|
+
attr_reader :project, :form, :gallery
|
35
46
|
|
36
47
|
def update_project
|
37
48
|
Decidim.traceability.update!(
|
@@ -15,12 +15,16 @@ module Decidim
|
|
15
15
|
#
|
16
16
|
# Returns an Order.
|
17
17
|
def current_order
|
18
|
-
@current_order ||= Order.includes(:projects).
|
18
|
+
@current_order ||= Order.includes(:projects).find_or_initialize_by(user: current_user, component: current_component)
|
19
19
|
end
|
20
20
|
|
21
21
|
def current_order=(order)
|
22
22
|
@current_order = order
|
23
23
|
end
|
24
|
+
|
25
|
+
def persisted_current_order
|
26
|
+
current_order if current_order&.persisted?
|
27
|
+
end
|
24
28
|
end
|
25
29
|
end
|
26
30
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module Budgets
|
7
|
+
# Common logic to sorting resources
|
8
|
+
module Orderable
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
include Decidim::Orderable
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Available orders based on enabled settings
|
17
|
+
def available_orders
|
18
|
+
@available_orders ||= begin
|
19
|
+
available_orders = []
|
20
|
+
available_orders << "random" if voting_is_open? || !votes_are_visible?
|
21
|
+
available_orders << "most_voted" if votes_are_visible?
|
22
|
+
available_orders += %w(highest_cost lowest_cost)
|
23
|
+
available_orders
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_order
|
28
|
+
available_orders.first
|
29
|
+
end
|
30
|
+
|
31
|
+
def voting_is_open?
|
32
|
+
current_settings.votes_enabled?
|
33
|
+
end
|
34
|
+
|
35
|
+
def votes_are_visible?
|
36
|
+
current_settings.show_votes?
|
37
|
+
end
|
38
|
+
|
39
|
+
def reorder(projects)
|
40
|
+
case order
|
41
|
+
when "highest_cost"
|
42
|
+
projects.order(budget: :desc)
|
43
|
+
when "lowest_cost"
|
44
|
+
projects.order(budget: :asc)
|
45
|
+
when "most_voted"
|
46
|
+
if votes_are_visible?
|
47
|
+
ids = projects.sort_by(&:confirmed_orders_count).map(&:id).reverse
|
48
|
+
projects.ordered_ids(ids)
|
49
|
+
else
|
50
|
+
projects
|
51
|
+
end
|
52
|
+
when "random"
|
53
|
+
projects.order_randomly(random_seed)
|
54
|
+
else
|
55
|
+
projects
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -5,12 +5,16 @@ module Decidim
|
|
5
5
|
module Admin
|
6
6
|
# This controller allows an admin to manage projects from a Participatory Process
|
7
7
|
class ProjectsController < Admin::ApplicationController
|
8
|
-
|
8
|
+
include Decidim::ApplicationHelper
|
9
|
+
include Decidim::Proposals::Admin::Picker
|
10
|
+
|
11
|
+
helper_method :projects, :finished_orders, :pending_orders, :present
|
9
12
|
|
10
13
|
def new
|
11
14
|
enforce_permission_to :create, :project
|
12
|
-
|
13
|
-
|
15
|
+
@form = form(ProjectForm).from_params(
|
16
|
+
attachment: form(AttachmentForm).instance
|
17
|
+
)
|
14
18
|
end
|
15
19
|
|
16
20
|
def create
|
@@ -33,13 +37,12 @@ module Decidim
|
|
33
37
|
|
34
38
|
def edit
|
35
39
|
enforce_permission_to :update, :project, project: project
|
36
|
-
|
37
40
|
@form = form(ProjectForm).from_model(project)
|
41
|
+
@form.attachment = form(AttachmentForm).instance
|
38
42
|
end
|
39
43
|
|
40
44
|
def update
|
41
45
|
enforce_permission_to :update, :project, project: project
|
42
|
-
|
43
46
|
@form = form(ProjectForm).from_params(params)
|
44
47
|
|
45
48
|
UpdateProject.call(@form, project) do
|
@@ -12,7 +12,7 @@ module Decidim
|
|
12
12
|
enforce_permission_to :vote, :project, project: project
|
13
13
|
|
14
14
|
respond_to do |format|
|
15
|
-
AddLineItem.call(
|
15
|
+
AddLineItem.call(persisted_current_order, project, current_user) do
|
16
16
|
on(:ok) do |order|
|
17
17
|
self.current_order = order
|
18
18
|
format.html { redirect_to :back }
|
@@ -7,13 +7,17 @@ module Decidim
|
|
7
7
|
include FilterResource
|
8
8
|
include NeedsCurrentOrder
|
9
9
|
include Orderable
|
10
|
+
include Decidim::Budgets::Orderable
|
10
11
|
|
11
12
|
helper_method :projects, :project
|
12
13
|
|
13
14
|
private
|
14
15
|
|
15
16
|
def projects
|
16
|
-
@projects
|
17
|
+
return @projects if @projects
|
18
|
+
|
19
|
+
@projects = search.results.page(params[:page]).per(current_component.settings.projects_per_page)
|
20
|
+
@projects = reorder(@projects)
|
17
21
|
end
|
18
22
|
|
19
23
|
def project
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Budgets
|
5
|
+
module Admin
|
6
|
+
# A form object for the budgets component. Used to attach the component
|
7
|
+
# to a participatory process from the admin panel.
|
8
|
+
#
|
9
|
+
class ComponentForm < Decidim::Admin::ComponentForm
|
10
|
+
validate :budget_voting_rule_enabled_setting, :budget_voting_rule_value_setting
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# Validations on budget settings:
|
15
|
+
# - a voting rule must be enabled.
|
16
|
+
def budget_voting_rule_enabled_setting
|
17
|
+
return unless manifest&.name == :budgets
|
18
|
+
|
19
|
+
i18n_error_scope = "decidim.components.budgets.settings.global.form.errors"
|
20
|
+
if settings.vote_rule_threshold_percent_enabled.blank? && settings.vote_rule_minimum_budget_projects_enabled.blank?
|
21
|
+
settings.errors.add(:vote_rule_threshold_percent_enabled, I18n.t(:budget_voting_rule_required, scope: i18n_error_scope))
|
22
|
+
settings.errors.add(:vote_rule_minimum_budget_projects_enabled, I18n.t(:budget_voting_rule_required, scope: i18n_error_scope))
|
23
|
+
end
|
24
|
+
|
25
|
+
if settings.vote_rule_threshold_percent_enabled && settings.vote_rule_minimum_budget_projects_enabled
|
26
|
+
settings.errors.add(:vote_rule_threshold_percent_enabled, I18n.t(:budget_voting_rule_only_one, scope: i18n_error_scope))
|
27
|
+
settings.errors.add(:vote_rule_minimum_budget_projects_enabled, I18n.t(:budget_voting_rule_only_one, scope: i18n_error_scope))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# - the value must be a valid number
|
32
|
+
def budget_voting_rule_value_setting
|
33
|
+
return unless manifest&.name == :budgets
|
34
|
+
|
35
|
+
invalid_percent_number = settings.vote_threshold_percent.blank? || settings.vote_threshold_percent.to_i.negative?
|
36
|
+
settings.errors.add(:vote_threshold_percent) if settings.vote_rule_threshold_percent_enabled && invalid_percent_number
|
37
|
+
|
38
|
+
invalid_minimum_number = settings.vote_minimum_budget_projects_number.blank? || (settings.vote_minimum_budget_projects_number.to_i < 1)
|
39
|
+
settings.errors.add(:vote_minimum_budget_projects_number) if settings.vote_rule_minimum_budget_projects_enabled && invalid_minimum_number
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -16,6 +16,9 @@ module Decidim
|
|
16
16
|
attribute :decidim_scope_id, Integer
|
17
17
|
attribute :decidim_category_id, Integer
|
18
18
|
attribute :proposal_ids, Array[Integer]
|
19
|
+
attribute :attachment, AttachmentForm
|
20
|
+
attribute :photos, Array[String]
|
21
|
+
attribute :add_photos, Array
|
19
22
|
|
20
23
|
validates :title, translatable_presence: true
|
21
24
|
validates :description, translatable_presence: true
|
@@ -26,6 +29,8 @@ module Decidim
|
|
26
29
|
|
27
30
|
validate :scope_belongs_to_participatory_space_scope
|
28
31
|
|
32
|
+
validate :notify_missing_attachment_if_errored
|
33
|
+
|
29
34
|
delegate :categories, to: :current_component
|
30
35
|
|
31
36
|
def map_model(model)
|
@@ -38,9 +43,8 @@ module Decidim
|
|
38
43
|
|
39
44
|
def proposals
|
40
45
|
@proposals ||= Decidim.find_resource_manifest(:proposals).try(:resource_scope, current_component)
|
41
|
-
&.
|
46
|
+
&.where(id: proposal_ids)
|
42
47
|
&.order(title: :asc)
|
43
|
-
&.map { |proposal| [present(proposal).title, proposal.id] }
|
44
48
|
end
|
45
49
|
|
46
50
|
# Finds the Category from the decidim_category_id.
|
@@ -69,6 +73,14 @@ module Decidim
|
|
69
73
|
def scope_belongs_to_participatory_space_scope
|
70
74
|
errors.add(:decidim_scope_id, :invalid) if current_participatory_space.out_of_scope?(scope)
|
71
75
|
end
|
76
|
+
|
77
|
+
# This method will add an error to the `attachment` field only if there's
|
78
|
+
# any error in any other field. This is needed because when the form has
|
79
|
+
# an error, the attachment is lost, so we need a way to inform the user of
|
80
|
+
# this problem.
|
81
|
+
def notify_missing_attachment_if_errored
|
82
|
+
errors.add(:add_photos, :needs_to_be_reattached) if errors.any? && add_photos.present?
|
83
|
+
end
|
72
84
|
end
|
73
85
|
end
|
74
86
|
end
|
@@ -16,6 +16,19 @@ module Decidim
|
|
16
16
|
current_order&.budget_percent.to_f.floor
|
17
17
|
end
|
18
18
|
|
19
|
+
# Return the minimum percentage of the current order budget from the total budget
|
20
|
+
def current_order_budget_percent_minimum
|
21
|
+
return 0 if current_order.minimum_projects_rule?
|
22
|
+
|
23
|
+
component_settings.vote_threshold_percent
|
24
|
+
end
|
25
|
+
|
26
|
+
def budget_confirm_disabled_attr
|
27
|
+
return if current_order_can_be_checked_out?
|
28
|
+
|
29
|
+
%( disabled="disabled" ).html_safe
|
30
|
+
end
|
31
|
+
|
19
32
|
# Return true if the current order is checked out
|
20
33
|
delegate :checked_out?, to: :current_order, prefix: true, allow_nil: true
|
21
34
|
|
@@ -23,6 +36,10 @@ module Decidim
|
|
23
36
|
def current_order_can_be_checked_out?
|
24
37
|
current_order&.can_checkout?
|
25
38
|
end
|
39
|
+
|
40
|
+
def projects_base_url
|
41
|
+
URI.parse(root_url).tap { |uri| uri.query = uri.fragment = nil } .to_s
|
42
|
+
end
|
26
43
|
end
|
27
44
|
end
|
28
45
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Budgets
|
5
|
+
class SendOrderSummaryJob < ApplicationJob
|
6
|
+
queue_as :default
|
7
|
+
|
8
|
+
def perform(order)
|
9
|
+
return if order&.user&.email.blank?
|
10
|
+
|
11
|
+
OrderSummaryMailer.order_summary(order).deliver_now
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Budgets
|
5
|
+
class OrderSummaryMailer < Decidim::ApplicationMailer
|
6
|
+
include Decidim::TranslationsHelper
|
7
|
+
include Decidim::SanitizeHelper
|
8
|
+
|
9
|
+
helper Decidim::TranslationsHelper
|
10
|
+
|
11
|
+
# Send an email to an user with the summary of the order.
|
12
|
+
#
|
13
|
+
# order - the order that was just created
|
14
|
+
def order_summary(order)
|
15
|
+
user = order.user
|
16
|
+
|
17
|
+
with_user(user) do
|
18
|
+
@user = user
|
19
|
+
@order = order
|
20
|
+
@space = order.participatory_space
|
21
|
+
@component = order.component
|
22
|
+
@organization = order.participatory_space.organization
|
23
|
+
|
24
|
+
subject = I18n.t(
|
25
|
+
"order_summary.subject",
|
26
|
+
scope: "decidim.budgets.order_summary_mailer",
|
27
|
+
space_name: translated_attribute(@space.title)
|
28
|
+
)
|
29
|
+
mail(to: user.email, subject: subject)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -27,6 +27,8 @@ module Decidim
|
|
27
27
|
less_than_or_equal_to: :maximum_budget
|
28
28
|
}
|
29
29
|
|
30
|
+
validate :reach_minimum_projects, if: :checked_out?
|
31
|
+
|
30
32
|
scope :finished, -> { where.not(checked_out_at: nil) }
|
31
33
|
scope :pending, -> { where(checked_out_at: nil) }
|
32
34
|
|
@@ -42,7 +44,11 @@ module Decidim
|
|
42
44
|
|
43
45
|
# Public: Check if the order total budget is enough to checkout
|
44
46
|
def can_checkout?
|
45
|
-
|
47
|
+
if minimum_projects_rule?
|
48
|
+
projects.count >= minimum_projects
|
49
|
+
else
|
50
|
+
total_budget.to_f >= minimum_budget
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
54
|
# Public: Returns the order budget percent from the settings total budget
|
@@ -53,6 +59,7 @@ module Decidim
|
|
53
59
|
# Public: Returns the required minimum budget to checkout
|
54
60
|
def minimum_budget
|
55
61
|
return 0 unless component
|
62
|
+
return 0 if minimum_projects_rule?
|
56
63
|
|
57
64
|
component.settings.total_budget.to_f * (component.settings.vote_threshold_percent.to_f / 100)
|
58
65
|
end
|
@@ -64,6 +71,20 @@ module Decidim
|
|
64
71
|
component.settings.total_budget.to_f
|
65
72
|
end
|
66
73
|
|
74
|
+
# Public: Returns if it is required a minimum projects limit to checkout
|
75
|
+
def minimum_projects_rule?
|
76
|
+
return unless component
|
77
|
+
|
78
|
+
component.settings.vote_rule_minimum_budget_projects_enabled
|
79
|
+
end
|
80
|
+
|
81
|
+
# Public: Returns the required minimum projects to checkout
|
82
|
+
def minimum_projects
|
83
|
+
return 0 unless component
|
84
|
+
|
85
|
+
component.settings.vote_minimum_budget_projects_number
|
86
|
+
end
|
87
|
+
|
67
88
|
def self.user_collection(user)
|
68
89
|
where(decidim_user_id: user.id)
|
69
90
|
end
|
@@ -87,6 +108,12 @@ module Decidim
|
|
87
108
|
|
88
109
|
errors.add(:user, :invalid) unless user.organization == organization
|
89
110
|
end
|
111
|
+
|
112
|
+
def reach_minimum_projects
|
113
|
+
return unless minimum_projects_rule?
|
114
|
+
|
115
|
+
errors.add(:projects, :invalid) if minimum_projects > projects.count
|
116
|
+
end
|
90
117
|
end
|
91
118
|
end
|
92
119
|
end
|