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

567 lines
18 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module Users
class DossiersController < UserController
include DossierHelper
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new, :transferer_all]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :submit_brouillon, :modifier, :update, :create_commentaire, :papertrail, :restore]
before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_brouillon, :submit_brouillon, :modifier, :update]
before_action :forbid_invite_submission!, only: [:submit_brouillon]
before_action :forbid_closed_submission!, only: [:submit_brouillon]
before_action :show_demarche_en_test_banner
before_action :store_user_location!, only: :new
def index
dossiers = Dossier.includes(:procedure).order_by_updated_at.page(page)
dossiers_visibles = dossiers.visible_by_user
@user_dossiers = current_user.dossiers.state_not_termine.merge(dossiers_visibles)
@dossiers_traites = current_user.dossiers.state_termine.merge(dossiers_visibles)
@dossiers_close_to_expiration = current_user.dossiers.close_to_expiration.merge(dossiers_visibles)
@dossiers_invites = current_user.dossiers_invites.merge(dossiers_visibles)
@dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(dossiers)
@dossiers_supprimes_definitivement = current_user.deleted_dossiers.order_by_updated_at.page(page)
@dossier_transfers = DossierTransfer
.includes(dossiers: :user)
.with_dossiers
.where(email: current_user.email)
.page(page)
@statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @dossier_transfers, @dossiers_close_to_expiration, params[:statut])
end
def show
if dossier.brouillon?
redirect_to brouillon_dossier_path(dossier)
return
end
@dossier = dossier
respond_to do |format|
format.pdf do
@include_infos_administration = false
render(template: 'dossiers/show', formats: [:pdf])
end
format.all
end
end
def demande
@dossier = dossier
end
def messagerie
@dossier = dossier
@commentaire = Commentaire.new
end
def attestation
if dossier.attestation&.pdf&.attached?
redirect_to dossier.attestation.pdf.service_url
else
flash.notice = t('.no_longer_available')
redirect_to dossier_path(dossier)
end
end
def papertrail
raise ActionController::BadRequest if dossier.brouillon?
@dossier = dossier
end
def identite
@dossier = dossier
@user = current_user
end
def update_identite
@dossier = dossier
if @dossier.individual.update(individual_params)
@dossier.update!(autorisation_donnees: true, identity_updated_at: Time.zone.now)
flash.notice = t('.identity_saved')
redirect_to brouillon_dossier_path(@dossier)
else
flash.now.alert = @dossier.individual.errors.full_messages
render :identite
end
end
def siret
@dossier = dossier
end
def update_siret
@dossier = dossier
# We use the user as the holder model object for the siret value
# (so that we can restore it on the form in case of error).
#
# This is the only remaining use of User#siret: it could be refactored away.
# However some existing users have a siret but no associated etablissement,
# so we would need to analyze the legacy data and decide what to do with it.
current_user.siret = siret_params[:siret]
siret_model = Siret.new(siret: siret_params[:siret])
if !siret_model.valid?
return render_siret_error(siret_model.errors.full_messages)
end
sanitized_siret = siret_model.siret
etablissement = begin
APIEntrepriseService.create_etablissement(@dossier, sanitized_siret, current_user.id)
rescue => error
if error.try(:network_error?) && !APIEntrepriseService.api_up?
# TODO: notify ops
APIEntrepriseService.create_etablissement_as_degraded_mode(@dossier, sanitized_siret, current_user.id)
else
Sentry.capture_exception(error, extra: { dossier_id: @dossier.id, siret: })
# probably random error, invite user to retry
return render_siret_error(t('errors.messages.siret_network_error'))
end
end
if etablissement.nil?
return render_siret_error(t('errors.messages.siret_unknown'))
end
current_user.update!(siret: sanitized_siret)
@dossier.update!(autorisation_donnees: true)
redirect_to etablissement_dossier_path
end
def etablissement
@dossier = dossier
# Redirect if the user attempts to access the page URL directly
if !@dossier.etablissement
flash.alert = t('.no_establishment')
return redirect_to siret_dossier_path(@dossier)
end
end
def brouillon
@dossier = dossier_with_champs
# TODO: remove when the champs are unifed
if !@dossier.autorisation_donnees
if dossier.procedure.for_individual
redirect_to identite_dossier_path(@dossier)
else
redirect_to siret_dossier_path(@dossier)
end
end
end
def submit_brouillon
@dossier = dossier_with_champs(pj_template: false)
errors = submit_dossier_and_compute_errors
if errors.blank?
@dossier.passer_en_construction!
@dossier.process_declarative!
NotificationMailer.send_en_construction_notification(@dossier).deliver_later
@dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later
end
redirect_to merci_dossier_path(@dossier)
else
flash.now.alert = errors
respond_to do |format|
format.html { render :brouillon }
format.turbo_stream
end
end
end
def extend_conservation
dossier.extend_conservation(dossier.procedure.duree_conservation_dossiers_dans_ds.months)
flash[:notice] = t('views.users.dossiers.archived_dossier', duree_conservation_dossiers_dans_ds: dossier.procedure.duree_conservation_dossiers_dans_ds)
redirect_back(fallback_location: dossier_path(@dossier))
end
def modifier
@dossier = dossier_with_champs
end
def update_brouillon
@dossier = dossier_with_champs
update_dossier_and_compute_errors
respond_to do |format|
format.html { render :brouillon }
format.turbo_stream do
@to_shows, @to_hides = @dossier.champs_public
.filter(&:conditional?)
.partition(&:visible?)
.map { |champs| champs_to_one_selector(champs) }
render(:update, layout: false)
end
end
end
def update
@dossier = dossier_with_champs(pj_template: false)
errors = update_dossier_and_compute_errors
if errors.present?
flash.now.alert = errors
end
respond_to do |format|
format.html { render :modifier }
format.turbo_stream do
@to_shows, @to_hides = @dossier.champs_public
.filter(&:conditional?)
.partition(&:visible?)
.map { |champs| champs_to_one_selector(champs) }
end
end
end
def merci
@dossier = current_user.dossiers.includes(:procedure).find(params[:id])
end
def create_commentaire
@commentaire = CommentaireService.create(current_user, dossier, commentaire_params)
if @commentaire.errors.empty?
@commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
dossier.followers_instructeurs
.with_instant_email_message_notifications
.each do |instructeur|
DossierMailer.notify_new_commentaire_to_instructeur(dossier, instructeur.email).deliver_later
end
flash.notice = t('.message_send')
redirect_to messagerie_dossier_path(dossier)
else
flash.now.alert = @commentaire.errors.full_messages
render :messagerie
end
end
def delete_dossier
if dossier.can_be_deleted_by_user?
dossier.hide_and_keep_track!(current_user, :user_request)
flash.notice = t('users.dossiers.ask_deletion.soft_deleted_dossier')
redirect_to dossiers_path
else
flash.alert = t('users.dossiers.ask_deletion.undergoingreview')
redirect_to dossiers_path
end
end
def recherche
@search_terms = params[:q]
return redirect_to dossiers_path if @search_terms.blank?
@dossiers = DossierSearchService.matching_dossiers_for_user(@search_terms, current_user).page(page)
if @dossiers.present?
# we need the page condition when accessing page n with n>1 when the page has only 1 result
# in order to avoid an unpleasant redirection when changing page
if @dossiers.count == 1 && page == 1
redirect_to url_for_dossier(@dossiers.first)
else
render :index
end
else
flash.alert = "Vous navez pas de dossiers contenant « #{@search_terms} »."
redirect_to dossiers_path
end
end
def new
erase_user_location!
begin
procedure = if params[:brouillon]
Procedure.publiees.or(Procedure.brouillons).find(params[:procedure_id])
else
Procedure.publiees.find(params[:procedure_id])
end
rescue ActiveRecord::RecordNotFound
flash.alert = t('errors.messages.procedure_not_found')
return redirect_to dossiers_path
end
dossier = Dossier.new(
revision: params[:brouillon] ? procedure.draft_revision : procedure.active_revision,
groupe_instructeur: procedure.defaut_groupe_instructeur_for_new_dossier,
user: current_user,
state: Dossier.states.fetch(:brouillon)
)
dossier.build_default_individual
dossier.save!
if dossier.procedure.for_individual
redirect_to identite_dossier_path(dossier)
else
redirect_to siret_dossier_path(id: dossier.id)
end
end
def dossier_for_help
dossier_id = params[:id] || params[:dossier_id]
@dossier || (dossier_id.present? && Dossier.visible_by_user.find_by(id: dossier_id.to_i))
end
def transferer
@transfer = DossierTransfer.new(dossiers: [dossier])
end
def transferer_all
@transfer = DossierTransfer.new(dossiers: current_user.dossiers)
end
def restore
dossier.restore(current_user)
flash.notice = t('users.dossiers.restore')
redirect_to dossiers_path
end
def clone
cloned_dossier = @dossier.clone
flash.notice = t('users.dossiers.cloned_success')
redirect_to brouillon_dossier_path(cloned_dossier)
rescue ActiveRecord::RecordInvalid => e
flash.alert = e.record.errors.full_messages
redirect_to dossier_path(@dossier)
end
private
# if the status tab is filled, then this tab
# else first filled tab
# else en-cours
def statut(mes_dossiers, dossiers_traites, dossiers_invites, dossiers_supprimes_recemment, dossiers_supprimes_definitivement, dossier_transfers, dossiers_close_to_expiration, params_statut)
tabs = {
'en-cours' => mes_dossiers.present?,
'traites' => dossiers_traites.present?,
'dossiers-invites' => dossiers_invites.present?,
'dossiers-supprimes-recemment' => dossiers_supprimes_recemment.present?,
'dossiers-supprimes-definitivement' => dossiers_supprimes_definitivement.present?,
'dossiers-transferes' => dossier_transfers.present?,
'dossiers-expirant' => dossiers_close_to_expiration.present?
}
if tabs[params_statut]
params_statut
else
tabs
.filter { |_tab, filled| filled }
.map { |tab, _| tab }
.first || 'en-cours'
end
end
def store_user_location!
store_location_for(:user, request.fullpath)
end
def erase_user_location!
clear_stored_location_for(:user)
end
def show_demarche_en_test_banner
if @dossier.present? && @dossier.revision.draft?
flash.now.alert = t('users.dossiers.test_procedure')
end
end
def ensure_dossier_can_be_updated
if !dossier.can_be_updated_by_user?
flash.alert = t('users.dossiers.no_longer_editable')
redirect_to dossiers_path
end
end
def page
[params[:page].to_i, 1].max
end
# FIXME: require(:dossier) when all the champs are united
def champs_params
params.permit(dossier: {
champs_public_attributes: [
:id, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :identifiant, :numero_fiscal, :reference_avis, :ine, :piece_justificative_file, :departement, :code_departement, value: [],
champs_attributes: [:id, :_destroy, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :identifiant, :numero_fiscal, :reference_avis, :ine, :piece_justificative_file, :departement, :code_departement, value: []]
]
})
end
def dossier_scope
if action_name == 'update_brouillon'
Dossier.visible_by_user.or(Dossier.for_procedure_preview)
elsif action_name == 'restore'
Dossier.hidden_by_user
else
Dossier.visible_by_user
end
end
def dossier
@dossier ||= dossier_scope.find(params[:id] || params[:dossier_id]).tap do |dossier|
# Ease search & groupments by tags
Sentry.configure_scope do |scope|
scope.set_tags(procedure: dossier.procedure.id)
scope.set_tags(dossier: dossier.id)
end
end
end
def dossier_with_champs(pj_template: true)
DossierPreloader.load_one(dossier, pj_template:)
end
def should_change_groupe_instructeur?
if params[:dossier].key?(:groupe_instructeur_id)
groupe_instructeur_id = params[:dossier][:groupe_instructeur_id]
if groupe_instructeur_id.nil?
@dossier.groupe_instructeur_id.present?
else
@dossier.groupe_instructeur_id != groupe_instructeur_id.to_i
end
end
end
def groupe_instructeur_from_params
groupe_instructeur_id = params[:dossier][:groupe_instructeur_id]
if groupe_instructeur_id.present?
@dossier.procedure.groupe_instructeurs.find(groupe_instructeur_id)
end
end
def should_fill_groupe_instructeur?
!@dossier.procedure.routing_enabled? && @dossier.groupe_instructeur_id.nil?
end
def defaut_groupe_instructeur
@dossier.procedure.defaut_groupe_instructeur
end
def update_dossier_and_compute_errors
errors = []
if champs_params[:dossier]
@dossier.assign_attributes(champs_params[:dossier])
# FIXME: in some cases a removed repetition bloc row is submitted.
# In this case it will be treated as a new record, and the action will fail.
@dossier.champs_public.filter(&:block?).each do |champ|
champ.champs = champ.champs.filter(&:persisted?)
end
if @dossier.champs_public.any?(&:changed_for_autosave?)
@dossier.last_champ_updated_at = Time.zone.now
end
if !@dossier.save(**validation_options)
errors += @dossier.errors.full_messages
end
if should_change_groupe_instructeur?
@dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params)
end
end
if dossier.en_construction?
errors += @dossier.check_mandatory_and_visible_champs
end
errors
end
def submit_dossier_and_compute_errors
errors = []
@dossier.valid?(**submit_validation_options)
errors += @dossier.errors.full_messages
errors += @dossier.check_mandatory_and_visible_champs
if should_fill_groupe_instructeur?
@dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur)
end
if @dossier.groupe_instructeur.nil?
errors << "Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"
end
errors
end
def ensure_ownership!
if !current_user.owns?(dossier)
forbidden!
end
end
def ensure_ownership_or_invitation!
if !current_user.owns_or_invite?(dossier)
forbidden!
end
end
def forbid_invite_submission!
if !current_user.owns?(dossier)
forbidden!
end
end
def forbid_closed_submission!
if !dossier.can_transition_to_en_construction?
forbidden!
end
end
def forbidden!
flash[:alert] = t('users.dossiers.no_access')
redirect_to root_path
end
def render_siret_error(error_message)
flash.alert = error_message
render :siret
end
def individual_params
params.require(:individual).permit(:gender, :nom, :prenom, :birthdate)
end
def siret_params
params.require(:user).permit(:siret)
end
def commentaire_params
params.require(:commentaire).permit(:body, :piece_jointe)
end
def submit_validation_options
# rubocop:disable Lint/BooleanSymbol
# Force ActiveRecord to re-validate associated records.
{ context: :false }
# rubocop:enable Lint/BooleanSymbol
end
def validation_options
if dossier.brouillon?
{ context: :brouillon }
else
submit_validation_options
end
end
def champs_to_one_selector(champs)
champs
.map(&:input_group_id)
.map { |id| "##{id}" }
.join(',')
end
end
end