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

630 lines
21 KiB
Ruby
Raw Normal View History

2019-03-25 10:53:45 +01:00
module Users
class DossiersController < UserController
include DossierHelper
2023-03-01 18:30:10 +01:00
include TurboChampsConcern
include LockableConcern
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
ACTIONS_ALLOWED_TO_ANY_USER = [:index, :new, :transferer_all]
2023-05-30 11:39:53 +02:00
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :destroy, :demande, :messagerie, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :update, :create_commentaire, :papertrail, :restore, :champ]
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
2023-05-30 11:39:53 +02:00
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_siret, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :update, :champ]
before_action :ensure_dossier_can_be_filled, only: [:brouillon, :modifier, :submit_brouillon, :submit_en_construction, :update]
before_action :ensure_dossier_can_be_viewed, only: [:show]
before_action :forbid_invite_submission!, only: [:submit_brouillon]
before_action :forbid_closed_submission!, only: [:submit_brouillon]
before_action :set_dossier_as_editing_fork, only: [:submit_en_construction]
before_action :show_demarche_en_test_banner
2018-11-01 13:00:35 +01:00
before_action :store_user_location!, only: :new
around_action only: :submit_en_construction do |_controller, action|
lock_action("lock-submit-en-construction-#{@dossier.id}", &action)
end
def index
ordered_dossiers = Dossier.includes(:procedure).order_by_updated_at
deleted_dossiers = current_user.deleted_dossiers.includes(:procedure).order_by_updated_at
user_revisions = ProcedureRevision.where(dossiers: current_user.dossiers.visible_by_user)
invite_revisions = ProcedureRevision.where(dossiers: current_user.dossiers_invites.visible_by_user)
deleted_dossier_procedures = Procedure.where(id: deleted_dossiers.pluck(:procedure_id))
all_dossier_procedures = Procedure.where(revisions: user_revisions.or(invite_revisions))
@procedures_for_select = all_dossier_procedures
.or(deleted_dossier_procedures)
.distinct(:procedure_id)
.order(:libelle)
.pluck(:libelle, :id)
@procedure_id = params[:procedure_id]
if @procedure_id.present?
ordered_dossiers = ordered_dossiers.where(procedures: { id: @procedure_id })
deleted_dossiers = deleted_dossiers.where(procedures: { id: @procedure_id })
end
@search_terms = params[:q]
if @search_terms.present?
dossiers_filter_by_search = DossierSearchService.matching_dossiers_for_user(@search_terms, current_user).page
ordered_dossiers = ordered_dossiers.merge(dossiers_filter_by_search)
deleted_dossiers = nil
end
@dossiers_visibles = ordered_dossiers.visible_by_user.preload(:etablissement, :individual, :invites)
@user_dossiers = current_user.dossiers.state_not_termine.merge(@dossiers_visibles)
@dossiers_traites = current_user.dossiers.state_termine.merge(@dossiers_visibles)
@dossiers_invites = current_user.dossiers_invites.merge(@dossiers_visibles)
@dossiers_supprimes_recemment = current_user.dossiers.hidden_by_user.merge(ordered_dossiers)
2024-02-05 12:21:14 +01:00
@dossier_transferes = @dossiers_visibles.where(dossier_transfer_id: DossierTransfer.for_email(current_user.email))
@dossiers_close_to_expiration = current_user.dossiers.close_to_expiration.merge(@dossiers_visibles)
@dossiers_supprimes_definitivement = deleted_dossiers
@statut = statut(@user_dossiers, @dossiers_traites, @dossiers_invites, @dossiers_supprimes_recemment, @dossiers_supprimes_definitivement, @dossier_transferes, @dossiers_close_to_expiration, params[:statut])
@dossiers = case @statut
when 'en-cours'
@user_dossiers
when 'traites'
@dossiers_traites
when 'dossiers-invites'
@dossiers_invites
when 'dossiers-supprimes-recemment'
@dossiers_supprimes_recemment
when 'dossiers-supprimes-definitivement'
@dossiers_supprimes_definitivement
when 'dossiers-transferes'
@dossier_transferes
when 'dossiers-expirant'
@dossiers_close_to_expiration
end.page(page)
@first_brouillon_recently_updated = current_user.dossiers.visible_by_user.brouillons_recently_updated.first
2023-04-28 15:28:04 +02:00
@filter = DossiersFilter.new(current_user, params)
2023-05-04 12:39:06 +02:00
@dossiers = @filter.filter_procedures(@dossiers).page(page)
end
def show
2024-03-08 17:14:21 +01:00
pj_service = PiecesJustificativesService.new(user_profile: current_user, export_template: nil)
2019-12-15 22:10:35 +01:00
respond_to do |format|
format.pdf do
@dossier = dossier_with_champs(pj_template: false)
@acls = pj_service.acl_for_dossier_export(@dossier.procedure)
render(template: 'dossiers/show', formats: [:pdf])
2019-12-15 22:10:35 +01:00
end
format.all do
@dossier = dossier
end
2019-12-15 22:10:35 +01:00
end
end
def demande
@dossier = dossier_with_champs(pj_template: false)
2018-08-14 15:06:44 +02:00
end
2018-09-05 13:56:12 +02:00
def messagerie
@dossier = dossier
@commentaire = Commentaire.new
end
def attestation
if dossier.attestation&.pdf&.attached?
redirect_to dossier.attestation.pdf.url, allow_other_host: true
else
2021-04-17 18:49:00 +02:00
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 set_accuse_lecture_agreement_at
@dossier = dossier
@dossier.update!(accuse_lecture_agreement_at: Time.zone.now)
flash.notice = 'Accusé de lecture accepté'
redirect_back(fallback_location: demande_dossier_path(@dossier))
end
def identite
@dossier = dossier
@user = current_user
@no_description = true
2024-04-12 17:59:50 +02:00
respond_to do |format|
format.html
format.turbo_stream do
@dossier.for_tiers = params[:dossier][:for_tiers]
2024-04-12 17:59:50 +02:00
end
end
end
2018-02-08 17:13:15 +01:00
def update_identite
@dossier = dossier
@no_description = true
2024-07-11 17:50:02 +02:00
email = dossier_params.dig('individual_attributes', 'email')
2018-02-08 17:13:15 +01:00
2023-11-23 16:38:01 +01:00
if @dossier.update(dossier_params) && @dossier.individual.valid?
2024-07-10 10:57:26 +02:00
# verify for_tiers email
if email.present?
User.create_or_promote_to_tiers(email, SecureRandom.hex, @dossier)
end
@dossier.update!(autorisation_donnees: true, identity_updated_at: Time.zone.now)
2021-04-17 18:49:00 +02:00
flash.notice = t('.identity_saved')
2018-02-08 17:13:15 +01:00
if dossier.en_construction?
redirect_to demande_dossier_path(@dossier)
else
redirect_to brouillon_dossier_path(@dossier)
end
2018-02-08 17:13:15 +01:00
else
2023-11-23 16:38:01 +01:00
flash.now.alert = @dossier.individual.errors.full_messages + @dossier.errors.full_messages
2018-02-08 17:13:15 +01:00
render :identite
end
end
def siret
@dossier = dossier
@no_description = true
end
def update_siret
@dossier = dossier
@no_description = true
# 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
2024-04-05 12:07:40 +02:00
if error.try(:network_error?) && !APIEntrepriseService.api_insee_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
2018-10-15 12:25:25 +02:00
def etablissement
@dossier = dossier
# Redirect if the user attempts to access the page URL directly
if !@dossier.etablissement
2021-04-17 18:49:00 +02:00
flash.alert = t('.no_establishment')
2018-10-15 12:25:25 +02:00
return redirect_to siret_dossier_path(@dossier)
end
end
def brouillon
2023-02-27 10:48:59 +01:00
session.delete(:prefill_token)
session.delete(:prefill_params)
2018-02-21 18:32:07 +01:00
@dossier = dossier_with_champs
@dossier.validate(:champs_public_value)
2018-02-21 18:32:07 +01:00
end
def submit_brouillon
@dossier = dossier_with_champs(pj_template: false)
submit_dossier_and_compute_errors
2018-02-21 18:32:07 +01:00
if @dossier.errors.blank? && @dossier.can_passer_en_construction?
@dossier.passer_en_construction!
@dossier.process_declarative!
@dossier.process_sva_svr!
@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
respond_to do |format|
format.html { render :brouillon }
format.turbo_stream
end
2018-02-21 18:32:07 +01:00
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
2018-09-05 18:23:10 +02:00
def modifier
@dossier = dossier_with_champs
end
# Transition to en_construction forks,
# so users editing en_construction dossiers won't completely break their changes.
# TODO: remove me after fork en_construction feature deploy (PR #8790)
def modifier_legacy
respond_to do |format|
format.turbo_stream do
flash.alert = "Une mise à jour de cette page est nécessaire pour poursuivre, veuillez la recharger (touche F5). Attention: le dernier champ modifié na pas été sauvegardé, vous devrez le ressaisir."
end
end
end
def submit_en_construction
@dossier = dossier_with_champs(pj_template: false)
editing_fork_origin = @dossier.editing_fork_origin
if cast_bool(params.dig(:dossier, :pending_correction))
editing_fork_origin.resolve_pending_correction
end
submit_dossier_and_compute_errors
if @dossier.errors.blank? && @dossier.can_passer_en_construction?
editing_fork_origin.merge_fork(@dossier)
editing_fork_origin.submit_en_construction!
redirect_to dossier_path(editing_fork_origin)
else
respond_to do |format|
format.html do
render :modifier
end
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
render :update, layout: false
end
end
end
end
2018-09-06 11:39:46 +02:00
def update
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
@dossier = dossier_with_champs(pj_template: false)
@can_passer_en_construction_was, @can_passer_en_construction_is = @dossier.track_can_passer_en_construction do
update_dossier_and_compute_errors
end
respond_to do |format|
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
render :update, layout: false
end
2018-09-06 11:39:46 +02:00
end
end
2018-02-27 09:49:58 +01:00
def merci
@dossier = current_user.dossiers.includes(:procedure).find(params[:id])
end
2023-05-30 11:39:53 +02:00
def champ
@dossier = dossier_with_champs(pj_template: false)
type_de_champ = @dossier.find_type_de_champ_by_stable_id(params[:stable_id], :public)
champ = @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 = [champ]
render :update, layout: false
end
end
end
2018-09-05 13:56:12 +02:00
def create_commentaire
2021-11-30 18:56:12 +01:00
@commentaire = CommentaireService.create(current_user, dossier, commentaire_params)
2018-09-05 13:56:12 +02:00
2021-11-30 18:56:12 +01:00
if @commentaire.errors.empty?
@commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now)
2021-04-17 18:49:00 +02:00
flash.notice = t('.message_send')
2018-09-05 13:56:12 +02:00
redirect_to messagerie_dossier_path(dossier)
else
flash.now.alert = @commentaire.errors.full_messages
render :messagerie
end
end
def destroy
if dossier.can_be_deleted_by_user?
if current_user.owns?(dossier)
dossier.hide_and_keep_track!(current_user, :user_request)
elsif current_user.invite?(dossier)
current_user.invites.where(dossier:).destroy_all
end
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
2018-11-01 13:00:35 +01:00
def new
erase_user_location!
begin
2021-06-24 11:57:05 +02:00
procedure = if params[:brouillon]
Procedure.publiees.or(Procedure.brouillons).find(params[:procedure_id])
else
2021-06-24 11:57:05 +02:00
Procedure.publiees.find(params[:procedure_id])
end
rescue ActiveRecord::RecordNotFound
flash.alert = t('errors.messages.procedure_not_found')
2021-06-24 11:57:05 +02:00
return redirect_to dossiers_path
2018-11-01 13:00:35 +01:00
end
dossier = Dossier.new(
2021-06-24 11:57:05 +02:00
revision: params[:brouillon] ? procedure.draft_revision : procedure.active_revision,
user: current_user,
state: Dossier.states.fetch(:brouillon)
)
dossier.build_default_individual
dossier.save!
DossierMailer.with(dossier:).notify_new_draft.deliver_later
2018-11-01 13:00:35 +01:00
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
2022-01-05 10:40:31 +01:00
def restore
dossier.restore(current_user)
2022-01-05 10:40:31 +01:00
flash.notice = t('users.dossiers.restore')
redirect_to dossiers_path
end
def clone
cloned_dossier = @dossier.clone
DossierMailer.with(dossier: cloned_dossier).notify_new_draft.deliver_later
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
2020-12-07 15:10:26 +01:00
# 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_transferes, dossiers_close_to_expiration, params_statut)
2020-12-07 15:10:26 +01:00
tabs = {
'en-cours' => mes_dossiers,
'traites' => dossiers_traites,
'dossiers-invites' => dossiers_invites,
'dossiers-supprimes-recemment' => dossiers_supprimes_recemment,
'dossiers-supprimes-definitivement' => dossiers_supprimes_definitivement,
'dossiers-transferes' => dossier_transferes,
'dossiers-expirant' => dossiers_close_to_expiration
2020-12-07 15:10:26 +01:00
}
if tabs[params_statut]&.present?
2020-12-07 15:10:26 +01:00
params_statut
2020-11-26 15:13:32 +01:00
else
tab = tabs.find { |_tab, scope| scope.present? }
tab&.first || 'en-cours'
2020-11-26 15:13:32 +01:00
end
end
2018-11-01 13:00:35 +01:00
def store_user_location!
store_location_for(:user, request.fullpath)
end
def erase_user_location!
clear_stored_location_for(:user)
2018-11-01 13:00:35 +01:00
end
def show_demarche_en_test_banner
2021-06-24 11:57:05 +02:00
if @dossier.present? && @dossier.revision.draft?
flash.now.alert = t('users.dossiers.test_procedure')
end
end
def ensure_dossier_can_be_updated
2019-02-06 18:20:35 +01:00
if !dossier.can_be_updated_by_user?
flash.alert = t('users.dossiers.no_longer_editable')
redirect_to dossier_path(dossier)
end
end
def ensure_dossier_can_be_filled
if !dossier.autorisation_donnees
if dossier.procedure.for_individual
flash.alert = t('users.dossiers.fill_identity.individual')
redirect_to identite_dossier_path(dossier)
else
flash.alert = t('users.dossiers.fill_identity.siret')
redirect_to siret_dossier_path(dossier)
end
end
end
def ensure_dossier_can_be_viewed
if dossier.brouillon?
redirect_to brouillon_dossier_path(dossier)
end
end
def page
[params[:page].to_i, 1].max
end
def champs_public_params
champ_attributes = [
2023-05-30 14:42:36 +02:00
:id,
:value,
:value_other,
:external_id,
:primary_value,
:secondary_value,
:numero_allocataire,
:code_postal,
:identifiant,
:numero_fiscal,
:reference_avis,
:ine,
:piece_justificative_file,
:code_departement,
:accreditation_number,
:accreditation_birthdate,
:feature,
2023-05-30 14:42:36 +02:00
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_public_attributes)&.keys || []
champs_public_attributes = public_ids.map { [_1, champ_attributes] }.to_h
params.require(:dossier).permit(champs_public_attributes:)
end
def champs_public_attributes_params
champs_public_params.fetch(:champs_public_attributes)
2018-02-21 18:32:07 +01:00
end
def dossier_scope
2023-05-30 11:39:53 +02:00
if action_name == 'update' || action_name == 'champ'
Dossier.visible_by_user.or(Dossier.for_procedure_preview).or(Dossier.for_editing_fork)
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
set_sentry_dossier(_1)
end
end
def dossier_with_champs(pj_template: true)
DossierPreloader.load_one(dossier, pj_template:)
2018-02-21 18:32:07 +01:00
end
def set_dossier_as_editing_fork
@dossier = dossier.find_editing_fork(dossier.user)
return if @dossier.present?
flash[:alert] = t('users.dossiers.en_construction_submitted')
redirect_to dossier_path(dossier)
end
def update_dossier_and_compute_errors
2024-05-13 16:53:33 +02:00
@dossier.update_champs_attributes(champs_public_attributes_params, :public, updated_by: current_user.email)
if @dossier.champs.any?(&:changed_for_autosave?)
@dossier.last_champ_updated_at = Time.zone.now
end
# We save the dossier without validating fields, and if it is successful and the client
# requests it, we ask for field validation errors.
if @dossier.save && params[:validate].present?
@dossier.valid?(:champs_public_value)
end
@dossier.errors
end
def submit_dossier_and_compute_errors
@dossier.validate(:champs_public_value)
@dossier.check_mandatory_and_visible_champs
if @dossier.editing_fork_origin&.pending_correction?
@dossier.editing_fork_origin.validate(:champs_public_value)
@dossier.editing_fork_origin.errors.where(:pending_correction).each do |error|
@dossier.errors.import(error)
end
end
end
def ensure_ownership!
2018-05-30 18:26:23 +02:00
if !current_user.owns?(dossier)
2018-03-29 15:25:05 +02:00
forbidden!
end
end
2018-02-08 17:13:15 +01:00
2018-03-29 15:25:05 +02:00
def ensure_ownership_or_invitation!
if !current_user.owns_or_invite?(dossier)
2018-03-29 15:25:05 +02:00
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
2018-03-29 15:25:05 +02:00
def forbidden!
2023-07-19 11:50:38 +02:00
flash[:alert] = t('users.dossiers.no_access_html', email: current_user.email)
2018-03-29 15:25:05 +02:00
redirect_to root_path
end
def render_siret_error(error_message)
flash.alert = error_message
render :siret
end
2023-11-20 10:59:57 +01:00
def dossier_params
params.require(:dossier).permit(:for_tiers, :mandataire_first_name, :mandataire_last_name, individual_attributes: [:gender, :nom, :prenom, :birthdate, :email, :notification_method])
2018-02-08 17:13:15 +01:00
end
def siret_params
params.require(:user).permit(:siret)
end
2018-09-05 13:56:12 +02:00
def commentaire_params
2024-02-13 08:09:58 +01:00
params.require(:commentaire).permit(:body, piece_jointe: [])
2018-09-05 13:56:12 +02:00
end
end
end