decidim-budgets 0.23.5 → 0.24.2

Sign up to get free protection for your applications and to get access to all the features.
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 +35 -7
  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 +34 -6
  35. data/config/locales/fr-CA.yml +35 -7
  36. data/config/locales/fr.yml +35 -7
  37. data/config/locales/gl.yml +8 -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 +35 -7
  45. data/config/locales/no.yml +2 -7
  46. data/config/locales/pl.yml +35 -7
  47. data/config/locales/pt-BR.yml +2 -5
  48. data/config/locales/pt.yml +2 -6
  49. data/config/locales/ro-RO.yml +10 -8
  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
@@ -133,10 +133,10 @@ zh-CN:
133
133
  description: 这些是你选择作为预算一部分的项目。
134
134
  title: 确认投票
135
135
  budget_excess:
136
+ budget_excess:
137
+ title: 超出最大预算
136
138
  close: 关闭
137
- description: 此项目超出了最大预算,无法添加。 如果你想要,你可以删除一个你已经选择添加的项目,或者选择你的首选项投票。
138
139
  ok: 好的
139
- title: 超出最大预算
140
140
  budget_summary:
141
141
  are_you_sure: 您确定要取消您的投票吗?
142
142
  assigned: '已指派:'
@@ -144,8 +144,6 @@ zh-CN:
144
144
  checked_out:
145
145
  description: 您已经投票给了预算。如果您改变了主意,您可以 %{cancel_link}。
146
146
  title: 预算投票完成
147
- description: 你认为我们应该为哪些项目分配预算? 给您想要的项目分配至少 %{minimum_budget} ,并且以您的首选项投票来定义预算。
148
- description_minimum_projects_rule: 你认为我们应该为哪些项目分配预算? 选择至少 %{minimum_number} 个项目,您想要并以您的首选项投票来定义预算。
149
147
  title: 您决定预算
150
148
  count:
151
149
  projects_count:
@@ -185,7 +183,6 @@ zh-CN:
185
183
  view: 查看
186
184
  votes:
187
185
  other: 投票
188
- you_voted: 您投票了
189
186
  project_budget_button:
190
187
  add: 添加到您的投票
191
188
  add_descriptive: 将项目 %{resource_name} 添加到您的投票
@@ -212,7 +209,6 @@ zh-CN:
212
209
  landing_page_content: 预算登陆页
213
210
  more_information_modal: 更多信息模式
214
211
  projects_per_page: 每页项目数
215
- resources_permissions_enabled: 每次会议都可以设置动作权限
216
212
  scope_id: 范围
217
213
  scopes_enabled: 范围已启用
218
214
  title: 标题
@@ -257,4 +253,3 @@ zh-CN:
257
253
  project_proposal: '本项目中的建议:'
258
254
  index:
259
255
  confirmed_orders_count: 所得票数
260
- total_budget: 预算总额
@@ -7,12 +7,12 @@ class VotesEnabledToVotesChoices < ActiveRecord::Migration[5.2]
7
7
 
8
8
  def up
9
9
  budget_components.each do |component|
10
- steps = component["settings"] && component["settings"].dig("steps")
11
- default_step = component["settings"] && component["settings"].dig("default_step")
10
+ steps = component["settings"] && component["settings"]["steps"]
11
+ default_step = component["settings"] && component["settings"]["default_step"]
12
12
 
13
13
  if steps.present?
14
14
  new_steps_settings = component["settings"]["steps"].each_with_object({}) do |(step, config), new_config|
15
- votes_value = config.dig("votes_enabled") ? "enabled" : "disabled"
15
+ votes_value = config["votes_enabled"] ? "enabled" : "disabled"
16
16
 
17
17
  new_config[step] = config.merge("votes": votes_value).except("votes_enabled")
18
18
  new_config
@@ -20,7 +20,7 @@ class VotesEnabledToVotesChoices < ActiveRecord::Migration[5.2]
20
20
  component["settings"]["steps"] = new_steps_settings
21
21
  component.save!
22
22
  elsif default_step.present?
