Merge pull request #5437 from betagouv/dev

2020-07-30-01
This commit is contained in:
Kara Diaby 2020-07-30 14:31:51 +02:00 committed by GitHub
commit e41bc76e6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 227 additions and 52 deletions

View file

@ -1,6 +1,15 @@
@import "colors";
@import "constants";
.france-connect-login {
h2 {
color: $black;
font-size: 24px;
}
}
.france-connect-login-buttons,
.france-connect-help-link {
text-align: center;
}
@ -22,7 +31,29 @@
}
.france-connect-login-separator {
margin: 24px 0;
font-size: 14px;
color: $dark-grey;
display: flex;
flex-basis: 100%;
align-items: center;
color: $black;
text-transform: uppercase;
padding-bottom: $default-padding;
padding-top: $default-padding;
&::before,
&::after {
content: "";
flex-grow: 1;
background: $dark-grey;
height: 1px;
font-size: 0;
line-height: 0;
}
&::before {
margin-right: $default-spacer;
}
&::after {
margin-left: $default-spacer;
}
}

View file

@ -35,6 +35,7 @@ module CreateAvisConcern
persisted, failed = create_results.partition(&:persisted?)
if persisted.any?
dossier.update!(last_avis_updated_at: Time.zone.now)
sent_emails_addresses = []
persisted.each do |avis|
avis.dossier.demander_un_avis!(avis)

View file

@ -44,6 +44,7 @@ module Instructeurs
def update
if @avis.update(avis_params)
flash.notice = 'Votre réponse est enregistrée.'
@avis.dossier.update!(last_avis_updated_at: Time.zone.now)
redirect_to instruction_instructeur_avis_path(@avis.procedure, @avis)
else
flash.now.alert = @avis.errors.full_messages
@ -60,6 +61,7 @@ module Instructeurs
@commentaire = CommentaireService.build(current_instructeur, avis.dossier, commentaire_params)
if @commentaire.save
@commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
flash.notice = "Message envoyé"
redirect_to messagerie_instructeur_avis_path(avis.procedure, avis)
else

View file

@ -169,6 +169,7 @@ module Instructeurs
@commentaire = CommentaireService.build(current_instructeur, dossier, commentaire_params)
if @commentaire.save
@commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
current_instructeur.follow(dossier)
flash.notice = "Message envoyé"
redirect_to messagerie_instructeur_dossier_path(procedure, dossier)
@ -191,8 +192,12 @@ module Instructeurs
def update_annotations
dossier = current_instructeur.dossiers.includes(champs_private: :type_de_champ).find(params[:dossier_id])
dossier.update(champs_private_params)
dossier.modifier_annotations!(current_instructeur)
dossier.assign_attributes(champs_private_params)
if dossier.champs_private.any?(&:changed?)
dossier.last_champ_private_updated_at = Time.zone.now
end
dossier.save
dossier.log_modifier_annotations!(current_instructeur)
redirect_to annotations_privees_instructeur_dossier_path(procedure, dossier)
end

View file

@ -198,6 +198,7 @@ module Users
@commentaire = CommentaireService.build(current_user, dossier, commentaire_params)
if @commentaire.save
@commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
dossier.followers_instructeurs
.with_instant_email_message_notifications
.each do |instructeur|
@ -344,7 +345,11 @@ module Users
errors = []
if champs_params[:dossier]
if !@dossier.update(champs_params[:dossier])
@dossier.assign_attributes(champs_params[:dossier])
if @dossier.champs.any?(&:changed?)
@dossier.last_champ_updated_at = Time.zone.now
end
if !@dossier.save
errors += @dossier.errors.full_messages
elsif change_groupe_instructeur?
groupe_instructeur = @dossier.procedure.groupe_instructeurs.find(params[:dossier][:groupe_instructeur_id])

View file

@ -0,0 +1,30 @@
class TmpSetDossiersLastUpdatedAtJob < ApplicationJob
def perform(except)
dossiers = Dossier.where
.not(id: except)
.where(last_champ_updated_at: nil)
.includes(:champs, :avis, :commentaires)
.limit(100)
dossiers.find_each do |dossier|
last_commentaire_updated_at = dossier.commentaires
.where.not(email: OLD_CONTACT_EMAIL)
.where.not(email: CONTACT_EMAIL)
.maximum(:updated_at)
last_avis_updated_at = dossier.avis.maximum(:updated_at)
last_champ_updated_at = dossier.champs.maximum(:updated_at)
last_champ_private_updated_at = dossier.champs_private.maximum(:updated_at)
dossier.update_columns(
last_commentaire_updated_at: last_commentaire_updated_at,
last_avis_updated_at: last_avis_updated_at,
last_champ_updated_at: last_champ_updated_at,
last_champ_private_updated_at: last_champ_private_updated_at
)
except << dossier.id
end
if dossiers.where.not(id: except).exists?
TmpSetDossiersLastUpdatedAtJob.perform_later(except)
end
end
end

