diff --git a/Gemfile b/Gemfile index 044088d47..d629ba148 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,7 @@ gem 'aasm' gem 'actiontext', git: 'https://github.com/kobaltz/actiontext.git', branch: 'archive', require: 'action_text' # Port of ActionText to Rails 5 gem 'active_link_to' # Automatically set a class on active links gem 'active_model_serializers' +gem 'active_storage_validations' gem 'activestorage-openstack' gem 'administrate' gem 'after_party' diff --git a/Gemfile.lock b/Gemfile.lock index 1c4931188..5d391447f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,25 +20,25 @@ GEM specs: aasm (5.0.1) concurrent-ruby (~> 1.0) - actioncable (5.2.3) - actionpack (= 5.2.3) + actioncable (5.2.4.1) + actionpack (= 5.2.4.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) - activejob (= 5.2.3) + actionmailer (5.2.4.1) + actionpack (= 5.2.4.1) + actionview (= 5.2.4.1) + activejob (= 5.2.4.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.3) - actionview (= 5.2.3) - activesupport (= 5.2.3) - rack (~> 2.0) + actionpack (5.2.4.1) + actionview (= 5.2.4.1) + activesupport (= 5.2.4.1) + rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.3) - activesupport (= 5.2.3) + actionview (5.2.4.1) + activesupport (= 5.2.4.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -46,30 +46,32 @@ GEM active_link_to (1.0.5) actionpack addressable - active_model_serializers (0.10.8) - actionpack (>= 4.1, < 6) - activemodel (>= 4.1, < 6) + active_model_serializers (0.10.10) + actionpack (>= 4.1, < 6.1) + activemodel (>= 4.1, < 6.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) + active_storage_validations (0.8.7) + rails (>= 5.2.0) activejob (5.2.3) activesupport (= 5.2.3) globalid (>= 0.3.6) - activemodel (5.2.3) - activesupport (= 5.2.3) - activerecord (5.2.3) - activemodel (= 5.2.3) - activesupport (= 5.2.3) + activemodel (5.2.4.1) + activesupport (= 5.2.4.1) + activerecord (5.2.4.1) + activemodel (= 5.2.4.1) + activesupport (= 5.2.4.1) arel (>= 9.0) - activestorage (5.2.3) - actionpack (= 5.2.3) - activerecord (= 5.2.3) + activestorage (5.2.4.1) + actionpack (= 5.2.4.1) + activerecord (= 5.2.4.1) marcel (~> 0.3.1) - activestorage-openstack (1.2.0) + activestorage-openstack (1.3.0) fog-openstack (~> 1.0) marcel mime-types rails (>= 5.2.2) - activesupport (5.2.3) + activesupport (5.2.4.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -151,7 +153,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.5) + concurrent-ruby (1.1.6) connection_pool (2.2.2) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -203,25 +205,25 @@ GEM ethon (0.11.0) ffi (>= 1.3.0) eventmachine (1.2.7) - excon (0.71.0) + excon (0.72.0) execjs (2.7.0) factory_bot (4.11.1) activesupport (>= 3.0.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) ffi (1.9.25) - flipper (0.16.2) - flipper-active_record (0.16.2) - activerecord (>= 3.2, < 6) - flipper (~> 0.16.2) - flipper-ui (0.16.2) - erubis (~> 2.7.0) - flipper (~> 0.16.2) + flipper (0.17.2) + flipper-active_record (0.17.2) + activerecord (>= 4.2, < 7) + flipper (~> 0.17.2) + flipper-ui (0.17.2) + erubi (>= 1.0.0, < 2.0.0) + flipper (~> 0.17.2) rack (>= 1.4, < 3) rack-protection (>= 1.5.3, < 2.1.0) - fog-core (2.1.2) + fog-core (2.2.0) builder - excon (~> 0.58) + excon (~> 0.71) formatador (~> 0.2) mime-types fog-json (1.2.0) @@ -237,25 +239,26 @@ GEM geocoder (1.6.0) globalid (0.4.2) activesupport (>= 4.2.0) - gon (6.2.1) - actionpack (>= 3.0) + gon (6.3.2) + actionpack (>= 3.0.20) + i18n (>= 0.7) multi_json request_store (>= 1.0) graphiql-rails (1.7.0) railties sprockets-rails - graphql (1.9.16) - graphql-batch (0.4.1) + graphql (1.10.3) + graphql-batch (0.4.2) graphql (>= 1.3, < 2) promise.rb (~> 0.7.2) - graphql-rails_logger (1.2.0) - actionpack (~> 5.0) - activesupport (~> 5.0) - railties (~> 5.0) + graphql-rails_logger (1.2.2) + actionpack (> 5.0) + activesupport (> 5.0) + railties (> 5.0) rouge (~> 3.0) - graphql-schema_comparator (0.6.1) + graphql-schema_comparator (1.0.0) bundler (>= 1.14) - graphql (~> 1.6) + graphql (~> 1.10) thor (>= 0.19, < 2.0) groupdate (4.1.1) activesupport (>= 4.2) @@ -320,25 +323,25 @@ GEM activesupport (>= 4.2) aes_key_wrap bindata - jsonapi-renderer (0.2.0) + jsonapi-renderer (0.2.2) jwt (2.1.0) - kaminari (1.1.1) + kaminari (1.2.0) activesupport (>= 4.1.0) - kaminari-actionview (= 1.1.1) - kaminari-activerecord (= 1.1.1) - kaminari-core (= 1.1.1) - kaminari-actionview (1.1.1) + kaminari-actionview (= 1.2.0) + kaminari-activerecord (= 1.2.0) + kaminari-core (= 1.2.0) + kaminari-actionview (1.2.0) actionview - kaminari-core (= 1.1.1) - kaminari-activerecord (1.1.1) + kaminari-core (= 1.2.0) + kaminari-activerecord (1.2.0) activerecord - kaminari-core (= 1.1.1) - kaminari-core (1.1.1) + kaminari-core (= 1.2.0) + kaminari-core (1.2.0) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.7.0) launchy (~> 2.2) - letter_opener_web (1.3.4) + letter_opener_web (1.4.0) actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) @@ -365,10 +368,10 @@ GEM marcel (0.3.3) mimemagic (~> 0.3.2) method_source (0.9.2) - mime-types (3.3) + mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2019.1009) - mimemagic (0.3.3) + mimemagic (0.3.4) mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.0) @@ -448,11 +451,11 @@ GEM pry-byebug (3.6.0) byebug (~> 10.0) pry (~> 0.10) - public_suffix (4.0.1) + public_suffix (4.0.3) puma (3.12.2) pundit (2.0.1) activesupport (>= 3.0.0) - rack (2.1.2) + rack (2.2.2) rack-attack (6.0.0) rack (>= 1.0, < 3) rack-mini-profiler (1.0.1) @@ -469,18 +472,18 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.3) - actioncable (= 5.2.3) - actionmailer (= 5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) - activejob (= 5.2.3) - activemodel (= 5.2.3) - activerecord (= 5.2.3) - activestorage (= 5.2.3) - activesupport (= 5.2.3) + rails (5.2.4.1) + actioncable (= 5.2.4.1) + actionmailer (= 5.2.4.1) + actionpack (= 5.2.4.1) + actionview (= 5.2.4.1) + activejob (= 5.2.4.1) + activemodel (= 5.2.4.1) + activerecord (= 5.2.4.1) + activestorage (= 5.2.4.1) + activesupport (= 5.2.4.1) bundler (>= 1.3.0) - railties (= 5.2.3) + railties (= 5.2.4.1) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.4) actionpack (>= 5.0.1.x) @@ -494,9 +497,9 @@ GEM rails-i18n (5.1.2) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) - railties (5.2.3) - actionpack (= 5.2.3) - activesupport (= 5.2.3) + railties (5.2.4.1) + actionpack (= 5.2.4.1) + activesupport (= 5.2.4.1) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -513,7 +516,7 @@ GEM railties (>= 3.2) tilt regexp_parser (1.6.0) - request_store (1.4.1) + request_store (1.5.0) rack (>= 1.4) responders (3.0.0) actionpack (>= 5.0) @@ -529,7 +532,7 @@ GEM activesupport (>= 3.0) builder (>= 3.0) rubyzip (>= 1.0) - rouge (3.9.0) + rouge (3.16.0) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) @@ -713,6 +716,7 @@ DEPENDENCIES actiontext! active_link_to active_model_serializers + active_storage_validations activestorage-openstack administrate after_party diff --git a/app/controllers/admin/attestation_templates_controller.rb b/app/controllers/admin/attestation_templates_controller.rb index f50cba788..d1eff3ba5 100644 --- a/app/controllers/admin/attestation_templates_controller.rb +++ b/app/controllers/admin/attestation_templates_controller.rb @@ -40,9 +40,16 @@ class Admin::AttestationTemplatesController < AdminController end def preview - @attestation = (@procedure.attestation_template || AttestationTemplate.new).render_attributes_for(activated_attestation_params) + attestation = (@procedure.attestation_template || AttestationTemplate.new) + attestation.assign_attributes(activated_attestation_params) - render 'admin/attestation_templates/show', formats: [:pdf] + if attestation.valid? + @attestation = attestation.render_attributes_for(activated_attestation_params) + + render 'admin/attestation_templates/show', formats: [:pdf] + else + flash.alert = attestation_template.errors.full_messages.join('
') + end end def delete_logo diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index ce7d05964..c2294ee67 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -90,4 +90,12 @@ class DossierMailer < ApplicationMailer mail(to: user.email, subject: @subject) end + + def notify_groupe_instructeur_changed(instructeur, dossier) + @subject = "Un dossier a changé de groupe instructeur" + @dossier_id = dossier.id + @demarche = dossier.procedure.libelle + + mail(from: NO_REPLY_EMAIL, to: instructeur.email, subject: @subject) + end end diff --git a/app/models/attestation_template.rb b/app/models/attestation_template.rb index d61ebc1fb..69056789b 100644 --- a/app/models/attestation_template.rb +++ b/app/models/attestation_template.rb @@ -11,6 +11,9 @@ class AttestationTemplate < ApplicationRecord validates :footer, length: { maximum: 190 } + validates :logo, content_type: [:png, :jpg, :jpeg] + validates :signature, content_type: [:png, :jpg, :jpeg] + DOSSIER_STATE = Dossier.states.fetch(:accepte) def attestation_for(dossier) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index a03338b4e..9c25ec40f 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -213,6 +213,7 @@ class Dossier < ApplicationRecord before_save :build_default_champs, if: Proc.new { groupe_instructeur_id_was.nil? } before_save :update_search_terms + after_save :unfollow_stale_instructeurs after_save :send_dossier_received after_save :send_web_hook after_create :send_draft_notification_email @@ -645,6 +646,18 @@ class Dossier < ApplicationRecord end end + def unfollow_stale_instructeurs + if saved_change_to_groupe_instructeur_id? && saved_change_to_groupe_instructeur_id[0].present? + followers_instructeurs.each do |instructeur| + if instructeur.groupe_instructeurs.exclude?(groupe_instructeur) + instructeur.unfollow(self) + DossierMailer.notify_groupe_instructeur_changed(instructeur, self).deliver_later + end + end + log_dossier_operation(user, :changer_groupe_instructeur, self) + end + end + def self.send_brouillon_expiration_notices brouillons = Dossier .brouillon_close_to_expiration diff --git a/app/models/dossier_operation_log.rb b/app/models/dossier_operation_log.rb index fbb3df559..bfad08b97 100644 --- a/app/models/dossier_operation_log.rb +++ b/app/models/dossier_operation_log.rb @@ -1,5 +1,6 @@ class DossierOperationLog < ApplicationRecord enum operation: { + changer_groupe_instructeur: 'changer_groupe_instructeur', passer_en_instruction: 'passer_en_instruction', repasser_en_construction: 'repasser_en_construction', repasser_en_instruction: 'repasser_en_instruction', diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 75872f175..944b2a07b 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -75,7 +75,6 @@ class ProcedurePresentation < ApplicationRecord end def sorted_ids(dossiers, instructeur) - dossiers.each { |dossier| assert_matching_procedure(dossier) } table, column, order = sort.values_at('table', 'column', 'order') case table @@ -110,7 +109,6 @@ class ProcedurePresentation < ApplicationRecord end def filtered_ids(dossiers, statut) - dossiers.each { |dossier| assert_matching_procedure(dossier) } filters[statut].group_by { |filter| filter.values_at('table', 'column') } .map do |(table, column), filters| values = filters.pluck('value') case table diff --git a/app/views/admin/attestation_templates/edit.html.haml b/app/views/admin/attestation_templates/edit.html.haml index 4239d89e7..548afc3aa 100644 --- a/app/views/admin/attestation_templates/edit.html.haml +++ b/app/views/admin/attestation_templates/edit.html.haml @@ -23,7 +23,7 @@ = f.label :logo, "Logo de l'attestation" - if @attestation_template.logo.attached? = link_to 'Supprimer le logo', admin_procedure_attestation_template_logo_path(@procedure), method: :delete - = f.file_field :logo, accept: 'image/png, image/jpg, image/jpeg' + = f.file_field :logo, accept: 'image/png,image/jpg,image/jpeg' %p.help-block Fichier accepté : JPG / JPEG / PNG %br @@ -87,3 +87,4 @@ - else - save_data = @procedure.locked? ? { toggle: :tooltip, confirm: "Attention: les modifications n'affecteront pas les attestations déjà délivrées." } : nil %button.btn.btn-success{ data: save_data } Enregistrer + diff --git a/app/views/dossier_mailer/notify_groupe_instructeur_changed.html.haml b/app/views/dossier_mailer/notify_groupe_instructeur_changed.html.haml new file mode 100644 index 000000000..753568e40 --- /dev/null +++ b/app/views/dossier_mailer/notify_groupe_instructeur_changed.html.haml @@ -0,0 +1,9 @@ +- content_for(:title, "#{@subject}") + +%p + = "Vous suiviez jusqu'à maintenant le dossier n°#{@dossier_id} de la démarche #{@demarche}." + L'usager a modifié le groupe de routage. Son dossier appartient maintenant à un groupe instructeur dont vous ne faites pas partie. +%p + Suite à cette modification, vous ne suivez plus ce dossier. + += render partial: "layouts/mailers/signature" diff --git a/spec/controllers/admin/attestation_templates_controller_spec.rb b/spec/controllers/admin/attestation_templates_controller_spec.rb index 11c9c094c..0cfb06397 100644 --- a/spec/controllers/admin/attestation_templates_controller_spec.rb +++ b/spec/controllers/admin/attestation_templates_controller_spec.rb @@ -26,11 +26,12 @@ describe Admin::AttestationTemplatesController, type: :controller do procedure_id: procedure.id, attestation_template: upload_params } + procedure.reload end context 'with an interlaced png' do let(:upload_params) { { logo: interlaced_logo } } - it { expect(assigns(:attestation)[:logo].read).to eq(uninterlaced_logo.read) } + it { expect(procedure.attestation_template.logo.download).to eq(uninterlaced_logo.read) } end context 'if an attestation template does not exist on the procedure' do diff --git a/spec/fixtures/files/beta-gouv.gif b/spec/fixtures/files/beta-gouv.gif new file mode 100644 index 000000000..72b151b65 Binary files /dev/null and b/spec/fixtures/files/beta-gouv.gif differ diff --git a/spec/fixtures/files/french-flag.gif b/spec/fixtures/files/french-flag.gif new file mode 100644 index 000000000..475ccb23e Binary files /dev/null and b/spec/fixtures/files/french-flag.gif differ diff --git a/spec/mailers/dossier_mailer_spec.rb b/spec/mailers/dossier_mailer_spec.rb index b7b50111c..c18b2ebb4 100644 --- a/spec/mailers/dossier_mailer_spec.rb +++ b/spec/mailers/dossier_mailer_spec.rb @@ -154,4 +154,16 @@ RSpec.describe DossierMailer, type: :mailer do it { expect(subject.body).to include("PDF") } it { expect(subject.body).to include("Vous avez un mois pour traiter le dossier.") } end + + describe '.notify_groupe_instructeur_changed_to_instructeur' do + let(:dossier) { create(:dossier) } + let(:instructeur) { create(:instructeur) } + + subject { described_class.notify_groupe_instructeur_changed(instructeur, dossier) } + + it { expect(subject.subject).to eq("Un dossier a changé de groupe instructeur") } + it { expect(subject.body).to include("n°#{dossier.id}") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + it { expect(subject.body).to include("Suite à cette modification, vous ne suivez plus ce dossier.") } + end end diff --git a/spec/models/attestation_template_spec.rb b/spec/models/attestation_template_spec.rb index 8cd9c6990..4b03779f3 100644 --- a/spec/models/attestation_template_spec.rb +++ b/spec/models/attestation_template_spec.rb @@ -87,6 +87,25 @@ describe AttestationTemplate, type: :model do end end + describe 'invalidate attestation if images attachments are not valid' do + before do + @logo = Rack::Test::UploadedFile.new('spec/fixtures/files/french-flag.gif', 'image/gif') + @signature = Rack::Test::UploadedFile.new('spec/fixtures/files/beta-gouv.gif', 'image/gif') + end + + after do + subject.destroy + end + + let(:attestation_template) { AttestationTemplate.create(attributes) } + subject { attestation_template.dup } + + context 'with an attestation which has gif files' do + let(:attributes) { { title: 't', body: 'b', footer: 'f', activated: true, logo: @logo, signature: @signature } } + it { is_expected.not_to be_valid } + end + end + describe 'attestation_for' do let(:procedure) do create(:procedure, diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 5136380e3..dbdc9ac69 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -398,6 +398,35 @@ describe Dossier do it { is_expected.to match([dossier3, dossier4, dossier2]) } end + describe "#unfollow_stale_instructeurs" do + let(:procedure) { create(:procedure) } + let(:instructeur) { create(:instructeur) } + let(:new_groupe_instructeur) { create(:groupe_instructeur) } + let(:instructeur2) { create(:instructeur, groupe_instructeurs: [procedure.defaut_groupe_instructeur, new_groupe_instructeur]) } + let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) } + let(:last_operation) { DossierOperationLog.last } + + before do + allow(DossierMailer).to receive(:notify_groupe_instructeur_changed).and_return(double(deliver_later: nil)) + end + + it "unfollows stale instructeurs when groupe instructeur change" do + instructeur.follow(dossier) + instructeur2.follow(dossier) + dossier.reload.update(groupe_instructeur: new_groupe_instructeur) + + expect(dossier.reload.followers_instructeurs).not_to include(instructeur) + expect(dossier.reload.followers_instructeurs).to include(instructeur2) + + expect(DossierMailer).to have_received(:notify_groupe_instructeur_changed).with(instructeur, dossier) + expect(DossierMailer).not_to have_received(:notify_groupe_instructeur_changed).with(instructeur2, dossier) + + expect(last_operation.operation).to eq("changer_groupe_instructeur") + expect(last_operation.dossier).to eq(dossier) + expect(last_operation.automatic_operation?).to be_falsey + end + end + describe "#send_dossier_received" do let(:procedure) { create(:procedure) } let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) }