23
- votes_value = component["settings"]["default_step"].dig("votes_enabled") ? "enabled" : "disabled"
23
+ votes_value = component["settings"]["default_step"]["votes_enabled"] ? "enabled" : "disabled"
24
24
 
25
25
  new_default_step_settings = component["settings"]["default_step"].merge("votes": votes_value).except("votes_enabled")
26
26
  component["settings"]["default_step"] = new_default_step_settings
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddFollowableCounterCacheToBudgets < ActiveRecord::Migration[5.2]
4
+ def change
5
+ add_column :decidim_budgets_projects, :follows_count, :integer, null: false, default: 0, index: true
6
+
7
+ reversible do |dir|
8
+ dir.up do
9
+ Decidim::Budgets::Project.reset_column_information
10
+ Decidim::Budgets::Project.find_each do |record|
11
+ record.class.reset_counters(record.id, :follows)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ class BudgetType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::ScopableInterface
7
+ implements Decidim::Core::TraceableInterface
8
+
9
+ description "A budget"
10
+
11
+ field :id, GraphQL::Types::ID, "The internal ID of this budget", null: false
12
+ field :title, Decidim::Core::TranslatedFieldType, "The title for this budget", null: false
13
+ field :description, Decidim::Core::TranslatedFieldType, "The description for this budget", null: false
14
+ field :total_budget, GraphQL::Types::Int, "The total budget", null: false, camelize: false
15
+ field :created_at, Decidim::Core::DateTimeType, "When this budget was created", null: true
16
+ field :updated_at, Decidim::Core::DateTimeType, "When this budget was updated", null: true
17
+
18
+ field :projects, [Decidim::Budgets::ProjectType, { null: true }], "The projects for this budget", null: false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ class BudgetsType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::ComponentInterface
7
+
8
+ graphql_name "Budgets"
9
+ description "A budget component of a participatory space."
10
+
11
+ field :budgets, Decidim::Budgets::BudgetType.connection_type, null: true, connection: true
12
+
13
+ def budgets
14
+ Budget.where(component: object).includes(:component)
15
+ end
16
+
17
+ field :budget, Decidim::Budgets::BudgetType, null: true do
18
+ argument :id, GraphQL::Types::ID, required: true
19
+ end
20
+
21
+ def budget(**args)
22
+ Budget.where(component: object).find_by(id: args[:id])
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ class ProjectType < Decidim::Api::Types::BaseObject
6
+ implements Decidim::Core::ScopableInterface
7
+ implements Decidim::Core::AttachableInterface
8
+ implements Decidim::Comments::CommentableInterface
9
+ implements Decidim::Core::CategorizableInterface
10
+
11
+ description "A project"
12
+
13
+ field :id, GraphQL::Types::ID, "The internal ID for this project", null: false
14
+ field :title, Decidim::Core::TranslatedFieldType, "The title for this project", null: true
15
+ field :description, Decidim::Core::TranslatedFieldType, "The description for this project", null: true
16
+ field :budget_amount, GraphQL::Types::Int, "The budget amount for this project", null: true, camelize: false
17
+ field :selected, GraphQL::Types::Boolean, "Whether this proposal is selected or not", method: :selected?, null: true
18
+ field :created_at, Decidim::Core::DateTimeType, "When this project was created", null: true
19
+ field :updated_at, Decidim::Core::DateTimeType, "When this project was updated", null: true
20
+ field :reference, GraphQL::Types::String, "The reference for this project", null: true
21
+ end
22
+ end
23
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "decidim/budgets/workflows"
4
4
  require "decidim/budgets/admin"
5
+ require "decidim/budgets/api"
5
6
  require "decidim/budgets/engine"
6
7
  require "decidim/budgets/admin_engine"
7
8
  require "decidim/budgets/component"
@@ -9,5 +10,6 @@ require "decidim/budgets/component"
9
10
  module Decidim
10
11
  # Base module for this engine.
11
12
  module Budgets
13
+ autoload :ProjectSerializer, "decidim/budgets/project_serializer"
12
14
  end
