512 lines
18 KiB
Ruby
512 lines
18 KiB
Ruby
# frozen_string_literal: true
|
||
|
||
module Instructeurs
|
||
class DossiersController < ProceduresController
|
||
include ActionView::Helpers::NumberHelper
|
||
include ActionView::Helpers::TextHelper
|
||
include CreateAvisConcern
|
||
include DossierHelper
|
||
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
|
||
dossier.extend_conservation(1.month)
|
||
flash[:notice] = t('views.instructeurs.dossiers.archived_dossier')
|
||
redirect_back(fallback_location: instructeur_dossier_path(@dossier.procedure, @dossier))
|
||
end
|
||
|
||
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))
|
||
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
|
||
|
||
def bilans_bdf
|
||
extension = params[:format]
|
||
render extension.to_sym => dossier.etablissement.entreprise_bilans_bdf_to_sheet(extension)
|
||
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?
|
||
respond_to do |format|
|
||
format.pdf do
|
||
@acls = PiecesJustificativesService.new(user_profile: current_instructeur, export_template: nil).acl_for_dossier_export(dossier.procedure)
|
||
render(template: 'dossiers/show', formats: [:pdf])
|
||
end
|
||
format.all
|
||
end
|
||
end
|
||
|
||
def messagerie
|
||
@commentaire = Commentaire.new
|
||
@messagerie_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.messagerie_seen_at
|
||
end
|
||
|
||
def annotations_privees
|
||
@annotations_privees_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.annotations_privees_seen_at
|
||
end
|
||
|
||
def avis
|
||
@avis_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.avis_seen_at
|
||
@avis = Avis.new
|
||
@experts_emails = Expert.autocomplete_mails(@dossier.procedure)
|
||
end
|
||
|
||
def avis_new
|
||
@avis_seen_at = current_instructeur.follows.find_by(dossier: dossier)&.avis_seen_at
|
||
@avis = Avis.new
|
||
@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)
|
||
@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
|
||
recipients = params['recipients'].presence || []
|
||
# instructeurs are scoped by groupe_instructeur to avoid enumeration
|
||
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
|
||
|
||
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)
|
||
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 terminer
|
||
motivation = params[:dossier] && params[:dossier][:motivation]
|
||
justificatif = params[:dossier] && params[:dossier][:justificatif_motivation]
|
||
|
||
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)
|
||
end
|
||
|
||
@dossier = dossier
|
||
render :change_state
|
||
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
|
||
@commentaire = CommentaireService.create(current_instructeur, dossier, commentaire_params)
|
||
|
||
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
|
||
@commentaire.piece_jointe.purge.reload
|
||
flash.alert = @commentaire.errors.full_messages
|
||
render :messagerie
|
||
end
|
||
end
|
||
|
||
def create_avis
|
||
@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
|
||
end
|
||
|
||
def update_annotations
|
||
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|
|
||
format.turbo_stream do
|
||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_private_attributes_params, dossier.champs.filter(&:private?))
|
||
end
|
||
end
|
||
end
|
||
|
||
def print
|
||
@dossier = dossier
|
||
render layout: "print"
|
||
end
|
||
|
||
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])
|
||
|
||
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'
|
||
Dossier
|
||
.where(id: current_instructeur.dossiers.visible_by_administration)
|
||
.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
|
||
params.require(:commentaire).permit(:body, piece_jointe: [])
|
||
end
|
||
|
||
def champs_private_params
|
||
champ_attributes = [
|
||
:id,
|
||
:value,
|
||
:value_other,
|
||
:external_id,
|
||
: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)
|
||
end
|
||
|
||
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 n’ont pas pu encore être vérifiées : il n’est 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 n’est 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)
|
||
.map(&:attachments)
|
||
.flatten
|
||
.map(&:id)
|
||
|
||
champs_attachments_ids + commentaires_attachments_ids
|
||
end
|
||
@gallery_attachments = ActiveStorage::Attachment.where(id: gallery_attachments_ids)
|
||
end
|
||
end
|
||
end
|