diff --git a/.ruby-version b/.ruby-version index e4604e3a..5ae69bd5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.1 +3.2.5 diff --git a/Gemfile b/Gemfile index 9126b81f..cd469d4c 100644 --- a/Gemfile +++ b/Gemfile @@ -1,11 +1,11 @@ -# ruby=3.2.1 +# ruby=3.2.5 # frozen_string_literal: true source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } # The following line is necessary to allow RVM choosing the correct ruby version. RVM 2.0 will probably be able to interpret the "~>" symbol and we will be able to safely remove the "#ruby=3.2.2" line. -ruby "~> 3.2.1" +ruby "~> 3.2.5" # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.0.8.1" @@ -89,6 +89,7 @@ gem "prawn" gem "prawn-table" gem "prawn-rails" gem "matrix", "~> 0.4.2" +gem "prawn-qrcode" # Redcarpet for Readme MarkDown (or README.md) - Credits Page gem "redcarpet" diff --git a/Gemfile.lock b/Gemfile.lock index 9f645c06..48c84ad6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,61 +29,61 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.4) - actionpack (= 7.0.8.4) - activesupport (= 7.0.8.4) + actioncable (7.0.8.5) + actionpack (= 7.0.8.5) + activesupport (= 7.0.8.5) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.4) - actionpack (= 7.0.8.4) - activejob (= 7.0.8.4) - activerecord (= 7.0.8.4) - activestorage (= 7.0.8.4) - activesupport (= 7.0.8.4) + actionmailbox (7.0.8.5) + actionpack (= 7.0.8.5) + activejob (= 7.0.8.5) + activerecord (= 7.0.8.5) + activestorage (= 7.0.8.5) + activesupport (= 7.0.8.5) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8.4) - actionpack (= 7.0.8.4) - actionview (= 7.0.8.4) - activejob (= 7.0.8.4) - activesupport (= 7.0.8.4) + actionmailer (7.0.8.5) + actionpack (= 7.0.8.5) + actionview (= 7.0.8.5) + activejob (= 7.0.8.5) + activesupport (= 7.0.8.5) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.8.4) - actionview (= 7.0.8.4) - activesupport (= 7.0.8.4) + actionpack (7.0.8.5) + actionview (= 7.0.8.5) + activesupport (= 7.0.8.5) rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.4) - actionpack (= 7.0.8.4) - activerecord (= 7.0.8.4) - activestorage (= 7.0.8.4) - activesupport (= 7.0.8.4) + actiontext (7.0.8.5) + actionpack (= 7.0.8.5) + activerecord (= 7.0.8.5) + activestorage (= 7.0.8.5) + activesupport (= 7.0.8.5) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.4) - activesupport (= 7.0.8.4) + actionview (7.0.8.5) + activesupport (= 7.0.8.5) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) active_scaffold_duplicate (1.1.4) active_scaffold (>= 3.6.0.pre) - activejob (7.0.8.4) - activesupport (= 7.0.8.4) + activejob (7.0.8.5) + activesupport (= 7.0.8.5) globalid (>= 0.3.6) - activemodel (7.0.8.4) - activesupport (= 7.0.8.4) - activerecord (7.0.8.4) - activemodel (= 7.0.8.4) - activesupport (= 7.0.8.4) + activemodel (7.0.8.5) + activesupport (= 7.0.8.5) + activerecord (7.0.8.5) + activemodel (= 7.0.8.5) + activesupport (= 7.0.8.5) activerecord-session_store (2.1.0) actionpack (>= 6.1) activerecord (>= 6.1) @@ -91,14 +91,14 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 4) railties (>= 6.1) - activestorage (7.0.8.4) - actionpack (= 7.0.8.4) - activejob (= 7.0.8.4) - activerecord (= 7.0.8.4) - activesupport (= 7.0.8.4) + activestorage (7.0.8.5) + actionpack (= 7.0.8.5) + activejob (= 7.0.8.5) + activerecord (= 7.0.8.5) + activesupport (= 7.0.8.5) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.8.4) + activesupport (7.0.8.5) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -148,6 +148,7 @@ GEM childprocess (5.1.0) logger (~> 1.5) choice (0.2.0) + chunky_png (1.4.0) cocoon (1.2.15) coderay (1.1.3) coffee-rails (5.0.0) @@ -206,7 +207,7 @@ GEM globalid (1.2.1) activesupport (>= 6.1) htmlentities (4.3.4) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) ice_nine (0.11.2) image_processing (1.12.2) @@ -262,7 +263,7 @@ GEM mini_magick (4.12.0) mini_mime (1.1.5) mini_portile2 (2.8.7) - minitest (5.25.0) + minitest (5.25.1) msgpack (1.7.2) multi_json (1.15.0) mysql2 (0.5.5) @@ -277,7 +278,7 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.5.9) + nio4r (2.7.3) nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) @@ -295,6 +296,9 @@ GEM prawn (2.4.0) pdf-core (~> 0.9.0) ttfunk (~> 1.7) + prawn-qrcode (0.5.2) + prawn (>= 1) + rqrcode (>= 1.0.0) prawn-rails (1.4.2) actionview (>= 3.1.0) prawn @@ -307,27 +311,27 @@ GEM psych (5.1.2) stringio public_suffix (6.0.1) - puma (6.4.2) + puma (6.4.3) nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) rack (2.2.8.1) rack-test (2.1.0) rack (>= 1.3) - rails (7.0.8.4) - actioncable (= 7.0.8.4) - actionmailbox (= 7.0.8.4) - actionmailer (= 7.0.8.4) - actionpack (= 7.0.8.4) - actiontext (= 7.0.8.4) - actionview (= 7.0.8.4) - activejob (= 7.0.8.4) - activemodel (= 7.0.8.4) - activerecord (= 7.0.8.4) - activestorage (= 7.0.8.4) - activesupport (= 7.0.8.4) + rails (7.0.8.5) + actioncable (= 7.0.8.5) + actionmailbox (= 7.0.8.5) + actionmailer (= 7.0.8.5) + actionpack (= 7.0.8.5) + actiontext (= 7.0.8.5) + actionview (= 7.0.8.5) + activejob (= 7.0.8.5) + activemodel (= 7.0.8.5) + activerecord (= 7.0.8.5) + activestorage (= 7.0.8.5) + activesupport (= 7.0.8.5) bundler (>= 1.15.0) - railties (= 7.0.8.4) + railties (= 7.0.8.5) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -340,9 +344,9 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (7.0.8.4) - actionpack (= 7.0.8.4) - activesupport (= 7.0.8.4) + railties (7.0.8.5) + actionpack (= 7.0.8.5) + activesupport (= 7.0.8.5) method_source rake (>= 12.2) thor (~> 1.0) @@ -363,9 +367,12 @@ GEM responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.6) - strscan + rexml (3.3.9) rouge (4.2.0) + rqrcode (2.2.0) + chunky_png (~> 1.0) + rqrcode_core (~> 1.0) + rqrcode_core (1.2.0) rspec-collection_matchers (1.2.1) rspec-expectations (>= 2.99.0.beta1) rspec-core (3.12.2) @@ -465,7 +472,6 @@ GEM sqlite3 (1.6.8-x86_64-linux) ssrf_filter (1.1.2) stringio (3.1.1) - strscan (3.1.0) thor (1.3.1) tilt (2.3.0) timeliness (0.4.5) @@ -537,6 +543,7 @@ DEPENDENCIES nokogiri (>= 1.16.5) paper_trail prawn + prawn-qrcode prawn-rails prawn-table pry @@ -568,7 +575,7 @@ DEPENDENCIES web-console RUBY VERSION - ruby 3.2.2p53 + ruby 3.2.5p208 BUNDLED WITH 2.4.19 diff --git a/app/controllers/admissions/admission_phases_controller.rb b/app/controllers/admissions/admission_phases_controller.rb index 04ddc077..3bbced83 100644 --- a/app/controllers/admissions/admission_phases_controller.rb +++ b/app/controllers/admissions/admission_phases_controller.rb @@ -17,7 +17,7 @@ class Admissions::AdmissionPhasesController < ApplicationController form_columns = [ :name, :member_form, :shared_form, :consolidation_form, :candidate_form, :can_edit_candidate, :candidate_can_edit, :candidate_can_see_member, :candidate_can_see_shared, - :candidate_can_see_consolidation, + :candidate_can_see_consolidation, :committee_can_see_other_individual, :approval_condition, :keep_in_phase_condition, :admission_phase_committees, ] diff --git a/app/controllers/admissions/filled_forms_controller.rb b/app/controllers/admissions/filled_forms_controller.rb index c48abac5..292bd0ff 100644 --- a/app/controllers/admissions/filled_forms_controller.rb +++ b/app/controllers/admissions/filled_forms_controller.rb @@ -20,7 +20,7 @@ class Admissions::FilledFormsController < ApplicationController protected def self.filled_form_params_definition [ - :id, :is_filled, :form_template_id, :disable_submission, + :id, :is_filled, :form_template_id, :enable_submission, fields_attributes: [ :id, :form_field_id, :value, :_destroy, :remove_file, :file, list: [], diff --git a/app/controllers/concerns/shared_pdf_concern.rb b/app/controllers/concerns/shared_pdf_concern.rb index 967c3548..718d9793 100644 --- a/app/controllers/concerns/shared_pdf_concern.rb +++ b/app/controllers/concerns/shared_pdf_concern.rb @@ -34,7 +34,7 @@ def render_course_classes_summary_pdf(course_class) ) end - def render_enrollments_academic_transcript_pdf(enrollment) + def render_enrollments_academic_transcript_pdf(enrollment, filename = "transcript.pdf") class_enrollments = enrollment.class_enrollments .where(situation: ClassEnrollment::APPROVED) .joins(:course_class) @@ -47,6 +47,7 @@ def render_enrollments_academic_transcript_pdf(enrollment) type: "application/pdf", formats: [:pdf], assigns: { + filename: filename, enrollment: enrollment, class_enrollments: class_enrollments, accomplished_phases: accomplished_phases, @@ -55,7 +56,7 @@ def render_enrollments_academic_transcript_pdf(enrollment) ) end - def render_enrollments_grades_report_pdf(enrollment) + def render_enrollments_grades_report_pdf(enrollment, filename = "grades_report.pdf") class_enrollments = enrollment.class_enrollments .where(situation: ClassEnrollment::APPROVED) .joins(:course_class) @@ -67,6 +68,7 @@ def render_enrollments_grades_report_pdf(enrollment) type: "application/pdf", formats: [:pdf], assigns: { + filename: filename, enrollment: enrollment, class_enrollments: class_enrollments, accomplished_phases: accomplished_phases, diff --git a/app/controllers/enrollments_controller.rb b/app/controllers/enrollments_controller.rb index cd206859..3b921f0b 100644 --- a/app/controllers/enrollments_controller.rb +++ b/app/controllers/enrollments_controller.rb @@ -126,6 +126,7 @@ class EnrollmentsController < ApplicationController :thesis_title, :thesis_defense_date, :obs, + :obs_to_academic_transcript, :advisements, :scholarship_durations, @@ -219,8 +220,9 @@ def academic_transcript_pdf format.pdf do title = I18n.t("pdf_content.enrollment.academic_transcript.title") student = enrollment.student.name - send_data render_enrollments_academic_transcript_pdf(enrollment), - filename: "#{title} - #{student}.pdf", + filename = "#{title} - #{student}.pdf" + send_data render_enrollments_academic_transcript_pdf(enrollment, filename), + filename: filename, type: "application/pdf" end end @@ -232,8 +234,9 @@ def grades_report_pdf format.pdf do title = I18n.t("pdf_content.enrollment.grades_report.title") student = enrollment.student.name - send_data render_enrollments_grades_report_pdf(enrollment), - filename: "#{title} - #{student}.pdf", + filename = "#{title} - #{student}.pdf" + send_data render_enrollments_grades_report_pdf(enrollment, filename), + filename: filename, type: "application/pdf" end end 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/paper_professors_controller.rb b/app/controllers/paper_professors_controller.rb new file mode 100644 index 00000000..5a032626 --- /dev/null +++ b/app/controllers/paper_professors_controller.rb @@ -0,0 +1,16 @@ +# 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 PaperProfessorsController < ApplicationController + authorize_resource + + active_scaffold :paper_professor do |config| + config.create.label = :create_paper_professor_label + config.columns = [:paper, :professor] + config.columns[:professor].form_ui = :record_select + + config.actions.exclude :deleted_records + end +end diff --git a/app/controllers/paper_students_controller.rb b/app/controllers/paper_students_controller.rb new file mode 100644 index 00000000..7c2681b0 --- /dev/null +++ b/app/controllers/paper_students_controller.rb @@ -0,0 +1,16 @@ +# 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 PaperStudentsController < ApplicationController + authorize_resource + + active_scaffold :paper_student do |config| + config.create.label = :create_paper_student_label + config.columns = [:paper, :student] + config.columns[:student].form_ui = :record_select + + config.actions.exclude :deleted_records + end +end diff --git a/app/controllers/papers_controller.rb b/app/controllers/papers_controller.rb new file mode 100644 index 00000000..50807054 --- /dev/null +++ b/app/controllers/papers_controller.rb @@ -0,0 +1,86 @@ +# 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 PapersController < ApplicationController + authorize_resource + + active_scaffold :paper do |config| + config.create.label = :create_paper_label + config.actions.swap :search, :field_search + config.list.sorting = { period: "DESC", owner: "ASC", order: "ASC" } + + config.columns.add :reason_group + config.columns.add :reason_group_end + form_columns = [ + :period, :owner, :reference, :order, :kind, :doi_issn_event, + :paper_professors, :paper_students, :other_authors, + :reason_group, + :reason_impact_factor, + :reason_international_list, + :reason_citations, + :reason_national_interest, + :reason_international_interest, + :reason_national_representativeness, + :reason_scientific_contribution, + :reason_tech_contribution, + :reason_innovation_contribution, + :reason_social_contribution, + :reason_other, + :reason_justify, + :reason_group_end, + :other, + ] + config.create.columns = form_columns + config.update.columns = form_columns + config.show.columns = [ + :period, :owner, :reference, :kind, :doi_issn_event, + :paper_professors, :paper_students, :other_authors, + :reason_impact_factor, + :reason_international_list, + :reason_citations, + :reason_national_interest, + :reason_international_interest, + :reason_national_representativeness, + :reason_scientific_contribution, + :reason_tech_contribution, + :reason_innovation_contribution, + :reason_social_contribution, + :reason_other, + :reason_justify, + :order, + :other, + ] + config.list.columns = [ + :period, :owner, :order, :reference + ] + + config.columns[:owner].form_ui = :record_select + config.columns[:kind].form_ui = :select + config.columns[:kind].options = { + options: Paper::KINDS, + include_blank: I18n.t("active_scaffold._select_") + } + config.columns[:order].form_ui = :select + config.columns[:order].options = { + options: Paper::ORDERS, + include_blank: I18n.t("active_scaffold._select_") + } + + + config.columns[:paper_students].show_blank_record = false + config.columns[:paper_professors].show_blank_record = false + + config.actions.exclude :deleted_records + end + + protected + def do_new + super + @record.period = CustomVariable.quadrennial_period + unless current_user.professor.blank? + @record.owner = current_user.professor + end + end +end diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index cee72e97..2f1cb399 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -5,24 +5,28 @@ 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| config.create.label = :create_report_configuration_label columns = [ - :name, :image, :scale, :x, :y, :order, :text, :signature_footer, - :preview, :use_at_report, :use_at_transcript, :use_at_grades_report, - :use_at_schedule, + :name, :image, :scale, :x, :y, :order, :text, :signature_type, + :preview, :use_at_report, :use_at_transcript, + :use_at_grades_report, :use_at_schedule, :expiration_in_months ] config.create.columns = columns config.update.columns = columns columns.delete(:preview) config.columns = columns config.list.columns = [ - :name, :order, :text, :signature_footer, :use_at_report, - :use_at_transcript, :use_at_grades_report, :use_at_schedule + :name, :order, :text, :signature_type, + :use_at_report, :use_at_transcript, :use_at_grades_report, + :use_at_schedule, :expiration_in_months ] + config.columns[:signature_type].form_ui = :select + config.columns[:signature_type].options = { options: ReportConfiguration.signature_types.keys.map(&:to_sym) } + config.columns[:expiration_in_months].description = I18n.t("active_scaffold.report_configurations.expiration_in_months_description") config.list.sorting = { name: "ASC" } config.actions << :duplicate config.duplicate.link.label = " @@ -41,7 +45,7 @@ def preview record.assign_attributes(record_params.except(:image_cache, :remove_image)) # up = ImageUploader.new record, :image # up.store(File.open(params[:record][:image])) - @pdf_config = record + @pdf_config = record.tap { |r| r.preview = true } respond_to do |format| format.pdf do title = I18n.t("pdf_content.report_configurations.preview") @@ -61,8 +65,8 @@ def logo def record_params params.required(:record).permit( :name, :use_at_report, :use_at_transcript, :use_at_grades_report, - :use_at_schedule, :text, :image, :signature_footer, :order, :scale, - :x, :y + :use_at_schedule, :text, :image, :order, :scale, + :x, :y, :signature_type, :expiration_in_months ) end end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb new file mode 100644 index 00000000..6aabf11e --- /dev/null +++ b/app/controllers/reports_controller.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +class ReportsController < ApplicationController + before_action :set_report, only: [:download, :download_by_identifier] + before_action :check_downloadable, only: [:download, :download_by_identifier] + authorize_resource + + skip_authorization_check only: :download_by_identifier + skip_authorize_resource only: :download_by_identifier + skip_before_action :authenticate_user!, only: :download_by_identifier + + active_scaffold :report do |config| + config.list.columns = [:user, :file_name, :identifier, :created_at, :expires_at] + config.show.columns = [:user, :file_name, :identifier, :created_at, :expires_at] + config.update.columns = [:expires_at] + config.columns[:user].clear_link + config.actions.exclude :create, :delete + config.action_links.add "download", + label: " + + ".html_safe, + page: true, + type: :member, + parameters: { format: :pdf }, + method: :get, + html_options: { target: "_blank" }, + ignore_method: :cant_download? + end + + def download + redirect_to download_path(medium_hash: @report.carrierwave_file.medium_hash) + end + + def download_by_identifier + send_data(@report.carrierwave_file.read, filename: @report.carrierwave_file.original_filename, disposition: :inline) + end + + private + def set_report + @report = params[:id] ? Report.find(params[:id]) : Report.find_by_identifier(params[:identifier]) + raise ActionController::RoutingError.new("Este documento não foi encontrado.") if @report.nil? + end + + def check_downloadable + raise ActionController::RoutingError.new("Este documento expirou.") if cant_download?(@report) + end + + def cant_download?(record) + record.carrierwave_file.blank? + end +end diff --git a/app/helpers/enrollments_pdf_helper.rb b/app/helpers/enrollments_pdf_helper.rb index b192b868..48fb8c58 100644 --- a/app/helpers/enrollments_pdf_helper.rb +++ b/app/helpers/enrollments_pdf_helper.rb @@ -295,7 +295,7 @@ def transcript_table(pdf, options = {}) if table_data.size >= page_size new_page = true - next_table_data = table_data.slice(page_size..-1) + next_table_data = table_data.slice(page_size + 1..-1) table_data = table_data.slice(0..page_size) page_size = 50 end @@ -711,6 +711,32 @@ def thesis_table(curr_pdf, options = {}) end end + def obs_table(pdf, options = {}) + if options[:enrollment]&.obs_to_academic_transcript.present? + data_table_rows = [[ + "#{I18n.t("pdf_content.enrollment.academic_transcript.obs_to_academic_transcript")} #{ + rescue_blank_text(options[:enrollment].obs_to_academic_transcript) + }" + ]] + + pdf.table( + data_table_rows, + cell_style: { + borders: [:left, :right, :bottom, :top], + border_bottom_width: 0.5, + border_top_width: 0.15, + width: 560, + padding_top: 5.5, + padding_bottom: 7.5, + inline_format: true, + font: "FreeMono", + size: 8, + border_color: "000080" + } + ) + end + end + def justification_grade_not_count_in_gpr_table(curr_pdf, options = {}) enrollment ||= options[:enrollment] 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/helpers/paper_professors_helper.rb b/app/helpers/paper_professors_helper.rb new file mode 100644 index 00000000..5e2d8c7e --- /dev/null +++ b/app/helpers/paper_professors_helper.rb @@ -0,0 +1,7 @@ +# 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 PaperProfessorsHelper +end \ No newline at end of file diff --git a/app/helpers/paper_students_helper.rb b/app/helpers/paper_students_helper.rb new file mode 100644 index 00000000..2e20d80a --- /dev/null +++ b/app/helpers/paper_students_helper.rb @@ -0,0 +1,7 @@ +# 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 PaperStudentsHelper +end \ No newline at end of file diff --git a/app/helpers/papers_helper.rb b/app/helpers/papers_helper.rb new file mode 100644 index 00000000..c1366ca5 --- /dev/null +++ b/app/helpers/papers_helper.rb @@ -0,0 +1,31 @@ +# 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 PapersHelper + def owner_form_column(record, options) + if can?(:edit_professor, record) + record_select_field :owner, record.owner || Professor.new, options + else + options[:value] = record.owner_id + label( + :record_value_1_params, record.id, record.owner.name + ) + hidden_field(:record, :owner, options) + end + end + + def period_form_column(record, options) + if can?(:edit_professor, record) + text_field :record, :period, options + else + label( + :record_value_1_params, record.id, record.period + ) + hidden_field(:record, :period, options) + end + end + + def reason_group_form_column(record, options) + "aaaa" + end +end \ No newline at end of file diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index 979de3ab..512b29f9 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -98,8 +98,7 @@ def signature_footer(pdf, options = {}) diff_width = options[:diff_width] diff_width ||= 0 - - last_box_height = 50 + last_box_height = options[:qr_code_signature] ? 80 : 50 last_box_width1 = 165 last_box_width2 = 335 @@ -109,47 +108,106 @@ def signature_footer(pdf, options = {}) [0, last_box_y], width: last_box_width1, height: last_box_height ) do - pdf.stroke_bounds current_x = x - pdf.move_down last_box_height / 2 - pdf.draw_text( - "#{I18n.t("pdf_content.enrollment.footer.location")}, #{I18n.localize( - Date.today, format: :long - )}", at: [current_x, pdf.cursor]) + if options[:qr_code_signature] + qrcode_signature(pdf, { size: 80 }) + else + pdf.stroke_bounds + + pdf.move_down last_box_height / 2 + pdf.draw_text( + "#{I18n.t("pdf_content.enrollment.footer.location")}, #{I18n.localize( + Date.today, format: :long + )}", at: [current_x, pdf.cursor]) + end end end - pdf.font("FreeMono", size: 6) do + signature_font_size = options[:qr_code_signature] ? 8 : 6 + + pdf.font("FreeMono", size: signature_font_size) do pdf.bounding_box( [last_box_width1, last_box_y], width: last_box_width2, height: last_box_height ) do pdf.stroke_bounds - current_x = x - pdf.move_down 8 - pdf.draw_text( - "#{I18n.t("pdf_content.enrollment.footer.warning1")}", - at: [current_x, pdf.cursor] - ) + if options[:qr_code_signature] + qrcode_url = download_by_identifier_reports_url(identifier: @qrcode_identifier) + qrcode_signature_warning = I18n.t("pdf_content.enrollment.footer.qrcode_signature_warning") + signed_at = "#{I18n.t("pdf_content.enrollment.footer.signed_by_qrcode")} #{I18n.l(Time.now, format: :defaultdatetime)} (Horário de Brasília)" + you_can_also_access = I18n.t("pdf_content.enrollment.footer.you_can_also_access") - underline_width = 3.7 - pdf.move_down 30 - underline = "_" * 74 - current_x += (last_box_width2 - underline.size * underline_width) / 2 + all_sentences = [qrcode_signature_warning, signed_at, you_can_also_access, qrcode_url] + if options[:expires_at] + valid_until = "#{I18n.t("pdf_content.enrollment.footer.valid_until")} #{I18n.l(Date.today + options[:expires_at].months, format: :default)}" + all_sentences.append(valid_until) + end - pdf.draw_text(underline, at: [current_x, pdf.cursor]) + center_around = all_sentences.max_by(&:size) + pdf.move_down (last_box_height - signature_font_size * (all_sentences.count - 1)) / 2 + current_x = (last_box_width2 - pdf.width_of(center_around)) / 2 - pdf.move_down 8 - font_width = 6.7 - coordinator_signature = I18n.t( - "pdf_content.enrollment.footer.coordinator_signature" - ) - current_x += ( - last_box_width2 - coordinator_signature.size * font_width - ) / 2 - pdf.draw_text(coordinator_signature, at: [current_x, pdf.cursor]) + pdf.draw_text( + "#{qrcode_signature_warning}", + at: [current_x, pdf.cursor] + ) + + pdf.move_down signature_font_size + + pdf.draw_text( + signed_at, + at: [current_x, pdf.cursor] + ) + + pdf.move_down signature_font_size + + pdf.draw_text( + you_can_also_access, + at: [current_x, pdf.cursor] + ) + + pdf.move_down signature_font_size + + pdf.draw_text( + qrcode_url, + at: [current_x, pdf.cursor] + ) + if options[:expires_at] + 2.times { pdf.move_down signature_font_size } + + pdf.draw_text( + valid_until, + at: [current_x, pdf.cursor] + ) + end + else + current_x = x + pdf.move_down 8 + + pdf.draw_text( + "#{I18n.t("pdf_content.enrollment.footer.warning1")}", + at: [current_x, pdf.cursor] + ) + + underline_width = 3.7 + pdf.move_down 30 + underline = "_" * 74 + current_x += (last_box_width2 - underline.size * underline_width) / 2 + + pdf.draw_text(underline, at: [current_x, pdf.cursor]) + + pdf.move_down 8 + font_width = 6.7 + coordinator_signature = I18n.t( + "pdf_content.enrollment.footer.coordinator_signature" + ) + current_x += ( + last_box_width2 - coordinator_signature.size * font_width + ) / 2 + pdf.draw_text(coordinator_signature, at: [current_x, pdf.cursor]) + end end end @@ -226,14 +284,9 @@ def text_table(pdf, data_table, default_margin_indent) def new_document(name, title, options = {}, &block) type = options[:pdf_type] || :report - pdf_type = :"use_at_#{type}" - pdf_config = ( - options[:pdf_config] || - ReportConfiguration.where(pdf_type => true).order(order: :desc).first || - ReportConfiguration.new - ) + pdf_config = setup_pdf_config(type, options) - prawn_document({ + document = prawn_document({ page_size: "A4", left_margin: 0.6.cm, right_margin: ( @@ -241,7 +294,7 @@ def new_document(name, title, options = {}, &block) ), top_margin: 0.8.cm, bottom_margin: ( - pdf_config.signature_footer ? 96 + FOOTER_TOP_MARGIN : 1.cm + !pdf_config.no_signature? ? 96 + FOOTER_TOP_MARGIN : 1.cm ), filename: name }.merge(options)) do |pdf| @@ -275,10 +328,16 @@ def new_document(name, title, options = {}, &block) header(pdf, title, pdf_config) yield pdf - pdf.repeat(:all, dynamic: true) do - if pdf_config.signature_footer + if pdf_config.qr_code? + pdf.repeat(:all, dynamic: true) do + signature_footer(pdf, { qr_code_signature: true, expires_at: pdf_config.expiration_in_months }) + end + elsif pdf_config.manual? + pdf.repeat(:all, dynamic: true) do signature_footer(pdf) - else + end + else + pdf.repeat(:all, dynamic: true) do datetime_footer(pdf) end end @@ -299,6 +358,21 @@ def new_document(name, title, options = {}, &block) end end end + + if pdf_config.qr_code_signature && !pdf_config.preview + uploader = PdfUploader.new + uploader.store!({ base64_contents: Base64.encode64(document), filename: name }) + + Report.create!( + expires_at: pdf_config.expiration_in_months.present? ? Date.today + pdf_config.expiration_in_months.months : nil, + user: current_user, + carrierwave_file: uploader.file&.file, + file_name: name, + identifier: @qrcode_identifier + ) + end + + document end def pdf_list_with_title(pdf, title, data, options = {}, &block) @@ -400,20 +474,18 @@ def simple_pdf_table(pdf, widths, header, data, options = {}, join_header_and_ta padding: 2 } ) do |table| - table.before_rendering_page do |page| - page.row(0).background_color = "E5E5FF" page.row(0).borders = [:top, :bottom, :left, :right] page.row(0).column(0).align = :center page.row(0).column(-1).align = :center - + if page.row_count > 1 page.rows(1 .. -1).text_color = "000000" page.row(-1).borders = [:bottom, :left, :right] end end - + yield table unless block.nil? end pdf.fill_color "000080" @@ -422,7 +494,28 @@ def simple_pdf_table(pdf, widths, header, data, options = {}, join_header_and_ta end end end + end + def qrcode_signature(pdf, options = {}) + @qrcode_identifier ||= generate_qr_code_key + while Report.where(identifier: @qrcode_identifier).exists? + @qrcode_identifier = generate_qr_code_key + end + + data = download_by_identifier_reports_url(identifier: @qrcode_identifier) + + pdf.print_qr_code(data, extent: options[:size] || 80, align: :center) + end + + def setup_pdf_config(pdf_type, options) + pdf_type_property = :"use_at_#{pdf_type}" + options[:pdf_config] || + ReportConfiguration.where(pdf_type_property => true).order(order: :desc).first || + ReportConfiguration.new + end + def generate_qr_code_key + 10.times.map { "2346789BCDFGHJKMPQRTVWXY".split("").sample } + .insert(5, "-").join("") end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 5e5feae8..fe451919 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, Paper, PaperProfessor, PaperStudent ] SCHOLARSHIP_MODELS = [ @@ -39,8 +39,12 @@ class Ability Major, Institution, ] + DOCUMENT_MODELS = [ + Report + ] + PLACE_MODELS = [ - City, State, Country, + City, State, Country ] CONFIGURATION_MODELS = [ @@ -81,6 +85,7 @@ def initialize(user) initialize_phases(user, roles) initialize_courses(user, roles) initialize_education(user, roles) + initialize_documents(user, roles) initialize_places(user, roles) initialize_admissions(user, roles) initialize_configurations(user, roles) @@ -140,6 +145,22 @@ def initialize_professors(user, roles) end if roles[Role::ROLE_PROFESSOR] can :read, Ability::PROFESSOR_MODELS + cannot :edit_professor, Grant + cannot :edit_professor, Paper + can :create, Grant + can :update, Grant, professor: user.professor + can :destroy, Grant, professor: user.professor + can :create, Paper + can :update, Paper, owner: user.professor + can :create, PaperProfessor + can :create, PaperStudent + can :update, PaperProfessor, paper: { owner: user.professor } + can :update, PaperStudent, paper: { owner: user.professor } + can :destroy, PaperProfessor, paper: { owner: user.professor } + can :destroy, PaperStudent, paper: { owner: user.professor } + can :destroy, PaperProfessor, paper: { owner: nil } + can :destroy, PaperStudent, paper: { owner: nil } + can :destroy, Paper, owner: user.professor end end @@ -218,6 +239,13 @@ def initialize_education(user, roles) end end + def initialize_documents(user, roles) + if roles[:manager] + can :manage, Ability::DOCUMENT_MODELS + cannot :update, Report unless roles[Role::ROLE_ADMINISTRADOR] + end + end + def initialize_places(user, roles) if roles[:manager] can :manage, Ability::PLACE_MODELS diff --git a/app/models/admissions/ability.rb b/app/models/admissions/ability.rb index dc4c4f23..be71c78f 100644 --- a/app/models/admissions/ability.rb +++ b/app/models/admissions/ability.rb @@ -77,6 +77,7 @@ def initialize_admissions(user, roles) can :cancel, Admissions::AdmissionApplication can :configuration, Admissions::AdmissionApplication can :read_all, Admissions::AdmissionApplication + can :read_pendencies, Admissions::AdmissionApplication can :map_student, Admissions::AdmissionApplication can :custom_report, Admissions::AdmissionProcess end diff --git a/app/models/admissions/admission_application.rb b/app/models/admissions/admission_application.rb index c5e94657..2f1aed7c 100644 --- a/app/models/admissions/admission_application.rb +++ b/app/models/admissions/admission_application.rb @@ -411,7 +411,7 @@ def assign_form( self.assign_attributes(params) all_forms = [] all_phase_forms = [] - if self.filled_form.try(:disable_submission) != "1" + if self.filled_form.try(:enable_submission) == "1" self.filled_form.prepare_missing_fields self.filled_form.sync_fields_after(self) all_forms << { @@ -423,7 +423,7 @@ def assign_form( end if has_letter_forms self.letter_requests.each do |letter_request| - if letter_request.filled_form.try(:disable_submission) != "1" + if letter_request.filled_form.try(:enable_submission) == "1" letter_request.filled_form.sync_fields_after(letter_request) all_forms << { was_filled: letter_request.filled_form.is_filled, @@ -449,7 +449,7 @@ def assign_form( # Do not solve pendency if it was not created by assign_attributes next if phase_form[:from_build] # Do not solve pendency if form submission was disabled - next if phase_form[:object].filled_form.try(:disable_submission) == "1" + next if phase_form[:object].filled_form.try(:enable_submission) != "1" phase_form[:was_filled] = phase_form[:object].filled_form.is_filled phase_form[:object].filled_form.is_filled = true all_phase_forms << phase_form @@ -462,7 +462,7 @@ def assign_form( self.ordered_rankings.each do |ranking| next if !ranking.filled_form.is_filled next if !can_edit_override - next if ranking.filled_form.try(:disable_submission) == "1" + next if ranking.filled_form.try(:enable_submission) != "1" ranking.filled_form.prepare_missing_fields all_forms << { was_filled: ranking.filled_form.is_filled, diff --git a/app/models/admissions/admission_phase.rb b/app/models/admissions/admission_phase.rb index 6506145f..93e5f797 100644 --- a/app/models/admissions/admission_phase.rb +++ b/app/models/admissions/admission_phase.rb @@ -246,7 +246,10 @@ def prepare_application_forms( ) && (phase_form[:mode] == :candidate) end if committee_permission_user.present? - next false if phase_form[:mode] == :member && phase_form[:user_id] != committee_permission_user.id + if phase_form[:mode] == :member + next false if phase_form[:user_id] != committee_permission_user.id && !current_phase.try(:committee_can_see_other_individual) + can_edit_form = can_edit_override || phase_form[:user_id] == committee_permission_user.id + end if phase_form[:mode] == :candidate can_edit_form = can_edit_override || current_phase.try(:can_edit_candidate) end diff --git a/app/models/admissions/filled_form.rb b/app/models/admissions/filled_form.rb index d291a99e..a4ea7985 100644 --- a/app/models/admissions/filled_form.rb +++ b/app/models/admissions/filled_form.rb @@ -6,7 +6,7 @@ class Admissions::FilledForm < ActiveRecord::Base has_paper_trail - attr_accessor :disable_submission + attr_accessor :enable_submission has_one :admission_application, dependent: :restrict_with_exception, class_name: "Admissions::AdmissionApplication" diff --git a/app/models/custom_variable.rb b/app/models/custom_variable.rb index ad6371e1..a7bb4d8c 100644 --- a/app/models/custom_variable.rb +++ b/app/models/custom_variable.rb @@ -24,6 +24,7 @@ class CustomVariable < ApplicationRecord "year_semester_range" => :text, "past_calendar_range" => :text, "academic_calendar_range" => :text, + "quadrennial_period" => :text, } validates :variable, presence: true @@ -109,6 +110,12 @@ def self.academic_calendar_range self.parse_calendar_range(config, [20, 10, false]) end + def self.quadrennial_period + config = CustomVariable.find_by_variable(:quadrennial_period) + config.blank? ? "Not defined" : config.value + end + + def to_label self.variable.to_s 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/app/models/paper.rb b/app/models/paper.rb new file mode 100644 index 00000000..a6a8e442 --- /dev/null +++ b/app/models/paper.rb @@ -0,0 +1,55 @@ +# 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 Paper < ApplicationRecord + has_paper_trail + + attr_accessor :reason_group, :reason_group_end, :header + + belongs_to :owner, optional: false, class_name: "Professor" + has_many :paper_professors, dependent: :destroy + has_many :paper_students, dependent: :destroy + has_many :professors, through: :paper_professors + has_many :students, through: :paper_students + + JOURNAL = I18n.translate( + "activerecord.attributes.paper.kinds.journal" + ) + CONFERENCE = I18n.translate( + "activerecord.attributes.paper.kinds.conference" + ) + KINDS = [JOURNAL, CONFERENCE] + + ORDERS = [1, 2, 3, 4, 5, 6, 7, 8] + + validates :reference, presence: true + validates :kind, presence: true, inclusion: { in: KINDS } + validates :doi_issn_event, presence: true + validates :reason_justify, presence: true + validates :order, presence: true, inclusion: { in: ORDERS }, uniqueness: { + scope: [:period, :owner_id], message: :order_uniqueness + } + + validate :that_professor_cannot_edit_other_papers, if: -> { cannot?(:edit_professor, self) } + + def that_professor_cannot_edit_other_papers + current_professor = current_user&.professor + return if current_professor.nil? + if self.changes[:owner_id].present? && self.changes[:owner_id] != [nil, current_professor.id] + self.errors.add(:owner, :cannot_edit_other_papers) + end + end + + def to_label + "[#{self.owner.to_label}] #{self.reference}" + end + + private + delegate :can?, :cannot?, to: :ability + + def ability + @ability ||= Ability.new(current_user) + end +end diff --git a/app/models/paper_professor.rb b/app/models/paper_professor.rb new file mode 100644 index 00000000..733596ca --- /dev/null +++ b/app/models/paper_professor.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 + +class PaperProfessor < ApplicationRecord + has_paper_trail + + belongs_to :paper, optional: false + belongs_to :professor, optional: false + + def to_label + "#{self.professor.to_label} - #{self.paper.to_label}" + end +end diff --git a/app/models/paper_student.rb b/app/models/paper_student.rb new file mode 100644 index 00000000..675ac399 --- /dev/null +++ b/app/models/paper_student.rb @@ -0,0 +1,10 @@ +class PaperStudent < ApplicationRecord + has_paper_trail + + belongs_to :paper + belongs_to :student + + def to_label + "#{self.student.to_label} - #{self.paper.to_label}" + end +end diff --git a/app/models/report.rb b/app/models/report.rb new file mode 100644 index 00000000..63b5410b --- /dev/null +++ b/app/models/report.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Report < ApplicationRecord + belongs_to :user, foreign_key: "generated_by_id" + belongs_to :carrierwave_file, foreign_key: "carrierwave_file_id", class_name: "CarrierWave::Storage::ActiveRecord::ActiveRecordFile", optional: true + + def to_label + "#{self.user.name} - #{I18n.l(self.created_at, format: '%d/%m/%Y %H:%M')}" + end +end diff --git a/app/models/report_configuration.rb b/app/models/report_configuration.rb index 6e8b75b5..7b6b8371 100644 --- a/app/models/report_configuration.rb +++ b/app/models/report_configuration.rb @@ -6,15 +6,20 @@ # Represents Configuration of a Report class ReportConfiguration < ApplicationRecord has_paper_trail + attr_accessor :preview validates :text, presence: true validates :order, presence: true validates :x, presence: true validates :y, presence: true validates :scale, presence: true + validates :expiration_in_months, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true + validate :expiration_in_months_only_for_qr_code mount_uploader :image, ImageUploader + enum signature_type: { no_signature: 0, manual: 1, qr_code: 2 } + def initialize_dup(other) super attrib = other.attributes.except("id", "created_at", "updated_at") @@ -24,4 +29,19 @@ def initialize_dup(other) def mount_uploader_name :image end + + def signature_footer + signature_type === "manual" + end + + def qr_code_signature + signature_type === "qr_code" + end + + private + def expiration_in_months_only_for_qr_code + if expiration_in_months.present? && signature_type != "qr_code" + errors.add(:expiration_in_months, :only_for_qr_code) + end + end end diff --git a/app/uploaders/pdf_uploader.rb b/app/uploaders/pdf_uploader.rb new file mode 100644 index 00000000..dd1c0ba5 --- /dev/null +++ b/app/uploaders/pdf_uploader.rb @@ -0,0 +1,39 @@ +# 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 PdfUploader < CarrierWave::Uploader::Base + storage :active_record + cache_storage :file + + configure do |config| + config.root = Rails.root + end + + class FilelessIO < StringIO + attr_accessor :original_filename + attr_accessor :content_type + end + + # Param must be a hash with to 'base64_contents' and 'filename'. + def cache!(file) + return if file.blank? + if defined? file.file + file = file.file + end + # avoid the carrier_wave to create duplicate database entry of same file due file termination case + if (defined? file.original_filename) && (file.original_filename.is_a? String) + file.original_filename.downcase! + end + if file.respond_to?(:has_key?) && file.has_key?(:base64_contents) && file.has_key?(:filename) + local_file = FilelessIO.new(Base64.decode64(file[:base64_contents])) + local_file.original_filename = file[:filename] + extension = File.extname(file[:filename])[1..-1] + local_file.content_type = Mime::Type.lookup_by_extension(extension).to_s + super(local_file) + else + super(file) + end + end +end diff --git a/app/views/admissions/filled_form/edit/_filled_form_template.html.erb b/app/views/admissions/filled_form/edit/_filled_form_template.html.erb index c8c09248..b0d745eb 100644 --- a/app/views/admissions/filled_form/edit/_filled_form_template.html.erb +++ b/app/views/admissions/filled_form/edit/_filled_form_template.html.erb @@ -32,13 +32,15 @@
  • - <%= filled_form.label :disable_submission, "Desabilitar formulário nesta submissão", style: "color:red;", + <%= filled_form.label :enable_submission, "Habilitar formulário nesta submissão", style: "color:red;", for: "#{filled_class}-toggle", - title: "Desabilita parte do formulário com suas validações e resolução de pendências. Desabilita apenas para esta submissão" %> + title: "Habilita parte do formulário com suas validações e resolução de pendências para esta submissão" %>
    -
    <%= filled_form.check_box :disable_submission, checked: true, data: { filled_class: }, id: "#{filled_class}-toggle" %>
    +
    <%= filled_form.check_box :enable_submission, checked: false, data: { filled_class: }, id: "#{filled_class}-toggle" %>
  • +<% else %> + <%= filled_form.hidden_field :enable_submission, value: "1" %> <% end %> <% groups.each do |gfield, gtag, fields| %> <% if gfield.present? %> @@ -64,12 +66,12 @@ diff --git a/config/locales/admissions/admission_phase.pt-BR.yml b/config/locales/admissions/admission_phase.pt-BR.yml index cc880776..9c2f77a0 100644 --- a/config/locales/admissions/admission_phase.pt-BR.yml +++ b/config/locales/admissions/admission_phase.pt-BR.yml @@ -22,6 +22,7 @@ pt-BR: candidate_can_see_member: "Candidato pode ver preenchimento de formulário individual" candidate_can_see_shared: "Candidato pode ver preenchimento de formulário compartilhado" candidate_can_see_consolidation: "Candidato pode ver preenchimento de consolidação" + committee_can_see_other_individual: "Comitê pode ver preenchimento de formulário individual de outros membros" models: admissions/admission_phase: one: "Fase" diff --git a/config/locales/enrollment.pt-BR.yml b/config/locales/enrollment.pt-BR.yml index c12d1c37..29edb2a2 100644 --- a/config/locales/enrollment.pt-BR.yml +++ b/config/locales/enrollment.pt-BR.yml @@ -41,6 +41,7 @@ pt-BR: thesis_defense_committee_participations: "Banca Avaliadora" thesis_defense_committee_professors: "Banca Avaliadora" accomplishment_conclusion_date_not_given: "---------/----" + obs_to_academic_transcript: "Observação para o Histórico" errors: models: @@ -92,6 +93,10 @@ pt-BR: location: "Niterói" page: "Pág. nº " warning1: "Este documento só é válido sem rasuras, com selo da UFF e com a assinatura do Coordenador." + qrcode_signature_warning: "O documento original pode ser visto acessando o QR Code ao lado." + signed_by_qrcode: "Assinado digitalmente em" + you_can_also_access: "Você também pode acessar o documento em:" + valid_until: "Documento válido até" academic_transcript: deferrals: "PRORROGAÇÕES" accomplished_phases: "ETAPAS OBRIGATÓRIAS CONCLUÍDAS" @@ -101,6 +106,7 @@ pt-BR: title: "Histórico Escolar" total_credits: "Total" scholarships: "BOLSAS" + obs_to_academic_transcript: "OBSERVAÇÕES:" grade_list: title: "DISCIPLINAS CURSADAS COM APROVEITAMENTO" course_approved: "AP" 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 1d7e58f3..78cee103 100644 --- a/config/locales/navigation.pt-BR.yml +++ b/config/locales/navigation.pt-BR.yml @@ -21,6 +21,8 @@ pt-BR: advisement: Orientações advisement_authorization: Credenciamentos thesis_defense_committee_participation: Bancas + grant: Coordenações de Projetos + paper: Avaliação quadrienal - 4N scholarships: label: Bolsas @@ -53,6 +55,10 @@ pt-BR: major: Cursos institution: Instituições + documents: + label: Documentos + report: Documentos Assinados + locations: label: Localidades city: Cidades diff --git a/config/locales/paper.pt-BR.yml b/config/locales/paper.pt-BR.yml new file mode 100644 index 00000000..ff33e6c7 --- /dev/null +++ b/config/locales/paper.pt-BR.yml @@ -0,0 +1,54 @@ +# 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: + paper: + period: Avaliação quadrienal - 4N + owner: "Professor" + reference: "Referência completa" + kind: "Tipo" + kinds: + journal: "Periódico" + conference: "Conferência" + doi_issn_event: "DOI ou ISSN ou Sigla Evento" + students: "Autores discentes da Pós ou egressos" + paper_students: "Autores discentes da Pós ou egressos" + professors: "Autores docentes da Pós" + paper_professors: "Autores docentes da Pós" + other_authors: "Outros autores separados por ';'" + reasons: "Quais aspectos atribui ao artigo para que ele entre na sua lista?" + reason_impact_factor: "Valor quantitativo do fórum (fator de impacto, h-index)" + reason_international_list: "Fórum em alguma lista de referência internacional (p.ex. CSRankings)" + reason_citations: "Número elevado de citações para a sua área" + reason_national_interest: "Receptividade da comunidade nacional (p.ex, se seu artigo teve muito interesse ao ser apresentado em um evento nacional, ou se muitas pessoas no Brasil te perguntaram sobre ele)" + reason_international_interest: "Recepetividade da comunidade internacional (p.ex, se seu artigo teve muito interesse ao ser apresentado em um evento internacional, ou se muitas pessoas fora do Brasil te perguntaram sobre ele)" + reason_national_representativeness: "Representatividade nacional (p.ex., se é um fórum que poucos brasileiros conseguem publicar)" + reason_scientific_contribution: "Contribuição científica (p.ex., é uma contribuição original de um método, um algoritmo, uma prova matemática, uma investigação científica)" + reason_tech_contribution: "Contribuição tecnológica (p.ex. é um produto de software de código aberto)" + reason_innovation_contribution: "Contribuição de inovação (p.ex., se o artigo poderia ser uma ideia inicial para uma startup)" + reason_social_contribution: "Contribuição social (p.ex., se o artigo apresenta uma solução inovadora para um problema da sociedade)" + reason_other: "Outro aspecto" + reason_justify: "Justifique brevemente as suas marcações na pergunta anterior" + order: "Relevância" + other: "Caso queira, descreva brevemente qualquer outro ponto que ache relevante para o artigo." + description: + paper: + reference: "(copiar do seu lattes)" + doi_issn_event: "(copiar do seu lattes)" + order: "Considerando o conjunto que você selecionou (1 é o mais relevante)" + + errors: + models: + paper: + cannot_edit_other_papers: "não pode ser editado por este usuário" + order_uniqueness: "deve ser única para professor no período quadrienal" + + models: + paper: + one: "Avaliação quadrienal - 4N" + other: "Artigo" + + active_scaffold: + create_paper_label: "Adicionar Artigo" diff --git a/config/locales/paper_professor.pt-BR.yml b/config/locales/paper_professor.pt-BR.yml new file mode 100644 index 00000000..73873d8f --- /dev/null +++ b/config/locales/paper_professor.pt-BR.yml @@ -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. + +pt-BR: + activerecord: + attributes: + paper_professor: + paper: "Artigo" + professor: "Docente" + + models: + paper_professor: + one: "Docente Autor" + other: "Docentes Autores" + + active_scaffold: + create_paper_professor_label: "Adicionar Docente Autor" diff --git a/config/locales/paper_student.pt-BR.yml b/config/locales/paper_student.pt-BR.yml new file mode 100644 index 00000000..f58806a3 --- /dev/null +++ b/config/locales/paper_student.pt-BR.yml @@ -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. + +pt-BR: + activerecord: + attributes: + paper_student: + paper: "Artigo" + student: "Discente" + + models: + paper_student: + one: "Discente Autor" + other: "Discentes Autores" + + active_scaffold: + create_paper_student_label: "Adicionar Discente Autor" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 8716d2f0..362171b4 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -96,6 +96,7 @@ pt-BR: show_block: "Mostrar" show_link: "Visualizar" show_model: "" + download_link: "Download" to_pdf: "Gerar relatório" total_label: "Total" update: "Atualizar" diff --git a/config/locales/report_configuration.pt-BR.yml b/config/locales/report_configuration.pt-BR.yml index a580ce52..24358b4a 100644 --- a/config/locales/report_configuration.pt-BR.yml +++ b/config/locales/report_configuration.pt-BR.yml @@ -7,7 +7,7 @@ pt-BR: report_configuration: name: "Nome" image: "Logo" - signature_footer: "Rodapé com assinatura" + signature_type: "Tipo de Assinatura" text: "Header" use_at_grades_report: "Usar no boletim" use_at_report: "Usar em relatórios" @@ -17,16 +17,28 @@ pt-BR: scale: "Escala" x: "X" y: "Y" + no_signature: "Sem Assinatura" + manual: "Manual" + qr_code: "Código QR" + expiration_in_months: "Validade (meses)" models: report_configuration: one: "Configuração de relatório" other: "Configurações de relatório" + errors: + models: + report_configuration: + attributes: + expiration_in_months: + only_for_qr_code: "só pode ser preenchido se o tipo de assinatura for QR Code" + active_scaffold: create_report_configuration_label: "Adicionar Configuração de Relatório" report_configurations: preview: "Visualizar" + expiration_in_months_description: "Deixe em branco caso não haja validade definida. Válido somente para assinaturas via QR Code." pdf_content: report_configurations: diff --git a/config/locales/reports.pt-BR.yml b/config/locales/reports.pt-BR.yml new file mode 100644 index 00000000..0b889d97 --- /dev/null +++ b/config/locales/reports.pt-BR.yml @@ -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. + +pt-BR: + activerecord: + attributes: + report: + user: "Gerado por" + file_name: "Nome do Arquivo" + identifier: "Identificador" + created_at: "Data de Criação" + expires_at: "Data de Expiração" + + models: + report: + one: "Documento Assinado" + other: "Documentos Assinados" diff --git a/config/navigation.rb b/config/navigation.rb index 190d49f3..fd4c8b48 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -149,6 +149,8 @@ def can_read?(*args) submenu.modelitem Advisement submenu.modelitem AdvisementAuthorization submenu.modelitem ThesisDefenseCommitteeParticipation + submenu.modelitem Grant + submenu.modelitem Paper end scholar_models = [Scholarship, ScholarshipType, ScholarshipDuration] @@ -189,6 +191,11 @@ def can_read?(*args) submenu.modelitem Institution end + documents_models = [Report] + mainhelper.listitem :documents, documents_models do |submenu| + submenu.modelitem Report + end + locations_models = [City, State, Country] mainhelper.listitem :locations, locations_models do |submenu| submenu.modelitem City diff --git a/config/routes.rb b/config/routes.rb index 6a1b347e..6596aab0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -210,6 +210,10 @@ record_select_routes end + resources :assertions do + concerns :active_scaffold + end + resources :levels do concerns :active_scaffold end @@ -525,7 +529,34 @@ end end + resources :reports do + concerns :active_scaffold + member do + get :download + end + collection do + get ":identifier.pdf", to: "reports#download_by_identifier", as: :download_by_identifier + end + end + if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end + + resources :grants do + concerns :active_scaffold + end + + resources :paper_professors do + concerns :active_scaffold + end + + resources :paper_students do + concerns :active_scaffold + end + + resources :papers do + concerns :active_scaffold + end + end diff --git a/db/migrate/20240422210224_add_qr_code_signature_to_report_configurations.rb b/db/migrate/20240422210224_add_qr_code_signature_to_report_configurations.rb new file mode 100644 index 00000000..987f8b2f --- /dev/null +++ b/db/migrate/20240422210224_add_qr_code_signature_to_report_configurations.rb @@ -0,0 +1,10 @@ +# 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 AddQrCodeSignatureToReportConfigurations < ActiveRecord::Migration[7.0] + def change + add_column :report_configurations, :qr_code_signature, :boolean, default: false + end +end diff --git a/db/migrate/20240605174859_create_reports.rb b/db/migrate/20240605174859_create_reports.rb new file mode 100644 index 00000000..f138fe83 --- /dev/null +++ b/db/migrate/20240605174859_create_reports.rb @@ -0,0 +1,10 @@ +class CreateReports < ActiveRecord::Migration[7.0] + def change + create_table :reports do |t| + t.references :generated_by, foreign_key: { to_table: :users } + t.references :carrierwave_file, foreign_key: { to_table: :carrier_wave_files } + + t.timestamps + end + end +end diff --git a/db/migrate/20240702214957_add_signature_type_enum_to_report_configuration_table.rb b/db/migrate/20240702214957_add_signature_type_enum_to_report_configuration_table.rb new file mode 100644 index 00000000..f9fa58e7 --- /dev/null +++ b/db/migrate/20240702214957_add_signature_type_enum_to_report_configuration_table.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddSignatureTypeEnumToReportConfigurationTable < ActiveRecord::Migration[7.0] + def up + add_column :report_configurations, :signature_type, :integer, default: 0 + ReportConfiguration.where(signature_footer: true).update_all(signature_type: 1) + ReportConfiguration.where(qr_code_signature: true).update_all(signature_type: 2) + remove_column :report_configurations, :signature_footer + remove_column :report_configurations, :qr_code_signature + end + + def down + add_column :report_configurations, :signature_footer, :boolean, default: false + add_column :report_configurations, :qr_code_signature, :boolean, default: false + ReportConfiguration.where(signature_type: 1).update_all(signature_footer: true) + ReportConfiguration.where(signature_type: 2).update_all(qr_code_signature: true) + remove_column :report_configurations, :signature_type + end +end diff --git a/db/migrate/20240813220016_add_expiration_in_months_to_report_configuration_table.rb b/db/migrate/20240813220016_add_expiration_in_months_to_report_configuration_table.rb new file mode 100644 index 00000000..8da619c9 --- /dev/null +++ b/db/migrate/20240813220016_add_expiration_in_months_to_report_configuration_table.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddExpirationInMonthsToReportConfigurationTable < ActiveRecord::Migration[7.0] + def up + add_column :report_configurations, :expiration_in_months, :integer, null: true + end + + def down + remove_column :report_configurations, :expiration_in_months + end +end diff --git a/db/migrate/20240813223037_add_expires_at_to_reports_table.rb b/db/migrate/20240813223037_add_expires_at_to_reports_table.rb new file mode 100644 index 00000000..6554036e --- /dev/null +++ b/db/migrate/20240813223037_add_expires_at_to_reports_table.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddExpiresAtToReportsTable < ActiveRecord::Migration[7.0] + def up + add_column :reports, :expires_at, :date, null: true + end + + def down + remove_column :reports, :expires_at + end +end diff --git a/db/migrate/20240912113419_add_obs_to_academic_transcript_to_enrollments.rb b/db/migrate/20240912113419_add_obs_to_academic_transcript_to_enrollments.rb new file mode 100644 index 00000000..b4f82fbd --- /dev/null +++ b/db/migrate/20240912113419_add_obs_to_academic_transcript_to_enrollments.rb @@ -0,0 +1,11 @@ +# Copyright (c) Universidade Federal Fluminense (UFF). +# This file is part of SAPOS. Please, consult the license terms in the LICENSE file. + +class AddObsToAcademicTranscriptToEnrollments < ActiveRecord::Migration[7.0] + def up + add_column :enrollments, :obs_to_academic_transcript, :text + end + def down + remove_column :enrollments, :obs_to_academic_transcript + end +end diff --git a/db/migrate/20241119221140_add_identifier_to_reports.rb b/db/migrate/20241119221140_add_identifier_to_reports.rb new file mode 100644 index 00000000..fa480c70 --- /dev/null +++ b/db/migrate/20241119221140_add_identifier_to_reports.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddIdentifierToReports < ActiveRecord::Migration[7.0] + def change + add_column :reports, :identifier, :string + end +end diff --git a/db/migrate/20241119221152_add_file_name_to_reports.rb b/db/migrate/20241119221152_add_file_name_to_reports.rb new file mode 100644 index 00000000..0e470b62 --- /dev/null +++ b/db/migrate/20241119221152_add_file_name_to_reports.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddFileNameToReports < ActiveRecord::Migration[7.0] + def change + add_column :reports, :file_name, :string + 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/migrate/20241206024033_create_papers.rb b/db/migrate/20241206024033_create_papers.rb new file mode 100644 index 00000000..0166ff24 --- /dev/null +++ b/db/migrate/20241206024033_create_papers.rb @@ -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. + +# frozen_string_literal: true + +class CreatePapers < ActiveRecord::Migration[7.0] + def change + create_table :papers do |t| + t.string :period + t.text :reference + t.string :kind + t.string :doi_issn_event + t.references :owner, null: false, foreign_key: { to_table: :professors } + t.text :other_authors + t.boolean :reason_impact_factor, null: false, default: false + t.boolean :reason_international_list, null: false, default: false + t.boolean :reason_citations, null: false, default: false + t.boolean :reason_national_interest, null: false, default: false + t.boolean :reason_international_interest, null: false, default: false + t.boolean :reason_national_representativeness, null: false, default: false + t.boolean :reason_scientific_contribution, null: false, default: false + t.boolean :reason_tech_contribution, null: false, default: false + t.boolean :reason_innovation_contribution, null: false, default: false + t.boolean :reason_social_contribution, null: false, default: false + t.text :reason_other + t.text :reason_justify + t.string :impact_factor + t.integer :order + t.text :other + + t.timestamps + end + end +end diff --git a/db/migrate/20241206024127_create_paper_students.rb b/db/migrate/20241206024127_create_paper_students.rb new file mode 100644 index 00000000..02760866 --- /dev/null +++ b/db/migrate/20241206024127_create_paper_students.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 + +class CreatePaperStudents < ActiveRecord::Migration[7.0] + def change + create_table :paper_students do |t| + t.references :paper, null: false, foreign_key: true + t.references :student, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20241206024146_create_paper_professors.rb b/db/migrate/20241206024146_create_paper_professors.rb new file mode 100644 index 00000000..dc699d4f --- /dev/null +++ b/db/migrate/20241206024146_create_paper_professors.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 + +class CreatePaperProfessors < ActiveRecord::Migration[7.0] + def change + create_table :paper_professors do |t| + t.references :paper, null: false, foreign_key: true + t.references :professor, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20241206034047_create_quadrennial_period_variable.rb b/db/migrate/20241206034047_create_quadrennial_period_variable.rb new file mode 100644 index 00000000..d23a74e8 --- /dev/null +++ b/db/migrate/20241206034047_create_quadrennial_period_variable.rb @@ -0,0 +1,16 @@ +# 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 CreateQuadrennialPeriodVariable < ActiveRecord::Migration[7.0] + def up + CustomVariable.where( + variable: "quadrennial_period" + ).first || CustomVariable.create( + description: "Periodo da Avaliação Quadrienal", + variable: "quadrennial_period", + value: "2021 - 2024" + ) + end +end diff --git a/db/migrate/20241208052442_remove_impact_factor_from_papers.rb b/db/migrate/20241208052442_remove_impact_factor_from_papers.rb new file mode 100644 index 00000000..387ba970 --- /dev/null +++ b/db/migrate/20241208052442_remove_impact_factor_from_papers.rb @@ -0,0 +1,5 @@ +class RemoveImpactFactorFromPapers < ActiveRecord::Migration[7.0] + def change + remove_column :papers, :impact_factor, :string + end +end diff --git a/db/migrate/20241208063322_add_committee_can_see_other_individual_to_admission_phases.rb b/db/migrate/20241208063322_add_committee_can_see_other_individual_to_admission_phases.rb new file mode 100644 index 00000000..40aed3f9 --- /dev/null +++ b/db/migrate/20241208063322_add_committee_can_see_other_individual_to_admission_phases.rb @@ -0,0 +1,10 @@ +# 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 AddCommitteeCanSeeOtherIndividualToAdmissionPhases < ActiveRecord::Migration[7.0] + def change + add_column :admission_phases, :committee_can_see_other_individual, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index c9c44de5..c1650cab 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_06_10_001559) do +ActiveRecord::Schema[7.0].define(version: 2024_12_08_063322) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -128,6 +128,8 @@ t.boolean "candidate_can_see_member", default: false, null: false t.boolean "candidate_can_see_shared", default: false, null: false t.boolean "candidate_can_see_consolidation", default: false, null: false + t.boolean "committee_can_see_other_individual", default: false, null: false + t.index ["approval_condition_id"], name: "index_admission_phases_on_approval_condition_id" t.index "\"ranking_config_id\"", name: "index_admission_phases_on_ranking_config_id" t.index ["candidate_form_id"], name: "index_admission_phases_on_candidate_form_id" t.index ["consolidation_form_id"], name: "index_admission_phases_on_consolidation_form_id" @@ -505,6 +507,7 @@ t.string "thesis_title", limit: 255 t.date "thesis_defense_date" t.integer "research_area_id" + t.text "obs_to_academic_transcript" t.index ["enrollment_number"], name: "index_enrollments_on_enrollment_number" t.index ["enrollment_status_id"], name: "index_enrollments_on_enrollment_status_id" t.index ["level_id"], name: "index_enrollments_on_level_id" @@ -581,6 +584,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 @@ -651,6 +667,50 @@ t.index ["query_id"], name: "index_notifications_on_query_id" end + create_table "paper_professors", force: :cascade do |t| + t.integer "paper_id", null: false + t.integer "professor_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["paper_id"], name: "index_paper_professors_on_paper_id" + t.index ["professor_id"], name: "index_paper_professors_on_professor_id" + end + + create_table "paper_students", force: :cascade do |t| + t.integer "paper_id", null: false + t.integer "student_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["paper_id"], name: "index_paper_students_on_paper_id" + t.index ["student_id"], name: "index_paper_students_on_student_id" + end + + create_table "papers", force: :cascade do |t| + t.string "period" + t.text "reference" + t.string "kind" + t.string "doi_issn_event" + t.integer "owner_id", null: false + t.text "other_authors" + t.boolean "reason_impact_factor", default: false, null: false + t.boolean "reason_international_list", default: false, null: false + t.boolean "reason_citations", default: false, null: false + t.boolean "reason_national_interest", default: false, null: false + t.boolean "reason_international_interest", default: false, null: false + t.boolean "reason_national_representativeness", default: false, null: false + t.boolean "reason_scientific_contribution", default: false, null: false + t.boolean "reason_tech_contribution", default: false, null: false + t.boolean "reason_innovation_contribution", default: false, null: false + t.boolean "reason_social_contribution", default: false, null: false + t.text "reason_other" + t.text "reason_justify" + t.integer "order" + t.text "other" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["owner_id"], name: "index_papers_on_owner_id" + end + create_table "phase_completions", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -821,13 +881,26 @@ 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" + t.index ["carrierwave_file_id"], name: "index_reports_on_carrierwave_file_id" + t.index ["generated_by_id"], name: "index_reports_on_generated_by_id" end create_table "research_areas", force: :cascade do |t| @@ -1016,4 +1089,12 @@ t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" end + add_foreign_key "grants", "professors" + add_foreign_key "paper_professors", "papers" + add_foreign_key "paper_professors", "professors" + add_foreign_key "paper_students", "papers" + add_foreign_key "paper_students", "students" + add_foreign_key "papers", "professors", column: "owner_id" + add_foreign_key "reports", "carrier_wave_files", column: "carrierwave_file_id" + add_foreign_key "reports", "users", column: "generated_by_id" end diff --git a/db/seeds.rb b/db/seeds.rb index 0671ab59..1b5d107f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -32,6 +32,7 @@ { description: "Nota mínima para aprovação", variable: "minimum_grade_for_approval", value: "6.0" }, { description: "Nota de reprovação por falta", variable: "grade_of_disapproval_for_absence", value: "0.0" }, { description: "Professor logado no sistema pode lançar notas. O valor yes habilita turmas do semestre atual, yes_all_semesters habilita qualquer semestre.", variable: "professor_login_can_post_grades", value: "no" }, + { description: "Periodo da Avaliação Quadrienal", variable: "quadrennial_period", value: "2021 - 2021" }, ]) ProgramLevel.create([{ value: "5", start_date: Time.now, end_date: nil }]) diff --git a/lib/tasks/maintenance.rake b/lib/tasks/maintenance.rake new file mode 100644 index 00000000..4222d5b5 --- /dev/null +++ b/lib/tasks/maintenance.rake @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +namespace :maintenance do + desc "Runs every maintenance task needed" + task run: [:environment] do + Rails.logger.info "[Maintenance] #{Time.now.to_fs} - Starting maintenance tasks" + Rake::Task["maintenance:remove_expired_reports"].invoke + Rake::Task["maintenance:trigger_notifications"].invoke + Rails.logger.info "[Maintenance] #{Time.now.to_fs} - Finished maintenance tasks" + end + + task remove_expired_reports: [:environment] do + Rails.logger.info "[Reports] #{Time.now.to_fs} Removing expired reports from DB" + expired_reports = Report.where(expires_at: ...Date.today).where.not(carrierwave_file_id: nil) + + expired_reports.map do |expired_report| + carrierwave_file = expired_report.carrierwave_file + expired_report.update!(carrierwave_file_id: nil) + carrierwave_file.delete + end + + Rails.logger.info "[Reports] #{Time.now.to_fs} Finished removing reports" + end + + task trigger_notifications: [:environment] do + Notifier.logger.info "Sending Registered Notifications" + + Notifier.logger.info "[Notifications] #{Time.now.to_fs} - Notifications from DB" + notifications = [] + notifications_attachments = {} + + # Get the next execution time arel table + next_execution = Notification.arel_table[:next_execution] + + # Find notifications that should run + Notification.where.not(frequency: Notification::MANUAL) + .where(next_execution.lt(Time.now)).each do |notification| + result = prepare_attachments(notification.execute) + notifications.concat(result[:notifications]) + notifications_attachments.merge!(result[:notifications_attachments]) + end + + Notifier.send_emails({ + notifications: notifications, + notifications_attachments: notifications_attachments + }) + + Notifier.logger.info "[Notifications] #{Time.now.to_fs} - Finished sending notifications" + end + + private + def prepare_attachments(notification_result) + include SharedPdfConcern + include AbstractController::Rendering + notification_result[:notifications].each do |message| + attachments = notification_result[:notifications_attachments][message] + next if attachments.blank? + if attachments[:grades_report_pdf] + enrollment = Enrollment.find(message[:enrollments_id]) + attachments[:grades_report_pdf][:file_contents] = + render_enrollments_grades_report_pdf(enrollment) + end + end + notification_result + end +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/factories/factory_paper.rb b/spec/factories/factory_paper.rb new file mode 100644 index 00000000..70701df1 --- /dev/null +++ b/spec/factories/factory_paper.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 + +FactoryBot.define do + factory :paper do + period { "2021 - 2024" } + reference { "Autor. Artigo. Ano"} + kind { "Periódico" } + doi_issn_event { "10000000" } + owner { create(:professor) } + reason_international_list { true } + reason_justify { "Internacional" } + order { 1 } + end +end diff --git a/spec/factories/factory_paper_professor.rb b/spec/factories/factory_paper_professor.rb new file mode 100644 index 00000000..612a0dab --- /dev/null +++ b/spec/factories/factory_paper_professor.rb @@ -0,0 +1,11 @@ +# 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 :paper_professor do + paper + professor + end +end diff --git a/spec/factories/factory_paper_student.rb b/spec/factories/factory_paper_student.rb new file mode 100644 index 00000000..5a5f5d80 --- /dev/null +++ b/spec/factories/factory_paper_student.rb @@ -0,0 +1,11 @@ +# 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 :paper_student do + paper + student + end +end diff --git a/spec/factories/factory_report.rb b/spec/factories/factory_report.rb new file mode 100644 index 00000000..0dd7d3b3 --- /dev/null +++ b/spec/factories/factory_report.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :report do + association :user, factory: :user + created_at { Time.now } + expires_at { 1.year.from_now } + end +end diff --git a/spec/factories/factory_report_configurations.rb b/spec/factories/factory_report_configurations.rb index e1e9283a..91646d7e 100644 --- a/spec/factories/factory_report_configurations.rb +++ b/spec/factories/factory_report_configurations.rb @@ -12,6 +12,7 @@ use_at_schedule { false } text { "MyText" } image { "MyString" } - signature_footer { false } + signature_type { 1 } + expiration_in_months { nil } end end diff --git a/spec/features/report_configurations_spec.rb b/spec/features/report_configurations_spec.rb index 07bf07d9..a813b1ab 100644 --- a/spec/features/report_configurations_spec.rb +++ b/spec/features/report_configurations_spec.rb @@ -38,12 +38,12 @@ it "should show table" do expect(page).to have_content "Configurações de relatório" expect(page.all("tr th").map(&:text)).to eq [ - "Nome", "Prioridade", "Header", "Rodapé com assinatura", "Usar em relatórios", "Usar no histórico", "Usar no boletim", "Usar no quadro de horários", "" + "Nome", "Prioridade", "Header", "Tipo de Assinatura", "Usar em relatórios", "Usar no histórico", "Usar no boletim", "Usar no quadro de horários", "Validade (meses)", "" ] end it "should sort the list by name, asc" do - expect(page.all("tr td.name-column").map(&:text)).to eq ["Boletim", "Histórico", "Padrão"] + expect(page.all("tr td.name-column").map(&:text)).to eq %w[Boletim Histórico Padrão] 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 diff --git a/spec/models/paper_professor_spec.rb b/spec/models/paper_professor_spec.rb new file mode 100644 index 00000000..93d11402 --- /dev/null +++ b/spec/models/paper_professor_spec.rb @@ -0,0 +1,37 @@ +# 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 PaperProfessor, type: :model do + it { should be_able_to_be_destroyed } + it { should belong_to(:paper).required(true) } + it { should belong_to(:professor).required(true) } + + let(:professor) { FactoryBot.build(:professor) } + let(:paper) { FactoryBot.build(:paper) } + let(:paper_professor) do + PaperProfessor.new( + paper: paper, + professor: professor + ) + end + subject { paper_professor } + describe "Validations" do + it { should be_valid } + it { should validate_presence_of(:paper) } + it { should validate_presence_of(:professor) } + end + describe "Methods" do + describe "to_label" do + it "should return 'professor - paper'" do + professor.name = "Bia" + paper.owner.name = "Ana" + paper.reference = "Ana. Artigo. 2024" + expect(paper_professor.to_label).to eq("Bia - [Ana] Ana. Artigo. 2024") + end + end + end +end diff --git a/spec/models/paper_spec.rb b/spec/models/paper_spec.rb new file mode 100644 index 00000000..2923cdd9 --- /dev/null +++ b/spec/models/paper_spec.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 + +require "spec_helper" + +RSpec.describe Paper, type: :model do + it { should be_able_to_be_destroyed } + it { should belong_to(:owner).required(true) } + it { should have_many(:paper_professors).dependent(:destroy) } + it { should have_many(:paper_students).dependent(:destroy) } + it { should have_many(:professors).through(:paper_professors) } + it { should have_many(:students).through(:paper_students) } + + let(:professor) { FactoryBot.build(:professor) } + let(:user) { FactoryBot.build(:user, professor: professor) } + let(:paper) do + Paper.new( + period: "2021 - 2024", + reference: "Autor. Artigo. Ano", + kind: Paper::JOURNAL, + doi_issn_event: "10000000", + owner: professor, + reason_international_list: true, + reason_justify: "Internacional", + order: 1 + ) + end + subject { paper } + describe "Validations" do + it { should be_valid } + it { should validate_presence_of(:reference) } + it { should validate_inclusion_of(:kind).in_array(Paper::KINDS) } + it { should validate_presence_of(:kind) } + it { should validate_inclusion_of(:order).in_array(Paper::ORDERS) } + it { should validate_uniqueness_of(:order).scoped_to([:period, :owner_id]).with_message(:order_uniqueness) } + it { should validate_presence_of(:order) } + it { should validate_presence_of(:doi_issn_event) } + it { should validate_presence_of(:reason_justify) } + # ToDo: professor cannot edit other papers + + end + describe "Methods" do + describe "to_label" do + it "should return '[professor] reference'" do + paper.owner.name = "Ana" + paper.reference = "Ana. Artigo. 2024" + expect(paper.to_label).to eq("[Ana] Ana. Artigo. 2024") + end + end + end +end diff --git a/spec/models/paper_student_spec.rb b/spec/models/paper_student_spec.rb new file mode 100644 index 00000000..23a35b2a --- /dev/null +++ b/spec/models/paper_student_spec.rb @@ -0,0 +1,37 @@ +# 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 PaperStudent, type: :model do + it { should be_able_to_be_destroyed } + it { should belong_to(:paper).required(true) } + it { should belong_to(:student).required(true) } + + let(:student) { FactoryBot.build(:student) } + let(:paper) { FactoryBot.build(:paper) } + let(:paper_student) do + PaperStudent.new( + paper: paper, + student: student + ) + end + subject { paper_student } + describe "Validations" do + it { should be_valid } + it { should validate_presence_of(:paper) } + it { should validate_presence_of(:student) } + end + describe "Methods" do + describe "to_label" do + it "should return 'student - paper'" do + student.name = "Bia" + paper.owner.name = "Ana" + paper.reference = "Ana. Artigo. 2024" + expect(paper_student.to_label).to eq("Bia - [Ana] Ana. Artigo. 2024") + end + end + end +end diff --git a/spec/models/report_configuration_spec.rb b/spec/models/report_configuration_spec.rb index 802f0eb8..6723a89b 100644 --- a/spec/models/report_configuration_spec.rb +++ b/spec/models/report_configuration_spec.rb @@ -23,5 +23,12 @@ it { should validate_presence_of(:x) } it { should validate_presence_of(:y) } it { should validate_presence_of(:scale) } + it { should validate_numericality_of(:expiration_in_months).only_integer.is_greater_than(0).allow_nil } + it "expiration_in_months_only_for_qr_code" do + query.signature_type = "manual" + query.expiration_in_months = 1 + query.valid? + expect(query.errors[:expiration_in_months]).to include(I18n.t("activerecord.errors.models.report_configuration.attributes.expiration_in_months.only_for_qr_code")) + end end end diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb new file mode 100644 index 00000000..8a8bf17a --- /dev/null +++ b/spec/models/report_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Report, type: :model do + let(:carrierwave_file) { CarrierWave::Storage::ActiveRecord::ActiveRecordFile.new } + let(:report) { FactoryBot.create(:report, carrierwave_file: carrierwave_file) } + + describe "#to_label" do + it "returns the correct label format" do + expect(report.to_label).to eq("#{report.user.name} - #{I18n.l(report.created_at, format: '%d/%m/%Y %H:%M')}") + end + end + + describe "associations" do + it { should belong_to(:user).with_foreign_key("generated_by_id") } + it { should belong_to(:carrierwave_file).with_foreign_key("carrierwave_file_id").class_name("CarrierWave::Storage::ActiveRecord::ActiveRecordFile").optional } + end +end