From 1040c667e3e6fd541577704200bfb8eb39cbf911 Mon Sep 17 00:00:00 2001 From: Joao Felipe Pimentel Date: Thu, 28 Nov 2024 23:10:47 -0300 Subject: [PATCH 1/2] Revert "Fix CodeScanner #8 - Remove skip verify_authenticity_token at preview" This reverts commit d96baf24e2d206f910122626569d83d4a4d3e504. --- app/controllers/report_configurations_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index 36a63823..cee72e97 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -5,6 +5,7 @@ class ReportConfigurationsController < ApplicationController authorize_resource + skip_before_action :verify_authenticity_token, only: [:preview] include ApplicationHelper active_scaffold :report_configuration do |config| From fc3f09c3d10cbecd16b879225f5ba93138baec1e Mon Sep 17 00:00:00 2001 From: Joao Felipe Pimentel Date: Fri, 29 Nov 2024 00:52:52 -0300 Subject: [PATCH 2/2] #503: add grants table --- app/controllers/grants_controller.rb | 33 ++++++++++ .../report_configurations_controller.rb | 2 +- app/helpers/grants_helper.rb | 17 +++++ app/models/ability.rb | 5 +- app/models/grant.rb | 53 ++++++++++++++++ config/locales/grant.pt-BR.yml | 34 ++++++++++ config/locales/navigation.pt-BR.yml | 1 + config/navigation.rb | 1 + config/routes.rb | 5 ++ db/migrate/20241129022042_create_grants.rb | 20 ++++++ db/schema.rb | 52 +++++++++++++-- spec/factories/factory_grant.rb | 15 +++++ spec/models/grant_spec.rb | 63 +++++++++++++++++++ 13 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 app/controllers/grants_controller.rb create mode 100644 app/helpers/grants_helper.rb create mode 100644 app/models/grant.rb create mode 100644 config/locales/grant.pt-BR.yml create mode 100644 db/migrate/20241129022042_create_grants.rb create mode 100644 spec/factories/factory_grant.rb create mode 100644 spec/models/grant_spec.rb diff --git a/app/controllers/grants_controller.rb b/app/controllers/grants_controller.rb new file mode 100644 index 00000000..4afcd0da --- /dev/null +++ b/app/controllers/grants_controller.rb @@ -0,0 +1,33 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +# frozen_string_literal: true + +class GrantsController < ApplicationController + authorize_resource + + active_scaffold :grant do |config| + config.create.label = :create_grant_label + config.columns = [:title, :start_year, :end_year, :professor, :kind, :funder, :amount] + config.actions.swap :search, :field_search + config.columns[:professor].form_ui = :record_select + config.columns[:kind].form_ui = :select + config.columns[:kind].options = { + options: Grant::KINDS, + default: Grant::PUBLIC, + include_blank: I18n.t("active_scaffold._select_") + } + config.columns[:amount].options[:format] = :currency + config.columns[:start_year].options[:format] = "%d" + config.columns[:end_year].options[:format] = "%d" + config.actions.exclude :deleted_records + end + + protected + def do_new + super + unless current_user.professor.blank? + @record.professor = current_user.professor + end + end +end diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index cee72e97..fadca143 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -5,7 +5,7 @@ class ReportConfigurationsController < ApplicationController authorize_resource - skip_before_action :verify_authenticity_token, only: [:preview] + skip_before_action :verify_authenticity_token, only: [:preview] # noqa # do not remove this line based on Code Scanner suggestions include ApplicationHelper active_scaffold :report_configuration do |config| diff --git a/app/helpers/grants_helper.rb b/app/helpers/grants_helper.rb new file mode 100644 index 00000000..ae6340cf --- /dev/null +++ b/app/helpers/grants_helper.rb @@ -0,0 +1,17 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +# frozen_string_literal: true + +module GrantsHelper + def professor_form_column(record, options) + if can?(:edit_professor, record) + record_select_field :professor, record.professor || Professor.new, options + else + options[:value] = record.professor_id + label( + :record_value_1_params, record.id, record.professor.name + ) + hidden_field(:record, :professor, options) + end + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index 591dd693..e0f4d76c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -16,7 +16,7 @@ class Ability PROFESSOR_MODELS = [ Professor, Advisement, AdvisementAuthorization, ThesisDefenseCommitteeParticipation, - ProfessorResearchArea, + ProfessorResearchArea, Grant ] SCHOLARSHIP_MODELS = [ @@ -140,6 +140,9 @@ def initialize_professors(user, roles) end if roles[Role::ROLE_PROFESSOR] can :read, Ability::PROFESSOR_MODELS + cannot :edit_professor, Grant + can :create, Grant + can :update, Grant, professor: user.professor end end diff --git a/app/models/grant.rb b/app/models/grant.rb new file mode 100644 index 00000000..95373194 --- /dev/null +++ b/app/models/grant.rb @@ -0,0 +1,53 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +# frozen_string_literal: true + +class Grant < ApplicationRecord + has_paper_trail + belongs_to :professor, optional: false + + PUBLIC = I18n.translate( + "activerecord.attributes.grant.kinds.public" + ) + PRIVATE = I18n.translate( + "activerecord.attributes.grant.kinds.private" + ) + KINDS = [PUBLIC, PRIVATE] + + validates :title, presence: true + validates :start_year, presence: true + validates :kind, presence: true, inclusion: { in: KINDS } + validates :funder, presence: true + validates :amount, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :professor, presence: true + validate :that_professor_cannot_edit_other_grants, if: -> { cannot?(:edit_professor, self) } + validate :that_end_year_is_greater_than_start_year + + + def that_professor_cannot_edit_other_grants + current_professor = current_user&.professor + return if current_professor.nil? + if self.changes[:professor_id].present? && self.changes[:professor_id] != [nil, current_professor.id] + self.errors.add(:professor, :cannot_edit_other_grants) + end + end + + def that_end_year_is_greater_than_start_year + return if self.end_year.nil? + if self.end_year < self.start_year + self.errors.add(:end_year, :start_greater_than_end) + end + end + + def to_label + "[#{self.start_year}] #{self.title}" + end + + private + delegate :can?, :cannot?, to: :ability + + def ability + @ability ||= Ability.new(current_user) + end +end diff --git a/config/locales/grant.pt-BR.yml b/config/locales/grant.pt-BR.yml new file mode 100644 index 00000000..26574ffe --- /dev/null +++ b/config/locales/grant.pt-BR.yml @@ -0,0 +1,34 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +pt-BR: + activerecord: + attributes: + grant: + title: "Título" + start_year: "Ano de Início" + end_year: "Ano de Término" + professor: "Coordenador" + kind: "Tipo de financiamento" + kinds: + public: "Público" + private: "Privado" + funder: "Financiador" + amount: "Valor total" + description: + grant: + funder: "(e.g., CNPq, FAPERJ, nome da empresa, etc)" + + errors: + models: + grant: + cannot_edit_other_grants: "não pode ser editado por este usuário" + start_greater_than_end: "não pode ser menor do que Ano de Início" + + models: + grant: + one: "Coordenação de Projeto" + other: "Coordenações de Projetos" + + active_scaffold: + create_grant_label: "Adicionar Coordenação de Projeto" diff --git a/config/locales/navigation.pt-BR.yml b/config/locales/navigation.pt-BR.yml index 720d60b5..d1b7f2ff 100644 --- a/config/locales/navigation.pt-BR.yml +++ b/config/locales/navigation.pt-BR.yml @@ -22,6 +22,7 @@ pt-BR: advisement: Orientações advisement_authorization: Credenciamentos thesis_defense_committee_participation: Bancas + grant: Coordenações de Projetos scholarships: label: Bolsas diff --git a/config/navigation.rb b/config/navigation.rb index d2505ce8..01ed9519 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -149,6 +149,7 @@ def can_read?(*args) submenu.modelitem Advisement submenu.modelitem AdvisementAuthorization submenu.modelitem ThesisDefenseCommitteeParticipation + submenu.modelitem Grant end scholar_models = [Scholarship, ScholarshipType, ScholarshipDuration] diff --git a/config/routes.rb b/config/routes.rb index 8eaf23eb..5a3c69ab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -519,4 +519,9 @@ if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end + + resources :grants do + concerns :active_scaffold + end + end diff --git a/db/migrate/20241129022042_create_grants.rb b/db/migrate/20241129022042_create_grants.rb new file mode 100644 index 00000000..a7727872 --- /dev/null +++ b/db/migrate/20241129022042_create_grants.rb @@ -0,0 +1,20 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +# frozen_string_literal: true + +class CreateGrants < ActiveRecord::Migration[7.0] + def change + create_table :grants do |t| + t.string :title + t.integer :start_year + t.integer :end_year + t.string :kind + t.string :funder + t.decimal :amount, precision: 14, scale: 2 + t.references :professor, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 5bc81fc6..71afefc9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_09_12_113419) do +ActiveRecord::Schema[7.0].define(version: 2024_11_29_022042) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -256,6 +256,17 @@ t.index ["professor_id"], name: "index_advisements_on_professor_id" end + create_table "affiliations", force: :cascade do |t| + t.integer "professor_id" + t.integer "institution_id" + t.datetime "start_date" + t.datetime "end_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["institution_id"], name: "index_affiliations_on_institution_id" + t.index ["professor_id"], name: "index_affiliations_on_professor_id" + end + create_table "allocations", force: :cascade do |t| t.string "day", limit: 255 t.string "room", limit: 255 @@ -571,6 +582,19 @@ t.datetime "updated_at", null: false end + create_table "grants", force: :cascade do |t| + t.string "title" + t.integer "start_year" + t.integer "end_year" + t.string "kind" + t.string "funder" + t.decimal "amount", precision: 14, scale: 2 + t.integer "professor_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["professor_id"], name: "index_grants_on_professor_id" + end + create_table "institutions", force: :cascade do |t| t.string "name", limit: 255 t.datetime "created_at", precision: nil, null: false @@ -703,7 +727,6 @@ t.string "siape", limit: 255 t.string "enrollment_number", limit: 255 t.string "identity_issuing_place", limit: 255 - t.integer "institution_id" t.string "email", limit: 255 t.date "academic_title_date" t.integer "academic_title_country_id" @@ -717,10 +740,17 @@ t.index ["city_id"], name: "index_professors_on_city_id" t.index ["cpf"], name: "index_professors_on_cpf" t.index ["email"], name: "index_professors_on_email" - t.index ["institution_id"], name: "index_professors_on_institution_id" t.index ["user_id"], name: "index_professors_on_user_id" end + create_table "program_levels", force: :cascade do |t| + t.integer "level", null: false + t.datetime "start_date", null: false + t.datetime "end_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "queries", force: :cascade do |t| t.string "name", limit: 255 t.text "sql" @@ -805,13 +835,24 @@ t.boolean "use_at_schedule", default: false, null: false t.text "text" t.string "image", limit: 255 - t.boolean "signature_footer", default: false, null: false t.integer "order", default: 2 t.decimal "scale", precision: 10, scale: 8 t.integer "x" t.integer "y" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.integer "signature_type", default: 0 + t.integer "expiration_in_months" + end + + create_table "reports", force: :cascade do |t| + t.integer "generated_by_id" + t.integer "carrierwave_file_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.date "expires_at" + t.string "identifier" + t.string "file_name" end create_table "research_areas", force: :cascade do |t| @@ -998,4 +1039,7 @@ t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" end + add_foreign_key "grants", "professors" + add_foreign_key "reports", "carrier_wave_files", column: "carrierwave_file_id" + add_foreign_key "reports", "users", column: "generated_by_id" end diff --git a/spec/factories/factory_grant.rb b/spec/factories/factory_grant.rb new file mode 100644 index 00000000..0dba6a85 --- /dev/null +++ b/spec/factories/factory_grant.rb @@ -0,0 +1,15 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +# frozen_string_literal: true + +FactoryBot.define do + factory :grant do + title { "Projeto" } + start_year { 2024 } + kind { Grant::PUBLIC } + funder { "CNPq" } + amount { 100000 } + professor + end +end diff --git a/spec/models/grant_spec.rb b/spec/models/grant_spec.rb new file mode 100644 index 00000000..17e8e978 --- /dev/null +++ b/spec/models/grant_spec.rb @@ -0,0 +1,63 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Grant, type: :model do + it { should be_able_to_be_destroyed } + it { should belong_to(:professor).required(true) } + + let(:professor) { FactoryBot.build(:professor) } + let(:user) { FactoryBot.build(:user, professor: professor) } + let(:grant) do + Grant.new( + title: "Title", + start_year: 2024, + kind: Grant::PUBLIC, + funder: "CNPq", + amount: 100000, + professor: professor + ) + end + subject { grant } + describe "Validations" do + it { should be_valid } + it { should validate_presence_of(:title) } + it { should validate_presence_of(:start_year) } + it { should validate_inclusion_of(:kind).in_array(Grant::KINDS) } + it { should validate_presence_of(:funder) } + it { should validate_presence_of(:amount) } + it { should validate_presence_of(:professor) } + it { should validate_numericality_of(:amount) } + # ToDo: professor cannot edit other grants + describe "end_year" do + context "should be valid when" do + it "is empty" do + grant.end_year = nil + expect(grant).to have(0).errors_on :end_year + end + it "is grater than start_year" do + grant.end_year = grant.start_year + 1 + expect(grant).to have(0).errors_on :end_year + end + end + context "should have start_greater_than_end error when" do + it "start_year is greater than end_year" do + grant.end_year = grant.start_year - 1 + expect(grant).to have_error(:start_greater_than_end).on :end_year + end + end + end + end + describe "Methods" do + describe "to_label" do + it "should return '[year] title'" do + grant.start_year = 2024 + grant.title = "Projeto" + expect(grant.to_label).to eq("[2024] Projeto") + end + end + end +end