From 1b04dca84b4bfebcb86981a90a8678477a97122a Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 5 Jun 2024 13:17:48 -0300 Subject: [PATCH 01/35] added pdf uploader with base64 support --- app/uploaders/pdf_uploader.rb | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 app/uploaders/pdf_uploader.rb 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 From 5020c9a3da3ed98b85f79e67b88f118d4c3778d7 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 5 Jun 2024 13:20:29 -0300 Subject: [PATCH 02/35] added qr code generation on report configurations and qr code signature footer --- Gemfile | 5 +- Gemfile.lock | 9 ++ app/helpers/pdf_helper.rb | 125 ++++++++++++------ config/locales/enrollment.pt-BR.yml | 2 + ...code_signature_to_report_configurations.rb | 10 ++ db/schema.rb | 3 +- 6 files changed, 112 insertions(+), 42 deletions(-) create mode 100644 db/migrate/20240422210224_add_qr_code_signature_to_report_configurations.rb diff --git a/Gemfile b/Gemfile index 6035ab20..191c4913 100644 --- a/Gemfile +++ b/Gemfile @@ -22,7 +22,7 @@ gem "puma", ">= 6.4.2" # HTML and XML parser gem "nokogiri", ">= 1.16.2" -#Wrapp HTTP requests and responses +# Wrapp HTTP requests and responses gem "rack", "~> 2.2.8.1" # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] @@ -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" @@ -122,7 +123,7 @@ gem "recaptcha", require: "recaptcha/rails" gem "rubyzip" gem "caxlsx" gem "caxlsx_rails" -#gem "acts_as_xlsx" +# gem "acts_as_xlsx" # Temporary fix of warnings # In the beggining of rails command executions, it shows some warnings related to these gems diff --git a/Gemfile.lock b/Gemfile.lock index be6d4777..3dadcb68 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -144,6 +144,7 @@ GEM caxlsx (>= 3.0) cgi (0.3.6) choice (0.2.0) + chunky_png (1.4.0) cocoon (1.2.15) coderay (1.1.3) coffee-rails (5.0.0) @@ -289,6 +290,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 @@ -359,6 +363,10 @@ GEM railties (>= 5.2) rexml (3.2.6) 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) @@ -525,6 +533,7 @@ DEPENDENCIES nokogiri (>= 1.16.2) paper_trail prawn + prawn-qrcode prawn-rails prawn-table pry diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index fc0c88b6..5f05570a 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,70 @@ 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_signature_warning = I18n.t("pdf_content.enrollment.footer.qrcode_signature_warning") + pdf.move_down last_box_height / 2 - signature_font_size / 2 + current_x = (last_box_width2 - pdf.width_of(qrcode_signature_warning)) / 2 + pdf.draw_text( + "#{qrcode_signature_warning}", + at: [current_x, pdf.cursor] + ) + pdf.move_down signature_font_size + pdf.draw_text( + "#{I18n.t("pdf_content.enrollment.footer.signed_by_qrcode")} #{I18n.localize(Time.now, format: :defaultdatetime)} (Horário de Brasília)", + at: [current_x, pdf.cursor] + ) + else + current_x = x + pdf.move_down 8 - underline_width = 3.7 - pdf.move_down 30 - underline = "_" * 74 - current_x += (last_box_width2 - underline.size * underline_width) / 2 + pdf.draw_text( + "#{I18n.t("pdf_content.enrollment.footer.warning1")}", + at: [current_x, pdf.cursor] + ) - pdf.draw_text(underline, 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.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(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 +248,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: ( @@ -275,10 +292,17 @@ 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_signature + pdf.repeat(:all, dynamic: true) do + signature_footer(pdf, { qr_code_signature: true }) + end + elsif pdf_config.signature_footer + pdf.repeat(:all, dynamic: true) do signature_footer(pdf) - else + end + end + pdf.repeat(:all, dynamic: true) do + unless pdf_config.signature_footer datetime_footer(pdf) end end @@ -299,6 +323,14 @@ def new_document(name, title, options = {}, &block) end end end + + if pdf_config.qr_code_signature + uploader = PdfUploader.new + uploader.store!({ base64_contents: Base64.encode64(document), filename: "academic_transcript.pdf" }) + uploader.file&.file&.update!(medium_hash: @qrcode_identifier) + end + + document end def pdf_list_with_title(pdf, title, data, options = {}, &block) @@ -383,6 +415,21 @@ def simple_pdf_table(pdf, widths, header, data, options = {}, &block) end end end + + def qrcode_signature(pdf, options = {}) + @qrcode_identifier ||= SecureRandom.uuid + ".pdf" + + data = "#{request.protocol}#{request.host_with_port}/files/#{@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 end Prawn::Document.extensions << PdfHelper diff --git a/config/locales/enrollment.pt-BR.yml b/config/locales/enrollment.pt-BR.yml index c12d1c37..779670bc 100644 --- a/config/locales/enrollment.pt-BR.yml +++ b/config/locales/enrollment.pt-BR.yml @@ -92,6 +92,8 @@ 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" academic_transcript: deferrals: "PRORROGAÇÕES" accomplished_phases: "ETAPAS OBRIGATÓRIAS CONCLUÍDAS" 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/schema.rb b/db/schema.rb index 5fc78926..e7a98e75 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_01_18_203814) do +ActiveRecord::Schema[7.0].define(version: 2024_04_22_210224) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -810,6 +810,7 @@ t.integer "y" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false + t.boolean "qr_code_signature", default: false end create_table "research_areas", force: :cascade do |t| From e6410709010ec8159f6d7262e0dca02d3a2a45ba Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 19 Jun 2024 14:30:46 -0300 Subject: [PATCH 03/35] added report record creation and report configuration on-screen for qrcode signatures --- app/controllers/report_configurations_controller.rb | 13 +++++++------ app/helpers/pdf_helper.rb | 7 ++++++- app/models/report.rb | 8 ++++++++ app/models/report_configuration.rb | 1 + config/locales/report_configuration.pt-BR.yml | 1 + db/migrate/20240605174859_create_reports.rb | 13 +++++++++++++ db/schema.rb | 11 ++++++++++- 7 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 app/models/report.rb create mode 100644 db/migrate/20240605174859_create_reports.rb diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index cee72e97..9e9eb69b 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -12,16 +12,17 @@ class ReportConfigurationsController < ApplicationController 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, + :preview, :qr_code_signature, :use_at_report, :use_at_transcript, + :use_at_grades_report, :use_at_schedule, ] 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_footer, :qr_code_signature, + :use_at_report, :use_at_transcript, :use_at_grades_report, + :use_at_schedule, ] config.list.sorting = { name: "ASC" } config.actions << :duplicate @@ -41,7 +42,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") @@ -62,7 +63,7 @@ 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 + :x, :y, :qr_code_signature ) end end diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index 5f05570a..fe1d5a8b 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -324,10 +324,15 @@ def new_document(name, title, options = {}, &block) end end - if pdf_config.qr_code_signature + if pdf_config.qr_code_signature && !pdf_config.preview uploader = PdfUploader.new uploader.store!({ base64_contents: Base64.encode64(document), filename: "academic_transcript.pdf" }) uploader.file&.file&.update!(medium_hash: @qrcode_identifier) + + report = Report.new + report.user = current_user + report.carrierwave_file = uploader.file&.file + report.save! end document diff --git a/app/models/report.rb b/app/models/report.rb new file mode 100644 index 00000000..af0464ed --- /dev/null +++ b/app/models/report.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Report < ApplicationRecord + attr_accessor :identifier + # relations + belongs_to :user, foreign_key: "generated_by_id" + belongs_to :carrierwave_file, foreign_key: "carrierwave_file_id", class_name: "CarrierWave::Storage::ActiveRecord::ActiveRecordFile" +end diff --git a/app/models/report_configuration.rb b/app/models/report_configuration.rb index 6e8b75b5..b7ef56b2 100644 --- a/app/models/report_configuration.rb +++ b/app/models/report_configuration.rb @@ -6,6 +6,7 @@ # Represents Configuration of a Report class ReportConfiguration < ApplicationRecord has_paper_trail + attr_accessor :preview validates :text, presence: true validates :order, presence: true diff --git a/config/locales/report_configuration.pt-BR.yml b/config/locales/report_configuration.pt-BR.yml index a580ce52..5ae42f63 100644 --- a/config/locales/report_configuration.pt-BR.yml +++ b/config/locales/report_configuration.pt-BR.yml @@ -8,6 +8,7 @@ pt-BR: name: "Nome" image: "Logo" signature_footer: "Rodapé com assinatura" + qr_code_signature: "Assinatura em QR Code" text: "Header" use_at_grades_report: "Usar no boletim" use_at_report: "Usar em relatórios" diff --git a/db/migrate/20240605174859_create_reports.rb b/db/migrate/20240605174859_create_reports.rb new file mode 100644 index 00000000..700d1fb9 --- /dev/null +++ b/db/migrate/20240605174859_create_reports.rb @@ -0,0 +1,13 @@ +class CreateReports < ActiveRecord::Migration[7.0] + def change + create_table :reports do |t| + t.integer :generated_by_id + t.integer :carrierwave_file_id + + t.foreign_key :users, column: :generated_by_id + t.foreign_key :carrier_wave_files, column: :carrierwave_file_id + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e7a98e75..783d4ea4 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_04_22_210224) do +ActiveRecord::Schema[7.0].define(version: 2024_06_05_174859) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -813,6 +813,13 @@ t.boolean "qr_code_signature", default: false 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 + end + create_table "research_areas", force: :cascade do |t| t.string "name", limit: 255 t.string "code", limit: 255 @@ -997,4 +1004,6 @@ t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" end + add_foreign_key "reports", "carrier_wave_files", column: "carrierwave_file_id" + add_foreign_key "reports", "users", column: "generated_by_id" end From 2e33bcba8a7d3f526006fa141a74ea47f5a12df8 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 17 Jul 2024 12:08:16 -0300 Subject: [PATCH 04/35] changes signature type to enum --- .../report_configurations_controller.rb | 12 ++++--- app/helpers/pdf_helper.rb | 34 +++++++++++++------ app/models/report_configuration.rb | 10 ++++++ .../_preview_form_column.html.erb | 7 ++++ config/locales/report_configuration.pt-BR.yml | 6 ++-- ...type_enum_to_report_configuration_table.rb | 19 +++++++++++ db/schema.rb | 5 ++- test/fixtures/reports.yml | 11 ++++++ test/models/report_test.rb | 7 ++++ 9 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 db/migrate/20240702214957_add_signature_type_enum_to_report_configuration_table.rb create mode 100644 test/fixtures/reports.yml create mode 100644 test/models/report_test.rb diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index 9e9eb69b..474ff401 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -11,8 +11,8 @@ class ReportConfigurationsController < ApplicationController active_scaffold :report_configuration do |config| config.create.label = :create_report_configuration_label columns = [ - :name, :image, :scale, :x, :y, :order, :text, :signature_footer, - :preview, :qr_code_signature, :use_at_report, :use_at_transcript, + :name, :image, :scale, :x, :y, :order, :text, :signature_type, + :preview, :use_at_report, :use_at_transcript, :use_at_grades_report, :use_at_schedule, ] config.create.columns = columns @@ -20,10 +20,12 @@ class ReportConfigurationsController < ApplicationController columns.delete(:preview) config.columns = columns config.list.columns = [ - :name, :order, :text, :signature_footer, :qr_code_signature, + :name, :order, :text, :signature_type, :use_at_report, :use_at_transcript, :use_at_grades_report, :use_at_schedule, ] + config.columns[:signature_type].form_ui = :select + config.columns[:signature_type].options = { options: ReportConfiguration.signature_types.keys.map(&:to_sym) } config.list.sorting = { name: "ASC" } config.actions << :duplicate config.duplicate.link.label = " @@ -62,8 +64,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, :qr_code_signature + :use_at_schedule, :text, :image, :order, :scale, + :x, :y, :signature_type ) end end diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index fe1d5a8b..73f192c5 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -134,16 +134,31 @@ def signature_footer(pdf, options = {}) pdf.stroke_bounds if options[:qr_code_signature] + qrcode_url = "#{request.protocol}#{request.host_with_port}/files/#{@qrcode_identifier}" qrcode_signature_warning = I18n.t("pdf_content.enrollment.footer.qrcode_signature_warning") - pdf.move_down last_box_height / 2 - signature_font_size / 2 - current_x = (last_box_width2 - pdf.width_of(qrcode_signature_warning)) / 2 + signed_at = "#{I18n.t("pdf_content.enrollment.footer.signed_by_qrcode")} #{I18n.localize(Time.now, format: :defaultdatetime)} (Horário de Brasília)" + you_can_also_access = "Você também pode acessar o documento em:" + # max between all of the above + center_around = [qrcode_signature_warning, signed_at, you_can_also_access].max_by(&:size) + pdf.move_down last_box_height / 2 - signature_font_size + current_x = (last_box_width2 - pdf.width_of(center_around)) / 2 pdf.draw_text( "#{qrcode_signature_warning}", at: [current_x, pdf.cursor] ) pdf.move_down signature_font_size pdf.draw_text( - "#{I18n.t("pdf_content.enrollment.footer.signed_by_qrcode")} #{I18n.localize(Time.now, format: :defaultdatetime)} (Horário de Brasília)", + 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] ) else @@ -258,7 +273,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| @@ -292,17 +307,16 @@ def new_document(name, title, options = {}, &block) header(pdf, title, pdf_config) yield pdf - if pdf_config.qr_code_signature + if pdf_config.qr_code? pdf.repeat(:all, dynamic: true) do signature_footer(pdf, { qr_code_signature: true }) end - elsif pdf_config.signature_footer + elsif pdf_config.manual? pdf.repeat(:all, dynamic: true) do signature_footer(pdf) end - end - pdf.repeat(:all, dynamic: true) do - unless pdf_config.signature_footer + else + pdf.repeat(:all, dynamic: true) do datetime_footer(pdf) end end @@ -422,7 +436,7 @@ def simple_pdf_table(pdf, widths, header, data, options = {}, &block) end def qrcode_signature(pdf, options = {}) - @qrcode_identifier ||= SecureRandom.uuid + ".pdf" + @qrcode_identifier ||= SecureRandom.alphanumeric(8).upcase + ".pdf" data = "#{request.protocol}#{request.host_with_port}/files/#{@qrcode_identifier}" diff --git a/app/models/report_configuration.rb b/app/models/report_configuration.rb index b7ef56b2..7f8c33f5 100644 --- a/app/models/report_configuration.rb +++ b/app/models/report_configuration.rb @@ -16,6 +16,8 @@ class ReportConfiguration < ApplicationRecord 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") @@ -25,4 +27,12 @@ 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 end diff --git a/app/views/report_configurations/_preview_form_column.html.erb b/app/views/report_configurations/_preview_form_column.html.erb index f3950ac5..406eab61 100644 --- a/app/views/report_configurations/_preview_form_column.html.erb +++ b/app/views/report_configurations/_preview_form_column.html.erb @@ -76,4 +76,11 @@ "#record_text_<%=record.id%>": ".header_text_field" }); }); + + $('.signature_type-input').change(function(){ + var selected = $(this).find('option:selected'); + $(this).find('option').removeAttr('selected'); + + selected.attr('selected', 'selected'); + }); diff --git a/config/locales/report_configuration.pt-BR.yml b/config/locales/report_configuration.pt-BR.yml index 5ae42f63..cb763b56 100644 --- a/config/locales/report_configuration.pt-BR.yml +++ b/config/locales/report_configuration.pt-BR.yml @@ -7,8 +7,7 @@ pt-BR: report_configuration: name: "Nome" image: "Logo" - signature_footer: "Rodapé com assinatura" - qr_code_signature: "Assinatura em QR Code" + signature_type: "Tipo de Assinatura" text: "Header" use_at_grades_report: "Usar no boletim" use_at_report: "Usar em relatórios" @@ -18,6 +17,9 @@ pt-BR: scale: "Escala" x: "X" y: "Y" + no_signature: "Sem Assinatura" + manual: "Manual" + qr_code: "Código QR" models: report_configuration: 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/schema.rb b/db/schema.rb index 783d4ea4..4e5aac74 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_05_174859) do +ActiveRecord::Schema[7.0].define(version: 2024_07_02_214957) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -803,14 +803,13 @@ t.boolean "use_at_schedule" t.text "text" t.string "image", limit: 255 - t.boolean "signature_footer" 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.boolean "qr_code_signature", default: false + t.integer "signature_type", default: 0 end create_table "reports", force: :cascade do |t| diff --git a/test/fixtures/reports.yml b/test/fixtures/reports.yml new file mode 100644 index 00000000..bef74c8f --- /dev/null +++ b/test/fixtures/reports.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + generated_by: 1 + carrierwave_file: one + signed_with_qrcode: false + +two: + generated_by: 1 + carrierwave_file: two + signed_with_qrcode: false diff --git a/test/models/report_test.rb b/test/models/report_test.rb new file mode 100644 index 00000000..c7d4f0ad --- /dev/null +++ b/test/models/report_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ReportTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 7e15b607d695bf08d98051a4e470c4e11b832ed3 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 17 Jul 2024 12:12:41 -0300 Subject: [PATCH 05/35] add a few locales for new qr code signature footer --- app/helpers/pdf_helper.rb | 13 ++++++++++--- config/locales/enrollment.pt-BR.yml | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index 73f192c5..dabb0632 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -137,26 +137,33 @@ def signature_footer(pdf, options = {}) qrcode_url = "#{request.protocol}#{request.host_with_port}/files/#{@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.localize(Time.now, format: :defaultdatetime)} (Horário de Brasília)" - you_can_also_access = "Você também pode acessar o documento em:" - # max between all of the above - center_around = [qrcode_signature_warning, signed_at, you_can_also_access].max_by(&:size) + you_can_also_access = I18n.t("pdf_content.enrollment.footer.you_can_also_access") + + center_around = [qrcode_signature_warning, signed_at, you_can_also_access, qrcode_url].max_by(&:size) pdf.move_down last_box_height / 2 - signature_font_size current_x = (last_box_width2 - pdf.width_of(center_around)) / 2 + 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] diff --git a/config/locales/enrollment.pt-BR.yml b/config/locales/enrollment.pt-BR.yml index 779670bc..3609ea37 100644 --- a/config/locales/enrollment.pt-BR.yml +++ b/config/locales/enrollment.pt-BR.yml @@ -94,6 +94,7 @@ pt-BR: 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:" academic_transcript: deferrals: "PRORROGAÇÕES" accomplished_phases: "ETAPAS OBRIGATÓRIAS CONCLUÍDAS" From 509ab82de490641933770ef4a80dc8c5d30bafcb Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 13 Aug 2024 20:46:18 -0300 Subject: [PATCH 06/35] add expiration configuration to reports --- .../report_configurations_controller.rb | 6 +-- app/helpers/pdf_helper.rb | 41 +++++++++++++++---- config/locales/enrollment.pt-BR.yml | 1 + config/locales/report_configuration.pt-BR.yml | 1 + ...in_months_to_report_configuration_table.rb | 11 +++++ ...3223037_add_expires_at_to_reports_table.rb | 11 +++++ db/schema.rb | 4 +- 7 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 db/migrate/20240813220016_add_expiration_in_months_to_report_configuration_table.rb create mode 100644 db/migrate/20240813223037_add_expires_at_to_reports_table.rb diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index 474ff401..f544bab5 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -13,7 +13,7 @@ class ReportConfigurationsController < ApplicationController columns = [ :name, :image, :scale, :x, :y, :order, :text, :signature_type, :preview, :use_at_report, :use_at_transcript, - :use_at_grades_report, :use_at_schedule, + :use_at_grades_report, :use_at_schedule, :expiration_in_months ] config.create.columns = columns config.update.columns = columns @@ -22,7 +22,7 @@ class ReportConfigurationsController < ApplicationController config.list.columns = [ :name, :order, :text, :signature_type, :use_at_report, :use_at_transcript, :use_at_grades_report, - :use_at_schedule, + :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) } @@ -65,7 +65,7 @@ def record_params params.required(:record).permit( :name, :use_at_report, :use_at_transcript, :use_at_grades_report, :use_at_schedule, :text, :image, :order, :scale, - :x, :y, :signature_type + :x, :y, :signature_type, :expiration_value, :expiration_unit ) end end diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index dabb0632..cceb923f 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -136,11 +136,17 @@ def signature_footer(pdf, options = {}) if options[:qr_code_signature] qrcode_url = "#{request.protocol}#{request.host_with_port}/files/#{@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.localize(Time.now, format: :defaultdatetime)} (Horário de Brasília)" + 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") - center_around = [qrcode_signature_warning, signed_at, you_can_also_access, qrcode_url].max_by(&:size) - pdf.move_down last_box_height / 2 - signature_font_size + 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 + + 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.draw_text( @@ -168,6 +174,14 @@ def signature_footer(pdf, options = {}) 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 @@ -316,7 +330,7 @@ def new_document(name, title, options = {}, &block) if pdf_config.qr_code? pdf.repeat(:all, dynamic: true) do - signature_footer(pdf, { qr_code_signature: true }) + 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 @@ -350,10 +364,11 @@ def new_document(name, title, options = {}, &block) uploader.store!({ base64_contents: Base64.encode64(document), filename: "academic_transcript.pdf" }) uploader.file&.file&.update!(medium_hash: @qrcode_identifier) - report = Report.new - report.user = current_user - report.carrierwave_file = uploader.file&.file - report.save! + 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 + ) end document @@ -443,7 +458,10 @@ def simple_pdf_table(pdf, widths, header, data, options = {}, &block) end def qrcode_signature(pdf, options = {}) - @qrcode_identifier ||= SecureRandom.alphanumeric(8).upcase + ".pdf" + @qrcode_identifier ||= generate_qr_code_key + ".pdf" + while CarrierWave::Storage::ActiveRecord::ActiveRecordFile.where(medium_hash: @qrcode_identifier).exists? + @qrcode_identifier = generate_qr_code_key + ".pdf" + end data = "#{request.protocol}#{request.host_with_port}/files/#{@qrcode_identifier}" @@ -456,6 +474,11 @@ def setup_pdf_config(pdf_type, options) ReportConfiguration.where(pdf_type_property => true).order(order: :desc).first || ReportConfiguration.new end + + def generate_qr_code_key + 12.times.map { "2346789BCDFGHJKMPQRTVWXY".split("").sample } + .insert(4, "-").insert(9, "-").join("") + end end Prawn::Document.extensions << PdfHelper diff --git a/config/locales/enrollment.pt-BR.yml b/config/locales/enrollment.pt-BR.yml index 3609ea37..df21d393 100644 --- a/config/locales/enrollment.pt-BR.yml +++ b/config/locales/enrollment.pt-BR.yml @@ -95,6 +95,7 @@ pt-BR: 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" diff --git a/config/locales/report_configuration.pt-BR.yml b/config/locales/report_configuration.pt-BR.yml index cb763b56..2da0d98f 100644 --- a/config/locales/report_configuration.pt-BR.yml +++ b/config/locales/report_configuration.pt-BR.yml @@ -20,6 +20,7 @@ pt-BR: no_signature: "Sem Assinatura" manual: "Manual" qr_code: "Código QR" + expiration_in_months: "Validade (meses)" models: report_configuration: 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/schema.rb b/db/schema.rb index 4e5aac74..e113b723 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_07_02_214957) do +ActiveRecord::Schema[7.0].define(version: 2024_08_13_223037) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -810,6 +810,7 @@ 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| @@ -817,6 +818,7 @@ t.integer "carrierwave_file_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.date "expires_at" end create_table "research_areas", force: :cascade do |t| From 8bc3a82f749ed403359a5cb5542cbc74bd436e1b Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 13 Aug 2024 20:46:34 -0300 Subject: [PATCH 07/35] add maintenance task for notifications and reports --- lib/tasks/maintenance.rake | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 lib/tasks/maintenance.rake diff --git a/lib/tasks/maintenance.rake b/lib/tasks/maintenance.rake new file mode 100644 index 00000000..1129e6d4 --- /dev/null +++ b/lib/tasks/maintenance.rake @@ -0,0 +1,50 @@ +# 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 +end From 5b5bc60135eb00693a59e035c00d072369d0adf4 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Mon, 26 Aug 2024 14:27:22 -0300 Subject: [PATCH 08/35] adds description and validation for report configuration expiration config --- app/controllers/report_configurations_controller.rb | 1 + app/models/report_configuration.rb | 2 ++ config/locales/report_configuration.pt-BR.yml | 1 + 3 files changed, 4 insertions(+) diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index f544bab5..00f7167c 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -26,6 +26,7 @@ class ReportConfigurationsController < ApplicationController ] 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 = " diff --git a/app/models/report_configuration.rb b/app/models/report_configuration.rb index 7f8c33f5..12464102 100644 --- a/app/models/report_configuration.rb +++ b/app/models/report_configuration.rb @@ -14,6 +14,8 @@ class ReportConfiguration < ApplicationRecord validates :y, presence: true validates :scale, presence: true + validates :expiration_in_months, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true + mount_uploader :image, ImageUploader enum signature_type: { no_signature: 0, manual: 1, qr_code: 2 } diff --git a/config/locales/report_configuration.pt-BR.yml b/config/locales/report_configuration.pt-BR.yml index 2da0d98f..853ac702 100644 --- a/config/locales/report_configuration.pt-BR.yml +++ b/config/locales/report_configuration.pt-BR.yml @@ -31,6 +31,7 @@ pt-BR: 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." pdf_content: report_configurations: From e3884eb06bb4c835647f568c67fcd1df6fd0dc24 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 28 Aug 2024 16:39:47 -0300 Subject: [PATCH 09/35] validates expiration field for QR Code signatures only + fix transcript duplicate row --- app/helpers/enrollments_pdf_helper.rb | 2 +- app/models/report_configuration.rb | 9 ++++++++- config/locales/report_configuration.pt-BR.yml | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/helpers/enrollments_pdf_helper.rb b/app/helpers/enrollments_pdf_helper.rb index b737bd8c..4871ded9 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 diff --git a/app/models/report_configuration.rb b/app/models/report_configuration.rb index 12464102..7b6b8371 100644 --- a/app/models/report_configuration.rb +++ b/app/models/report_configuration.rb @@ -13,8 +13,8 @@ class ReportConfiguration < ApplicationRecord 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 @@ -37,4 +37,11 @@ def signature_footer 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/config/locales/report_configuration.pt-BR.yml b/config/locales/report_configuration.pt-BR.yml index 853ac702..24358b4a 100644 --- a/config/locales/report_configuration.pt-BR.yml +++ b/config/locales/report_configuration.pt-BR.yml @@ -27,11 +27,18 @@ pt-BR: 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." + 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: From c9b00a1e770c777b6ca3b3deedf130059d03bdd4 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 11 Sep 2024 09:59:41 -0300 Subject: [PATCH 10/35] fix a few tests related to reports --- app/models/report.rb | 2 +- lib/tasks/maintenance.rake | 2 +- spec/factories/factory_report_configurations.rb | 3 ++- spec/features/report_configurations_spec.rb | 4 ++-- spec/models/report_configuration_spec.rb | 7 +++++++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/models/report.rb b/app/models/report.rb index af0464ed..a806501a 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -4,5 +4,5 @@ class Report < ApplicationRecord attr_accessor :identifier # relations belongs_to :user, foreign_key: "generated_by_id" - belongs_to :carrierwave_file, foreign_key: "carrierwave_file_id", class_name: "CarrierWave::Storage::ActiveRecord::ActiveRecordFile" + belongs_to :carrierwave_file, foreign_key: "carrierwave_file_id", class_name: "CarrierWave::Storage::ActiveRecord::ActiveRecordFile", optional: true end diff --git a/lib/tasks/maintenance.rake b/lib/tasks/maintenance.rake index 1129e6d4..a145f82c 100644 --- a/lib/tasks/maintenance.rake +++ b/lib/tasks/maintenance.rake @@ -16,7 +16,7 @@ namespace :maintenance do expired_reports.map do |expired_report| carrierwave_file = expired_report.carrierwave_file expired_report.update!(carrierwave_file_id: nil) - carrierwave_file.delete! + carrierwave_file.delete end Rails.logger.info "[Reports] #{Time.now.to_fs} Finished removing reports" 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/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 From 2cc60b7d3986dcb94806e247e1f63bee987e0567 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 11 Sep 2024 11:23:59 -0300 Subject: [PATCH 11/35] remove scaffold fixtures --- test/fixtures/reports.yml | 11 ----------- test/models/report_test.rb | 7 ------- 2 files changed, 18 deletions(-) delete mode 100644 test/fixtures/reports.yml delete mode 100644 test/models/report_test.rb diff --git a/test/fixtures/reports.yml b/test/fixtures/reports.yml deleted file mode 100644 index bef74c8f..00000000 --- a/test/fixtures/reports.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html - -one: - generated_by: 1 - carrierwave_file: one - signed_with_qrcode: false - -two: - generated_by: 1 - carrierwave_file: two - signed_with_qrcode: false diff --git a/test/models/report_test.rb b/test/models/report_test.rb deleted file mode 100644 index c7d4f0ad..00000000 --- a/test/models/report_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require "test_helper" - -class ReportTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end From 67aa95bf0f349561f0ed3b9e8cb9595af776e56c Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 11 Sep 2024 15:55:02 -0300 Subject: [PATCH 12/35] fix report configurations record params --- app/controllers/report_configurations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/report_configurations_controller.rb b/app/controllers/report_configurations_controller.rb index 00f7167c..097d5817 100644 --- a/app/controllers/report_configurations_controller.rb +++ b/app/controllers/report_configurations_controller.rb @@ -66,7 +66,7 @@ def record_params params.required(:record).permit( :name, :use_at_report, :use_at_transcript, :use_at_grades_report, :use_at_schedule, :text, :image, :order, :scale, - :x, :y, :signature_type, :expiration_value, :expiration_unit + :x, :y, :signature_type, :expiration_in_months ) end end From 95126dff33efdcc25a276c446dce5128cf9e0c00 Mon Sep 17 00:00:00 2001 From: caiovelp Date: Tue, 21 May 2024 22:32:38 -0300 Subject: [PATCH 13/35] Remove duplicated tag --- config/locales/navigation.pt-BR.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/locales/navigation.pt-BR.yml b/config/locales/navigation.pt-BR.yml index 720d60b5..4f248dd0 100644 --- a/config/locales/navigation.pt-BR.yml +++ b/config/locales/navigation.pt-BR.yml @@ -10,7 +10,6 @@ pt-BR: student: Alunos dismissal: Desligamentos enrollment: Matrículas - enrollment: Matrículas enrollment_hold: Trancamentos level: Níveis dismissal_reason: Razões de Desligamento @@ -54,6 +53,9 @@ pt-BR: major: Cursos institution: Instituições + documents: + label: Documentos + locations: label: Localidades city: Cidades From 21e3de98d84c9afa262b9380b937c57dbb6f888a Mon Sep 17 00:00:00 2001 From: caiovelp Date: Tue, 21 May 2024 22:33:14 -0300 Subject: [PATCH 14/35] Creates Documents tab --- app/models/ability.rb | 15 ++++++++++++++- config/navigation.rb | 4 ++++ config/routes.rb | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 591dd693..78447dc9 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -39,8 +39,11 @@ class Ability Major, Institution, ] + DOCUMENT_MODELS = [ + ] + PLACE_MODELS = [ - City, State, Country, + City, State, Country ] CONFIGURATION_MODELS = [ @@ -81,6 +84,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) @@ -218,6 +222,15 @@ def initialize_education(user, roles) end end + def initialize_documents(user, roles) + if roles[:manager] + can :manage, Ability::DOCUMENT_MODELS + end + if roles[Role::ROLE_ALUNO] + can :manage, Ability::DOCUMENT_MODELS + end + end + def initialize_places(user, roles) if roles[:manager] can :manage, Ability::PLACE_MODELS diff --git a/config/navigation.rb b/config/navigation.rb index d2505ce8..60224ece 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -189,6 +189,10 @@ def can_read?(*args) submenu.modelitem Institution end + documents_models = [] + mainhelper.listitem :documents, documents_models do |submenu| + 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 8eaf23eb..efccaa45 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -205,6 +205,10 @@ record_select_routes end + resources :assertions do + concerns :active_scaffold + end + resources :levels do concerns :active_scaffold end From 4712de25c491ccf13d53a52234f2131209275c69 Mon Sep 17 00:00:00 2001 From: caiovelp Date: Tue, 4 Jun 2024 21:37:39 -0300 Subject: [PATCH 15/35] minor fixes --- app/models/ability.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 78447dc9..6ac18050 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -223,9 +223,6 @@ def initialize_education(user, roles) end def initialize_documents(user, roles) - if roles[:manager] - can :manage, Ability::DOCUMENT_MODELS - end if roles[Role::ROLE_ALUNO] can :manage, Ability::DOCUMENT_MODELS end From 0a7c02653a2598195f1899d21e39126e83e91689 Mon Sep 17 00:00:00 2001 From: caiovelp Date: Wed, 17 Jul 2024 08:48:50 -0300 Subject: [PATCH 16/35] =?UTF-8?q?permiss=C3=B5es=20para=20secretaria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/ability.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 6ac18050..50b85b80 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -223,7 +223,7 @@ def initialize_education(user, roles) end def initialize_documents(user, roles) - if roles[Role::ROLE_ALUNO] + if roles[Role::ROLE_SECRETARIA] can :manage, Ability::DOCUMENT_MODELS end end From a29845be249ddc8f3e56cb47d22a2d4a3a33832b Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 1 Oct 2024 16:32:35 -0300 Subject: [PATCH 17/35] adds signed reports listing page --- app/controllers/reports_controller.rb | 34 +++++++++++++++++++++++++++ app/models/report.rb | 6 +++-- config/locales/navigation.pt-BR.yml | 1 + config/locales/pt-BR.yml | 1 + config/locales/reports.pt-BR.yml | 15 ++++++++++++ config/navigation.rb | 3 ++- config/routes.rb | 7 ++++++ 7 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 app/controllers/reports_controller.rb create mode 100644 config/locales/reports.pt-BR.yml diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb new file mode 100644 index 00000000..9a2d81fe --- /dev/null +++ b/app/controllers/reports_controller.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class ReportsController < ApplicationController + authorize_resource + + active_scaffold :report do |config| + config.list.columns = [:user, :created_at, :expires_at] + config.show.columns = [:user, :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 + report = Report.find(params[:id]) + redirect_to download_path(medium_hash: report.carrierwave_file.medium_hash) + end + + private + def cant_download?(record) + record.carrierwave_file.blank? + end +end diff --git a/app/models/report.rb b/app/models/report.rb index a806501a..63b5410b 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true class Report < ApplicationRecord - attr_accessor :identifier - # relations 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/config/locales/navigation.pt-BR.yml b/config/locales/navigation.pt-BR.yml index 4f248dd0..f01a3ce4 100644 --- a/config/locales/navigation.pt-BR.yml +++ b/config/locales/navigation.pt-BR.yml @@ -55,6 +55,7 @@ pt-BR: documents: label: Documentos + report: Documentos Assinados locations: label: Localidades 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/reports.pt-BR.yml b/config/locales/reports.pt-BR.yml new file mode 100644 index 00000000..e288b204 --- /dev/null +++ b/config/locales/reports.pt-BR.yml @@ -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. + +pt-BR: + activerecord: + attributes: + report: + user: "Gerado por" + 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 60224ece..24e85a4e 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -189,8 +189,9 @@ def can_read?(*args) submenu.modelitem Institution end - documents_models = [] + documents_models = [Report] mainhelper.listitem :documents, documents_models do |submenu| + submenu.modelitem Report end locations_models = [City, State, Country] diff --git a/config/routes.rb b/config/routes.rb index efccaa45..75958735 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -520,6 +520,13 @@ end end + resources :reports do + concerns :active_scaffold + member do + get "download" + end + end + if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end From dc6a45cacc9704f12d5c68e09e2b46c93b336885 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 1 Oct 2024 16:44:38 -0300 Subject: [PATCH 18/35] let managers see reports but only admins can update it --- app/models/ability.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 50b85b80..8756bc67 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -40,6 +40,7 @@ class Ability ] DOCUMENT_MODELS = [ + Report ] PLACE_MODELS = [ @@ -223,8 +224,9 @@ def initialize_education(user, roles) end def initialize_documents(user, roles) - if roles[Role::ROLE_SECRETARIA] + if roles[:manager] can :manage, Ability::DOCUMENT_MODELS + cannot :update, Report unless roles[Role::ROLE_ADMINISTRADOR] end end From b66ba3a14947912887218b6ed7ba826596d2d6b1 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Thu, 24 Oct 2024 14:05:15 -0300 Subject: [PATCH 19/35] change select value based on selected option --- app/views/report_configurations/_preview_form_column.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/report_configurations/_preview_form_column.html.erb b/app/views/report_configurations/_preview_form_column.html.erb index 406eab61..d3085f7b 100644 --- a/app/views/report_configurations/_preview_form_column.html.erb +++ b/app/views/report_configurations/_preview_form_column.html.erb @@ -82,5 +82,6 @@ $(this).find('option').removeAttr('selected'); selected.attr('selected', 'selected'); + $(this).val(selected.attr('value')); }); From 82be1b3cb5d543634faf42259b306ca1dc61dc05 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 19 Nov 2024 22:38:02 -0300 Subject: [PATCH 20/35] add identifier and file name to reports table --- app/controllers/reports_controller.rb | 27 +++++++++++++++---- app/helpers/pdf_helper.rb | 18 ++++++------- config/locales/reports.pt-BR.yml | 2 ++ config/routes.rb | 5 +++- ...0241119221140_add_identifier_to_reports.rb | 7 +++++ ...20241119221152_add_file_name_to_reports.rb | 7 +++++ db/schema.rb | 4 ++- 7 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20241119221140_add_identifier_to_reports.rb create mode 100644 db/migrate/20241119221152_add_file_name_to_reports.rb diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 9a2d81fe..553dbaf2 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,11 +1,16 @@ # frozen_string_literal: true class ReportsController < ApplicationController - authorize_resource + before_action :set_report, only: [:download, :download_by_identifier] + before_action :check_downloadable, only: [:download, :download_by_identifier] + + 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, :created_at, :expires_at] - config.show.columns = [:user, :created_at, :expires_at] + 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 @@ -23,11 +28,23 @@ class ReportsController < ApplicationController end def download - report = Report.find(params[:id]) - redirect_to download_path(medium_hash: report.carrierwave_file.medium_hash) + redirect_to download_path(medium_hash: @report.carrierwave_file.medium_hash) + end + + def download_by_identifier + redirect_to download_path(medium_hash: @report.carrierwave_file.medium_hash) 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 diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index 0015cb12..86f10204 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -134,7 +134,7 @@ def signature_footer(pdf, options = {}) pdf.stroke_bounds if options[:qr_code_signature] - qrcode_url = "#{request.protocol}#{request.host_with_port}/files/#{@qrcode_identifier}" + qrcode_url = "#{request.protocol}#{request.host_with_port}/reports/#{@qrcode_identifier}.pdf" 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") @@ -361,13 +361,14 @@ def new_document(name, title, options = {}, &block) if pdf_config.qr_code_signature && !pdf_config.preview uploader = PdfUploader.new - uploader.store!({ base64_contents: Base64.encode64(document), filename: "academic_transcript.pdf" }) - uploader.file&.file&.update!(medium_hash: @qrcode_identifier) + 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 + carrierwave_file: uploader.file&.file, + file_name: name, + identifier: @qrcode_identifier ) end @@ -497,14 +498,13 @@ def simple_pdf_table(pdf, widths, header, data, options = {}, join_header_and_ta end end - def qrcode_signature(pdf, options = {}) - @qrcode_identifier ||= generate_qr_code_key + ".pdf" - while CarrierWave::Storage::ActiveRecord::ActiveRecordFile.where(medium_hash: @qrcode_identifier).exists? - @qrcode_identifier = generate_qr_code_key + ".pdf" + @qrcode_identifier ||= generate_qr_code_key + while Report.where(identifier: @qrcode_identifier).exists? + @qrcode_identifier = generate_qr_code_key end - data = "#{request.protocol}#{request.host_with_port}/files/#{@qrcode_identifier}" + data = "#{request.protocol}#{request.host_with_port}/reports/#{@qrcode_identifier}.pdf" pdf.print_qr_code(data, extent: options[:size] || 80, align: :center) end diff --git a/config/locales/reports.pt-BR.yml b/config/locales/reports.pt-BR.yml index e288b204..0b889d97 100644 --- a/config/locales/reports.pt-BR.yml +++ b/config/locales/reports.pt-BR.yml @@ -6,6 +6,8 @@ pt-BR: attributes: report: user: "Gerado por" + file_name: "Nome do Arquivo" + identifier: "Identificador" created_at: "Data de Criação" expires_at: "Data de Expiração" diff --git a/config/routes.rb b/config/routes.rb index 75958735..e20bf756 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -523,7 +523,10 @@ resources :reports do concerns :active_scaffold member do - get "download" + get :download + end + collection do + get ":identifier.pdf", to: "reports#download_by_identifier", as: :download_by_identifier 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/schema.rb b/db/schema.rb index 69d617da..b7076c20 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_19_221152) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -821,6 +821,8 @@ 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| From d3260831b17573ac46af1870ac5ca6a1017c1eec Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 19 Nov 2024 23:14:03 -0300 Subject: [PATCH 21/35] fix maintenance rake and add reports spec --- lib/tasks/maintenance.rake | 14 ++++++++++++++ spec/factories/factory_report.rb | 9 +++++++++ spec/models/report_spec.rb | 19 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 spec/factories/factory_report.rb create mode 100644 spec/models/report_spec.rb diff --git a/lib/tasks/maintenance.rake b/lib/tasks/maintenance.rake index a145f82c..6203712c 100644 --- a/lib/tasks/maintenance.rake +++ b/lib/tasks/maintenance.rake @@ -47,4 +47,18 @@ namespace :maintenance do Notifier.logger.info "[Notifications] #{Time.now.to_fs} - Finished sending notifications" end + + private + def prepare_attachments(notification_result) + 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_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/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 From 4dcd6cbdeef40cc86810cc6ab58804a46e46165d Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 19 Nov 2024 23:32:54 -0300 Subject: [PATCH 22/35] pass down filename to store on report record --- app/controllers/concerns/shared_pdf_concern.rb | 6 ++++-- app/controllers/enrollments_controller.rb | 10 ++++++---- .../enrollments/academic_transcript_pdf.pdf.prawn | 2 +- app/views/enrollments/grades_report_pdf.pdf.prawn | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/controllers/concerns/shared_pdf_concern.rb b/app/controllers/concerns/shared_pdf_concern.rb index 12b51ee3..ef64c531 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) 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, @@ -54,7 +55,7 @@ def render_enrollments_academic_transcript_pdf(enrollment) ) end - def render_enrollments_grades_report_pdf(enrollment) + def render_enrollments_grades_report_pdf(enrollment, filename) class_enrollments = enrollment.class_enrollments .where(situation: ClassEnrollment::APPROVED) .joins(:course_class) @@ -66,6 +67,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 4741ff59..8e4dcb7c 100644 --- a/app/controllers/enrollments_controller.rb +++ b/app/controllers/enrollments_controller.rb @@ -219,8 +219,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 +233,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/views/enrollments/academic_transcript_pdf.pdf.prawn b/app/views/enrollments/academic_transcript_pdf.pdf.prawn index 716d4c38..8d1f849a 100644 --- a/app/views/enrollments/academic_transcript_pdf.pdf.prawn +++ b/app/views/enrollments/academic_transcript_pdf.pdf.prawn @@ -6,7 +6,7 @@ require "prawn/measurement_extensions" new_document( - "transcript.pdf", + @filename, I18n.t("pdf_content.enrollment.header.title"), watermark: cannot?( :generate_report_without_watermark, @enrollment diff --git a/app/views/enrollments/grades_report_pdf.pdf.prawn b/app/views/enrollments/grades_report_pdf.pdf.prawn index f2b5d38c..6225dfc2 100644 --- a/app/views/enrollments/grades_report_pdf.pdf.prawn +++ b/app/views/enrollments/grades_report_pdf.pdf.prawn @@ -4,7 +4,7 @@ # frozen_string_literal: true new_document( - "grades_report.pdf", + @filename, I18n.t("pdf_content.enrollment.grades_report.title"), watermark: ( current_user.nil? ? false : cannot?( From 7fc64c8907dc28e409752be4db14b1993d44d760 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 19 Nov 2024 23:35:21 -0300 Subject: [PATCH 23/35] reduce identifier to 10 chars --- app/helpers/pdf_helper.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index 86f10204..008ca5af 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -474,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" @@ -496,7 +494,6 @@ 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 @@ -517,8 +514,8 @@ def setup_pdf_config(pdf_type, options) end def generate_qr_code_key - 12.times.map { "2346789BCDFGHJKMPQRTVWXY".split("").sample } - .insert(4, "-").insert(9, "-").join("") + 10.times.map { "2346789BCDFGHJKMPQRTVWXY".split("").sample } + .insert(5, "-").join("") end end From f18adec90c8be0a259c27376250fa00bf556688f Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Wed, 20 Nov 2024 17:33:32 -0300 Subject: [PATCH 24/35] add back default file name for reports --- app/controllers/concerns/shared_pdf_concern.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/shared_pdf_concern.rb b/app/controllers/concerns/shared_pdf_concern.rb index ef64c531..af78e2ce 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, filename) + def render_enrollments_academic_transcript_pdf(enrollment, filename = "transcript.pdf") class_enrollments = enrollment.class_enrollments .where(situation: ClassEnrollment::APPROVED) .joins(:course_class) @@ -55,7 +55,7 @@ def render_enrollments_academic_transcript_pdf(enrollment, filename) ) end - def render_enrollments_grades_report_pdf(enrollment, filename) + def render_enrollments_grades_report_pdf(enrollment, filename = "grades_report.pdf") class_enrollments = enrollment.class_enrollments .where(situation: ClassEnrollment::APPROVED) .joins(:course_class) From 62244daf82931c9003bc2fd2bd036e6f8e231c37 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Thu, 21 Nov 2024 18:08:33 -0300 Subject: [PATCH 25/35] change qr code data url method and add authorize_resource to reports_controller --- app/controllers/reports_controller.rb | 3 ++- app/helpers/pdf_helper.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 553dbaf2..6aabf11e 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -3,6 +3,7 @@ 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 @@ -32,7 +33,7 @@ def download end def download_by_identifier - redirect_to download_path(medium_hash: @report.carrierwave_file.medium_hash) + send_data(@report.carrierwave_file.read, filename: @report.carrierwave_file.original_filename, disposition: :inline) end private diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index 008ca5af..8b046bbe 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -501,7 +501,7 @@ def qrcode_signature(pdf, options = {}) @qrcode_identifier = generate_qr_code_key end - data = "#{request.protocol}#{request.host_with_port}/reports/#{@qrcode_identifier}.pdf" + data = reports_url(identifier: @qrcode_identifier) pdf.print_qr_code(data, extent: options[:size] || 80, align: :center) end From f04a022f2b2fa29403a39b07ee84570c8d351992 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Thu, 21 Nov 2024 18:30:13 -0300 Subject: [PATCH 26/35] including modules needed to render notification attachments --- lib/tasks/maintenance.rake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/tasks/maintenance.rake b/lib/tasks/maintenance.rake index 6203712c..4222d5b5 100644 --- a/lib/tasks/maintenance.rake +++ b/lib/tasks/maintenance.rake @@ -50,6 +50,8 @@ namespace :maintenance do 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? From 478cebe174e2b96b8bbe09082223bdfa2da6274a Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Mon, 2 Dec 2024 21:03:11 -0300 Subject: [PATCH 27/35] fix foreign key types on reports table --- db/migrate/20240605174859_create_reports.rb | 7 ++----- db/schema.rb | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/db/migrate/20240605174859_create_reports.rb b/db/migrate/20240605174859_create_reports.rb index 700d1fb9..f138fe83 100644 --- a/db/migrate/20240605174859_create_reports.rb +++ b/db/migrate/20240605174859_create_reports.rb @@ -1,11 +1,8 @@ class CreateReports < ActiveRecord::Migration[7.0] def change create_table :reports do |t| - t.integer :generated_by_id - t.integer :carrierwave_file_id - - t.foreign_key :users, column: :generated_by_id - t.foreign_key :carrier_wave_files, column: :carrierwave_file_id + t.references :generated_by, foreign_key: { to_table: :users } + t.references :carrierwave_file, foreign_key: { to_table: :carrier_wave_files } t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index bdf2e644..2d4d0379 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -855,6 +855,8 @@ 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| From 1d58981d92d13023248b6f62dacf1ba80fe0c079 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Mon, 2 Dec 2024 22:19:43 -0300 Subject: [PATCH 28/35] add relative url host to qr_code data --- app/helpers/pdf_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index 8b046bbe..b3ccef11 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -134,7 +134,7 @@ def signature_footer(pdf, options = {}) pdf.stroke_bounds if options[:qr_code_signature] - qrcode_url = "#{request.protocol}#{request.host_with_port}/reports/#{@qrcode_identifier}.pdf" + qrcode_url = reports_url(identifier: @qrcode_identifier, host: ENV["RAILS_RELATIVE_URL_ROOT"] || "") 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") @@ -501,7 +501,7 @@ def qrcode_signature(pdf, options = {}) @qrcode_identifier = generate_qr_code_key end - data = reports_url(identifier: @qrcode_identifier) + data = reports_url(identifier: @qrcode_identifier, host: ENV["RAILS_RELATIVE_URL_ROOT"] || "") pdf.print_qr_code(data, extent: options[:size] || 80, align: :center) end From 961ab31f7e7c2351d3a691769bdf8e9993bf3151 Mon Sep 17 00:00:00 2001 From: Anderson Meireles Date: Tue, 3 Dec 2024 13:49:26 -0300 Subject: [PATCH 29/35] fix report download by identifier helper --- app/helpers/pdf_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/pdf_helper.rb b/app/helpers/pdf_helper.rb index b3ccef11..512b29f9 100644 --- a/app/helpers/pdf_helper.rb +++ b/app/helpers/pdf_helper.rb @@ -134,7 +134,7 @@ def signature_footer(pdf, options = {}) pdf.stroke_bounds if options[:qr_code_signature] - qrcode_url = reports_url(identifier: @qrcode_identifier, host: ENV["RAILS_RELATIVE_URL_ROOT"] || "") + 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") @@ -501,7 +501,7 @@ def qrcode_signature(pdf, options = {}) @qrcode_identifier = generate_qr_code_key end - data = reports_url(identifier: @qrcode_identifier, host: ENV["RAILS_RELATIVE_URL_ROOT"] || "") + data = download_by_identifier_reports_url(identifier: @qrcode_identifier) pdf.print_qr_code(data, extent: options[:size] || 80, align: :center) end From 05174348d1ef3c9af4fab0c9182674b7ed06ba8f Mon Sep 17 00:00:00 2001 From: Joao Felipe Pimentel Date: Fri, 6 Dec 2024 02:49:51 -0300 Subject: [PATCH 30/35] #505: add paper page for quadrenial --- .../paper_professors_controller.rb | 16 ++++ app/controllers/paper_students_controller.rb | 16 ++++ app/controllers/papers_controller.rb | 90 +++++++++++++++++++ app/helpers/paper_professors_helper.rb | 7 ++ app/helpers/paper_students_helper.rb | 7 ++ app/helpers/papers_helper.rb | 31 +++++++ app/models/ability.rb | 13 ++- app/models/custom_variable.rb | 7 ++ app/models/paper.rb | 56 ++++++++++++ app/models/paper_professor.rb | 15 ++++ app/models/paper_student.rb | 10 +++ app/views/papers/_header_form_column.html.erb | 10 +++ .../_reason_group_end_form_column.html.erb | 4 + .../papers/_reason_group_form_column.html.erb | 25 ++++++ config/locales/navigation.pt-BR.yml | 1 + config/locales/paper.pt-BR.yml | 56 ++++++++++++ config/locales/paper_professor.pt-BR.yml | 17 ++++ config/locales/paper_student.pt-BR.yml | 17 ++++ config/navigation.rb | 1 + config/routes.rb | 12 +++ db/migrate/20241206024033_create_papers.rb | 34 +++++++ .../20241206024127_create_paper_students.rb | 15 ++++ .../20241206024146_create_paper_professors.rb | 15 ++++ ...4047_create_quadrennial_period_variable.rb | 16 ++++ db/schema.rb | 71 ++++++++++----- db/seeds.rb | 1 + spec/factories/factory_paper.rb | 18 ++++ spec/factories/factory_paper_professor.rb | 11 +++ spec/factories/factory_paper_student.rb | 11 +++ spec/models/paper_professor_spec.rb | 37 ++++++++ spec/models/paper_spec.rb | 55 ++++++++++++ spec/models/paper_student_spec.rb | 37 ++++++++ 32 files changed, 711 insertions(+), 21 deletions(-) create mode 100644 app/controllers/paper_professors_controller.rb create mode 100644 app/controllers/paper_students_controller.rb create mode 100644 app/controllers/papers_controller.rb create mode 100644 app/helpers/paper_professors_helper.rb create mode 100644 app/helpers/paper_students_helper.rb create mode 100644 app/helpers/papers_helper.rb create mode 100644 app/models/paper.rb create mode 100644 app/models/paper_professor.rb create mode 100644 app/models/paper_student.rb create mode 100644 app/views/papers/_header_form_column.html.erb create mode 100644 app/views/papers/_reason_group_end_form_column.html.erb create mode 100644 app/views/papers/_reason_group_form_column.html.erb create mode 100644 config/locales/paper.pt-BR.yml create mode 100644 config/locales/paper_professor.pt-BR.yml create mode 100644 config/locales/paper_student.pt-BR.yml create mode 100644 db/migrate/20241206024033_create_papers.rb create mode 100644 db/migrate/20241206024127_create_paper_students.rb create mode 100644 db/migrate/20241206024146_create_paper_professors.rb create mode 100644 db/migrate/20241206034047_create_quadrennial_period_variable.rb create mode 100644 spec/factories/factory_paper.rb create mode 100644 spec/factories/factory_paper_professor.rb create mode 100644 spec/factories/factory_paper_student.rb create mode 100644 spec/models/paper_professor_spec.rb create mode 100644 spec/models/paper_spec.rb create mode 100644 spec/models/paper_student_spec.rb 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..feb89caf --- /dev/null +++ b/app/controllers/papers_controller.rb @@ -0,0 +1,90 @@ +# 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 :header + config.columns.add :reason_group + config.columns.add :reason_group_end + form_columns = [ + :header, + :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, + :impact_factor, + :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, + :impact_factor, + :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/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/models/ability.rb b/app/models/ability.rb index 95d66b3c..b84d5833 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -16,7 +16,7 @@ class Ability PROFESSOR_MODELS = [ Professor, Advisement, AdvisementAuthorization, ThesisDefenseCommitteeParticipation, - ProfessorResearchArea, Grant + ProfessorResearchArea, Grant, Paper, PaperProfessor, PaperStudent ] SCHOLARSHIP_MODELS = [ @@ -146,8 +146,19 @@ def initialize_professors(user, roles) 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, Paper, owner: user.professor end end diff --git a/app/models/custom_variable.rb b/app/models/custom_variable.rb index 4ed0fee1..09277ac1 100644 --- a/app/models/custom_variable.rb +++ b/app/models/custom_variable.rb @@ -25,6 +25,7 @@ class CustomVariable < ApplicationRecord "year_semester_range" => :text, "past_calendar_range" => :text, "academic_calendar_range" => :text, + "quadrennial_period" => :text, } validates :variable, presence: true @@ -115,6 +116,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/paper.rb b/app/models/paper.rb new file mode 100644 index 00000000..34d06364 --- /dev/null +++ b/app/models/paper.rb @@ -0,0 +1,56 @@ +# 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 :impact_factor, 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/views/papers/_header_form_column.html.erb b/app/views/papers/_header_form_column.html.erb new file mode 100644 index 00000000..f14930ed --- /dev/null +++ b/app/views/papers/_header_form_column.html.erb @@ -0,0 +1,10 @@ +Lembre que o relatório final é a coleção de artigos do programa. Assim: + +
    +
  • Tente informar entre quatro e oito artigos. Mas não deixe de preencher se sua escolha for menor que essa.
  • +
  • Caso tenha mais de um artigo de destaque por ano, informe também.
  • +
  • Os artigos podem ser publicações em periódicos ou conferências.
  • +
