diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index ff7a212ee..923d3f69c 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -627,6 +627,15 @@ } } +.milestone-status { + background: $budget; + border-radius: rem-calc(4); + color: #fff; + display: inline-block; + margin-top: $line-height / 6; + padding: $line-height / 4 $line-height / 2; +} + .show-actions-menu { [class^="icon-"] { diff --git a/app/controllers/admin/budget_investment_milestones_controller.rb b/app/controllers/admin/budget_investment_milestones_controller.rb index d2fdf5700..d54dc0174 100644 --- a/app/controllers/admin/budget_investment_milestones_controller.rb +++ b/app/controllers/admin/budget_investment_milestones_controller.rb @@ -3,6 +3,7 @@ class Admin::BudgetInvestmentMilestonesController < Admin::BaseController before_action :load_budget_investment, only: [:index, :new, :create, :edit, :update, :destroy] before_action :load_budget_investment_milestone, only: [:edit, :update, :destroy] + before_action :load_statuses, only: [:index, :new, :create, :edit, :update] def index end @@ -45,7 +46,7 @@ class Admin::BudgetInvestmentMilestonesController < Admin::BaseController def milestone_params image_attributes = [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] documents_attributes = [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy] - attributes = [:title, :description, :publication_date, :budget_investment_id, + attributes = [:title, :description, :publication_date, :budget_investment_id, :status_id, image_attributes: image_attributes, documents_attributes: documents_attributes] params.require(:budget_investment_milestone).permit(*attributes, translation_params(params[:budget_investment_milestone])) @@ -71,4 +72,8 @@ class Admin::BudgetInvestmentMilestonesController < Admin::BaseController get_milestone end + def load_statuses + @statuses = Budget::Investment::Status.all + end + end diff --git a/app/models/budget/investment/milestone.rb b/app/models/budget/investment/milestone.rb index 3f8ba4f3a..b00744138 100644 --- a/app/models/budget/investment/milestone.rb +++ b/app/models/budget/investment/milestone.rb @@ -11,11 +11,12 @@ class Budget globalize_accessors locales: [:en, :es, :fr, :nl, :val, :pt_br] belongs_to :investment + belongs_to :status, class_name: 'Budget::Investment::Status' validates :title, presence: true - validates :description, presence: true validates :investment, presence: true validates :publication_date, presence: true + validate :description_or_status_present? scope :order_by_publication_date, -> { order(publication_date: :asc) } @@ -23,6 +24,11 @@ class Budget 80 end + def description_or_status_present? + unless description.present? || status_id.present? + errors.add(:description) + end + end end end end diff --git a/app/views/admin/budget_investment_milestones/_form.html.erb b/app/views/admin/budget_investment_milestones/_form.html.erb index 6643c7317..4e7017d75 100644 --- a/app/views/admin/budget_investment_milestones/_form.html.erb +++ b/app/views/admin/budget_investment_milestones/_form.html.erb @@ -5,6 +5,15 @@ <%= f.hidden_field :title, value: l(Time.current, format: :datetime), maxlength: Budget::Investment::Milestone.title_max_length %> +
+ <%= f.select :status_id, + @statuses.collect { |s| [s.name, s.id] }, + { include_blank: @statuses.any? ? '' : t('admin.milestones.form.no_statuses_defined') }, + { disabled: @statuses.blank? } %> + <%= link_to t('admin.milestones.form.admin_statuses'), + admin_budget_investment_statuses_path %> +
+ <%= f.label :description, t("admin.milestones.new.description") %> <% @milestone.globalize_locales.each do |locale| %> <%= hidden_field_tag "delete_translations[#{locale}]", 0 %> diff --git a/app/views/admin/budget_investments/_milestones.html.erb b/app/views/admin/budget_investments/_milestones.html.erb index 75d4c65a1..757fb97e4 100644 --- a/app/views/admin/budget_investments/_milestones.html.erb +++ b/app/views/admin/budget_investments/_milestones.html.erb @@ -6,6 +6,7 @@ <%= t("admin.milestones.index.table_title") %> <%= t("admin.milestones.index.table_description") %> <%= t("admin.milestones.index.table_publication_date") %> + <%= t("admin.milestones.index.table_status") %> <%= t("admin.milestones.index.image") %> <%= t("admin.milestones.index.documents") %> <%= t("admin.milestones.index.table_actions") %> @@ -25,6 +26,9 @@ <%= l(milestone.publication_date.to_date) if milestone.publication_date.present? %> + + <%= milestone.status.present? ? milestone.status.name : '' %> + <%= link_to t("admin.milestones.index.show_image"), milestone.image_url(:large), diff --git a/app/views/budgets/investments/milestones/_milestone.html.erb b/app/views/budgets/investments/milestones/_milestone.html.erb index 774819d0c..79c8c05a0 100644 --- a/app/views/budgets/investments/milestones/_milestone.html.erb +++ b/app/views/budgets/investments/milestones/_milestone.html.erb @@ -10,6 +10,18 @@ <% end %> + <% if milestone.status.present? %> +

+ + <%= t("budgets.investments.show.milestone_status_changed") %> + +
+ + <%= milestone.status.name %> + +

+ <% end %> + <%= image_tag(milestone.image_url(:large), { id: "image_#{milestone.id}", alt: milestone.image.title, class: "margin" }) if milestone.image.present? %> <% globalize(neutral_locale(locale)) do %> diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml index c7a69a8aa..7ea038a92 100644 --- a/config/locales/en/activerecord.yml +++ b/config/locales/en/activerecord.yml @@ -126,8 +126,9 @@ en: image: "Proposal descriptive image" image_title: "Image title" budget/investment/milestone: + status_id: "Current investment status (optional)" title: "Title" - description: "Description" + description: "Description (optional if there's an status assigned)" publication_date: "Publication date" budget/investment/status: name: "Name" diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 2a21a1621..965278bb9 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -251,6 +251,7 @@ en: table_title: "Title" table_description: "Description" table_publication_date: "Publication date" + table_status: Status table_actions: "Actions" delete: "Delete milestone" no_milestones: "Don't have defined milestones" @@ -260,6 +261,8 @@ en: form: add_language: Add language remove_language: Remove language + admin_statuses: Admin investment statuses + no_statuses_defined: There are no defined investment statuses yet new: creating: Create milestone date: Date diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml index f43042052..ffd2a50cd 100644 --- a/config/locales/en/budgets.yml +++ b/config/locales/en/budgets.yml @@ -124,6 +124,7 @@ en: milestones_tab: Milestones no_milestones: Don't have defined milestones milestone_publication_date: "Published %{publication_date}" + milestone_status_changed: Investment status changed to author: Author project_unfeasible_html: 'This investment project has been marked as not feasible and will not go to balloting phase.' project_not_selected_html: 'This investment project has not been selected for balloting phase.' diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index db6d7a42c..3b137d67b 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -126,8 +126,9 @@ es: image: "Imagen descriptiva del proyecto de gasto" image_title: "Título de la imagen" budget/investment/milestone: + status_id: "Estado actual del proyecto (opcional)" title: "Título" - description: "Descripción" + description: "Descripción (opcional si se ha asignado un estado de proyecto)" publication_date: "Fecha de publicación" budget/investment/status: name: "Nombre" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 57c1a6f90..141360e98 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -251,6 +251,7 @@ es: table_title: "Título" table_description: "Descripción" table_publication_date: "Fecha de publicación" + table_status: Estado table_actions: "Acciones" delete: "Eliminar hito" no_milestones: "No hay hitos definidos" @@ -260,6 +261,8 @@ es: form: add_language: Añadir idioma remove_language: Eliminar idioma + admin_statuses: Gestionar estados de proyectos + no_statuses_defined: No hay estados definidos new: creating: Crear hito date: Fecha diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml index 974d9b09e..9ca162cf1 100644 --- a/config/locales/es/budgets.yml +++ b/config/locales/es/budgets.yml @@ -124,6 +124,7 @@ es: milestones_tab: Seguimiento no_milestones: No hay hitos definidos milestone_publication_date: "Publicado el %{publication_date}" + milestone_status_changed: El proyecto ha cambiado al estado author: Autor project_unfeasible_html: 'Este proyecto de inversión ha sido marcado como inviable y no pasará a la fase de votación.' project_not_selected_html: 'Este proyecto de inversión no ha sido seleccionado para la fase de votación.' diff --git a/db/migrate/20180321180149_add_status_to_milestones.rb b/db/migrate/20180321180149_add_status_to_milestones.rb new file mode 100644 index 000000000..527d87181 --- /dev/null +++ b/db/migrate/20180321180149_add_status_to_milestones.rb @@ -0,0 +1,8 @@ +class AddStatusToMilestones < ActiveRecord::Migration + disable_ddl_transaction! + + def change + add_column :budget_investment_milestones, :status_id, :integer + add_index :budget_investment_milestones, :status_id, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index e0a25c219..d1408273d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -137,8 +137,11 @@ ActiveRecord::Schema.define(version: 20180519132610) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.datetime "publication_date" + t.integer "status_id" end + add_index "budget_investment_milestones", ["status_id"], name: "index_budget_investment_milestones_on_status_id", using: :btree + create_table "budget_investment_statuses", force: :cascade do |t| t.string "name" t.text "description" diff --git a/spec/factories.rb b/spec/factories.rb index 32670b63f..3aeaef9e2 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -411,6 +411,7 @@ FactoryBot.define do factory :budget_investment_milestone, class: 'Budget::Investment::Milestone' do association :investment, factory: :budget_investment + association :status, factory: :budget_investment_status sequence(:title) { |n| "Budget investment milestone #{n} title" } description 'Milestone description' publication_date Date.current diff --git a/spec/features/admin/budget_investment_milestones_spec.rb b/spec/features/admin/budget_investment_milestones_spec.rb index 81b9be430..a4272432d 100644 --- a/spec/features/admin/budget_investment_milestones_spec.rb +++ b/spec/features/admin/budget_investment_milestones_spec.rb @@ -21,6 +21,7 @@ feature 'Admin budget investment milestones' do expect(page).to have_content(milestone.title) expect(page).to have_content(milestone.id) expect(page).to have_content(milestone.publication_date.to_date) + expect(page).to have_content(milestone.status.name) expect(page).to have_link 'Show image' expect(page).to have_link document.title end @@ -35,10 +36,12 @@ feature 'Admin budget investment milestones' do context "New" do scenario "Add milestone" do + status = create(:budget_investment_status) visit admin_budget_budget_investment_path(@investment.budget, @investment) click_link 'Create new milestone' + select status.name, from: 'budget_investment_milestone_status_id' fill_in 'budget_investment_milestone_description_en', with: 'New description milestone' fill_in 'budget_investment_milestone_publication_date', with: Date.current @@ -46,6 +49,14 @@ feature 'Admin budget investment milestones' do expect(page).to have_content 'New description milestone' expect(page).to have_content Date.current + expect(page).to have_content status.name + end + + scenario "Status select is disabled if there are no statuses available" do + visit admin_budget_budget_investment_path(@investment.budget, @investment) + + click_link 'Create new milestone' + expect(find("#budget_investment_milestone_status_id").disabled?).to be true end scenario "Show validation errors on milestone form" do diff --git a/spec/features/budgets/investments_spec.rb b/spec/features/budgets/investments_spec.rb index 1ad7a82e9..51445bccc 100644 --- a/spec/features/budgets/investments_spec.rb +++ b/spec/features/budgets/investments_spec.rb @@ -1017,6 +1017,7 @@ feature 'Budget Investments' do expect(page.find("#image_#{first_milestone.id}")['alt']).to have_content(image.title) expect(page).to have_link(document.title) expect(page).to have_link("https://consul.dev") + expect(page).to have_content(first_milestone.status.name) end select('Español', from: 'locale-switcher') diff --git a/spec/models/budget/investment/milestone_spec.rb b/spec/models/budget/investment/milestone_spec.rb index b019e8a40..ad844865d 100644 --- a/spec/models/budget/investment/milestone_spec.rb +++ b/spec/models/budget/investment/milestone_spec.rb @@ -14,15 +14,59 @@ describe Budget::Investment::Milestone do expect(milestone).not_to be_valid end - it "is not valid without a description" do + it "is not valid without a description if status is empty" do + milestone.status = nil milestone.description = nil expect(milestone).not_to be_valid end + it "is valid without a description if status is present" do + milestone.description = nil + expect(milestone).to be_valid + end + it "is not valid without an investment" do milestone.investment_id = nil expect(milestone).not_to be_valid end + + it "is not valid if description and status are not present" do + milestone.description = nil + milestone.status_id = nil + expect(milestone).not_to be_valid + end + + it "is valid without status if description is present" do + milestone.status_id = nil + expect(milestone).to be_valid + end + + it "is valid without description if status is present" do + milestone.description = nil + expect(milestone).to be_valid + end + end + + describe "#description_or_status_present?" do + let(:milestone) { build(:budget_investment_milestone) } + + it "is not valid when status is removed and there's no description" do + milestone.update(description: nil) + expect(milestone.update(status_id: nil)).to be false + end + + it "is not valid when description is removed and there's no status" do + milestone.update(status_id: nil) + expect(milestone.update(description: nil)).to be false + end + + it "is valid when description is removed and there is a status" do + expect(milestone.update(description: nil)).to be true + end + + it "is valid when status is removed and there is a description" do + expect(milestone.update(status_id: nil)).to be true + end end end