Merge pull request #10918 from demarches-simplifiees/gallery_v2_add_origins

ETQ instructeur je vois dans la galerie les pjs issues des avis, annotations privées et justificatifs de décision
This commit is contained in:
Eric Leroy-Terquem 2024-11-05 16:01:32 +00:00 committed by GitHub
commit 3f43e3433a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 221 additions and 22 deletions

View file

@ -17,17 +17,35 @@ class Attachment::GalleryItemComponent < ApplicationComponent
def gallery_demande? = @gallery_demande def gallery_demande? = @gallery_demande
def libelle def libelle
from_dossier? ? attachment.record.libelle : 'Pièce jointe au message' if from_champ?
attachment.record.libelle
elsif from_messagerie?
'Pièce jointe au message'
elsif from_avis_externe?
'Pièce jointe à lavis'
elsif from_justificatif_motivation?
'Pièce jointe à la décision'
end
end end
def origin def origin
case case
when from_dossier? when from_public_champ?
'Dossier usager' 'Dossier usager'
when from_private_champ?
'Annotation privée'
when from_messagerie_expert?
'Messagerie (expert)'
when from_messagerie_instructeur? when from_messagerie_instructeur?
'Messagerie (instructeur)' 'Messagerie (instructeur)'
when from_messagerie_usager? when from_messagerie_usager?
'Messagerie (usager)' 'Messagerie (usager)'
when from_avis_externe_instructeur?
'Avis externe (instructeur)'
when from_avis_externe_expert?
'Avis externe (expert)'
when from_justificatif_motivation?
'Justificatif de décision'
end end
end end
@ -52,7 +70,7 @@ class Attachment::GalleryItemComponent < ApplicationComponent
end end
def updated? def updated?
from_dossier? && updated_at > attachment.record.dossier.depose_at from_public_champ? && updated_at > attachment.record.dossier.depose_at
end end
def updated_at def updated_at
@ -68,10 +86,18 @@ class Attachment::GalleryItemComponent < ApplicationComponent
private private
def from_dossier? def from_champ?
attachment.record.class.in?([Champs::PieceJustificativeChamp, Champs::TitreIdentiteChamp]) attachment.record.class.in?([Champs::PieceJustificativeChamp, Champs::TitreIdentiteChamp])
end end
def from_public_champ?
from_champ? && !attachment.record.private?
end
def from_private_champ?
from_champ? && attachment.record.private?
end
def from_messagerie? def from_messagerie?
attachment.record.is_a?(Commentaire) attachment.record.is_a?(Commentaire)
end end
@ -80,7 +106,27 @@ class Attachment::GalleryItemComponent < ApplicationComponent
from_messagerie? && attachment.record.instructeur.present? from_messagerie? && attachment.record.instructeur.present?
end end
def from_messagerie_expert?
from_messagerie? && attachment.record.expert.present?
end
def from_messagerie_usager? def from_messagerie_usager?
from_messagerie? && attachment.record.instructeur.nil? from_messagerie? && attachment.record.instructeur.nil? && attachment.record.expert.nil?
end
def from_avis_externe?
attachment.record.is_a?(Avis)
end
def from_avis_externe_instructeur?
from_avis_externe? && attachment.name == 'introduction_file'
end
def from_avis_externe_expert?
from_avis_externe? && attachment.name == 'piece_justificative_file'
end
def from_justificatif_motivation?
attachment.name == 'justificatif_motivation'
end end
end end

View file

