decidim-budgets 0.23.3 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/budgets/projects.js.es6 +30 -13
  3. data/app/cells/decidim/budgets/limit_announcement_cell.rb +2 -9
  4. data/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb +1 -0
  5. data/app/cells/decidim/budgets/project_list_item_cell.rb +4 -0
  6. data/app/commands/decidim/budgets/admin/import_proposals_to_budgets.rb +8 -1
  7. data/app/controllers/decidim/budgets/admin/budgets_controller.rb +22 -1
  8. data/app/controllers/decidim/budgets/orders_controller.rb +1 -1
  9. data/app/forms/decidim/budgets/admin/component_form.rb +57 -10
  10. data/app/forms/decidim/budgets/admin/project_form.rb +3 -2
  11. data/app/forms/decidim/budgets/admin/project_import_proposals_form.rb +11 -0
  12. data/app/helpers/decidim/budgets/projects_helper.rb +45 -1
  13. data/app/models/decidim/budgets/order.rb +94 -18
  14. data/app/permissions/decidim/budgets/admin/permissions.rb +1 -1
  15. data/app/views/decidim/budgets/admin/budgets/index.html.erb +22 -5
  16. data/app/views/decidim/budgets/admin/projects/index.html.erb +13 -6
  17. data/app/views/decidim/budgets/admin/proposals_imports/new.html.erb +6 -1
  18. data/app/views/decidim/budgets/projects/_budget_excess.html.erb +2 -2
  19. data/app/views/decidim/budgets/projects/_budget_summary.html.erb +32 -12
  20. data/app/views/decidim/budgets/projects/_filters_small_view.html.erb +1 -1
  21. data/app/views/decidim/budgets/projects/_order_progress.html.erb +1 -1
  22. data/app/views/decidim/budgets/projects/_order_total_budget.html.erb +5 -1
  23. data/config/locales/ar.yml +2 -5
  24. data/config/locales/ca.yml +36 -8
  25. data/config/locales/cs.yml +34 -6
  26. data/config/locales/de.yml +34 -6
  27. data/config/locales/el.yml +2 -6
  28. data/config/locales/en.yml +35 -7
  29. data/config/locales/es-MX.yml +35 -7
  30. data/config/locales/es-PY.yml +35 -7
  31. data/config/locales/es.yml +35 -7
  32. data/config/locales/eu.yml +2 -5
  33. data/config/locales/fi-plain.yml +34 -6
  34. data/config/locales/fi.yml +36 -8
  35. data/config/locales/fr-CA.yml +35 -7
  36. data/config/locales/fr.yml +35 -7
  37. data/config/locales/gl.yml +23 -7
  38. data/config/locales/hu.yml +2 -5
  39. data/config/locales/id-ID.yml +2 -5
  40. data/config/locales/is-IS.yml +2 -4
  41. data/config/locales/it.yml +2 -6
  42. data/config/locales/ja.yml +3 -7
  43. data/config/locales/lv.yml +2 -6
  44. data/config/locales/nl.yml +49 -7
  45. data/config/locales/no.yml +2 -7
  46. data/config/locales/pl.yml +41 -13
  47. data/config/locales/pt-BR.yml +2 -5
  48. data/config/locales/pt.yml +2 -6
  49. data/config/locales/ro-RO.yml +69 -6
  50. data/config/locales/ru.yml +2 -5
  51. data/config/locales/sk.yml +2 -6
  52. data/config/locales/sr-CS.yml +0 -2
  53. data/config/locales/sv.yml +35 -7
  54. data/config/locales/tr-TR.yml +31 -7
  55. data/config/locales/uk.yml +2 -5
  56. data/config/locales/zh-CN.yml +2 -7
  57. data/db/migrate/20200804175222_votes_enabled_to_votes_choices.rb +4 -4
  58. data/db/migrate/20210310120613_add_followable_counter_cache_to_budgets.rb +16 -0
  59. data/lib/decidim/api/budget_type.rb +21 -0
  60. data/lib/decidim/api/budgets_type.rb +26 -0
  61. data/lib/decidim/api/project_type.rb +23 -0
  62. data/lib/decidim/budgets.rb +2 -0
  63. data/lib/decidim/budgets/api.rb +9 -0
  64. data/lib/decidim/budgets/component.rb +31 -14
  65. data/lib/decidim/budgets/project_serializer.rb +81 -0
  66. data/lib/decidim/budgets/test/factories.rb +35 -1
  67. data/lib/decidim/budgets/version.rb +1 -1
  68. data/lib/decidim/budgets/workflows/all.rb +1 -1
  69. data/lib/decidim/budgets/workflows/base.rb +3 -3
  70. data/lib/decidim/budgets/workflows/one.rb +1 -1
  71. metadata +20 -17
  72. data/app/types/decidim/budgets/budget_type.rb +0 -24
  73. data/app/types/decidim/budgets/budgets_type.rb +0 -32
  74. data/app/types/decidim/budgets/project_type.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32962ebf7d38fb028abe8713049817d63e21772fc6666b01e2eee2ff782448b5