13
15
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ autoload :ProjectType, "decidim/api/project_type"
6
+ autoload :BudgetType, "decidim/api/budget_type"
7
+ autoload :BudgetsType, "decidim/api/budgets_type"
8
+ end
9
+ end
@@ -62,6 +62,19 @@ Decidim.register_component(:budgets) do |component|
62
62
  Decidim::Follow.where(decidim_followable_type: "Decidim::Budgets::Project", decidim_followable_id: projects_ids).count
63
63
  end
64
64
 
65
+ component.exports :projects do |exports|
66
+ exports.collection do |component_instance, _user, resource_id|
67
+ budgets = resource_id ? Decidim::Budgets::Budget.find(resource_id) : Decidim::Budgets::Budget.where(decidim_component_id: component_instance)
68
+ Decidim::Budgets::Project
69
+ .where(decidim_budgets_budget_id: budgets)
70
+ .includes(:category, :component)
71
+ end
72
+
73
+ exports.include_in_open_data = true
74
+
75
+ exports.serializer Decidim::Budgets::ProjectSerializer
76
+ end
77
+
65
78
  component.settings(:global) do |settings|
66
79
  settings.attribute :scopes_enabled, type: :boolean, default: false
67
80
  settings.attribute :scope_id, type: :scope
@@ -69,8 +82,12 @@ Decidim.register_component(:budgets) do |component|
69
82
  settings.attribute :projects_per_page, type: :integer, default: 12
70
83
  settings.attribute :vote_rule_threshold_percent_enabled, type: :boolean, default: true
71
84
  settings.attribute :vote_threshold_percent, type: :integer, default: 70
85
+ settings.attribute :vote_threshold_percent, type: :integer, default: 70
72
86
  settings.attribute :vote_rule_minimum_budget_projects_enabled, type: :boolean, default: false
73
87
  settings.attribute :vote_minimum_budget_projects_number, type: :integer, default: 1
88
+ settings.attribute :vote_rule_selected_projects_enabled, type: :boolean, default: false
89
+ settings.attribute :vote_selected_projects_minimum, type: :integer, default: 0
90
+ settings.attribute :vote_selected_projects_maximum, type: :integer, default: 1
74
91
  settings.attribute :comments_enabled, type: :boolean, default: true
75
92
  settings.attribute :comments_max_length, type: :integer, required: false
76
93
  settings.attribute :resources_permissions_enabled, type: :boolean, default: true
@@ -105,7 +122,7 @@ Decidim.register_component(:budgets) do |component|
105
122
  participatory_space: participatory_space,
106
123
  settings: {
107
124
  landing_page_content: landing_page_content,
108
- more_information_modal: Decidim::Faker::Localized.paragraph(4),
125
+ more_information_modal: Decidim::Faker::Localized.paragraph(sentence_count: 4),
109
126
  workflow: %w(one random all).sample
110
127
  }
111
128
  )
@@ -113,11 +130,11 @@ Decidim.register_component(:budgets) do |component|
113
130
  rand(1...3).times do
114
131
  Decidim::Budgets::Budget.create!(
115
132
  component: component,
116
- title: Decidim::Faker::Localized.sentence(2),
133
+ title: Decidim::Faker::Localized.sentence(word_count: 2),
117
134
  description: Decidim::Faker::Localized.wrapped("<p>", "</p>") do
118
- Decidim::Faker::Localized.paragraph(3)
135
+ Decidim::Faker::Localized.paragraph(sentence_count: 3)
119
136
  end,
120
- total_budget: Faker::Number.number(8)
137
+ total_budget: Faker::Number.number(digits: 8)
121
138
  )
122
139
  end
123
140
 
@@ -127,35 +144,35 @@ Decidim.register_component(:budgets) do |component|
127
144
  budget: budget,
128
145
  scope: participatory_space.organization.scopes.sample,
129
146
  category: participatory_space.categories.sample,
130
- title: Decidim::Faker::Localized.sentence(2),
147
+ title: Decidim::Faker::Localized.sentence(word_count: 2),
131
148
  description: Decidim::Faker::Localized.wrapped("<p>", "</p>") do
132
- Decidim::Faker::Localized.paragraph(3)
149
+ Decidim::Faker::Localized.paragraph(sentence_count: 3)
133
150
  end,