View file

@ -615,7 +615,7 @@ class Dossier < ApplicationRecord
end
end
def modifier_annotations!(instructeur)
def log_modifier_annotations!(instructeur)
champs_private.filter(&:value_previously_changed?).each do |champ|
log_dossier_operation(instructeur, :modifier_annotation, champ)
end

View file

@ -3,12 +3,12 @@
.commencer.form
- if !user_signed_in?
%h2.huge-title Commencer la démarche
= render partial: 'shared/france_connect_login', locals: { url: commencer_france_connect_path(path: @procedure.path) }
= link_to commencer_sign_up_path(path: @procedure.path), class: ['button large expand primary'] do
Créer un compte
%span.optional-on-small-screens
#{APPLICATION_NAME}
= link_to 'Jai déjà un compte', commencer_sign_in_path(path: @procedure.path), class: ['button large expand']
= render partial: 'shared/france_connect_login', locals: { url: commencer_france_connect_path(path: @procedure.path) }
- else
- dossiers = current_user.dossiers.where(groupe_instructeur: @procedure.groupe_instructeurs)

View file

@ -44,27 +44,28 @@
%span.icon.in-progress
.dropdown-description
%h4 Tester
%li
= link_to admin_procedure_clone_path(procedure.id), class: 'clone-btn', data: { method: :put } do
%span.icon.new-folder
.dropdown-description
%h4 Cloner
- if procedure.publiee?
%li
= link_to admin_procedure_clone_path(procedure.id), class: 'clone-btn', data: { method: :put } do
%span.icon.new-folder
= link_to admin_procedure_archive_path(procedure_id: procedure.id), method: :put, data: { confirm: "Voulez-vous vraiment clore la démarche ? \nLes dossiers en cours pourront être instruits, mais aucun nouveau dossier ne pourra plus être déposé.", disable_with: "Archivage..."} do
%span.icon.archive
.dropdown-description
%h4 Cloner
%h4 Clore
- if procedure.publiee?
%li
= link_to admin_procedure_archive_path(procedure_id: procedure.id), method: :put, data: { confirm: "Voulez-vous vraiment clore la démarche ? \nLes dossiers en cours pourront être instruits, mais aucun nouveau dossier ne pourra plus être déposé.", disable_with: "Archivage..."} do
%span.icon.archive
.dropdown-description
%h4 Clore
- if procedure.brouillon?
%li
= link_to admin_procedure_path(procedure), method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do
%span.icon.refuse
.dropdown-description
%h4 Supprimer
- if procedure.brouillon?
%li
= link_to admin_procedure_path(procedure), method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do
%span.icon.refuse
.dropdown-description
%h4 Supprimer
- else
- if procedure.close?
%li
= link_to admin_procedure_publication_path(procedure) do
%span.icon.unarchive

View file

@ -1,6 +1,6 @@
.sub-header
.procedure-admin-listing-container
= link_to "Nouvelle Démarche", new_admin_procedure_path, id: 'new-procedure', class: 'button primary'
= link_to "Nouvelle Démarche", new_from_existing_admin_procedures_path, id: 'new-procedure', class: 'button primary'
.container
%ul.tabs

View file

@ -1,7 +1,11 @@
.france-connect-login
.france-connect-login-separator
ou
%h2
= t('views.shared.france_connect_login.title')
%p
= t('views.shared.france_connect_login.description')
.france-connect-login-buttons
= link_to "Sidentifier avec FranceConnect", url, class: "france-connect-login-button"
= link_to t('views.shared.france_connect_login.login_button'), url, class: "france-connect-login-button"
.france-connect-help-link
= link_to "Quest-ce que FranceConnect ?", "https://franceconnect.gouv.fr/", target: "_blank", rel: "noopener", class: "link"
= link_to t('views.shared.france_connect_login.help_link'), "https://franceconnect.gouv.fr/", target: "_blank", rel: "noopener", class: "link"
.france-connect-login-separator
= t('views.shared.france_connect_login.separator')

View file