4
- data.tar.gz: aaf12de2e0adc6393b5a86449cad9ebdd76bc80eb912165154369e52f1785356
3
+ metadata.gz: 4d91881aea249a69386309da0bbf83a61b74543236fd59d962b09e218d67bae5
4
+ data.tar.gz: cd1379113a70f33668e8814ae51c92e3d8c805893793dddc8caf4f8c0ac95789
5
5
  SHA512:
6
- metadata.gz: 06461cbe6156e79b241330a6bb2e36804b41c3742d945ab7a7495c5ff2534cba8220c81f6935586ba2bb16e8340e88375f5e6f3a76027399c01da49964d3d1de
7
- data.tar.gz: 81a4b80fb4bd652a8474d999ed34e0df1af6ebb5a2a25d38a7076de52e59a3985d5da90faa96cd54aa7cd789d1cc5c37225f4b76ec4785e727f0fbac2882e67d
6
+ metadata.gz: b53ea096b8718ddbe6630ee5702cc30623850dc24357c3014db58755d40a296daa4518060de3c8d555b5f6fd90053a2af4f7e6f5615b5265cf42b7edc5528398
7
+ data.tar.gz: 18dc70348221c5fd6fe95bc08827eb0edfa7c49e527d992e3bb26a27564bfc703cdad90f495a0a7d8dadb44a159cf2717ec71bf12bb7aadcc751689e241f0956
@@ -6,22 +6,31 @@ $(() => {
6
6
  const $budgetSummaryTotal = $(".budget-summary__total");
7
7
  const $budgetExceedModal = $("#budget-excess");
8
8
  const $budgetSummary = $(".budget-summary__progressbox");
9
- const totalBudget = parseInt($budgetSummaryTotal.attr("data-total-budget"), 10);
9
+ const totalAllocation = parseInt($budgetSummaryTotal.attr("data-total-allocation"), 10);
10
10
 
11
11
  const cancelEvent = (event) => {
12
12
  event.stopPropagation();
13
13
  event.preventDefault();
14
14
  };
15
15
 
16
+ const allowExitFrom = ($el) => {
17
+ if ($el.parents("#loginModal").length > 0) {
18
+ return true;
19
+ } else if ($el.parents("#authorizationModal").length > 0) {
20
+ return true;
21
+ }
22
+
23
+ return false;
24
+ }
25
+
16
26
  $projects.on("click", ".budget-list__action", (event) => {
17
- const currentBudget = parseInt($budgetSummary.attr("data-current-budget"), 10);
27
+ const currentAllocation = parseInt($budgetSummary.attr("data-current-allocation"), 10);
18
28
  const $currentTarget = $(event.currentTarget);
19
- const projectBudget = parseInt($currentTarget.attr("data-budget"), 10);
29
+ const projectAllocation = parseInt($currentTarget.attr("data-allocation"), 10);
20
30
 
21
31
  if ($currentTarget.attr("disabled")) {
22
32
  cancelEvent(event);
23
-
24
- } else if (($currentTarget.attr("data-add") === "true") && ((currentBudget + projectBudget) > totalBudget)) {
33
+ } else if (($currentTarget.attr("data-add") === "true") && ((currentAllocation + projectAllocation) > totalAllocation)) {
25
34
  $budgetExceedModal.foundation("toggle");
26
35
  cancelEvent(event);
27
36
  }
@@ -30,22 +39,30 @@ $(() => {
30
39
  if ($("#order-progress [data-toggle=budget-confirm]").length > 0) {
31
40
  const safeUrl = $(".budget-summary").attr("data-safe-url").split("?")[0];
32
41
  $(document).on("click", "a", (event) => {
33
- window.exitUrl = event.currentTarget.href;
42
+ if (allowExitFrom($(event.currentTarget))) {
43
+ window.exitUrl = null;
44
+ } else {
45
+ window.exitUrl = event.currentTarget.href;
46
+ }
34
47
  });
35
48
  $(document).on("submit", "form", (event) => {
36
- window.exitUrl = event.currentTarget.action;
49
+ if (allowExitFrom($(event.currentTarget))) {
50
+ window.exitUrl = null;
51
+ } else {
52
+ window.exitUrl = event.currentTarget.action;
53
+ }
37
54
  });
38
55
 
39
- window.onbeforeunload = () => {
40
- const currentBudget = parseInt($budgetSummary.attr("data-current-budget"), 10);
56
+ window.addEventListener("beforeunload", (event) => {
57
+ const currentAllocation = parseInt($budgetSummary.attr("data-current-allocation"), 10);
41
58
  const exitUrl = window.exitUrl;
42
59
  window.exitUrl = null;
43
60
 
44
- if (currentBudget === 0 || (exitUrl && exitUrl.startsWith(safeUrl))) {
45
- return null;
61
+ if (currentAllocation === 0 || (exitUrl && exitUrl.startsWith(safeUrl))) {
62
+ return;
46
63
  }
47
64
 
48
- return "";
49
- }
65
+ event.returnValue = true;
66
+ });
50
67
  }
51
68
  });
@@ -9,7 +9,7 @@ module Decidim
9
9
  delegate :voting_open?, to: :controller
10
10
 
11
11
  def show
12
- cell("decidim/announcement", announcement_args) if announce?
12
+ cell("decidim/announcement", announcement_message, callout_class: "warning") if announce?
13
13
  end
14
14
 
15
15
  private
@@ -20,14 +20,7 @@ module Decidim
20
20
  return if vote_allowed?(budget)
21
21
  return if voted?(budget)
22
22
 
23
- discardable.any? || !vote_allowed?(budget, false)
24
- end
25
-
26
- def announcement_args
27
- {
28
- callout_class: "warning",
29
- announcement: announcement_message
30
- }
23
+ discardable.any? || !vote_allowed?(budget, consider_progress: false)
31
24
  end
32
25
 
33
26
  def announcement_message
@@ -7,6 +7,7 @@
7
7
  add: !resource_added?,
8
8
  disable: true,
9
9
  budget: model.budget_amount,
10
+ allocation: resource_allocation,
10
11
  "redirect-url": resource_path
11
12
  },
12
13
  disabled: vote_button_disabled?,
@@ -28,6 +28,10 @@ module Decidim
28
28
  current_order && current_order.projects.include?(model)
29
29
  end
30
30
 
31
+ def resource_allocation
32
+ current_order.allocation_for(model)
33
+ end
34
+
31
35
  def data_class
32
36
  [].tap do |list|
33
37
  list << "budget-list__data--added" if can_have_order? && resource_added?
@@ -60,7 +60,14 @@ module Decidim
60
60
  end
61
61
 
62
62
  def proposals
63
- Decidim::Proposals::Proposal.where(component: origin_component).where(state: "accepted")
63
+ return all_proposals if form.scope_id.blank?
64
+
65
+ all_proposals.where(decidim_scope_id: form.scope_id)
66
+ end
67
+
68
+ def all_proposals
69
+ Decidim::Proposals::Proposal.where(component: origin_component)
70
+ .where(state: :accepted)
64
71
  end
65
72
 
66
73
  def origin_component
@@ -5,7 +5,8 @@ module Decidim
5
5
  module Admin
6
6
  # This controller allows the create or update a budget.
7
7
  class BudgetsController < Admin::ApplicationController
8
- helper_method :budgets, :budget
8
+ helper_method :budgets, :budget, :finished_orders, :pending_orders,
9
+ :users_with_pending_orders, :users_with_finished_orders
9
10
 
10
11
  def new
11
12
  enforce_permission_to :create, :budget
@@ -76,6 +77,26 @@ module Decidim
76
77
  def budget
77
78
  @budget ||= budgets.find_by(id: params[:id])
78
79
  end
80
+
81
+ def orders
82
+ @orders ||= Order.where(decidim_budgets_budget_id: budgets)
83
+ end
84
+
85
+ def pending_orders
86
+ orders.pending
87
+ end
88
+
89
+ def finished_orders
90
+ orders.finished
91
+ end
92
+
93
+ def users_with_pending_orders
94
+ orders.pending.pluck(:decidim_user_id).uniq
95
+ end
96
+
97
+ def users_with_finished_orders
98
+ orders.finished.pluck(:decidim_user_id).uniq
99
+ end
79
100
  end
80
101
  end
81
102
  end
@@ -43,7 +43,7 @@ module Decidim
43
43
  end
44
44
 
45
45
  def redirect_path
46
- if params.dig(:return_to) == "budget"
46
+ if params[:return_to] == "budget"
47
47
  budget_path(budget)
48
48
  else
49
49
  budgets_path
@@ -7,7 +7,10 @@ module Decidim
7
7
  # to a participatory process from the admin panel.
8
8
  #
9
9
  class ComponentForm < Decidim::Admin::ComponentForm
10
- validate :budget_voting_rule_enabled_setting, :budget_voting_rule_value_setting
10
+ validate :budget_voting_rule_enabled_setting,
11
+ :budget_voting_rule_threshold_value_setting,
12
+ :budget_voting_rule_minimum_value_setting,
13
+ :budget_voting_rule_projects_value_setting
11
14
 
12
15
  private
13
16
 
@@ -16,27 +19,71 @@ module Decidim
16
19
  def budget_voting_rule_enabled_setting
17
20
  return unless manifest&.name == :budgets
18
21
 
22
+ rule_settings = [
23
+ :vote_rule_threshold_percent_enabled,
24
+ :vote_rule_minimum_budget_projects_enabled,
25
+ :vote_rule_selected_projects_enabled
26
+ ]
27
+ active_rules = rule_settings.select { |key| settings.public_send(key) == true }
19
28
  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))
29
+ if active_rules.blank?
30
+ rule_settings.each do |key|
31
+ settings.errors.add(key, I18n.t(:budget_voting_rule_required, scope: i18n_error_scope))
32
+ end
23
33
  end
24
34
 
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))
35
+ if active_rules.length > 1
36
+ rule_settings.each do |key|
37
+ settings.errors.add(key, I18n.t(:budget_voting_rule_only_one, scope: i18n_error_scope))
38
+ end
28
39
  end