134
- budget_amount: Faker::Number.number(8)
151
+ budget_amount: Faker::Number.number(digits: 8)
135
152
  )
136
153
 
137
154
  attachment_collection = Decidim::AttachmentCollection.create!(
138
155
  name: Decidim::Faker::Localized.word,
139
- description: Decidim::Faker::Localized.sentence(5),
156
+ description: Decidim::Faker::Localized.sentence(word_count: 5),
140
157
  collection_for: project
141
158
  )
142
159
 
143
160
  Decidim::Attachment.create!(
144
- title: Decidim::Faker::Localized.sentence(2),
145
- description: Decidim::Faker::Localized.sentence(5),
161
+ title: Decidim::Faker::Localized.sentence(word_count: 2),
162
+ description: Decidim::Faker::Localized.sentence(word_count: 5),
146
163
  attachment_collection: attachment_collection,
147
164
  attached_to: project,
148
165
  file: File.new(File.join(__dir__, "seeds", "Exampledocument.pdf"))
149
166
  )
150
167
  Decidim::Attachment.create!(
151
- title: Decidim::Faker::Localized.sentence(2),
152
- description: Decidim::Faker::Localized.sentence(5),
168
+ title: Decidim::Faker::Localized.sentence(word_count: 2),
169
+ description: Decidim::Faker::Localized.sentence(word_count: 5),
153
170
  attached_to: project,
154
171
  file: File.new(File.join(__dir__, "seeds", "city.jpeg"))
155
172
  )
156
173
  Decidim::Attachment.create!(
157
- title: Decidim::Faker::Localized.sentence(2),
158
- description: Decidim::Faker::Localized.sentence(5),
174
+ title: Decidim::Faker::Localized.sentence(word_count: 2),
175
+ description: Decidim::Faker::Localized.sentence(word_count: 5),
159
176
  attached_to: project,
160
177
  file: File.new(File.join(__dir__, "seeds", "Exampledocument.pdf"))
161
178
  )
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Budgets
5
+ class ProjectSerializer < Decidim::Exporters::Serializer
6
+ include Decidim::ApplicationHelper
7
+ include Decidim::ResourceHelper
8
+ include Decidim::TranslationsHelper
9
+
10
+ # Public: Initializes the serializer with a project.
11
+ def initialize(project)
12
+ @project = project
13
+ end
14
+
15
+ # Public: Exports a hash with the serialized data for this project.
16
+ def serialize
17
+ {
18
+ id: project.id,
19
+ category: {
20
+ id: project.category.try(:id),
21
+ name: project.category.try(:name) || empty_translatable
22
+ },
23
+ scope: {
24
+ id: project.scope.try(:id),
25
+ name: project.scope.try(:name) || empty_translatable
26
+ },
27
+ participatory_space: {
28
+ id: project.participatory_space.id,
29
+ url: Decidim::ResourceLocatorPresenter.new(project.participatory_space).url
30
+ },
31
+ component: { id: component.id },
32
+ title: project.title,
33
+ description: project.description,
34
+ budget: { id: project.budget.id },
35
+ budget_amount: project.budget_amount,
36
+ confirmed_votes: project.confirmed_orders_count,
37
+ comments: project.comments.count,
38
+ created_at: project.created_at,
39
+ url: project.polymorphic_resource_url({}),
40
+ related_proposals: related_proposals,
41
+ related_proposal_titles: related_proposal_titles,
42
+ related_proposal_urls: related_proposal_urls
43
+ }
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :project
49
+
50
+ def component
51
+ project.component
52
+ end
53
+
54
+ def related_proposals
55
+ project.linked_resources(:proposals, "included_proposals").map(&:id)
56
+ end
57
+
58
+ def related_proposal_titles
59
+ project.linked_resources(:proposals, "included_proposals").map do |proposal|
60
+ Decidim::Proposals::ProposalPresenter.new(proposal).title
61
+ end
62
+ end
63
+
64
+ def related_proposal_urls
65
+ project.linked_resources(:proposals, "included_proposals").map do |proposal|
66
+ Decidim::ResourceLocatorPresenter.new(proposal).url
67
+ end
68
+ end
69
+
70
+ def url
71
+ Decidim::ResourceLocatorPresenter.new(project).url
72
+ end
73
+
74
+ def empty_translatable(locales = Decidim.available_locales)
75
+ locales.each_with_object({}) do |locale, result|
76
+ result[locale.to_s] = ""
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -16,6 +16,7 @@ FactoryBot.define do
16
16
  transient do