@ -5,6 +5,8 @@
= form_for resource, url: user_registration_path, html: { class: "form" } do |f|
%h1 Créez-vous un compte #{APPLICATION_NAME}
= render partial: 'shared/france_connect_login', locals: { url: france_connect_particulier_path }
= f.label :email, "Email (nom@site.com)", id: :user_email_label
= f.text_field :email, type: :email, autocomplete: 'email', autofocus: true, placeholder: "Votre adresse email", 'aria-describedby': :user_email_label
@ -24,4 +26,4 @@
= f.submit "Créer un compte", class: "button large primary expand"
= render partial: 'shared/france_connect_login', locals: { url: france_connect_particulier_path }

View file

@ -5,6 +5,8 @@
= form_for User.new, url: user_session_path, html: { class: "form" } do |f|
%h1.huge-title Connectez-vous
= render partial: 'shared/france_connect_login', locals: { url: france_connect_particulier_path }
= f.label :email, "Email (nom@site.com)"
= f.text_field :email, type: :email, autocomplete: 'username', autofocus: true
@ -21,8 +23,6 @@
= f.submit "Se connecter", class: "button large primary expand"
= render partial: 'shared/france_connect_login', locals: { url: france_connect_particulier_path }
%hr
%p.center
%span Vous êtes nouveau sur demarches&#8209;simplifiees.fr ?

View file

@ -9,3 +9,9 @@ fr:
demande_revoquee_le: "Demande d'avis révoquée le %{date}"
reponse_donnee_le: "Réponse donnée le %{date}"
en_attente: "En attente de réponse"
france_connect_login:
title: 'Avec FranceConnect'
description: "France connect est la solution proposée par lÉtat pour sécuriser et simplifier la connexion aux services en ligne."
login_button: "Sidentifier avec FranceConnect"
help_link: "Quest-ce que FranceConnect ?"
separator: 'ou'

View file

@ -0,0 +1,8 @@
class AddDossiersLatestUpdatesToDossiers < ActiveRecord::Migration[6.0]
def change
add_column :dossiers, :last_champ_updated_at, :datetime
add_column :dossiers, :last_champ_private_updated_at, :datetime
add_column :dossiers, :last_avis_updated_at, :datetime
add_column :dossiers, :last_commentaire_updated_at, :datetime
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_07_15_143010) do
ActiveRecord::Schema.define(version: 2020_07_22_135121) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -258,6 +258,10 @@ ActiveRecord::Schema.define(version: 2020_07_15_143010) do
t.interval "en_construction_conservation_extension", default: "PT0S"
t.datetime "termine_close_to_expiration_notice_sent_at"
t.bigint "revision_id"
t.datetime "last_champ_updated_at"
t.datetime "last_champ_private_updated_at"
t.datetime "last_avis_updated_at"
t.datetime "last_commentaire_updated_at"
t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin
t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
t.index ["archived"], name: "index_dossiers_on_archived"

View file