29
40
  end
30
41
 
31
42
  # - the value must be a valid number
32
- def budget_voting_rule_value_setting
43
+ def budget_voting_rule_threshold_value_setting
33
44
  return unless manifest&.name == :budgets
45
+ return unless settings.vote_rule_threshold_percent_enabled
34
46
 
35
47
  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
48
+ settings.errors.add(:vote_threshold_percent) if invalid_percent_number
49
+ end
50
+
51
+ def budget_voting_rule_minimum_value_setting
52
+ return unless manifest&.name == :budgets
53
+ return unless settings.vote_rule_minimum_budget_projects_enabled
37
54
 
38
55
  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
56
+ settings.errors.add(:vote_minimum_budget_projects_number) if invalid_minimum_number
57
+ end
58
+
59
+ def budget_voting_rule_projects_value_setting
60
+ return unless manifest&.name == :budgets
61
+ return unless settings.vote_rule_selected_projects_enabled
62
+
63
+ budget_voting_projects_value_setting_min
64
+ budget_voting_projects_value_setting_max
65
+ budget_voting_projects_value_setting_both
66
+ end
67
+
68
+ def budget_voting_projects_value_setting_min
69
+ return if settings.vote_selected_projects_minimum.present? && settings.vote_selected_projects_minimum.to_i >= 0
70
+
71
+ settings.errors.add(:vote_selected_projects_minimum)
72
+ end
73
+
74
+ def budget_voting_projects_value_setting_max
75
+ return if settings.vote_selected_projects_maximum.present? && settings.vote_selected_projects_maximum.to_i.positive?
76
+
77
+ settings.errors.add(:vote_selected_projects_maximum)
78
+ end
79
+
80
+ def budget_voting_projects_value_setting_both
81
+ return if settings.errors[:vote_selected_projects_minimum].present?
82
+ return if settings.errors[:vote_selected_projects_maximum].present?
83
+ return if settings.vote_selected_projects_maximum >= settings.vote_selected_projects_minimum
84
+
85
+ settings.errors.add(:vote_selected_projects_minimum)
86
+ settings.errors.add(:vote_selected_projects_maximum)
40
87
  end