17
17
  vote_rule_threshold_percent_enabled { true }
18
18
  vote_rule_minimum_budget_projects_enabled { false }
19
+ vote_rule_projects_enabled { false }
19
20
  vote_threshold_percent { 70 }
20
21
  end
21
22
 
@@ -23,6 +24,7 @@ FactoryBot.define do
23
24
  {
24
25
  vote_rule_threshold_percent_enabled: vote_rule_threshold_percent_enabled,
25
26
  vote_rule_minimum_budget_projects_enabled: vote_rule_minimum_budget_projects_enabled,
27
+ vote_rule_selected_projects_enabled: vote_rule_projects_enabled,
26
28
  vote_threshold_percent: vote_threshold_percent
27
29
  }
28
30
  end
@@ -32,6 +34,7 @@ FactoryBot.define do
32
34
  transient do
33
35
  vote_rule_threshold_percent_enabled { false }
34
36
  vote_rule_minimum_budget_projects_enabled { true }
37
+ vote_rule_projects_enabled { false }
35
38
  vote_minimum_budget_projects_number { 3 }
36
39
  end
37
40
 
@@ -39,11 +42,32 @@ FactoryBot.define do
39
42
  {
40
43
  vote_rule_threshold_percent_enabled: vote_rule_threshold_percent_enabled,
41
44
  vote_rule_minimum_budget_projects_enabled: vote_rule_minimum_budget_projects_enabled,
45
+ vote_rule_selected_projects_enabled: vote_rule_projects_enabled,
42
46
  vote_minimum_budget_projects_number: vote_minimum_budget_projects_number
43
47
  }
44
48
  end
45
49
  end
46
50
 
51
+ trait :with_budget_projects_range do
52
+ transient do
53
+ vote_rule_threshold_percent_enabled { false }
54
+ vote_rule_minimum_budget_projects_enabled { false }
55
+ vote_rule_projects_enabled { true }
56
+ vote_minimum_budget_projects_number { 3 }
57
+ vote_maximum_budget_projects_number { 6 }
58
+ end
59
+
60
+ settings do
61
+ {
62
+ vote_rule_threshold_percent_enabled: vote_rule_threshold_percent_enabled,
63
+ vote_rule_minimum_budget_projects_enabled: vote_rule_minimum_budget_projects_enabled,
64
+ vote_rule_selected_projects_enabled: vote_rule_projects_enabled,
65
+ vote_selected_projects_minimum: vote_minimum_budget_projects_number,
66
+ vote_selected_projects_maximum: vote_maximum_budget_projects_number
67
+ }
68
+ end
69
+ end
70
+
47
71
  trait :with_votes_disabled do
48
72
  step_settings do
49
73
  {
@@ -96,12 +120,22 @@ FactoryBot.define do
96
120
  factory :project, class: "Decidim::Budgets::Project" do
97
121
  title { generate_localized_title }
98
122
  description { Decidim::Faker::Localized.wrapped("<p>", "</p>") { generate_localized_title } }
99
- budget_amount { Faker::Number.number(8) }
123
+ budget_amount { Faker::Number.number(digits: 8) }
100
124
  budget { create(:budget) }
101
125
 
102
126
  trait :selected do
103
127
  selected_at { Time.current }
104
128
  end
129
+
130
+ trait :with_photos do
131
+ transient do
132
+ photos_number { 2 }
133
+ end
134
+
135
+ after :create do |project, evaluator|
136
+ project.attachments = create_list(:attachment, evaluator.photos_number, :with_image, attached_to: project)
137
+ end
138
+ end
105
139
  end
106
140
 
107
141
  factory :order, class: "Decidim::Budgets::Order" do