@ -2,6 +2,7 @@ describe Instructeurs::AvisController, type: :controller do
context 'with a instructeur signed in' do
render_views
let(:now) { Time.zone.parse('01/02/2345') }
let(:claimant) { create(:instructeur) }
let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, :published, instructeurs: [claimant]) }
@ -79,21 +80,24 @@ describe Instructeurs::AvisController, type: :controller do
end
describe '#update' do
describe 'without attachment' do
context 'without attachment' do
before do
Timecop.freeze(now)
patch :update, params: { id: avis_without_answer.id, procedure_id: procedure.id, avis: { answer: 'answer' } }
avis_without_answer.reload
end
after { Timecop.return }
it 'should be ok' do
expect(response).to redirect_to(instruction_instructeur_avis_path(avis_without_answer.procedure, avis_without_answer))
expect(avis_without_answer.answer).to eq('answer')
expect(avis_without_answer.piece_justificative_file).to_not be_attached
expect(dossier.reload.last_avis_updated_at).to eq(now)
expect(flash.notice).to eq('Votre réponse est enregistrée.')
end
end
describe 'with attachment' do
context 'with attachment' do
include ActiveJob::TestHelper
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
@ -117,18 +121,23 @@ describe Instructeurs::AvisController, type: :controller do
describe '#create_commentaire' do
let(:file) { nil }
let(:scan_result) { true }
let(:now) { Time.zone.parse("14/07/1789") }
subject { post :create_commentaire, params: { id: avis_without_answer.id, procedure_id: procedure.id, commentaire: { body: 'commentaire body', piece_jointe: file } } }
before do
allow(ClamavService).to receive(:safe_file?).and_return(scan_result)
Timecop.freeze(now)
end
after { Timecop.return }
it do
subject
expect(response).to redirect_to(messagerie_instructeur_avis_path(avis_without_answer.procedure, avis_without_answer))
expect(dossier.commentaires.map(&:body)).to match(['commentaire body'])
expect(dossier.reload.last_commentaire_updated_at).to eq(now)
end
context "with a file" do
@ -152,10 +161,12 @@ describe Instructeurs::AvisController, type: :controller do
let(:invite_linked_dossiers) { nil }
before do
Timecop.freeze(now)
@introduction_file = fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf')
post :create_avis, params: { id: previous_avis.id, procedure_id: procedure.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers, introduction_file: @introduction_file } }
created_avis.reload
end
after { Timecop.return }
context 'when an invalid email' do
let(:previous_avis_confidentiel) { false }
@ -165,6 +176,7 @@ describe Instructeurs::AvisController, type: :controller do
it { expect(response).to render_template :instruction }
it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) }
it { expect(Avis.last).to eq(previous_avis) }
it { expect(dossier.last_avis_updated_at).to eq(nil) }
end
context 'ask review with attachment' do
@ -174,6 +186,7 @@ describe Instructeurs::AvisController, type: :controller do
it { expect(created_avis.introduction_file).to be_attached }
it { expect(created_avis.introduction_file.filename).to eq("piece_justificative_0.pdf") }
it { expect(created_avis.dossier.reload.last_avis_updated_at).to eq(now) }
it { expect(flash.notice).to eq("Une demande d'avis a été envoyée à toto@totomail.com") }
end

View file

@ -390,6 +390,7 @@ describe Instructeurs::DossiersController, type: :controller do
let(:body) { "avant\napres" }
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
let(:scan_result) { true }
let(:now) { Timecop.freeze("09/11/1989") }
subject {
post :create_commentaire, params: {
@ -404,14 +405,18 @@ describe Instructeurs::DossiersController, type: :controller do
before do
allow(ClamavService).to receive(:safe_file?).and_return(scan_result)
Timecop.freeze(now)
end
after { Timecop.return }
it "creates a commentaire" do
expect { subject }.to change(Commentaire, :count).by(1)
expect(instructeur.followed_dossiers).to include(dossier)
expect(response).to redirect_to(messagerie_instructeur_dossier_path(dossier.procedure, dossier))
expect(flash.notice).to be_present
expect(dossier.reload.last_commentaire_updated_at).to eq(now)
end
context "when the commentaire created with virus file" do
@ -459,6 +464,7 @@ describe Instructeurs::DossiersController, type: :controller do
it { expect(response).to render_template :avis }
it { expect(flash.alert).to eq(["emaila.com : Email n'est pas valide"]) }
it { expect { subject }.not_to change(Avis, :count) }
it { expect(dossier.last_avis_updated_at).to eq(nil) }
end
context 'with multiple emails' do
@ -578,9 +584,26 @@ describe Instructeurs::DossiersController, type: :controller do
create(:dossier, :en_construction, procedure: procedure, champs_private: [champ_multiple_drop_down_list, champ_linked_drop_down_list, champ_datetime, champ_repetition])
end
let(:now) { Time.zone.parse('01/01/2100') }
before do
patch :update_annotations, params: {
procedure_id: procedure.id,
Timecop.freeze(now)
patch :update_annotations, params: params
champ_multiple_drop_down_list.reload
champ_linked_drop_down_list.reload
champ_datetime.reload
champ_repetition.reload
end
after do
Timecop.return
end
context "with new values for champs_private" do
let(:params) do
{
procedure_id: procedure.id,
dossier_id: dossier.id,
dossier: {
champs_private_attributes: {
@ -610,20 +633,36 @@ describe Instructeurs::DossiersController, type: :controller do
}
}
}
}
champ_multiple_drop_down_list.reload
champ_linked_drop_down_list.reload
champ_datetime.reload
champ_repetition.reload
}
end
it { expect(champ_multiple_drop_down_list.value).to eq('["un", "deux"]') }
it { expect(champ_linked_drop_down_list.primary_value).to eq('primary') }
it { expect(champ_linked_drop_down_list.secondary_value).to eq('secondary') }
it { expect(champ_datetime.value).to eq('21/12/2019 13:17') }
it { expect(champ_repetition.champs.first.value).to eq('text') }
it { expect(dossier.reload.last_champ_private_updated_at).to eq(now) }
it { expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier)) }
end
it { expect(champ_multiple_drop_down_list.value).to eq('["un", "deux"]') }
it { expect(champ_linked_drop_down_list.primary_value).to eq('primary') }
it { expect(champ_linked_drop_down_list.secondary_value).to eq('secondary') }
it { expect(champ_datetime.value).to eq('21/12/2019 13:17') }
it { expect(champ_repetition.champs.first.value).to eq('text') }
it { expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier)) }
context "without new values for champs_private" do
let(:params) do
{
procedure_id: procedure.id,
dossier_id: dossier.id,
dossier: {
champs_private_attributes: {},
champs_attributes: {
'0': {
id: champ_multiple_drop_down_list.id,
value: ['', 'un', 'deux']
}
}
}
}
end
it { expect(dossier.reload.last_champ_private_updated_at).to eq(nil) }
it { expect(response).to redirect_to(annotations_privees_instructeur_dossier_path(dossier.procedure, dossier)) }
end
end
describe "#telecharger_pjs" do