41
88
  end
42
89
  end
@@ -6,6 +6,7 @@ module Decidim
6
6
  # This class holds a Form to create/update projects from Decidim's admin panel.
7
7
  class ProjectForm < Decidim::Form
8
8
  include TranslatableAttributes
9
+ include AttachmentAttributes
9
10
  include TranslationsHelper
10
11
  include Decidim::ApplicationHelper
11
12
 
@@ -17,10 +18,10 @@ module Decidim
17
18
  attribute :decidim_category_id, Integer
18
19
  attribute :proposal_ids, Array[Integer]
19
20
  attribute :attachment, AttachmentForm
20
- attribute :photos, Array[String]
21
- attribute :add_photos, Array
22
21
  attribute :selected, Boolean
23
22
 
23
+ attachments_attribute :photos
24
+
24
25
  validates :title, translatable_presence: true
25
26
  validates :description, translatable_presence: true
26
27
  validates :budget_amount, presence: true, numericality: { greater_than: 0 }
@@ -9,12 +9,15 @@ module Decidim
9
9
  mimic :proposals_import
10
10
 
11
11
  attribute :origin_component_id, Integer
12
+ attribute :scope_id, Integer
12
13
  attribute :default_budget, Integer
13
14
  attribute :import_all_accepted_proposals, Boolean