@ -9,7 +9,7 @@
Visualiser Visualiser
- if !gallery_demande? - if !gallery_demande?
.fr-text--sm.fr-mt-2v.fr-mb-1v .fr-text--sm.fr-mt-2v.fr-mb-1v
= libelle.truncate(25) = libelle.truncate(30)
= render Attachment::ShowComponent.new(attachment:, truncate: true, new_tab: gallery_demande?) = render Attachment::ShowComponent.new(attachment:, truncate: true, new_tab: gallery_demande?)
- if !gallery_demande? - if !gallery_demande?
.fr-mt-2v.fr-mb-2v{ class: badge_updated_class } .fr-mt-2v.fr-mb-2v{ class: badge_updated_class }
@ -19,7 +19,7 @@
= image_tag('apercu-indisponible.png') = image_tag('apercu-indisponible.png')
- if !gallery_demande? - if !gallery_demande?
.fr-text--sm.fr-mt-2v.fr-mb-1v .fr-text--sm.fr-mt-2v.fr-mb-1v
= libelle.truncate(25) = libelle.truncate(30)
= render Attachment::ShowComponent.new(attachment:, truncate: true, new_tab: gallery_demande?) = render Attachment::ShowComponent.new(attachment:, truncate: true, new_tab: gallery_demande?)
- if !gallery_demande? - if !gallery_demande?
.fr-mt-2v.fr-mb-2v{ class: badge_updated_class } .fr-mt-2v.fr-mb-2v{ class: badge_updated_class }

View file

@ -104,7 +104,12 @@ module Experts
updated_recently = @avis.updated_recently? updated_recently = @avis.updated_recently?
if @avis.update(avis_params) if @avis.update(avis_params)
flash.notice = 'Votre réponse est enregistrée.' flash.notice = 'Votre réponse est enregistrée.'
@avis.dossier.update!(last_avis_updated_at: Time.zone.now)
timestamps = [:last_avis_updated_at, :updated_at]
timestamps << :last_avis_piece_jointe_updated_at if @avis.piece_justificative_file.attached?
@avis.dossier.touch(*timestamps)
if !updated_recently if !updated_recently
@avis.dossier.followers_instructeurs @avis.dossier.followers_instructeurs
.with_instant_expert_avis_email_notifications_enabled .with_instant_expert_avis_email_notifications_enabled
@ -162,7 +167,11 @@ module Experts
@commentaire = CommentaireService.create(current_expert, avis.dossier, commentaire_params) @commentaire = CommentaireService.create(current_expert, avis.dossier, commentaire_params)
if @commentaire.errors.empty? if @commentaire.errors.empty?
@commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now) timestamps = [:last_commentaire_updated_at, :updated_at]
timestamps << :last_commentaire_piece_jointe_updated_at if @commentaire.piece_jointe.attached?
@commentaire.dossier.touch(*timestamps)
flash.notice = "Message envoyé" flash.notice = "Message envoyé"
redirect_to messagerie_expert_avis_path(avis.procedure, avis) redirect_to messagerie_expert_avis_path(avis.procedure, avis)
else else

View file

@ -513,11 +513,21 @@ module Instructeurs
.commentaires .commentaires
.includes(piece_jointe_attachments: :blob) .includes(piece_jointe_attachments: :blob)
.map(&:piece_jointe) .map(&:piece_jointe)
.map(&:attachments) .flat_map(&:attachments)
.flatten
.map(&:id) .map(&:id)
champs_attachments_ids + commentaires_attachments_ids avis_attachments_ids = dossier
.avis.flat_map { [_1.introduction_file, _1.piece_justificative_file] }
.flat_map(&:attachments)
.compact
.map(&:id)
justificatif_motivation_id = dossier
.justificatif_motivation
&.attachment
&.id
champs_attachments_ids + commentaires_attachments_ids + avis_attachments_ids + [justificatif_motivation_id]
end end
@gallery_attachments = ActiveStorage::Attachment.where(id: gallery_attachments_ids) @gallery_attachments = ActiveStorage::Attachment.where(id: gallery_attachments_ids)
end end

View file