View file

@ -378,6 +378,22 @@ describe Users::DossiersController, type: :controller do
expect(dossier.reload.state).to eq(Dossier.states.fetch(:en_construction))
end
context 'without new values for champs' do
let(:submit_payload) do
{
id: dossier.id,
dossier: {
champs_attributes: {}
}
}
end
it "doesn't set last_champ_updated_at" do
subject
expect(dossier.reload.last_champ_updated_at).to eq(nil)
end
end
context 'with instructeurs ok to be notified instantly' do
let!(:instructeur_with_instant_email_dossier) { create(:instructeur) }
let!(:instructeur_without_instant_email_dossier) { create(:instructeur) }
@ -576,6 +592,7 @@ describe Users::DossiersController, type: :controller do
it 'updates the champs' do
subject
expect(first_champ.reload.value).to eq('beautiful value')
expect(first_champ.dossier.reload.last_champ_updated_at).to eq(now)
expect(piece_justificative_champ.reload.piece_justificative_file).to be_attached
end
@ -802,6 +819,7 @@ describe Users::DossiersController, type: :controller do
let(:body) { "avant\napres" }
let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
let(:scan_result) { true }
let(:now) { Time.zone.parse("18/09/1981") }
subject {
post :create_commentaire, params: {
@ -814,6 +832,7 @@ describe Users::DossiersController, type: :controller do
}
before do
Timecop.freeze(now)
sign_in(user)
allow(ClamavService).to receive(:safe_file?).and_return(scan_result)
allow(DossierMailer).to receive(:notify_new_commentaire_to_instructeur).and_return(double(deliver_later: nil))
@ -823,6 +842,8 @@ describe Users::DossiersController, type: :controller do
create(:assign_to, instructeur: instructeur_without_instant_message, procedure: procedure, instant_email_message_notifications_enabled: false, groupe_instructeur: procedure.defaut_groupe_instructeur)
end
after { Timecop.return }
it "creates a commentaire" do
expect { subject }.to change(Commentaire, :count).by(1)
@ -830,6 +851,7 @@ describe Users::DossiersController, type: :controller do
expect(DossierMailer).to have_received(:notify_new_commentaire_to_instructeur).with(dossier, instructeur_with_instant_message.email)
expect(DossierMailer).not_to have_received(:notify_new_commentaire_to_instructeur).with(dossier, instructeur_without_instant_message.email)
expect(flash.notice).to be_present
expect(dossier.reload.last_commentaire_updated_at).to eq(now)
end
end

View file

@ -33,7 +33,8 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
expect(page).to have_selector('#new-procedure')
find('#new-procedure').click
expect(page).to have_current_path(new_admin_procedure_path)
expect(page).to have_current_path(new_from_existing_admin_procedures_path)
click_on 'Créer une nouvelle démarche de zéro'
expect(find('#procedure_for_individual_true')).to be_checked
expect(find('#procedure_for_individual_false')).not_to be_checked
fill_in 'procedure_duree_conservation_dossiers_dans_ds', with: '3'
@ -54,7 +55,8 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
expect(page).to have_selector('#new-procedure')
find('#new-procedure').click
expect(page).to have_current_path(new_admin_procedure_path)
expect(page).to have_current_path(new_from_existing_admin_procedures_path)
click_on 'Créer une nouvelle démarche de zéro'
fill_in_dummy_procedure_details
click_on 'Créer la démarche'