14
15
 
15
16
  validates :origin_component_id, :origin_component, :current_component, presence: true
16
17
  validates :import_all_accepted_proposals, allow_nil: false, acceptance: true
17
18
  validates :default_budget, presence: true, numericality: { greater_than: 0 }
19
+ validates :scope, presence: true, if: ->(form) { form.scope_id.present? }
20
+ validates :scope_id, scope_belongs_to_component: true, if: ->(form) { form.scope_id.present? }
18
21
 
19
22
  def origin_component
20
23
  @origin_component ||= origin_components.find_by(id: origin_component_id)
@@ -30,6 +33,14 @@ module Decidim
30
33
  end
31
34
  end
32
35
 
36
+ def scope
37
+ @scope ||= @scope_id ? current_component.scopes.find_by(id: @scope_id) : current_component.scope
38
+ end
39
+
40
+ def scope_id
41
+ @scope_id || scope&.id
42
+ end
43
+
33
44
  def budget
34
45
  @budget ||= context[:budget]
35
46
  end
@@ -20,7 +20,11 @@ module Decidim
20
20
  def current_order_budget_percent_minimum
21
21
  return 0 if current_order.minimum_projects_rule?
22
22
 
23
- component_settings.vote_threshold_percent
23
+ if current_order.projects_rule?
24
+ (current_order.minimum_projects.to_f / current_order.maximum_projects)
25
+ else
26
+ component_settings.vote_threshold_percent
27
+ end
24
28
  end