@ -10,7 +10,7 @@ module BlobImageProcessorConcern
end end
def representation_required? def representation_required?
from_champ? || from_messagerie? || logo? || from_action_text? from_champ? || from_messagerie? || logo? || from_action_text? || from_avis? || from_justificatif_motivation?
end end
private private
@ -31,7 +31,15 @@ module BlobImageProcessorConcern
attachments.any? { _1.record.class == ActionText::RichText } attachments.any? { _1.record.class == ActionText::RichText }
end end
def from_avis?
attachments.any? { _1.record.class == Avis }
end
def watermark_required? def watermark_required?
attachments.any? { _1.record.class == Champs::TitreIdentiteChamp } attachments.any? { _1.record.class == Champs::TitreIdentiteChamp }
end end
def from_justificatif_motivation?
attachments.any? { _1.name == 'justificatif_motivation' }
end
end end

View file

@ -378,7 +378,8 @@ class Dossier < ApplicationRecord
' OR last_avis_updated_at > follows.avis_seen_at' \ ' OR last_avis_updated_at > follows.avis_seen_at' \
' OR last_commentaire_updated_at > follows.messagerie_seen_at' \ ' OR last_commentaire_updated_at > follows.messagerie_seen_at' \
' OR last_commentaire_piece_jointe_updated_at > follows.pieces_jointes_seen_at' \ ' OR last_commentaire_piece_jointe_updated_at > follows.pieces_jointes_seen_at' \
' OR last_champ_piece_jointe_updated_at > follows.pieces_jointes_seen_at') ' OR last_champ_piece_jointe_updated_at > follows.pieces_jointes_seen_at' \
' OR last_avis_piece_jointe_updated_at > follows.pieces_jointes_seen_at')
.distinct .distinct
end end

View file

@ -125,7 +125,7 @@ class Instructeur < ApplicationRecord
annotations_privees = dossier.last_champ_private_updated_at&.>(follow.annotations_privees_seen_at) || false annotations_privees = dossier.last_champ_private_updated_at&.>(follow.annotations_privees_seen_at) || false
avis_notif = dossier.last_avis_updated_at&.>(follow.avis_seen_at) || false avis_notif = dossier.last_avis_updated_at&.>(follow.avis_seen_at) || false
messagerie = dossier.last_commentaire_updated_at&.>(follow.messagerie_seen_at) || false messagerie = dossier.last_commentaire_updated_at&.>(follow.messagerie_seen_at) || false
pieces_jointes = dossier.last_champ_piece_jointe_updated_at&.>(follow.pieces_jointes_seen_at) || dossier.last_commentaire_piece_jointe_updated_at&.>(follow.pieces_jointes_seen_at) || false pieces_jointes = dossier.last_champ_piece_jointe_updated_at&.>(follow.pieces_jointes_seen_at) || dossier.last_commentaire_piece_jointe_updated_at&.>(follow.pieces_jointes_seen_at) || dossier.last_avis_piece_jointe_updated_at&.>(follow.pieces_jointes_seen_at) || false
annotations_hash(demande, annotations_privees, avis_notif, messagerie, pieces_jointes) annotations_hash(demande, annotations_privees, avis_notif, messagerie, pieces_jointes)
else else

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddLastAvisPieceJointeUpdatedAt < ActiveRecord::Migration[7.0]
def change
add_column :dossiers, :last_avis_piece_jointe_updated_at, :datetime
end
end

View file

@ -495,6 +495,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_10_14_084333) do
t.string "hidden_by_reason" t.string "hidden_by_reason"
t.datetime "hidden_by_user_at", precision: nil t.datetime "hidden_by_user_at", precision: nil
t.datetime "identity_updated_at", precision: nil t.datetime "identity_updated_at", precision: nil
t.datetime "last_avis_piece_jointe_updated_at"
t.datetime "last_avis_updated_at", precision: nil t.datetime "last_avis_updated_at", precision: nil
t.datetime "last_champ_piece_jointe_updated_at" t.datetime "last_champ_piece_jointe_updated_at"
t.datetime "last_champ_private_updated_at", precision: nil t.datetime "last_champ_private_updated_at", precision: nil

View file

