demarches-normaliennes/app/controllers/instructeurs/dossiers_controller.rb

536 lines
19 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
module Instructeurs
class DossiersController < ProceduresController
2017-12-01 12:57:01 +01:00
include ActionView::Helpers::NumberHelper
include ActionView::Helpers::TextHelper
include CreateAvisConcern
include DossierHelper
2023-03-01 18:30:10 +01:00
include TurboChampsConcern
include ActionController::Streaming
include Zipline
before_action :redirect_on_dossier_not_found, only: :show
before_action :redirect_on_dossier_in_batch_operation, only: [:archive, :unarchive, :follow, :unfollow, :passer_en_instruction, :repasser_en_construction, :repasser_en_instruction, :terminer, :restore, :destroy, :extend_conservation]
before_action :set_gallery_attachments, only: [:show, :pieces_jointes, :annotations_privees, :avis, :messagerie, :personnes_impliquees, :reaffectation]
after_action :mark_demande_as_read, only: :show
after_action :mark_messagerie_as_read, only: [:messagerie, :create_commentaire, :pending_correction]
after_action :mark_avis_as_read, only: [:avis, :create_avis]
after_action :mark_annotations_privees_as_read, only: [:annotations_privees, :update_annotations]
after_action :mark_pieces_jointes_as_read, only: [:pieces_jointes]
def extend_conservation
2024-07-23 15:06:23 +02:00
dossier.extend_conservation(1.month)
flash[:notice] = t('views.instructeurs.dossiers.archived_dossier')
redirect_back(fallback_location: instructeur_dossier_path(@dossier.procedure, @dossier))
end
2024-07-23 15:06:23 +02:00
def extend_conservation_and_restore
dossier.extend_conservation_and_restore(1.month, current_instructeur)
flash[:notice] = t('views.instructeurs.dossiers.archived_dossier')
redirect_back(fallback_location: instructeur_dossier_path(@dossier.procedure, @dossier))
2024-07-23 15:06:23 +02:00
end
def geo_data
send_data dossier.to_feature_collection.to_json,
type: 'application/json',
filename: "dossier-#{dossier.id}-features.json"
end
def apercu_attestation
send_data dossier.attestation_template.send(:build_pdf, dossier),
filename: 'attestation.pdf',
type: 'application/pdf',
disposition: 'inline'
end
2020-04-30 11:25:22 +02:00
def bilans_bdf
2020-06-04 12:32:09 +02:00
extension = params[:format]
render extension.to_sym => dossier.etablissement.entreprise_bilans_bdf_to_sheet(extension)
2020-04-30 11:25:22 +02:00
end
def show
@demande_seen_at = current_instructeur.follows.find_by(dossier: dossier_with_champs)&.demande_seen_at
@is_dossier_in_batch_operation = dossier.batch_operation.present?
2019-12-15 22:09:23 +01:00
respond_to do |format|
format.pdf do
2024-03-08 17:14:21 +01:00
@acls = PiecesJustificativesService.new(user_profile: current_instructeur, export_template: nil).acl_for_dossier_export(dossier.procedure)
render(template: 'dossiers/show', formats: [:pdf])
2019-12-15 22:09:23 +01:00
end
format.all
end
end
def dossier_labels
2024-10-29 14:52:14 +01:00
labels = params[:label_id]&.map(&:to_i) || []
@dossier = dossier
2024-10-29 14:52:14 +01:00
labels.each { |params_label| DossierLabel.find_or_create_by(dossier_id: @dossier.id, label_id: params_label) }
2024-10-29 14:52:14 +01:00
all_labels = DossierLabel.where(dossier_id: @dossier.id).pluck(:label_id)
2024-10-29 14:52:14 +01:00
(all_labels - labels).each { DossierLabel.find_by(dossier_id: @dossier.id, label_id: _1).destroy }
render :change_state
end
2017-07-19 11:08:53 +02:00
def messagerie
@commentaire = Commentaire.new
@messagerie_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.messagerie_seen_at
2017-07-19 11:08:53 +02:00
end
def annotations_privees
@annotations_privees_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.annotations_privees_seen_at
end
2017-09-26 14:38:16 +02:00
def avis
@avis_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.avis_seen_at
@avis = Avis.new
2024-04-16 11:12:32 +02:00
@experts_emails = Expert.autocomplete_mails(@dossier.procedure)
2017-09-26 14:38:16 +02:00
end
def avis_new
@avis_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.avis_seen_at
@avis = Avis.new
2024-04-16 11:12:32 +02:00
@experts_emails = Expert.autocomplete_mails(@dossier.procedure)
end
def personnes_impliquees
# sort following_instructeurs (last follower on top) for the API of Agence de l'Eau Loire-Bretagne
@following_instructeurs_emails = dossier.followers_instructeurs.joins(:follows).merge(Follow.order(id: :desc)).map(&:email)
previous_followers = dossier.previous_followers_instructeurs - dossier.followers_instructeurs
@previous_following_instructeurs_emails = previous_followers.map(&:email)
2021-02-25 09:53:09 +01:00
@avis_emails = dossier.experts.map(&:email)
@invites_emails = dossier.invites.map(&:email)
@potential_recipients = dossier.groupe_instructeur.instructeurs.reject { |g| g == current_instructeur }
@manual_assignments = dossier.dossier_assignments.manual.includes(:groupe_instructeur, :previous_groupe_instructeur)
end
def send_to_instructeurs
2024-05-15 23:16:55 +02:00
recipients = params['recipients'].presence || []
# instructeurs are scoped by groupe_instructeur to avoid enumeration
2024-05-15 23:16:55 +02:00
recipients = dossier.groupe_instructeur.instructeurs.where(id: recipients)
if recipients.present?
recipients.each do |recipient|
recipient.follow(dossier)
InstructeurMailer.send_dossier(current_instructeur, dossier, recipient).deliver_later
end
flash.notice = "Dossier envoyé"
else
flash.alert = "Instructeur inconnu ou non présent sur la procédure"
end
redirect_to(personnes_impliquees_instructeur_dossier_path(procedure, dossier))
end
def follow
current_instructeur.follow(dossier)
flash.notice = 'Dossier suivi'
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
def unfollow
current_instructeur.unfollow(dossier)
flash.notice = "Vous ne suivez plus le dossier nº #{dossier.id}"
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
def archive
dossier.archiver!(current_instructeur)
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
def unarchive
dossier.desarchiver!
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
def passer_en_instruction
begin
dossier.passer_en_instruction!(instructeur: current_instructeur)
flash.notice = 'Dossier passé en instruction.'
rescue AASM::InvalidTransition => e
flash.alert = aasm_error_message(e, target_state: :en_instruction)
end
@dossier = dossier
respond_to do |format|
format.turbo_stream do
render :change_state
end
format.html do
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
end
end
def repasser_en_construction
begin
dossier.repasser_en_construction!(instructeur: current_instructeur)
flash.notice = 'Dossier repassé en construction.'
rescue AASM::InvalidTransition => e
flash.alert = aasm_error_message(e, target_state: :en_construction)
end
@dossier = dossier
respond_to do |format|
format.turbo_stream do
render :change_state
end
format.html do
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
end
end
2019-07-01 16:16:40 +02:00
def repasser_en_instruction
begin
flash.notice = "Le dossier #{dossier.id} a été repassé en instruction."
dossier.repasser_en_instruction!(instructeur: current_instructeur)
rescue AASM::InvalidTransition => e
flash.alert = aasm_error_message(e, target_state: :en_instruction)
2019-07-11 11:52:24 +02:00
end
2019-07-01 16:16:40 +02:00
@dossier = dossier
respond_to do |format|
format.turbo_stream do
render :change_state
end
format.html do
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
end
2019-07-01 16:16:40 +02:00
end
2017-12-01 12:57:01 +01:00
def terminer
motivation = params[:dossier] && params[:dossier][:motivation]
justificatif = params[:dossier] && params[:dossier][:justificatif_motivation]
2017-12-01 12:57:01 +01:00
h = { instructeur: current_instructeur, motivation: motivation, justificatif: justificatif }
begin
case params[:process_action]
when "refuser"
target_state = :refuse
dossier.refuser!(h)
flash.notice = "Dossier considéré comme refusé."
when "classer_sans_suite"
target_state = :sans_suite
dossier.classer_sans_suite!(h)
flash.notice = "Dossier considéré comme sans suite."
when "accepter"
target_state = :accepte
dossier.accepter!(h)
flash.notice = "Dossier traité avec succès."
end
rescue AASM::InvalidTransition => e
flash.alert = aasm_error_message(e, target_state: target_state)
2017-12-01 12:57:01 +01:00
end
@dossier = dossier
render :change_state
2017-12-01 12:57:01 +01:00
end
def pending_correction
message, piece_jointe = params.require(:dossier).permit(:motivation, :justificatif_motivation).values
if message.empty?
flash.alert = "Vous devez préciser quelle correction est attendue."
elsif !dossier.may_flag_as_pending_correction?
flash.alert = dossier.termine? ? "Impossible de demander de corriger un dossier terminé." : "Le dossier est déjà en attente de correction."
else
commentaire = CommentaireService.build(current_instructeur, dossier, { body: message, piece_jointe: })
if commentaire.valid?
dossier.flag_as_pending_correction!(commentaire, params[:reason].presence)
dossier.update!(last_commentaire_updated_at: Time.zone.now)
current_instructeur.follow(dossier)
flash.notice = "Dossier marqué comme en attente de correction."
else
flash.alert = commentaire.errors.full_messages.map { "Commentaire : #{_1}" }
end
end
respond_to do |format|
format.turbo_stream do
@dossier = dossier
render :change_state
end
format.html do
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
end
end
def create_commentaire
2021-11-30 18:56:12 +01:00
@commentaire = CommentaireService.create(current_instructeur, dossier, commentaire_params)
2021-11-30 18:56:12 +01:00
if @commentaire.errors.empty?
@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)
else
2024-02-13 08:09:58 +01:00
@commentaire.piece_jointe.purge.reload
flash.alert = @commentaire.errors.full_messages
render :messagerie
end
end
2017-08-28 14:16:13 +02:00
def create_avis
2021-02-25 09:53:09 +01:00
@avis = create_avis_from_params(dossier, current_instructeur)
if @avis.nil?
redirect_to avis_instructeur_dossier_path(procedure, dossier)
else
@avis_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.avis_seen_at
render :avis
end
2017-08-28 14:16:13 +02:00
end
2017-08-02 15:33:23 +02:00
def update_annotations
2024-05-13 16:53:33 +02:00
dossier_with_champs.update_champs_attributes(champs_private_attributes_params, :private, updated_by: current_user.email)
if dossier.champs.any?(&:changed_for_autosave?)
dossier.last_champ_private_updated_at = Time.zone.now
end
dossier.save(context: :champs_private_value)
dossier.index_search_terms_later
respond_to do |format|
2023-03-01 18:30:10 +01:00
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_attributes_params, dossier.champs.filter(&:private?))
2023-03-01 18:30:10 +01:00
end
end
2017-08-02 15:33:23 +02:00
end
def print
@dossier = dossier
render layout: "print"
end
2023-05-30 11:39:53 +02:00
def annotation
@dossier = dossier_with_champs(pj_template: false)
type_de_champ = @dossier.find_type_de_champ_by_stable_id(params[:stable_id], :private)
annotation = @dossier.project_champ(type_de_champ, params[:row_id])
2023-05-30 11:39:53 +02:00
respond_to do |format|
format.turbo_stream do
@to_show, @to_hide = []
@to_update = [annotation]
render :update_annotations
end
end
end
def telecharger_pjs
files = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers: Dossier.where(id: dossier.id), user_profile: current_instructeur)
cleaned_files = ActiveStorage::DownloadableFile.cleanup_list_from_dossier(files)
zipline(cleaned_files, "dossier-#{dossier.id}.zip")
end
def destroy
if dossier.termine?
dossier.hide_and_keep_track!(current_instructeur, :instructeur_request)
flash.notice = t('instructeurs.dossiers.deleted_by_instructeur')
else
flash.alert = t('instructeurs.dossiers.impossible_deletion')
end
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
def restore
dossier = current_instructeur.dossiers.find(params[:dossier_id])
dossier.restore(current_instructeur)
flash.notice = t('instructeurs.dossiers.restore')
if dossier.termine?
redirect_to instructeur_procedure_path(procedure, statut: :traites)
else
redirect_back(fallback_location: instructeur_procedure_path(procedure))
end
end
def reaffectation
@dossier = current_instructeur.dossiers.find(params[:dossier_id])
@groupe_instructeur = @dossier.groupe_instructeur
@groupes_instructeurs = Kaminari.paginate_array(@groupe_instructeur.other_groupe_instructeurs)
.page(params[:page])
.per(ITEMS_PER_PAGE)
end
def reaffecter
dossier = current_instructeur.dossiers.find(params[:dossier_id])
new_group = dossier
.procedure
.groupe_instructeurs.find(params[:groupe_instructeur_id])
dossier.assign_to_groupe_instructeur(new_group, DossierAssignment.modes.fetch(:manual), current_instructeur)
flash.notice = t('instructeurs.dossiers.reaffectation', dossier_id: dossier.id, label: new_group.label)
redirect_to instructeur_procedure_path(procedure)
end
def pieces_jointes
@dossier = dossier
@pieces_jointes_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.pieces_jointes_seen_at
end
private
def dossier_scope
if action_name == 'update_annotations'
Dossier
.where(id: current_instructeur.dossiers.visible_by_administration)
.or(Dossier.where(id: current_user.dossiers.for_procedure_preview))
elsif action_name == 'extend_conservation_and_restore'
2024-06-11 15:54:54 +02:00
Dossier
.where(id: current_instructeur.dossiers.visible_by_administration)
2024-06-12 16:32:42 +02:00
.or(Dossier.where(id: current_instructeur.dossiers.hidden_by_expired))
else
current_instructeur.dossiers.visible_by_administration
end
end
def dossier
@dossier ||= DossierPreloader.load_one(dossier_scope.find(params[:dossier_id])).tap do
set_sentry_dossier(_1)
end
end
def dossier_with_champs
@dossier ||= DossierPreloader.load_one(dossier_scope.find(params[:dossier_id]))
end
def commentaire_params
2024-02-13 08:09:58 +01:00
params.require(:commentaire).permit(:body, piece_jointe: [])
end
2017-08-28 14:16:13 +02:00
2017-08-02 15:33:23 +02:00
def champs_private_params
champ_attributes = [
:id,
:value,
:value_other,
:external_id,
2024-05-06 21:44:41 +02:00
:code,
:primary_value,
:secondary_value,
:numero_allocataire,
:code_postal,
:identifiant,
:numero_fiscal,
:reference_avis,
:ine,
:piece_justificative_file,
:code_departement,
:accreditation_number,
:accreditation_birthdate,
:feature,
value: []
]
# Strong attributes do not support records (indexed hash); they only support hashes with
# static keys. We create a static hash based on the available keys.
public_ids = params.dig(:dossier, :champs_private_attributes)&.keys || []
champs_private_attributes = public_ids.map { [_1, champ_attributes] }.to_h
params.require(:dossier).permit(champs_private_attributes:)
end
def champs_private_attributes_params
champs_private_params.fetch(:champs_private_attributes)
2017-08-02 15:33:23 +02:00
end
2017-12-01 12:57:01 +01:00
def mark_demande_as_read
current_instructeur.mark_tab_as_seen(dossier, :demande)
end
def mark_messagerie_as_read
current_instructeur.mark_tab_as_seen(dossier, :messagerie)
end
def mark_avis_as_read
current_instructeur.mark_tab_as_seen(dossier, :avis)
end
def mark_annotations_privees_as_read
current_instructeur.mark_tab_as_seen(dossier, :annotations_privees)
end
def mark_pieces_jointes_as_read
current_instructeur.mark_tab_as_seen(dossier, :pieces_jointes)
end
def aasm_error_message(exception, target_state:)
if exception.originating_state == target_state
"Le dossier est déjà #{dossier_display_state(target_state, lower: true)}."
elsif exception.failures.include?(:can_terminer?) && dossier.any_etablissement_as_degraded_mode?
"Les données relatives au SIRET de ce dossier nont pas pu encore être vérifiées : il nest pas possible de le passer #{dossier_display_state(target_state, lower: true)}."
else
"Le dossier est en ce moment #{dossier_display_state(exception.originating_state, lower: true)} : il nest pas possible de le passer #{dossier_display_state(target_state, lower: true)}."
end
end
def redirect_on_dossier_not_found
if !dossier_scope.exists?(id: params[:dossier_id])
redirect_to instructeur_procedure_path(procedure)
end
end
def redirect_on_dossier_in_batch_operation
dossier_in_batch = begin
dossier
rescue ActiveRecord::RecordNotFound
current_instructeur.dossiers.find(params[:dossier_id])
end
if dossier_in_batch.batch_operation.present?
flash.alert = "Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse."
redirect_back(fallback_location: instructeur_dossier_path(procedure, dossier_in_batch))
end
end
def set_gallery_attachments
gallery_attachments_ids = Rails.cache.fetch([dossier, "gallery_attachments"], expires_in: 10.minutes) do
champs_attachments_ids = dossier
.champs
.where(type: [Champs::PieceJustificativeChamp.name, Champs::TitreIdentiteChamp.name])
.flat_map(&:piece_justificative_file)
.map(&:id)
commentaires_attachments_ids = dossier
.commentaires
.includes(piece_jointe_attachments: :blob)
.map(&:piece_jointe)
.flat_map(&:attachments)
.map(&:id)
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
@gallery_attachments = ActiveStorage::Attachment.where(id: gallery_attachments_ids)
end
end
end