25
29
 
26
30
  def budget_confirm_disabled_attr
@@ -36,6 +40,46 @@ module Decidim
36
40
  def current_order_can_be_checked_out?
37
41
  current_order&.can_checkout?
38
42
  end
43
+
44
+ def current_rule_explanation
45
+ return unless current_order
46
+
47
+ if current_order.projects_rule?
48
+ if current_order.minimum_projects.positive? && current_order.minimum_projects < current_order.maximum_projects
49
+ t(
50
+ ".projects_rule.instruction",
51
+ minimum_number: current_order.minimum_projects,
52
+ maximum_number: current_order.maximum_projects
53
+ )
54
+ else
55
+ t(".projects_rule_maximum_only.instruction", maximum_number: current_order.maximum_projects)
56
+ end
57
+ elsif current_order.minimum_projects_rule?
58
+ t(".minimum_projects_rule.instruction", minimum_number: current_order.minimum_projects)
59
+ else
60
+ t(".vote_threshold_percent_rule.instruction", minimum_budget: budget_to_currency(current_order.minimum_budget))
61
+ end
62
+ end
63
+
64
+ def current_rule_description
65
+ return unless current_order
66
+
67
+ if current_order.projects_rule?
68
+ if current_order.minimum_projects.positive? && current_order.minimum_projects < current_order.maximum_projects
69
+ t(
70
+ ".projects_rule.description",
71
+ minimum_number: current_order.minimum_projects,
72
+ maximum_number: current_order.maximum_projects
73
+ )
74
+ else
75
+ t(".projects_rule_maximum_only.description", maximum_number: current_order.maximum_projects)
76
+ end
77
+ elsif current_order.minimum_projects_rule?
78
+ t(".minimum_projects_rule.description", minimum_number: current_order.minimum_projects)
79
+ else
80
+ t(".vote_threshold_percent_rule.description", minimum_budget: budget_to_currency(current_order.minimum_budget))
81
+ end
82
+ end
39
83
  end
40
84
  end
41
85
  end
@@ -18,24 +18,76 @@ module Decidim
18
18
  validates :budget, presence: true
19
19
  validate :user_belongs_to_organization
20
20
 
21
- validates :total_budget, numericality: {
22
- greater_than_or_equal_to: :minimum_budget
23
- }, if: :checked_out?
21
+ # Rules active for the budget threshold and minimum budgets rules.
22
+ with_options if: -> { !projects_rule? && checked_out? } do
23
+ validates :total_budget, numericality: {
24
+ greater_than_or_equal_to: :minimum_budget
25
+ }
26
+ end
27
+ with_options unless: :projects_rule? do
28
+ validates :total_budget, numericality: {
29
+ less_than_or_equal_to: :maximum_budget
30
+ }
31
+ end
24
32
 
25
- validates :total_budget, numericality: {
26
- less_than_or_equal_to: :maximum_budget
27
- }
33
+ # Rules active for the minimum projects rule.
34
+ with_options if: -> { minimum_projects_rule? && checked_out? } do
35
+ validates :total_projects, numericality: {
36
+ greater_than_or_equal_to: :minimum_projects
37
+ }
38
+ end
28
39
 
29
- validate :reach_minimum_projects, if: :checked_out?
40
+ # Rules active for the projects rule.
41
+ with_options if: -> { projects_rule? && checked_out? } do
42
+ validates :total_projects, numericality: {
43
+ greater_than_or_equal_to: :minimum_projects
44
+ }
45
+
46
+ validates :total_projects, numericality: {
47
+ less_than_or_equal_to: :maximum_projects
48
+ }
49
+ end
30
50
 
31
51
  scope :finished, -> { where.not(checked_out_at: nil) }
32
52
  scope :pending, -> { where(checked_out_at: nil) }
33
53
 