+ +
+
\ No newline at end of file diff --git a/app/views/papers/_reason_group_end_form_column.html.erb b/app/views/papers/_reason_group_end_form_column.html.erb new file mode 100644 index 00000000..d0203440 --- /dev/null +++ b/app/views/papers/_reason_group_end_form_column.html.erb @@ -0,0 +1,4 @@ + + + +
  • \ No newline at end of file diff --git a/app/views/papers/_reason_group_form_column.html.erb b/app/views/papers/_reason_group_form_column.html.erb new file mode 100644 index 00000000..398c96d2 --- /dev/null +++ b/app/views/papers/_reason_group_form_column.html.erb @@ -0,0 +1,25 @@ +
  • + + +<% id ||= "group_#{SecureRandom.random_number(1_000_000)}" %> +
    Motivos Ocultar
    +
    + Quais aspectos atribui ao artigo para que ele entre na sua lista? +
  • \ No newline at end of file diff --git a/config/locales/navigation.pt-BR.yml b/config/locales/navigation.pt-BR.yml index a8809e2c..1e493224 100644 --- a/config/locales/navigation.pt-BR.yml +++ b/config/locales/navigation.pt-BR.yml @@ -22,6 +22,7 @@ pt-BR: advisement_authorization: Credenciamentos thesis_defense_committee_participation: Bancas grant: Coordenações de Projetos + paper: Avaliação quadrienal - 4N scholarships: label: Bolsas diff --git a/config/locales/paper.pt-BR.yml b/config/locales/paper.pt-BR.yml new file mode 100644 index 00000000..e20cf0d0 --- /dev/null +++ b/config/locales/paper.pt-BR.yml @@ -0,0 +1,56 @@ +# 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" + impact_factor: "Valor quantitativo do fórum (fator de impacto, h-index, JCR, etc)" + order: "Relevância" + other: "Caso queira, descreva brevemente qualquer outro ponto que ache relevante para o artigo." + description: + paper: + reference: "(conforme informado para o Sucupira)" + doi_issn_event: "(conforme informado na Sucupira)" + other_authors: "(caso não tenha, informar NA)" + 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/navigation.rb b/config/navigation.rb index 640a3206..71455181 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -150,6 +150,7 @@ def can_read?(*args) submenu.modelitem AdvisementAuthorization submenu.modelitem ThesisDefenseCommitteeParticipation submenu.modelitem Grant + submenu.modelitem Paper end scholar_models = [Scholarship, ScholarshipType, ScholarshipDuration] diff --git a/config/routes.rb b/config/routes.rb index 0ef13e69..14a0c2f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -538,4 +538,16 @@ 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/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/schema.rb b/db/schema.rb index 2d4d0379..08c0faaf 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_11_29_022042) do +ActiveRecord::Schema[7.0].define(version: 2024_12_06_034047) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -256,17 +256,6 @@ 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 @@ -665,6 +654,51 @@ 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.string "impact_factor" + 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" @@ -745,14 +779,6 @@ 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" @@ -1044,6 +1070,11 @@ 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 a96c2d47..9f89bc0e 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" }, ]) ReportConfiguration.create([ diff --git a/spec/factories/factory_paper.rb b/spec/factories/factory_paper.rb new file mode 100644 index 00000000..412018df --- /dev/null +++ b/spec/factories/factory_paper.rb @@ -0,0 +1,18 @@ +# 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" } + impact_factor { "1" } + 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/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..79f5ed20 --- /dev/null +++ b/spec/models/paper_spec.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 + +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", + impact_factor: "1", + 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) } + it { should validate_presence_of(:impact_factor) } + # 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 From 66c2c805c01ed9b19b0f8ad3a8a4cc56fd344749 Mon Sep 17 00:00:00 2001 From: Joao Felipe Pimentel Date: Fri, 6 Dec 2024 19:36:12 -0300 Subject: [PATCH 31/35] #505: fix form bugs --- app/models/ability.rb | 2 ++ config/locales/paper.pt-BR.yml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index b84d5833..6cc965b2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -158,6 +158,8 @@ def initialize_professors(user, roles) 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 diff --git a/config/locales/paper.pt-BR.yml b/config/locales/paper.pt-BR.yml index e20cf0d0..09db0bac 100644 --- a/config/locales/paper.pt-BR.yml +++ b/config/locales/paper.pt-BR.yml @@ -38,7 +38,6 @@ pt-BR: paper: reference: "(conforme informado para o Sucupira)" doi_issn_event: "(conforme informado na Sucupira)" - other_authors: "(caso não tenha, informar NA)" order: "Considerando o conjunto que você selecionou (1 é o mais relevante)" errors: From 11ef8a2c20f8657292676722a472306b222ec09b Mon Sep 17 00:00:00 2001 From: Joao Felipe Pimentel Date: Sun, 8 Dec 2024 02:28:47 -0300 Subject: [PATCH 32/35] #505: remove impact factor field and form header. Change description text --- app/controllers/papers_controller.rb | 4 ---- app/models/paper.rb | 1 - app/views/papers/_header_form_column.html.erb | 10 ---------- config/locales/paper.pt-BR.yml | 5 ++--- .../20241208052442_remove_impact_factor_from_papers.rb | 5 +++++ db/schema.rb | 3 +-- spec/factories/factory_paper.rb | 1 - spec/models/paper_spec.rb | 2 -- 8 files changed, 8 insertions(+), 23 deletions(-) delete mode 100644 app/views/papers/_header_form_column.html.erb create mode 100644 db/migrate/20241208052442_remove_impact_factor_from_papers.rb diff --git a/app/controllers/papers_controller.rb b/app/controllers/papers_controller.rb index feb89caf..50807054 100644 --- a/app/controllers/papers_controller.rb +++ b/app/controllers/papers_controller.rb @@ -11,11 +11,9 @@ class PapersController < ApplicationController config.actions.swap :search, :field_search config.list.sorting = { period: "DESC", owner: "ASC", order: "ASC" } - config.columns.add :header config.columns.add :reason_group config.columns.add :reason_group_end form_columns = [ - :header, :period, :owner, :reference, :order, :kind, :doi_issn_event, :paper_professors, :paper_students, :other_authors, :reason_group, @@ -31,7 +29,6 @@ class PapersController < ApplicationController :reason_social_contribution, :reason_other, :reason_justify, - :impact_factor, :reason_group_end, :other, ] @@ -52,7 +49,6 @@ class PapersController < ApplicationController :reason_social_contribution, :reason_other, :reason_justify, - :impact_factor, :order, :other, ] diff --git a/app/models/paper.rb b/app/models/paper.rb index 34d06364..a6a8e442 100644 --- a/app/models/paper.rb +++ b/app/models/paper.rb @@ -28,7 +28,6 @@ class Paper < ApplicationRecord validates :kind, presence: true, inclusion: { in: KINDS } validates :doi_issn_event, presence: true validates :reason_justify, presence: true - validates :impact_factor, presence: true validates :order, presence: true, inclusion: { in: ORDERS }, uniqueness: { scope: [:period, :owner_id], message: :order_uniqueness } diff --git a/app/views/papers/_header_form_column.html.erb b/app/views/papers/_header_form_column.html.erb deleted file mode 100644 index f14930ed..00000000 --- a/app/views/papers/_header_form_column.html.erb +++ /dev/null @@ -1,10 +0,0 @@ -Lembre que o relatório final é a coleção de artigos do programa. Assim: - -
      -
    • Tente informar entre quatro e oito artigos. Mas não deixe de preencher se sua escolha for menor que essa.
    • -
    • Caso tenha mais de um artigo de destaque por ano, informe também.
    • -
    • Os artigos podem ser publicações em periódicos ou conferências.
    • -
    - -
    -
    \ No newline at end of file diff --git a/config/locales/paper.pt-BR.yml b/config/locales/paper.pt-BR.yml index 09db0bac..ff33e6c7 100644 --- a/config/locales/paper.pt-BR.yml +++ b/config/locales/paper.pt-BR.yml @@ -31,13 +31,12 @@ pt-BR: 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" - impact_factor: "Valor quantitativo do fórum (fator de impacto, h-index, JCR, etc)" order: "Relevância" other: "Caso queira, descreva brevemente qualquer outro ponto que ache relevante para o artigo." description: paper: - reference: "(conforme informado para o Sucupira)" - doi_issn_event: "(conforme informado na Sucupira)" + reference: "(copiar do seu lattes)" + doi_issn_event: "(copiar do seu lattes)" order: "Considerando o conjunto que você selecionou (1 é o mais relevante)" errors: 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/schema.rb b/db/schema.rb index 08c0faaf..489b3ff4 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_12_06_034047) do +ActiveRecord::Schema[7.0].define(version: 2024_12_08_052442) do create_table "accomplishments", force: :cascade do |t| t.integer "enrollment_id" t.integer "phase_id" @@ -691,7 +691,6 @@ t.boolean "reason_social_contribution", default: false, null: false t.text "reason_other" t.text "reason_justify" - t.string "impact_factor" t.integer "order" t.text "other" t.datetime "created_at", null: false diff --git a/spec/factories/factory_paper.rb b/spec/factories/factory_paper.rb index 412018df..70701df1 100644 --- a/spec/factories/factory_paper.rb +++ b/spec/factories/factory_paper.rb @@ -12,7 +12,6 @@ owner { create(:professor) } reason_international_list { true } reason_justify { "Internacional" } - impact_factor { "1" } order { 1 } end end diff --git a/spec/models/paper_spec.rb b/spec/models/paper_spec.rb index 79f5ed20..2923cdd9 100644 --- a/spec/models/paper_spec.rb +++ b/spec/models/paper_spec.rb @@ -24,7 +24,6 @@ owner: professor, reason_international_list: true, reason_justify: "Internacional", - impact_factor: "1", order: 1 ) end @@ -39,7 +38,6 @@ it { should validate_presence_of(:order) } it { should validate_presence_of(:doi_issn_event) } it { should validate_presence_of(:reason_justify) } - it { should validate_presence_of(:impact_factor) } # ToDo: professor cannot edit other papers end From d8f6cb9419aab6156a9a5b4ba69ab253c70ada77 Mon Sep 17 00:00:00 2001 From: Joao Felipe Pimentel Date: Sun, 8 Dec 2024 03:31:19 -0300 Subject: [PATCH 33/35] Fix pendency on individual admission committee form submission --- .../admissions/filled_forms_controller.rb | 2 +- app/models/admissions/admission_application.rb | 8 ++++---- app/models/admissions/filled_form.rb | 2 +- .../filled_form/edit/_filled_form_template.html.erb | 12 +++++++----- 4 files changed, 13 insertions(+), 11 deletions(-) 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/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/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/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 @@