@ -4,9 +4,10 @@ require 'rails_helper'
RSpec.describe Attachment::GalleryItemComponent, type: :component do RSpec.describe Attachment::GalleryItemComponent, type: :component do
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, :published, types_de_champ_public:) } let(:procedure) { create(:procedure, :published, types_de_champ_public:, types_de_champ_private:) }
let(:types_de_champ_public) { [{ type: :piece_justificative }] } let(:types_de_champ_public) { [{ type: :piece_justificative }] }
let(:dossier) { create(:dossier, :with_populated_champs, :en_construction, procedure:) } let(:types_de_champ_private) { [{ type: :piece_justificative }] }
let(:dossier) { create(:dossier, :with_populated_champs, :with_populated_annotations, :en_construction, procedure:) }
let(:filename) { attachment.blob.filename.to_s } let(:filename) { attachment.blob.filename.to_s }
let(:gallery_demande) { false } let(:gallery_demande) { false }
let(:seen_at) { nil } let(:seen_at) { nil }
@ -16,8 +17,10 @@ RSpec.describe Attachment::GalleryItemComponent, type: :component do
subject { render_inline(component).to_html } subject { render_inline(component).to_html }
context "when attachment is from a piece justificative champ" do context "when attachment is from a public piece justificative champ" do
let(:champ) { dossier.champs.first } let(:champ) do
dossier.champs.where(private: false).first
end
let(:libelle) { champ.libelle } let(:libelle) { champ.libelle }
let(:attachment) { champ.piece_justificative_file.attachments.first } let(:attachment) { champ.piece_justificative_file.attachments.first }
@ -56,6 +59,38 @@ RSpec.describe Attachment::GalleryItemComponent, type: :component do
end end
end end
context "when attachment is from a private piece justificative champ" do
let(:annotation) do
dossier.champs.where(private: true).first
end
let(:libelle) { annotation.libelle }
let(:attachment) { annotation.piece_justificative_file.attachments.first }
# Correspond au cas standard où le blob est créé avant le dépôt du dossier
before { dossier.touch(:depose_at) }
it "displays libelle, link, tag and renders title" do
expect(subject).to have_text(libelle)
expect(subject).to have_link(filename)
expect(subject).to have_text('Annotation privée')
expect(component.title).to eq("#{libelle} -- #{filename}")
end
it "displays when gallery item has been added" do
expect(subject).to have_text('Ajoutée le')
expect(subject).not_to have_css('.highlighted')
expect(subject).to have_text(component.helpers.try_format_datetime(attachment.record.created_at, format: :veryshort))
end
context "when gallery item is in page Demande" do
let(:gallery_demande) { true }
it "does not display libelle" do
expect(subject).not_to have_text(libelle)
end
end
end
context "when attachment is from a commentaire" do context "when attachment is from a commentaire" do
let(:commentaire) { create(:commentaire, :with_file, dossier: dossier) } let(:commentaire) { create(:commentaire, :with_file, dossier: dossier) }
let(:attachment) { commentaire.piece_jointe.first } let(:attachment) { commentaire.piece_jointe.first }
@ -100,5 +135,75 @@ RSpec.describe Attachment::GalleryItemComponent, type: :component do
expect(subject).to have_text('Messagerie (instructeur)') expect(subject).to have_text('Messagerie (instructeur)')
end end
end end
context 'from an expert' do
let(:expert) { create(:expert) }
before { commentaire.update!(expert:) }
it "displays the right tag" do
expect(subject).to have_text('Messagerie (expert)')
end
end
end
context "when attachment is from a justificatif motivation" do
let(:fake_justificatif) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') }
let(:attachment) { dossier.justificatif_motivation.attachment }
before { dossier.update!(justificatif_motivation: fake_justificatif) }
it "displays a generic libelle, link, tag and renders title" do
expect(subject).to have_text('Justificatif de décision')
expect(subject).to have_link(filename)
expect(subject).to have_text('Pièce jointe à la décision')
expect(component.title).to eq("Pièce jointe à la décision -- #{filename}")
end
end
context "when attachment is from an avis" do
context 'from an instructeur' do
let(:avis) { create(:avis, :with_introduction, dossier: dossier) }
let(:attachment) { avis.introduction_file.attachment }
it "displays a generic libelle, link, tag and renders title" do
expect(subject).to have_text('Pièce jointe à lavis')
expect(subject).to have_link(filename)
expect(subject).to have_text('Avis externe (instructeur)')
expect(component.title).to eq("Pièce jointe à lavis -- #{filename}")
end
context "when instructeur has not seen it yet" do
let(:seen_at) { now - 1.day }
before do
attachment.blob.update(created_at: now)
end
it 'displays datetime in the right style' do
expect(subject).to have_css('.highlighted')
end
end
context "when instructeur has already seen it" do
let!(:seen_at) { now }
before do
freeze_time
attachment.blob.touch(:created_at)
end
it 'displays datetime in the right style' do
expect(subject).not_to have_css('.highlighted')
end
end
end
context 'from an expert' do
let(:avis) { create(:avis, :with_piece_justificative, dossier: dossier) }
let(:attachment) { avis.piece_justificative_file.attachment }
it "displays the right tag" do
expect(subject).to have_text('Avis externe (expert)')
end
end
end end
end end