54
+ # Public: Returns the available budget allocation the user is able to
55
+ # allocate to this order or the maximum amount of projects to be selected
56
+ # in case the project selection voting is enabled.
57
+ def available_allocation
58
+ return maximum_projects if projects_rule?
59
+
60
+ maximum_budget
61
+ end
62
+
63
+ # Public: Returns the numeric amount the given project should allocate
64
+ # from the total available allocation when it is added to the order. The
65
+ # allocation is normally the project's budget but for project selection
66
+ # voting, the allocation is one.
67
+ def allocation_for(project)
68
+ return 1 if projects_rule?
69
+
70
+ project.budget_amount
71
+ end
72
+
34
73
  # Public: Returns the sum of project budgets
35
74
  def total_budget
36
75
  projects.to_a.sum(&:budget_amount)
37
76
  end
38
77
 
78
+ # Public: Returns the count of projects
79
+ def total_projects
80
+ projects.count
81
+ end
82
+
83
+ # Public: For budget voting returns the total budget and for project
84
+ # selection voting, returns the amount of selected projects.
85
+ def total
86
+ return total_projects if projects_rule?
87
+
88
+ total_budget
89
+ end
90
+
39
91
  # Public: Returns true if the order has been checked out
40
92
  def checked_out?
41
93
  checked_out_at.present?
@@ -43,22 +95,28 @@ module Decidim
43
95
 
44
96
  # Public: Check if the order total budget is enough to checkout
45
97
  def can_checkout?
46
- if minimum_projects_rule?
47
- projects.count >= minimum_projects
98
+ if projects_rule?
99
+ total_projects >= minimum_projects && total_projects <= maximum_projects
100
+ elsif minimum_projects_rule?
101
+ total_projects >= minimum_projects
48
102
  else
49
103
  total_budget.to_f >= minimum_budget
50
104
  end
51
105
  end
52
106
 
53
107
  # Public: Returns the order budget percent from the settings total budget
108
+ # or the progress for selected projects if the selected project rule is
109
+ # enabled
54
110
  def budget_percent
55
- (total_budget.to_f / budget.total_budget.to_f) * 100
111
+ return (total_projects.to_f / maximum_projects) * 100 if projects_rule?
112
+
113
+ (total_budget.to_f / budget.total_budget) * 100
56
114
  end
57
115
 
58
116
  # Public: Returns the required minimum budget to checkout
59
117
  def minimum_budget
60
118
  return 0 unless budget
61
- return 0 if minimum_projects_rule?
119
+ return 0 if minimum_projects_rule? || projects_rule?
62
120
 
63
121
  budget.total_budget.to_f * (budget.settings.vote_threshold_percent.to_f / 100)
64
122
  end
@@ -77,11 +135,35 @@ module Decidim
77
135
  budget.settings.vote_rule_minimum_budget_projects_enabled
78
136
  end
79
137
 
138
+ # Public: Returns true if the project voting rule is enabled
139
+ def projects_rule?
140
+ return unless budget
141
+
142
+ budget.settings.vote_rule_selected_projects_enabled
143
+ end
144
+
80
145
  # Public: Returns the required minimum projects to checkout
81
146
  def minimum_projects
82
147
  return 0 unless budget
83
148
 
84
- budget.settings.vote_minimum_budget_projects_number
149
+ if minimum_projects_rule?
150
+ budget.settings.vote_minimum_budget_projects_number
151
+ elsif projects_rule?
152
+ budget.settings.vote_selected_projects_minimum
153
+ else
154
+ 0
155
+ end
156
+ end
157
+
158
+ # Public: Returns the required maximum projects to checkout
159
+ def maximum_projects
160
+ return nil unless budget
161
+
162
+ if projects_rule?
163
+ budget.settings.vote_selected_projects_maximum
164
+ else
165
+ 0
166
+ end
85
167
  end
86
168
 
87
169
  def self.user_collection(user)
@@ -112,12 +194,6 @@ module Decidim
112
194
 
113
195
  errors.add(:user, :invalid) unless user.organization == organization
114
196
  end
115
-
116
- def reach_minimum_projects
117
- return unless minimum_projects_rule?
118
-
119
- errors.add(:projects, :invalid) if minimum_projects > projects.count
120
- end
121
197
  end
122
198
  end
123
199
  end