View file

@ -1486,6 +1486,9 @@ describe Instructeurs::DossiersController, type: :controller do
let(:logo_path) { 'spec/fixtures/files/logo_test_procedure.png' } let(:logo_path) { 'spec/fixtures/files/logo_test_procedure.png' }
let(:rib_path) { 'spec/fixtures/files/RIB.pdf' } let(:rib_path) { 'spec/fixtures/files/RIB.pdf' }
let(:commentaire) { create(:commentaire, dossier: dossier) } let(:commentaire) { create(:commentaire, dossier: dossier) }
let(:expert) { create(:expert) }
let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: procedure) }
let(:avis) { create(:avis, :with_answer, :with_piece_justificative, dossier: dossier, claimant: expert, experts_procedure: experts_procedure) }
before do before do
dossier.champs.first.piece_justificative_file.attach( dossier.champs.first.piece_justificative_file.attach(
@ -1502,20 +1505,29 @@ describe Instructeurs::DossiersController, type: :controller do
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE } metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
) )
avis.piece_justificative_file.attach(
io: File.open(rib_path),
filename: "RIB.pdf",
content_type: "application/pdf",
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
get :pieces_jointes, params: { get :pieces_jointes, params: {
procedure_id: procedure.id, procedure_id: procedure.id,
dossier_id: dossier.id dossier_id: dossier.id
} }
end end
it 'returns pieces jointes from champs and from messagerie' do it 'returns pieces jointes from champs, messagerie and avis' do
expect(response.body).to include('Télécharger le fichier toto.txt') expect(response.body).to include('Télécharger le fichier toto.txt')
expect(response.body).to include('Télécharger le fichier logo_test_procedure.png') expect(response.body).to include('Télécharger le fichier logo_test_procedure.png')
expect(response.body).to include('Télécharger le fichier RIB.pdf') expect(response.body).to include('Télécharger le fichier RIB.pdf')
expect(response.body).to include('Visualiser') expect(response.body).to include('Visualiser')
expect(assigns(:gallery_attachments).count).to eq 3 expect(response.body).to include('Pièce jointe au message')
expect(response.body).to include('Pièce jointe à lavis')
expect(assigns(:gallery_attachments).count).to eq 4
expect(assigns(:gallery_attachments)).to all(be_a(ActiveStorage::Attachment)) expect(assigns(:gallery_attachments)).to all(be_a(ActiveStorage::Attachment))
expect([Champs::PieceJustificativeChamp, Champs::TitreIdentiteChamp, Commentaire]).to include(*assigns(:gallery_attachments).map { _1.record.class }) expect([Champs::PieceJustificativeChamp, Champs::TitreIdentiteChamp, Commentaire, Avis]).to include(*assigns(:gallery_attachments).map { _1.record.class })
end end
end end