Merge pull request #9021 from colinux/feat-en-construction-fork
ETQ usager, je modifie et soumets à nouveau mon dossier “en construction"
This commit is contained in:
commit
79f450a422
53 changed files with 839 additions and 303 deletions
|
@ -25,4 +25,3 @@ $blue-france-500: #000091;
|
|||
$blue-france-400: #7F7FC8;
|
||||
$blue-cumulus-950: #E6EEFE;
|
||||
$g700: #383838;
|
||||
$alt-blue-france: rgba(245, 245, 254, 1);
|
||||
|
|
|
@ -142,7 +142,7 @@ $landing-breakpoint: 1040px;
|
|||
.usagers-panel,
|
||||
.numbers-panel,
|
||||
.cta-panel-2 {
|
||||
background-color: $alt-blue-france;
|
||||
background-color: var(--background-alt-blue-france);
|
||||
}
|
||||
|
||||
.more-info {
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
.status-overview {
|
||||
text-align: center;
|
||||
margin-bottom: $default-padding * 2;
|
||||
}
|
||||
|
||||
.status-timeline {
|
||||
display: inline-block;
|
||||
margin-top: $default-padding * 2;
|
||||
margin-bottom: $default-padding * 2;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 3px;
|
||||
|
||||
|
@ -46,15 +39,6 @@
|
|||
}
|
||||
|
||||
.status-explanation {
|
||||
text-align: left;
|
||||
|
||||
.brouillon,
|
||||
.en-construction,
|
||||
.en-instruction {
|
||||
max-width: 650px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: $default-padding;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@import "constants";
|
||||
|
||||
.sub-header {
|
||||
background-color: $alt-blue-france;
|
||||
background-color: var(--background-alt-blue-france);
|
||||
padding-top: $default-padding;
|
||||
margin-bottom: $sub-header-bottom-margin;
|
||||
border-bottom: 1px solid $border-grey;
|
||||
|
|
|
@ -5,9 +5,9 @@ en:
|
|||
confirmation: Draft saved
|
||||
error: Impossible to save the draft
|
||||
en_construction:
|
||||
explanation: Your file is automatically saved.
|
||||
confirmation: File saved
|
||||
error: Impossible to save the file
|
||||
explanation: Your modifications are automatically saved. Submit them when you’re done.
|
||||
confirmation: Modifications saved
|
||||
error: Impossible to save the modifications.
|
||||
annotations:
|
||||
explanation: Your annotations are automatically saved.
|
||||
confirmation: Annotations saved
|
||||
|
|
|
@ -5,11 +5,11 @@ fr:
|
|||
confirmation: Brouillon enregistré
|
||||
error: Impossible d’enregistrer le brouillon
|
||||
en_construction:
|
||||
explanation: Votre dossier est automatiquement enregistré.
|
||||
confirmation: Dossier enregistré
|
||||
error: Impossible d’enregistrer le dossier
|
||||
explanation: Vos modifications sont automatiquement enregistrées. Déposez-les quand vous aurez terminé.
|
||||
confirmation: Modifications enregistrées.
|
||||
error: Impossible d’enregistrer les modifications
|
||||
annotations:
|
||||
explanation: Vos annotations sont automatiquement enregistrées.
|
||||
confirmation: Annotations enregistré
|
||||
confirmation: Annotations enregistrées
|
||||
error: Impossible d’enregistrer les annotations
|
||||
more_information: En savoir plus
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
%span.autosave-explanation-text
|
||||
- if annotation?
|
||||
= t('.annotations.explanation')
|
||||
- elsif dossier.brouillon?
|
||||
= t('.brouillon.explanation')
|
||||
- else
|
||||
- elsif dossier.editing_fork?
|
||||
= t('.en_construction.explanation')
|
||||
- else
|
||||
= t('.brouillon.explanation')
|
||||
- if !annotation?
|
||||
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
|||
%span.autosave-label
|
||||
- if annotation?
|
||||
= t('.annotations.confirmation')
|
||||
- elsif dossier.brouillon?
|
||||
= t('.brouillon.confirmation')
|
||||
- else
|
||||
- elsif dossier.editing_fork?
|
||||
= t('.en_construction.confirmation')
|
||||
- else
|
||||
= t('.brouillon.confirmation')
|
||||
- if !annotation?
|
||||
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
|
||||
|
||||
|
@ -27,11 +27,11 @@
|
|||
%span.autosave-label
|
||||
- if annotation?
|
||||
= t('.annotations.error')
|
||||
- elsif dossier.brouillon?
|
||||
= t('.brouillon.error')
|
||||
- else
|
||||
- elsif dossier.editing_fork?
|
||||
= t('.en_construction.error')
|
||||
%button.button.small.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
|
||||
%span.autosave-retry-label réessayer
|
||||
%span.autosave-retrying-label enregistrement en cours…
|
||||
- else
|
||||
= t('.brouillon.error')
|
||||
%button.fr-btn.fr-btn--tertiary.fr-btn--sm.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
|
||||
%span.autosave-retry-label Réessayer
|
||||
%span.autosave-retrying-label Enregistrement en cours…
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class Dossiers::EditFooterComponent < ApplicationComponent
|
|||
@annotation.present?
|
||||
end
|
||||
|
||||
def button_options
|
||||
def submit_draft_button_options
|
||||
{
|
||||
class: 'fr-btn fr-btn--sm',
|
||||
disabled: !owner?,
|
||||
|
@ -23,6 +23,14 @@ class Dossiers::EditFooterComponent < ApplicationComponent
|
|||
}
|
||||
end
|
||||
|
||||
def submit_en_construction_button_options
|
||||
{
|
||||
class: 'fr-btn fr-btn--sm',
|
||||
method: :post,
|
||||
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit' }
|
||||
}
|
||||
end
|
||||
|
||||
def render?
|
||||
!@dossier.for_procedure_preview?
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
en:
|
||||
submit: Submit the file
|
||||
submit_changes: Submit file changes
|
||||
submitting: Submitting…
|
||||
invite_notice: You are invited to make amendments to this file but only the owner themselves can submit it.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
fr:
|
||||
submit: Déposer le dossier
|
||||
submit_changes: Déposer les modifications
|
||||
submitting: Envoi en cours…
|
||||
invite_notice: En tant qu’invité, vous pouvez remplir ce formulaire – mais le titulaire du dossier doit le déposer lui-même.
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
|
||||
|
||||
- if !annotation? && @dossier.can_transition_to_en_construction?
|
||||
= button_to t('.submit'), brouillon_dossier_url(@dossier), button_options
|
||||
= button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options
|
||||
- elsif @dossier.forked_with_changes?
|
||||
= button_to t('.submit_changes'), modifier_dossier_url(@dossier.editing_fork_origin), submit_en_construction_button_options
|
||||
|
||||
|
||||
- if @dossier.brouillon? && !owner?
|
||||
.send-notice.invite-cannot-submit
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Dossiers::EnConstructionNotSubmittedComponent < ApplicationComponent
|
||||
attr_reader :dossier
|
||||
attr_reader :user
|
||||
|
||||
def initialize(dossier:, user:)
|
||||
@dossier = dossier
|
||||
@user = user
|
||||
|
||||
@fork = @dossier.find_editing_fork(user, rebase: false)
|
||||
end
|
||||
|
||||
def render?
|
||||
@fork&.forked_with_changes?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
en:
|
||||
title: Modifications not yet submitted
|
||||
body: |
|
||||
You have made changes after submitting your file. Submit them
|
||||
so that the administration can take them into account.
|
||||
buttons:
|
||||
edit: Continue to edit
|
||||
submit: Submit file changes
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
fr:
|
||||
title: Des modifications n’ont pas encore été déposées
|
||||
body: |
|
||||
Vous avez apporté des modifications après le dépôt de votre dossier.
|
||||
Déposez-les afin que l’administration les prenne en compte.
|
||||
buttons:
|
||||
edit: Continuer à modifier
|
||||
submit: Déposer les modifications
|
|
@ -0,0 +1,9 @@
|
|||
= render Dsfr::CalloutComponent.new(title: t(".title"), icon: "fr-fi-information-line") do |c|
|
||||
- c.body do
|
||||
= t(".body")
|
||||
|
||||
- c.bottom do
|
||||
%ul.fr-mt-2w.fr-btns-group.fr-btns-group--inline
|
||||
%li= link_to t(".buttons.edit"), modifier_dossier_path(dossier), class: "fr-btn"
|
||||
%li= button_to t(".buttons.submit"), modifier_dossier_path(dossier), class: "fr-btn fr-btn--secondary", method: :post
|
||||
|
|
@ -27,7 +27,7 @@ class Dsfr::CalloutComponent < ApplicationComponent
|
|||
when :success
|
||||
"fr-callout--green-emeraude"
|
||||
else
|
||||
# info is default theme
|
||||
"fr-background-alt--blue-france"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
= @form.label @champ.main_value_name, id: @champ.labelledby_id, for: @champ.input_id do
|
||||
- render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
|
||||
- else
|
||||
.form-label.mb-4
|
||||
.form-label.mb-4{ id: @champ.labelledby_id }
|
||||
= render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
|
||||
|
||||
- if @champ.description.present?
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
en:
|
||||
changes_to_save: "modifications to submit"
|
||||
modified_at: "modified on %{datetime}"
|
||||
check_content_rebased: The type of this field or its description has been modified by the administration. Check its content.
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
fr:
|
||||
changes_to_save: "modification à déposer"
|
||||
modified_at: "modifié le %{datetime}"
|
||||
check_content_rebased: Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.
|
|
@ -2,10 +2,13 @@
|
|||
- if @champ.mandatory?
|
||||
%span.mandatory *
|
||||
|
||||
- if @champ.updated_at.present? && @seen_at.present?
|
||||
- if @champ.forked_with_changes?
|
||||
%span.updated-at.highlighted
|
||||
= t('.changes_to_save')
|
||||
- elsif @champ.updated_at.present? && @seen_at.present?
|
||||
%span.updated-at{ class: highlight_if_unseen_class }
|
||||
= "modifié le #{try_format_datetime(@champ.updated_at)}"
|
||||
= t('.modified_at', datetime: try_format_datetime(@champ.updated_at))
|
||||
|
||||
- if @champ.rebased_at.present? && @champ.rebased_at > (@seen_at || @champ.updated_at) && current_user.owns_or_invite?(@champ.dossier)
|
||||
%span.updated-at.highlighted
|
||||
Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.
|
||||
= t('.check_content_rebased')
|
||||
|
|
|
@ -6,7 +6,7 @@ module TurboChampsConcern
|
|||
def champs_to_turbo_update(params, champs)
|
||||
champ_ids = params.keys.map(&:to_i)
|
||||
|
||||
to_update = champs.filter { _1.id.in?(champ_ids) && _1.refresh_after_update? }
|
||||
to_update = champs.filter { _1.id.in?(champ_ids) && (_1.refresh_after_update? || _1.forked_with_changes?) }
|
||||
to_show, to_hide = champs.filter(&:conditional?)
|
||||
.partition(&:visible?)
|
||||
.map { champs_to_one_selector(_1 - to_update) }
|
||||
|
|
|
@ -6,12 +6,12 @@ module Users
|
|||
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, :destroy, :demande, :messagerie, :brouillon, :update_brouillon, :submit_brouillon, :modifier, :update, :create_commentaire, :papertrail, :restore]
|
||||
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :destroy, :demande, :messagerie, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :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_siret, :brouillon, :update_brouillon, :submit_brouillon, :modifier, :update]
|
||||
before_action :ensure_dossier_can_be_filled, only: [:brouillon, :modifier, :update_brouillon, :submit_brouillon, :update]
|
||||
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_siret, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :update]
|
||||
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]
|
||||
|
@ -174,6 +174,7 @@ module Users
|
|||
errors = submit_dossier_and_compute_errors
|
||||
|
||||
if errors.blank?
|
||||
RoutingEngine.compute(@dossier)
|
||||
@dossier.passer_en_construction!
|
||||
@dossier.process_declarative!
|
||||
NotificationMailer.send_en_construction_notification(@dossier).deliver_later
|
||||
|
@ -202,21 +203,47 @@ module Users
|
|||
@dossier = dossier_with_champs
|
||||
end
|
||||
|
||||
def update_brouillon
|
||||
@dossier = dossier_with_champs
|
||||
update_dossier_and_compute_errors
|
||||
|
||||
# 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.html { render :brouillon }
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
|
||||
flash.alert = "Une mise à jour de cette page est nécessaire pour poursuivre, veuillez la recharger (touche F5). Attention: le dernier champ modifié n’a pas été sauvegardé, vous devrez le ressaisir."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
render(:update, layout: false)
|
||||
def submit_en_construction
|
||||
@dossier = dossier.find_editing_fork(dossier.user)
|
||||
@dossier = dossier_with_champs(pj_template: false)
|
||||
errors = submit_dossier_and_compute_errors
|
||||
|
||||
if errors.blank?
|
||||
editing_fork_origin = @dossier.editing_fork_origin
|
||||
editing_fork_origin.merge_fork(@dossier)
|
||||
RoutingEngine.compute(editing_fork_origin)
|
||||
|
||||
redirect_to dossier_path(editing_fork_origin)
|
||||
else
|
||||
flash.now.alert = errors
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@dossier = @dossier.editing_fork_origin
|
||||
render :modifier
|
||||
end
|
||||
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
|
||||
render :update, layout: false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
|
||||
@dossier = dossier_with_champs(pj_template: false)
|
||||
errors = update_dossier_and_compute_errors
|
||||
|
||||
|
@ -225,9 +252,9 @@ module Users
|
|||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :modifier }
|
||||
format.turbo_stream do
|
||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
|
||||
render :update, layout: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -425,8 +452,8 @@ module Users
|
|||
end
|
||||
|
||||
def dossier_scope
|
||||
if action_name == 'update_brouillon'
|
||||
Dossier.visible_by_user.or(Dossier.for_procedure_preview)
|
||||
if action_name == 'update'
|
||||
Dossier.visible_by_user.or(Dossier.for_procedure_preview).or(Dossier.for_editing_fork)
|
||||
elsif action_name == 'restore'
|
||||
Dossier.hidden_by_user
|
||||
else
|
||||
|
@ -484,14 +511,6 @@ module Users
|
|||
@dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params)
|
||||
end
|
||||
|
||||
if @dossier.procedure.feature_enabled?(:routing_rules)
|
||||
RoutingEngine.compute(@dossier)
|
||||
end
|
||||
|
||||
if dossier.en_construction?
|
||||
errors += format_errors(errors: @dossier.check_mandatory_and_visible_champs)
|
||||
end
|
||||
|
||||
errors
|
||||
end
|
||||
|
||||
|
@ -506,7 +525,7 @@ module Users
|
|||
@dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur)
|
||||
end
|
||||
|
||||
if @dossier.groupe_instructeur.nil?
|
||||
if !@dossier.procedure.feature_enabled?(:routing_rules) && @dossier.groupe_instructeur.nil?
|
||||
errors += format_errors(errors: ["Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"])
|
||||
end
|
||||
|
||||
|
@ -528,9 +547,12 @@ module Users
|
|||
|
||||
def append_anchor_link(str_error, model)
|
||||
return str_error.full_message if !model.is_a?(Champ)
|
||||
|
||||
route_helper = @dossier.editing_fork? ? :modifier_dossier_path : :brouillon_dossier_path
|
||||
|
||||
[
|
||||
"Le champ « #{model.libelle.truncate(200)} » #{str_error}",
|
||||
helpers.link_to(t('views.users.dossiers.fix_champ'), brouillon_dossier_path(anchor: model.input_id))
|
||||
helpers.link_to(t('views.users.dossiers.fix_champ'), public_send(route_helper, anchor: model.input_id))
|
||||
].join(", ")
|
||||
rescue # case of invalid type de champ on champ
|
||||
str_error
|
||||
|
|
|
@ -11,7 +11,7 @@ module Mutations
|
|||
field :errors, [Types::ValidationErrorType], null: true
|
||||
|
||||
def resolve(dossier:, groupe_instructeur:)
|
||||
dossier.update!(groupe_instructeur:)
|
||||
dossier.assign_to_groupe_instructeur(groupe_instructeur)
|
||||
|
||||
{ dossier: }
|
||||
end
|
||||
|
|
7
app/jobs/destroy_record_later_job.rb
Normal file
7
app/jobs/destroy_record_later_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class DestroyRecordLaterJob < ApplicationJob
|
||||
discard_on ActiveRecord::RecordNotFound
|
||||
|
||||
def perform(record)
|
||||
record.destroy
|
||||
end
|
||||
end
|
8
app/jobs/dossier_update_search_terms_job.rb
Normal file
8
app/jobs/dossier_update_search_terms_job.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
class DossierUpdateSearchTermsJob < ApplicationJob
|
||||
discard_on ActiveRecord::RecordNotFound
|
||||
|
||||
def perform(dossier)
|
||||
dossier.update_search_terms
|
||||
dossier.save!(touch: false)
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@
|
|||
# id :integer not null, primary key
|
||||
# data :jsonb
|
||||
# fetch_external_data_exceptions :string is an Array
|
||||
# prefilled :boolean default(FALSE)
|
||||
# prefilled :boolean
|
||||
# private :boolean default(FALSE), not null
|
||||
# rebased_at :datetime
|
||||
# type :string
|
||||
|
@ -111,6 +111,10 @@ class Champ < ApplicationRecord
|
|||
parent_id.present?
|
||||
end
|
||||
|
||||
def stable_id_with_row
|
||||
[row_id, stable_id].compact
|
||||
end
|
||||
|
||||
def sections
|
||||
@sections ||= dossier.sections_for(self)
|
||||
end
|
||||
|
@ -226,10 +230,10 @@ class Champ < ApplicationRecord
|
|||
update!(data: data)
|
||||
end
|
||||
|
||||
def clone
|
||||
def clone(fork = false)
|
||||
champ_attributes = [:parent_id, :private, :row_id, :type, :type_de_champ_id]
|
||||
value_attributes = private? ? [] : [:value, :value_json, :data, :external_id]
|
||||
relationships = private? ? [] : [:etablissement, :geo_areas]
|
||||
value_attributes = fork || !private? ? [:value, :value_json, :data, :external_id] : []
|
||||
relationships = fork || !private? ? [:etablissement, :geo_areas] : []
|
||||
|
||||
deep_clone(only: champ_attributes + value_attributes, include: relationships) do |original, kopy|
|
||||
PiecesJustificativesService.clone_attachments(original, kopy)
|
||||
|
@ -240,6 +244,10 @@ class Champ < ApplicationRecord
|
|||
input_id
|
||||
end
|
||||
|
||||
def forked_with_changes?
|
||||
public? && dossier.champ_forked_with_changes?(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def html_id
|
||||
|
|
174
app/models/concerns/dossier_clone_concern.rb
Normal file
174
app/models/concerns/dossier_clone_concern.rb
Normal file
|
@ -0,0 +1,174 @@
|
|||
module DossierCloneConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :parent_dossier, class_name: 'Dossier', optional: true
|
||||
has_many :cloned_dossiers, class_name: 'Dossier', foreign_key: :parent_dossier_id, dependent: :nullify, inverse_of: :parent_dossier
|
||||
|
||||
belongs_to :editing_fork_origin, class_name: 'Dossier', optional: true
|
||||
has_many :editing_forks, -> { where(hidden_by_reason: nil) }, class_name: 'Dossier', foreign_key: :editing_fork_origin_id, dependent: :destroy, inverse_of: :editing_fork_origin
|
||||
end
|
||||
|
||||
def find_or_create_editing_fork(user)
|
||||
find_editing_fork(user) || clone(user:, fork: true)
|
||||
end
|
||||
|
||||
def find_editing_fork(user, rebase: true)
|
||||
fork = editing_forks.find_by(user:)
|
||||
fork.rebase! if rebase && fork
|
||||
|
||||
fork
|
||||
end
|
||||
|
||||
def owner_editing_fork
|
||||
find_or_create_editing_fork(user).tap { DossierPreloader.load_one(_1) }
|
||||
end
|
||||
|
||||
def reset_editing_fork!
|
||||
if editing_fork? && forked_with_changes?
|
||||
destroy_editing_fork!
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_editing_fork!
|
||||
if editing_fork?
|
||||
update!(hidden_by_administration_at: Time.current, hidden_by_reason: :stale_fork)
|
||||
DestroyRecordLaterJob.perform_later(self)
|
||||
end
|
||||
end
|
||||
|
||||
def editing_fork?
|
||||
editing_fork_origin_id.present?
|
||||
end
|
||||
|
||||
def make_diff(editing_fork)
|
||||
origin_champs_index = champs_public_all.index_by(&:stable_id_with_row)
|
||||
forked_champs_index = editing_fork.champs_public_all.index_by(&:stable_id_with_row)
|
||||
updated_champs_index = editing_fork
|
||||
.champs_public_all
|
||||
.filter { _1.updated_at > editing_fork.created_at }
|
||||
.index_by(&:stable_id_with_row)
|
||||
|
||||
added = forked_champs_index.keys - origin_champs_index.keys
|
||||
removed = origin_champs_index.keys - forked_champs_index.keys
|
||||
updated = updated_champs_index.keys - added
|
||||
|
||||
{
|
||||
added: added.map { forked_champs_index[_1] },
|
||||
updated: updated.map { forked_champs_index[_1] },
|
||||
removed: removed.map { origin_champs_index[_1] }
|
||||
}
|
||||
end
|
||||
|
||||
def merge_fork(editing_fork)
|
||||
return false if invalid? || editing_fork.invalid?
|
||||
return false if revision_id > editing_fork.revision_id
|
||||
|
||||
diff = make_diff(editing_fork)
|
||||
|
||||
transaction do
|
||||
apply_diff(diff)
|
||||
update(revision_id: editing_fork.revision_id, last_champ_updated_at: Time.zone.now)
|
||||
assign_to_groupe_instructeur(editing_fork.groupe_instructeur)
|
||||
end
|
||||
reload
|
||||
update_search_terms_later
|
||||
editing_fork.destroy_editing_fork!
|
||||
end
|
||||
|
||||
def clone(user: nil, fork: false)
|
||||
dossier_attributes = [:autorisation_donnees, :revision_id, :groupe_instructeur_id]
|
||||
relationships = [:individual, :etablissement]
|
||||
|
||||
cloned_champs = champs
|
||||
.index_by(&:id)
|
||||
.transform_values { [_1, _1.clone(fork)] }
|
||||
|
||||
cloned_dossier = deep_clone(only: dossier_attributes, include: relationships) do |original, kopy|
|
||||
PiecesJustificativesService.clone_attachments(original, kopy)
|
||||
|
||||
if original.is_a?(Dossier)
|
||||
if fork
|
||||
kopy.editing_fork_origin = original
|
||||
else
|
||||
kopy.parent_dossier = original
|
||||
end
|
||||
|
||||
kopy.user = user || original.user
|
||||
kopy.state = Dossier.states.fetch(:brouillon)
|
||||
|
||||
kopy.champs = cloned_champs.values.map do |(_, champ)|
|
||||
champ.dossier = kopy
|
||||
champ.parent = cloned_champs[champ.parent_id].second if champ.child?
|
||||
champ
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
transaction do
|
||||
cloned_dossier.save!
|
||||
|
||||
if fork
|
||||
cloned_champs.values.each do |(original, champ)|
|
||||
champ.update_columns(created_at: original.created_at, updated_at: original.updated_at)
|
||||
end
|
||||
cloned_dossier.rebase!
|
||||
end
|
||||
end
|
||||
|
||||
cloned_dossier.reload
|
||||
end
|
||||
|
||||
def forked_with_changes?
|
||||
if forked_diff.present?
|
||||
forked_diff.values.any?(&:present?) || forked_groupe_instructeur_changed?
|
||||
end
|
||||
end
|
||||
|
||||
def champ_forked_with_changes?(champ)
|
||||
if forked_diff.present?
|
||||
forked_diff.values.any? { _1.include?(champ) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def forked_diff
|
||||
@forked_diff ||= editing_fork? ? editing_fork_origin.make_diff(self) : nil
|
||||
end
|
||||
|
||||
def forked_groupe_instructeur_changed?
|
||||
editing_fork_origin.groupe_instructeur_id != groupe_instructeur_id
|
||||
end
|
||||
|
||||
def apply_diff(diff)
|
||||
champs_index = (champs + diff[:added]).index_by(&:stable_id_with_row)
|
||||
|
||||
diff[:added].each do |champ|
|
||||
if champ.child?
|
||||
champ.update_columns(dossier_id: id, parent_id: champs_index[champ.parent.stable_id_with_row].id)
|
||||
else
|
||||
champ.update_column(:dossier_id, id)
|
||||
end
|
||||
end
|
||||
|
||||
champs_to_remove = []
|
||||
diff[:updated].each do |champ|
|
||||
old_champ = champs_index[champ.stable_id_with_row]
|
||||
champs_to_remove << old_champ
|
||||
|
||||
if champ.child?
|
||||
# we need to do that in order to avoid a foreign key constraint
|
||||
old_champ.update(row_id: nil)
|
||||
champ.update_columns(dossier_id: id, parent_id: champs_index[champ.parent.stable_id_with_row].id)
|
||||
else
|
||||
champ.update_column(:dossier_id, id)
|
||||
end
|
||||
end
|
||||
|
||||
champs_to_remove += diff[:removed]
|
||||
champs_to_remove
|
||||
.filter { !_1.child? || !champs_to_remove.include?(_1.parent) }
|
||||
.each(&:destroy!)
|
||||
end
|
||||
end
|
23
app/models/concerns/dossier_searchable_concern.rb
Normal file
23
app/models/concerns/dossier_searchable_concern.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
module DossierSearchableConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_save :update_search_terms
|
||||
|
||||
def update_search_terms
|
||||
self.search_terms = [
|
||||
user&.email,
|
||||
*champs_public.flat_map(&:search_terms),
|
||||
*etablissement&.search_terms,
|
||||
individual&.nom,
|
||||
individual&.prenom
|
||||
].compact_blank.join(' ')
|
||||
|
||||
self.private_search_terms = champs_private.flat_map(&:search_terms).compact_blank.join(' ')
|
||||
end
|
||||
|
||||
def update_search_terms_later
|
||||
DossierUpdateSearchTermsJob.perform_later(self)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -40,6 +40,7 @@
|
|||
# updated_at :datetime
|
||||
# batch_operation_id :bigint
|
||||
# dossier_transfer_id :bigint
|
||||
# editing_fork_origin_id :bigint
|
||||
# groupe_instructeur_id :bigint
|
||||
# parent_dossier_id :bigint
|
||||
# revision_id :bigint
|
||||
|
@ -49,7 +50,9 @@ class Dossier < ApplicationRecord
|
|||
include DossierFilteringConcern
|
||||
include DossierPrefillableConcern
|
||||
include DossierRebaseConcern
|
||||
include DossierSearchableConcern
|
||||
include DossierSectionsConcern
|
||||
include DossierCloneConcern
|
||||
|
||||
enum state: {
|
||||
brouillon: 'brouillon',
|
||||
|
@ -148,7 +151,6 @@ class Dossier < ApplicationRecord
|
|||
belongs_to :groupe_instructeur, optional: true
|
||||
belongs_to :revision, class_name: 'ProcedureRevision', optional: false
|
||||
belongs_to :user, optional: true
|
||||
belongs_to :parent_dossier, class_name: 'Dossier', optional: true
|
||||
belongs_to :batch_operation, optional: true
|
||||
has_many :dossier_batch_operations, dependent: :destroy
|
||||
has_many :batch_operations, through: :dossier_batch_operations
|
||||
|
@ -161,7 +163,6 @@ class Dossier < ApplicationRecord
|
|||
|
||||
belongs_to :transfer, class_name: 'DossierTransfer', foreign_key: 'dossier_transfer_id', optional: true, inverse_of: :dossiers
|
||||
has_many :transfer_logs, class_name: 'DossierTransferLog', dependent: :destroy
|
||||
has_many :cloned_dossiers, class_name: 'Dossier', foreign_key: 'parent_dossier_id', dependent: :nullify, inverse_of: :parent_dossier
|
||||
|
||||
accepts_nested_attributes_for :champs
|
||||
accepts_nested_attributes_for :champs_public
|
||||
|
@ -236,7 +237,7 @@ class Dossier < ApplicationRecord
|
|||
scope :prefilled, -> { where(prefilled: true) }
|
||||
scope :hidden_by_user, -> { where.not(hidden_by_user_at: nil) }
|
||||
scope :hidden_by_administration, -> { where.not(hidden_by_administration_at: nil) }
|
||||
scope :visible_by_user, -> { where(for_procedure_preview: false).or(where(for_procedure_preview: nil)).where(hidden_by_user_at: nil) }
|
||||
scope :visible_by_user, -> { where(for_procedure_preview: false).or(where(for_procedure_preview: nil)).where(hidden_by_user_at: nil, editing_fork_origin_id: nil) }
|
||||
scope :visible_by_administration, -> {
|
||||
state_not_brouillon
|
||||
.where(hidden_by_administration_at: nil)
|
||||
|
@ -247,6 +248,7 @@ class Dossier < ApplicationRecord
|
|||
state_not_brouillon.hidden_by_administration.or(state_en_construction.hidden_by_user)
|
||||
}
|
||||
scope :for_procedure_preview, -> { where(for_procedure_preview: true) }
|
||||
scope :for_editing_fork, -> { where.not(editing_fork_origin_id: nil) }
|
||||
|
||||
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) }
|
||||
scope :order_by_created_at, -> (order = :asc) { order(depose_at: order, created_at: order, id: order) }
|
||||
|
@ -453,8 +455,7 @@ class Dossier < ApplicationRecord
|
|||
delegate :siret, :siren, to: :etablissement, allow_nil: true
|
||||
delegate :france_connect_information, to: :user, allow_nil: true
|
||||
|
||||
before_save :build_default_champs_for_new_dossier, if: Proc.new { revision_id_was.nil? && parent_dossier_id.nil? }
|
||||
before_save :update_search_terms
|
||||
before_save :build_default_champs_for_new_dossier, if: Proc.new { revision_id_was.nil? && parent_dossier_id.nil? && editing_fork_origin_id.nil? }
|
||||
|
||||
after_save :send_web_hook
|
||||
|
||||
|
@ -503,17 +504,6 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def update_search_terms
|
||||
self.search_terms = [
|
||||
user&.email,
|
||||
*champs_public.flat_map(&:search_terms),
|
||||
*etablissement&.search_terms,
|
||||
individual&.nom,
|
||||
individual&.prenom
|
||||
].compact.join(' ')
|
||||
self.private_search_terms = champs_private.flat_map(&:search_terms).compact.join(' ')
|
||||
end
|
||||
|
||||
def build_default_champs_for_new_dossier
|
||||
revision.build_champs_public.each do |champ|
|
||||
champs_public << champ
|
||||
|
@ -556,7 +546,7 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
|
||||
def can_transition_to_en_construction?
|
||||
brouillon? && procedure.dossier_can_transition_to_en_construction? && !for_procedure_preview?
|
||||
brouillon? && procedure.dossier_can_transition_to_en_construction? && !for_procedure_preview? && !editing_fork?
|
||||
end
|
||||
|
||||
def can_terminer?
|
||||
|
@ -679,20 +669,16 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
|
||||
def assign_to_groupe_instructeur(groupe_instructeur, author = nil)
|
||||
if (groupe_instructeur.nil? || groupe_instructeur.procedure == procedure) && self.groupe_instructeur != groupe_instructeur
|
||||
if update(groupe_instructeur:, groupe_instructeur_updated_at: Time.zone.now)
|
||||
if !brouillon?
|
||||
unfollow_stale_instructeurs
|
||||
return if groupe_instructeur.present? && groupe_instructeur.procedure != procedure
|
||||
return if self.groupe_instructeur == groupe_instructeur
|
||||
|
||||
if author.present?
|
||||
log_dossier_operation(author, :changer_groupe_instructeur, self)
|
||||
end
|
||||
end
|
||||
update!(groupe_instructeur:, groupe_instructeur_updated_at: Time.zone.now)
|
||||
|
||||
true
|
||||
if !brouillon?
|
||||
unfollow_stale_instructeurs
|
||||
if author.present?
|
||||
log_dossier_operation(author, :changer_groupe_instructeur, self)
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1238,32 +1224,6 @@ class Dossier < ApplicationRecord
|
|||
termine_expired_to_delete.find_each(&:purge_discarded)
|
||||
end
|
||||
|
||||
def clone
|
||||
dossier_attributes = [:autorisation_donnees, :user_id, :revision_id, :groupe_instructeur_id]
|
||||
relationships = [:individual, :etablissement]
|
||||
|
||||
cloned_dossier = deep_clone(only: dossier_attributes, include: relationships) do |original, kopy|
|
||||
PiecesJustificativesService.clone_attachments(original, kopy)
|
||||
|
||||
if original.is_a?(Dossier)
|
||||
kopy.parent_dossier_id = original.id
|
||||
kopy.state = Dossier.states.fetch(:brouillon)
|
||||
cloned_champs = original.champs
|
||||
.index_by(&:id)
|
||||
.transform_values(&:clone)
|
||||
|
||||
kopy.champs = cloned_champs.values.map do |champ|
|
||||
champ.dossier = kopy
|
||||
champ.parent = cloned_champs[champ.parent_id] if champ.child?
|
||||
champ
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
transaction { cloned_dossier.save! }
|
||||
cloned_dossier.reload
|
||||
end
|
||||
|
||||
def find_champs_by_stable_ids(stable_ids)
|
||||
return [] if stable_ids.compact.empty?
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
module RoutingEngine
|
||||
def self.compute(dossier)
|
||||
return if !dossier.procedure.feature_enabled?(:routing_rules)
|
||||
|
||||
matching_groupe = dossier.procedure.groupe_instructeurs.active.find do |gi|
|
||||
gi.routing_rule&.compute(dossier.champs)
|
||||
end
|
||||
matching_groupe ||= dossier.procedure.defaut_groupe_instructeur
|
||||
dossier.update!(groupe_instructeur: matching_groupe)
|
||||
dossier.assign_to_groupe_instructeur(matching_groupe)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
- dossier_for_editing = dossier.en_construction? ? dossier.owner_editing_fork : dossier
|
||||
|
||||
- if dossier.france_connect_information.present?
|
||||
- content_for(:notice_info) do
|
||||
= render partial: "shared/dossiers/france_connect_informations_notice", locals: { user_information: dossier.france_connect_information }
|
||||
|
||||
.dossier-edit.container.counter-start-header-section
|
||||
= render partial: "shared/dossiers/submit_is_over", locals: { dossier: dossier }
|
||||
|
||||
- if dossier.brouillon?
|
||||
- form_options = { url: brouillon_dossier_url(dossier), method: :patch }
|
||||
- else
|
||||
- form_options = { url: modifier_dossier_url(dossier), method: :patch }
|
||||
= render NestedForms::FormOwnerComponent.new
|
||||
= form_for dossier, form_options.merge({ html: { id: 'dossier-edit-form', class: 'form', multipart: true, novalidate: 'novalidate' } }) do |f|
|
||||
|
||||
= form_for dossier_for_editing, url: brouillon_dossier_url(dossier), method: :patch, html: { id: 'dossier-edit-form', class: 'form', multipart: true, novalidate: 'novalidate' } do |f|
|
||||
%header.mb-6
|
||||
.fr-highlight
|
||||
%p.fr-text--sm
|
||||
|
@ -42,5 +38,7 @@
|
|||
= f.select :groupe_instructeur_id,
|
||||
dossier.procedure.groupe_instructeurs.active.map { |gi| [gi.label, gi.id] },
|
||||
{ include_blank: dossier.brouillon? }
|
||||
= render EditableChamp::SectionComponent.new(champs: dossier.champs_public)
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier, annotation: false)
|
||||
|
||||
|
||||
= render EditableChamp::SectionComponent.new(champs: dossier_for_editing.champs_public)
|
||||
= render Dossiers::EditFooterComponent.new(dossier: dossier_for_editing, annotation: false)
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
.dossier-container.mb-4
|
||||
= render partial: 'users/dossiers/show/header', locals: { dossier: @dossier }
|
||||
|
||||
- if @dossier.en_construction?
|
||||
.fr-container
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-md-10.fr-col-lg-9
|
||||
= render Dossiers::EnConstructionNotSubmittedComponent.new(dossier: @dossier, user: current_user)
|
||||
|
||||
= render partial: 'shared/dossiers/demande', locals: { dossier: @dossier, demande_seen_at: nil, profile: 'usager' }
|
||||
|
||||
.container
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
.dossier-container.mb-4
|
||||
= render partial: 'users/dossiers/show/header', locals: { dossier: @dossier }
|
||||
|
||||
.container
|
||||
.fr-container
|
||||
= render partial: 'users/dossiers/show/status_overview', locals: { dossier: @dossier }
|
||||
= render partial: 'users/dossiers/show/papertrail', locals: { dossier: @dossier }
|
||||
|
||||
|
|
|
@ -1,80 +1,87 @@
|
|||
.status-overview
|
||||
.fr-mb-4w
|
||||
- if !dossier.termine?
|
||||
%ul.status-timeline
|
||||
- if dossier.brouillon?
|
||||
%li.brouillon{ class: dossier.brouillon? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_draft')
|
||||
%li.en-construction{ class: dossier.en_construction? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_in_progress')
|
||||
%li.en-instruction{ class: dossier.en_instruction? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_review')
|
||||
%li.termine{ class: dossier.termine? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_completed')
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-md-8.text-center
|
||||
%ul.status-timeline.fr-mb-4w
|
||||
- if dossier.brouillon?
|
||||
%li.brouillon{ class: dossier.brouillon? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_draft')
|
||||
%li.en-construction{ class: dossier.en_construction? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_in_progress')
|
||||
%li.en-instruction{ class: dossier.en_instruction? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_review')
|
||||
%li.termine{ class: dossier.termine? ? 'active' : nil }
|
||||
= t('views.users.dossiers.show.status_overview.status_completed')
|
||||
|
||||
- if dossier.en_construction?
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-md-10.fr-col-lg-9
|
||||
= render Dossiers::EnConstructionNotSubmittedComponent.new(dossier: dossier, user: current_user)
|
||||
|
||||
.fr-grid-row.fr-grid-row--center
|
||||
.fr-col-md-10.fr-col-lg-9.status-explanation
|
||||
-# brouillon does not occure
|
||||
- if dossier.en_construction?
|
||||
.en-construction
|
||||
%p{ role: 'status' }
|
||||
= t('views.users.dossiers.show.status_overview.en_construction_html')
|
||||
|
||||
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
||||
|
||||
%p
|
||||
= t('views.users.dossiers.show.status_overview.use_mailbox_for_questions_html', mailbox_url: messagerie_dossier_url(dossier))
|
||||
|
||||
- elsif dossier.en_instruction?
|
||||
.en-instruction
|
||||
%p{ role: 'status' }
|
||||
= t('views.users.dossiers.show.status_overview.admin_review')
|
||||
|
||||
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
||||
|
||||
%p
|
||||
= t('views.users.dossiers.show.status_overview.use_mailbox_for_questions_html', mailbox_url: messagerie_dossier_url(dossier))
|
||||
|
||||
- elsif dossier.accepte?
|
||||
.accepte
|
||||
%p.decision{ role: 'status' }
|
||||
%span.icon.accept
|
||||
= t('views.users.dossiers.show.status_overview.acceptee_html')
|
||||
|
||||
- if dossier.motivation.present?
|
||||
%h3= t('views.users.dossiers.show.status_overview.accepte_motivation')
|
||||
%blockquote= dossier.motivation
|
||||
|
||||
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||
|
||||
- if dossier.attestation.present?
|
||||
.action
|
||||
= link_to attestation_dossier_path(dossier), target: '_blank', rel: 'noopener', class: 'button primary' do
|
||||
%span.icon.download-white
|
||||
= t('views.users.dossiers.show.status_overview.accepte_attestation')
|
||||
|
||||
|
||||
.status-explanation
|
||||
-# brouillon does not occure
|
||||
- if dossier.en_construction?
|
||||
.en-construction
|
||||
%p{ role: 'status' }
|
||||
= t('views.users.dossiers.show.status_overview.en_construction_html')
|
||||
- elsif dossier.refuse?
|
||||
.refuse
|
||||
%p.decision{ role: 'status' }
|
||||
%span.icon.refuse
|
||||
= t('views.users.dossiers.show.status_overview.refuse_html')
|
||||
|
||||
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
||||
- if dossier.motivation.present?
|
||||
%h3= t('views.users.dossiers.show.status_overview.refuse_motivation')
|
||||
%blockquote= dossier.motivation
|
||||
|
||||
%p
|
||||
= t('views.users.dossiers.show.status_overview.use_mailbox_for_questions_html', mailbox_url: messagerie_dossier_url(dossier))
|
||||
|
||||
- elsif dossier.en_instruction?
|
||||
.en-instruction
|
||||
%p{ role: 'status' }
|
||||
= t('views.users.dossiers.show.status_overview.admin_review')
|
||||
|
||||
= render partial: 'users/dossiers/show/estimated_delay', locals: { procedure: dossier.procedure }
|
||||
|
||||
%p
|
||||
= t('views.users.dossiers.show.status_overview.use_mailbox_for_questions_html', mailbox_url: messagerie_dossier_url(dossier))
|
||||
|
||||
- elsif dossier.accepte?
|
||||
.accepte
|
||||
%p.decision{ role: 'status' }
|
||||
%span.icon.accept
|
||||
= t('views.users.dossiers.show.status_overview.acceptee_html')
|
||||
|
||||
- if dossier.motivation.present?
|
||||
%h3= t('views.users.dossiers.show.status_overview.accepte_motivation')
|
||||
%blockquote= dossier.motivation
|
||||
|
||||
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||
|
||||
- if dossier.attestation.present?
|
||||
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||
.action
|
||||
= link_to attestation_dossier_path(dossier), target: '_blank', rel: 'noopener', class: 'button primary' do
|
||||
%span.icon.download-white
|
||||
= t('views.users.dossiers.show.status_overview.accepte_attestation')
|
||||
= link_to t('views.users.dossiers.show.status_overview.refuse_reply'), messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'fr-link'
|
||||
|
||||
- elsif dossier.sans_suite?
|
||||
.sans-suite
|
||||
%p.decision{ role: 'status' }
|
||||
%span.icon.without-continuation
|
||||
= t('views.users.dossiers.show.status_overview.sans_suite_html')
|
||||
|
||||
- elsif dossier.refuse?
|
||||
.refuse
|
||||
%p.decision{ role: 'status' }
|
||||
%span.icon.refuse
|
||||
= t('views.users.dossiers.show.status_overview.refuse_html')
|
||||
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||
|
||||
- if dossier.motivation.present?
|
||||
%h3= t('views.users.dossiers.show.status_overview.refuse_motivation')
|
||||
%blockquote= dossier.motivation
|
||||
|
||||
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||
.action
|
||||
= link_to t('views.users.dossiers.show.status_overview.refuse_reply'), messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'fr-link'
|
||||
|
||||
- elsif dossier.sans_suite?
|
||||
.sans-suite
|
||||
%p.decision{ role: 'status' }
|
||||
%span.icon.without-continuation
|
||||
= t('views.users.dossiers.show.status_overview.sans_suite_html')
|
||||
|
||||
= render partial: 'users/dossiers/show/download_justificatif', locals: { dossier: dossier }
|
||||
|
||||
- if dossier.motivation.present?
|
||||
%h3= t('views.users.dossiers.show.status_overview.sans_suite_motivation')
|
||||
%blockquote= dossier.motivation
|
||||
- if dossier.motivation.present?
|
||||
%h3= t('views.users.dossiers.show.status_overview.sans_suite_motivation')
|
||||
%blockquote= dossier.motivation
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
= turbo_stream.hide_all(@to_hide)
|
||||
- @to_update.each do |champ|
|
||||
= fields_for champ.input_name, champ do |form|
|
||||
= turbo_stream.replace champ.input_group_id do
|
||||
= render EditableChamp::EditableChampComponent.new champ:, form:
|
||||
- if champ.refresh_after_update?
|
||||
= turbo_stream.replace champ.input_group_id do
|
||||
= render EditableChamp::EditableChampComponent.new champ:, form:
|
||||
- else
|
||||
= turbo_stream.update champ.labelledby_id do
|
||||
= render EditableChamp::ChampLabelContentComponent.new champ:
|
||||
|
||||
= turbo_stream.remove_all(".editable-champ .spinner-removable");
|
||||
= turbo_stream.hide_all(".editable-champ .spinner");
|
||||
= turbo_stream.replace_all '.dossier-edit-sticky-footer' do
|
||||
= render Dossiers::EditFooterComponent.new(dossier: @dossier, annotation: false)
|
||||
|
|
|
@ -315,10 +315,11 @@ Rails.application.routes.draw do
|
|||
post 'siret', to: 'dossiers#update_siret'
|
||||
get 'etablissement'
|
||||
get 'brouillon'
|
||||
patch 'brouillon', to: 'dossiers#update_brouillon'
|
||||
patch 'brouillon', to: 'dossiers#update'
|
||||
post 'brouillon', to: 'dossiers#submit_brouillon'
|
||||
get 'modifier', to: 'dossiers#modifier'
|
||||
patch 'modifier', to: 'dossiers#update'
|
||||
post 'modifier', to: 'dossiers#submit_en_construction'
|
||||
patch 'modifier', to: 'dossiers#modifier_legacy'
|
||||
get 'merci'
|
||||
get 'demande'
|
||||
get 'messagerie'
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
class AddEditingForksToDossiers < ActiveRecord::Migration[6.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_belongs_to :dossiers, :editing_fork_origin, null: true, index: { algorithm: :concurrently }
|
||||
end
|
||||
end
|
|
@ -362,6 +362,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_02_160046) do
|
|||
t.string "deleted_user_email_never_send"
|
||||
t.datetime "depose_at", precision: 6
|
||||
t.bigint "dossier_transfer_id"
|
||||
t.bigint "editing_fork_origin_id"
|
||||
t.datetime "en_construction_at", precision: 6
|
||||
t.datetime "en_construction_close_to_expiration_notice_sent_at", precision: 6
|
||||
t.datetime "en_instruction_at", precision: 6
|
||||
|
@ -393,6 +394,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_02_160046) do
|
|||
t.index ["archived"], name: "index_dossiers_on_archived"
|
||||
t.index ["batch_operation_id"], name: "index_dossiers_on_batch_operation_id"
|
||||
t.index ["dossier_transfer_id"], name: "index_dossiers_on_dossier_transfer_id"
|
||||
t.index ["editing_fork_origin_id"], name: "index_dossiers_on_editing_fork_origin_id"
|
||||
t.index ["groupe_instructeur_id"], name: "index_dossiers_on_groupe_instructeur_id"
|
||||
t.index ["hidden_at"], name: "index_dossiers_on_hidden_at"
|
||||
t.index ["prefill_token"], name: "index_dossiers_on_prefill_token", unique: true
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Dossiers::EnConstructionNotSubmittedComponent, type: :component do
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
|
||||
subject {
|
||||
render_inline(described_class.new(dossier:, user: dossier.user)).to_html
|
||||
}
|
||||
|
||||
context "without fork" do
|
||||
it { expect(subject).to be_empty }
|
||||
end
|
||||
|
||||
context "with a fork" do
|
||||
let!(:fork) { dossier.find_or_create_editing_fork(dossier.user) }
|
||||
|
||||
it "render nothing without changes" do
|
||||
expect(subject).to be_empty
|
||||
end
|
||||
|
||||
context "with changes" do
|
||||
before { fork.champs_public.first.update(value: "new value") }
|
||||
|
||||
it "inform user" do
|
||||
expect(subject).to include("Des modifications n’ont pas encore été déposées")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -445,11 +445,80 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#update_brouillon' do
|
||||
describe '#submit_en_construction' do
|
||||
before { sign_in(user) }
|
||||
|
||||
let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_piece_justificative) }
|
||||
let!(:dossier) { create(:dossier, user: user, procedure: procedure) }
|
||||
let!(:dossier) { create(:dossier, :en_construction, user: user) }
|
||||
let(:first_champ) { dossier.owner_editing_fork.champs_public.first }
|
||||
let(:anchor_to_first_champ) { controller.helpers.link_to I18n.t('views.users.dossiers.fix_champ'), modifier_dossier_path(anchor: first_champ.input_id) }
|
||||
let(:value) { 'beautiful value' }
|
||||
let(:now) { Time.zone.parse('01/01/2100') }
|
||||
let(:payload) { { id: dossier.id } }
|
||||
|
||||
before { dossier.owner_editing_fork }
|
||||
|
||||
subject do
|
||||
Timecop.freeze(now) do
|
||||
post :submit_en_construction, params: payload
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the dossier cannot be updated by the user' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, user: user) }
|
||||
|
||||
it 'redirects to the dossiers list' do
|
||||
subject
|
||||
|
||||
expect(response).to redirect_to(dossier_path(dossier))
|
||||
expect(flash.alert).to eq('Votre dossier ne peut plus être modifié')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the update fails' do
|
||||
render_views
|
||||
|
||||
before do
|
||||
expect_any_instance_of(Dossier).to receive(:valid?).and_return(false)
|
||||
expect_any_instance_of(Dossier).to receive(:errors).and_return(
|
||||
[double(class: ActiveModel::Error, full_message: 'nop', base: first_champ)]
|
||||
)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it { expect(response).to render_template(:modifier) }
|
||||
it { expect(flash.alert).to eq(["Le champ « #{first_champ.libelle} » nop, #{anchor_to_first_champ}"]) }
|
||||
it { expect(response.body).to include("Dossier nº #{dossier.id}") }
|
||||
end
|
||||
|
||||
context 'when a mandatory champ is missing' do
|
||||
let(:value) { nil }
|
||||
|
||||
before do
|
||||
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
|
||||
subject
|
||||
end
|
||||
|
||||
it { expect(response).to render_template(:modifier) }
|
||||
it { expect(flash.alert).to eq(["Le champ « l » doit être rempli, #{anchor_to_first_champ}"]) }
|
||||
end
|
||||
|
||||
context 'when dossier has no champ' do
|
||||
let(:submit_payload) { { id: dossier.id } }
|
||||
|
||||
it 'does not raise any errors' do
|
||||
subject
|
||||
|
||||
expect(response).to redirect_to(dossier_path(dossier))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update brouillon' do
|
||||
before { sign_in(user) }
|
||||
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{}, { type: :piece_justificative }]) }
|
||||
let(:dossier) { create(:dossier, user:, procedure:) }
|
||||
let(:first_champ) { dossier.champs_public.first }
|
||||
let(:piece_justificative_champ) { dossier.champs_public.last }
|
||||
let(:value) { 'beautiful value' }
|
||||
|
@ -461,16 +530,16 @@ describe Users::DossiersController, type: :controller do
|
|||
id: dossier.id,
|
||||
dossier: {
|
||||
groupe_instructeur_id: dossier.groupe_instructeur_id,
|
||||
champs_public_attributes: [
|
||||
{
|
||||
champs_public_attributes: {
|
||||
first_champ.id => {
|
||||
id: first_champ.id,
|
||||
value: value
|
||||
},
|
||||
{
|
||||
piece_justificative_champ.id => {
|
||||
id: piece_justificative_champ.id,
|
||||
piece_justificative_file: file
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -478,12 +547,12 @@ describe Users::DossiersController, type: :controller do
|
|||
|
||||
subject do
|
||||
Timecop.freeze(now) do
|
||||
patch :update_brouillon, params: payload
|
||||
patch :update, params: payload, format: :turbo_stream
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the dossier cannot be updated by the user' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, user: user) }
|
||||
let(:dossier) { create(:dossier, :en_instruction, user:, procedure:) }
|
||||
|
||||
it 'redirects to the dossiers list' do
|
||||
subject
|
||||
|
@ -507,7 +576,7 @@ describe Users::DossiersController, type: :controller do
|
|||
{
|
||||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: [{ value: '' }]
|
||||
champs_public_attributes: { first_champ.id => { id: first_champ.id } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -520,7 +589,7 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
|
||||
context 'when the user has an invitation but is not the owner' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
||||
let!(:invite) { create(:invite, dossier: dossier, user: user) }
|
||||
|
||||
before { subject }
|
||||
|
@ -530,11 +599,11 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#update' do
|
||||
describe '#update en_construction' do
|
||||
before { sign_in(user) }
|
||||
|
||||
let(:procedure) { create(:procedure, :published, :with_type_de_champ, :with_piece_justificative) }
|
||||
let!(:dossier) { create(:dossier, :en_construction, user: user, procedure: procedure) }
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{}, { type: :piece_justificative }]) }
|
||||
let!(:dossier) { create(:dossier, :en_construction, user:, procedure:) }
|
||||
let(:first_champ) { dossier.champs_public.first }
|
||||
let(:anchor_to_first_champ) { controller.helpers.link_to I18n.t('views.users.dossiers.fix_champ'), brouillon_dossier_path(anchor: first_champ.input_id) }
|
||||
let(:piece_justificative_champ) { dossier.champs_public.last }
|
||||
|
@ -547,16 +616,16 @@ describe Users::DossiersController, type: :controller do
|
|||
id: dossier.id,
|
||||
dossier: {
|
||||
groupe_instructeur_id: dossier.groupe_instructeur_id,
|
||||
champs_public_attributes: [
|
||||
{
|
||||
champs_public_attributes: {
|
||||
first_champ.id => {
|
||||
id: first_champ.id,
|
||||
value: value
|
||||
},
|
||||
{
|
||||
piece_justificative_champ.id => {
|
||||
id: piece_justificative_champ.id,
|
||||
piece_justificative_file: file
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -564,12 +633,12 @@ describe Users::DossiersController, type: :controller do
|
|||
|
||||
subject do
|
||||
Timecop.freeze(now) do
|
||||
patch :update, params: payload
|
||||
patch :update, params: payload, format: :turbo_stream
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the dossier cannot be updated by the user' do
|
||||
let!(:dossier) { create(:dossier, :en_instruction, user: user) }
|
||||
let!(:dossier) { create(:dossier, :en_instruction, user:, procedure:) }
|
||||
|
||||
it 'redirects to the dossiers list' do
|
||||
subject
|
||||
|
@ -612,12 +681,12 @@ describe Users::DossiersController, type: :controller do
|
|||
{
|
||||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: [
|
||||
{
|
||||
champs_public_attributes: {
|
||||
piece_justificative_champ.id => {
|
||||
id: piece_justificative_champ.id,
|
||||
piece_justificative_file: file
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
@ -640,7 +709,7 @@ describe Users::DossiersController, type: :controller do
|
|||
subject
|
||||
end
|
||||
|
||||
it { expect(response).to render_template(:modifier) }
|
||||
it { expect(response).to render_template(:update) }
|
||||
it { expect(flash.alert).to eq(["Le champ « #{first_champ.libelle} » nop, #{anchor_to_first_champ}"]) }
|
||||
|
||||
it 'does not update the dossier timestamps' do
|
||||
|
@ -656,18 +725,6 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when a mandatory champ is missing' do
|
||||
let(:value) { nil }
|
||||
|
||||
before do
|
||||
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
|
||||
subject
|
||||
end
|
||||
|
||||
it { expect(response).to render_template(:modifier) }
|
||||
it { expect(flash.alert).to eq(["Le champ « l » doit être rempli, #{anchor_to_first_champ}"]) }
|
||||
end
|
||||
|
||||
context 'when a champ validation fails' do
|
||||
let(:value) { 'abc' }
|
||||
|
||||
|
@ -682,8 +739,8 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
|
||||
context 'when the user has an invitation but is not the owner' do
|
||||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
let!(:invite) { create(:invite, dossier: dossier, user: user) }
|
||||
let(:dossier) { create(:dossier, :en_construction, procedure:) }
|
||||
let!(:invite) { create(:invite, dossier:, user:) }
|
||||
|
||||
before { subject }
|
||||
|
||||
|
@ -692,9 +749,9 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
|
||||
context 'when the dossier is followed by an instructeur' do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:dossier) { create(:dossier, procedure:) }
|
||||
let(:instructeur) { create(:instructeur) }
|
||||
let!(:invite) { create(:invite, dossier: dossier, user: user) }
|
||||
let!(:invite) { create(:invite, dossier:, user:) }
|
||||
|
||||
before do
|
||||
instructeur.follow(dossier)
|
||||
|
@ -708,8 +765,8 @@ describe Users::DossiersController, type: :controller do
|
|||
end
|
||||
|
||||
context 'when the champ is a phone number' do
|
||||
let(:procedure) { create(:procedure, :published, :with_phone) }
|
||||
let!(:dossier) { create(:dossier, :en_construction, user: user, procedure: procedure) }
|
||||
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :phone }]) }
|
||||
let!(:dossier) { create(:dossier, :en_construction, user:, procedure:) }
|
||||
let(:first_champ) { dossier.champs_public.first }
|
||||
let(:now) { Time.zone.parse('01/01/2100') }
|
||||
|
||||
|
@ -717,12 +774,12 @@ describe Users::DossiersController, type: :controller do
|
|||
{
|
||||
id: dossier.id,
|
||||
dossier: {
|
||||
champs_public_attributes: [
|
||||
{
|
||||
champs_public_attributes: {
|
||||
first_champ.id => {
|
||||
id: first_champ.id,
|
||||
value: value
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
15
spec/jobs/dossier_update_search_terms_job_spec.rb
Normal file
15
spec/jobs/dossier_update_search_terms_job_spec.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
RSpec.describe DossierUpdateSearchTermsJob, type: :job do
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:champ_public) { dossier.champs_public.first }
|
||||
let(:champ_private) { dossier.champs_private.first }
|
||||
|
||||
subject(:perform_job) { described_class.perform_now(dossier) }
|
||||
|
||||
context 'with an update' do
|
||||
before do
|
||||
create(:champ_text, dossier: dossier, value: "un nouveau champ")
|
||||
end
|
||||
|
||||
it { expect { perform_job }.to change { dossier.reload.search_terms }.to(/un nouveau champ/) }
|
||||
end
|
||||
end
|
128
spec/models/concern/dossier_clone_concern_spec.rb
Normal file
128
spec/models/concern/dossier_clone_concern_spec.rb
Normal file
|
@ -0,0 +1,128 @@
|
|||
RSpec.describe DossierCloneConcern do
|
||||
let(:procedure) do
|
||||
create(:procedure, types_de_champ_public: [
|
||||
{ type: :text, libelle: "Un champ text", stable_id: 99 },
|
||||
{ type: :text, libelle: "Un autre champ text", stable_id: 991 },
|
||||
{ type: :yes_no, libelle: "Un champ yes no", stable_id: 992 },
|
||||
{ type: :repetition, libelle: "Un champ répétable", stable_id: 993, mandatory: true, children: [{ type: :text, libelle: 'Nom', stable_id: 994 }] }
|
||||
])
|
||||
end
|
||||
let(:dossier) { create(:dossier, procedure:) }
|
||||
let(:forked_dossier) { dossier.find_or_create_editing_fork(dossier.user) }
|
||||
|
||||
before { procedure.publish! }
|
||||
|
||||
describe '#make_diff' do
|
||||
subject { dossier.make_diff(forked_dossier) }
|
||||
|
||||
context 'with no changes' do
|
||||
it { is_expected.to eq(added: [], updated: [], removed: []) }
|
||||
end
|
||||
|
||||
context 'with updated groupe instructeur' do
|
||||
before {
|
||||
dossier.update(groupe_instructeur: nil)
|
||||
forked_dossier.assign_to_groupe_instructeur(dossier.procedure.defaut_groupe_instructeur)
|
||||
}
|
||||
|
||||
it { is_expected.to eq(added: [], updated: [], removed: []) }
|
||||
it { expect(forked_dossier.forked_with_changes?).to be_truthy }
|
||||
end
|
||||
|
||||
context 'with updated champ' do
|
||||
let(:updated_champ) { forked_dossier.champs.find { _1.stable_id == 99 } }
|
||||
|
||||
before { updated_champ.update(value: 'new value') }
|
||||
|
||||
it { is_expected.to eq(added: [], updated: [updated_champ], removed: []) }
|
||||
it 'forked_with_changes? should reflect dossier state' do
|
||||
expect(dossier.forked_with_changes?).to be_falsey
|
||||
expect(forked_dossier.forked_with_changes?).to be_truthy
|
||||
expect(updated_champ.forked_with_changes?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'with new revision' do
|
||||
let(:added_champ) { forked_dossier.champs.find { _1.libelle == "Un nouveau champ text" } }
|
||||
let(:removed_champ) { dossier.champs.find { _1.stable_id == 99 } }
|
||||
|
||||
before do
|
||||
procedure.draft_revision.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un nouveau champ text"
|
||||
})
|
||||
procedure.draft_revision.remove_type_de_champ(removed_champ.stable_id)
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it {
|
||||
expect(dossier.revision_id).to eq(procedure.revisions.first.id)
|
||||
expect(forked_dossier.revision_id).to eq(procedure.published_revision_id)
|
||||
is_expected.to eq(added: [added_champ], updated: [], removed: [removed_champ])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge_fork' do
|
||||
subject { dossier.merge_fork(forked_dossier) }
|
||||
|
||||
context 'with updated champ' do
|
||||
let(:updated_champ) { forked_dossier.champs.find { _1.stable_id == 99 } }
|
||||
let(:updated_repetition_champ) { forked_dossier.champs.find { _1.stable_id == 994 } }
|
||||
|
||||
before do
|
||||
dossier.champs.each do |champ|
|
||||
champ.update(value: 'old value')
|
||||
end
|
||||
updated_champ.update(value: 'new value')
|
||||
updated_repetition_champ.update(value: 'new value in repetition')
|
||||
end
|
||||
|
||||
it { expect { subject }.to change { dossier.reload.champs.size }.by(0) }
|
||||
it { expect { subject }.not_to change { dossier.reload.champs.order(:created_at).reject { _1.stable_id.in?([99, 994]) }.map(&:value) } }
|
||||
it { expect { subject }.to change { dossier.reload.champs.find { _1.stable_id == 99 }.value }.from('old value').to('new value') }
|
||||
it { expect { subject }.to change { dossier.reload.champs.find { _1.stable_id == 994 }.value }.from('old value').to('new value in repetition') }
|
||||
|
||||
it 'update dossier search terms' do
|
||||
expect { subject }.to have_enqueued_job(DossierUpdateSearchTermsJob).with(dossier)
|
||||
end
|
||||
|
||||
it 'fork is hidden after merge' do
|
||||
subject
|
||||
expect(forked_dossier.reload.hidden_by_reason).to eq("stale_fork")
|
||||
expect(dossier.reload.editing_forks).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with new revision' do
|
||||
let(:added_champ) { forked_dossier.champs.find { _1.libelle == "Un nouveau champ text" } }
|
||||
let(:removed_champ) { dossier.champs.find { _1.stable_id == 99 } }
|
||||
|
||||
before do
|
||||
dossier.champs.each do |champ|
|
||||
champ.update(value: 'old value')
|
||||
end
|
||||
procedure.draft_revision.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un nouveau champ text"
|
||||
})
|
||||
procedure.draft_revision.remove_type_de_champ(removed_champ.stable_id)
|
||||
procedure.publish_revision!
|
||||
end
|
||||
|
||||
it { expect { subject }.to change { dossier.reload.champs.size }.by(0) }
|
||||
it { expect { subject }.to change { dossier.reload.champs.order(:created_at).map(&:to_s) }.from(['old value', 'old value', 'Non', 'old value', 'old value']).to(['old value', 'Non', 'old value', 'old value', '']) }
|
||||
|
||||
it "dossier after merge should be on last published revision" do
|
||||
expect(dossier.revision_id).to eq(procedure.revisions.first.id)
|
||||
expect(forked_dossier.revision_id).to eq(procedure.published_revision_id)
|
||||
|
||||
subject
|
||||
perform_enqueued_jobs only: DestroyRecordLaterJob
|
||||
|
||||
expect(dossier.revision_id).to eq(procedure.published_revision_id)
|
||||
expect(Dossier.exists?(forked_dossier.id)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
41
spec/models/concern/dossier_searchable_concern_spec.rb
Normal file
41
spec/models/concern/dossier_searchable_concern_spec.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
describe DossierSearchableConcern do
|
||||
let(:champ_public) { dossier.champs_public.first }
|
||||
let(:champ_private) { dossier.champs_private.first }
|
||||
|
||||
subject { dossier }
|
||||
|
||||
describe '#update_search_terms' do
|
||||
let(:etablissement) { dossier.etablissement }
|
||||
let(:dossier) { create(:dossier, :with_entreprise, user: user) }
|
||||
let(:etablissement) { build(:etablissement, entreprise_nom: 'Dupont', entreprise_prenom: 'Thomas', association_rna: '12345', association_titre: 'asso de test', association_objet: 'tests unitaires') }
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) }
|
||||
let(:dossier) { create(:dossier, etablissement: etablissement, user: user, procedure: procedure) }
|
||||
let(:france_connect_information) { build(:france_connect_information, given_name: 'Chris', family_name: 'Harrisson') }
|
||||
let(:user) { build(:user, france_connect_information: france_connect_information) }
|
||||
|
||||
before do
|
||||
champ_public.update_attribute(:value, "champ public")
|
||||
champ_private.update_attribute(:value, "champ privé")
|
||||
|
||||
dossier.update_search_terms
|
||||
end
|
||||
|
||||
it { expect(dossier.search_terms).to eq("#{user.email} champ public #{etablissement.entreprise_siren} #{etablissement.entreprise_numero_tva_intracommunautaire} #{etablissement.entreprise_forme_juridique} #{etablissement.entreprise_forme_juridique_code} #{etablissement.entreprise_nom_commercial} #{etablissement.entreprise_raison_sociale} #{etablissement.entreprise_siret_siege_social} #{etablissement.entreprise_nom} #{etablissement.entreprise_prenom} #{etablissement.association_rna} #{etablissement.association_titre} #{etablissement.association_objet} #{etablissement.siret} #{etablissement.naf} #{etablissement.libelle_naf} #{etablissement.adresse} #{etablissement.code_postal} #{etablissement.localite} #{etablissement.code_insee_localite}") }
|
||||
it { expect(dossier.private_search_terms).to eq('champ privé') }
|
||||
|
||||
context 'with an update' do
|
||||
before do
|
||||
dossier.update(
|
||||
champs_public_attributes: [{ id: champ_public.id, value: 'nouvelle valeur publique' }],
|
||||
champs_private_attributes: [{ id: champ_private.id, value: 'nouvelle valeur privee' }]
|
||||
)
|
||||
|
||||
perform_enqueued_jobs(only: DossierUpdateSearchTermsJob)
|
||||
dossier.reload
|
||||
end
|
||||
|
||||
it { expect(dossier.search_terms).to include('nouvelle valeur publique') }
|
||||
it { expect(dossier.private_search_terms).to include('nouvelle valeur privee') }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -279,38 +279,6 @@ describe Dossier do
|
|||
|
||||
subject { dossier }
|
||||
|
||||
describe '#update_search_terms' do
|
||||
let(:etablissement) { build(:etablissement, entreprise_nom: 'Dupont', entreprise_prenom: 'Thomas', association_rna: '12345', association_titre: 'asso de test', association_objet: 'tests unitaires') }
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) }
|
||||
let(:dossier) { create(:dossier, etablissement: etablissement, user: user, procedure: procedure) }
|
||||
let(:france_connect_information) { build(:france_connect_information, given_name: 'Chris', family_name: 'Harrisson') }
|
||||
let(:user) { build(:user, france_connect_information: france_connect_information) }
|
||||
let(:champ_public) { dossier.champs_public.first }
|
||||
let(:champ_private) { dossier.champs_private.first }
|
||||
|
||||
before do
|
||||
champ_public.update_attribute(:value, "champ public")
|
||||
champ_private.update_attribute(:value, "champ privé")
|
||||
|
||||
dossier.update_search_terms
|
||||
end
|
||||
|
||||
it { expect(dossier.search_terms).to eq("#{user.email} champ public #{etablissement.entreprise_siren} #{etablissement.entreprise_numero_tva_intracommunautaire} #{etablissement.entreprise_forme_juridique} #{etablissement.entreprise_forme_juridique_code} #{etablissement.entreprise_nom_commercial} #{etablissement.entreprise_raison_sociale} #{etablissement.entreprise_siret_siege_social} #{etablissement.entreprise_nom} #{etablissement.entreprise_prenom} #{etablissement.association_rna} #{etablissement.association_titre} #{etablissement.association_objet} #{etablissement.siret} #{etablissement.naf} #{etablissement.libelle_naf} #{etablissement.adresse} #{etablissement.code_postal} #{etablissement.localite} #{etablissement.code_insee_localite}") }
|
||||
it { expect(dossier.private_search_terms).to eq('champ privé') }
|
||||
|
||||
context 'with an update' do
|
||||
before do
|
||||
dossier.update(
|
||||
champs_public_attributes: [{ id: champ_public.id, value: 'nouvelle valeur publique' }],
|
||||
champs_private_attributes: [{ id: champ_private.id, value: 'nouvelle valeur privee' }]
|
||||
)
|
||||
end
|
||||
|
||||
it { expect(dossier.search_terms).to include('nouvelle valeur publique') }
|
||||
it { expect(dossier.private_search_terms).to include('nouvelle valeur privee') }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create' do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure, user: user) }
|
||||
|
@ -612,12 +580,12 @@ describe Dossier do
|
|||
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
|
||||
it "can change groupe instructeur" do
|
||||
expect(dossier.assign_to_groupe_instructeur(new_groupe_instructeur_new_procedure)).to be_falsey
|
||||
dossier.assign_to_groupe_instructeur(new_groupe_instructeur_new_procedure)
|
||||
expect(dossier.groupe_instructeur).not_to eq(new_groupe_instructeur_new_procedure)
|
||||
end
|
||||
|
||||
it "can not change groupe instructeur if new groupe is from another procedure" do
|
||||
expect(dossier.assign_to_groupe_instructeur(new_groupe_instructeur)).to be_truthy
|
||||
dossier.assign_to_groupe_instructeur(new_groupe_instructeur)
|
||||
expect(dossier.groupe_instructeur).to eq(new_groupe_instructeur)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
describe RoutingEngine, type: :model do
|
||||
include Logic
|
||||
|
||||
before { Flipper.enable(:routing_rules, procedure) }
|
||||
|
||||
describe '.compute' do
|
||||
let(:procedure) do
|
||||
create(:procedure).tap do |p|
|
||||
|
|
|
@ -119,6 +119,8 @@ describe 'The routing', js: true do
|
|||
fill_in litteraire_user.dossiers.first.champs_public.first.libelle, with: 'some value'
|
||||
wait_for_autosave(false)
|
||||
|
||||
click_on 'Déposer les modifications'
|
||||
|
||||
log_out
|
||||
|
||||
# the litteraires instructeurs should have a notification
|
||||
|
@ -217,6 +219,8 @@ describe 'The routing', js: true do
|
|||
|
||||
expect(page).to have_text(new_group)
|
||||
|
||||
click_on 'Déposer les modifications'
|
||||
|
||||
log_out
|
||||
end
|
||||
|
||||
|
|
|
@ -146,6 +146,8 @@ describe 'The routing with rules', js: true do
|
|||
fill_in litteraire_user.dossiers.first.champs_public.first.libelle, with: 'some value'
|
||||
wait_for_autosave(false)
|
||||
|
||||
click_on 'Déposer les modifications'
|
||||
|
||||
log_out
|
||||
|
||||
# the litteraires instructeurs should have a notification
|
||||
|
@ -245,6 +247,8 @@ describe 'The routing with rules', js: true do
|
|||
|
||||
expect(page).to have_text(new_group)
|
||||
|
||||
click_on 'Déposer les modifications'
|
||||
|
||||
log_out
|
||||
end
|
||||
|
||||
|
|
|
@ -483,14 +483,14 @@ describe 'The user' do
|
|||
fill_individual
|
||||
|
||||
# Test autosave failure
|
||||
allow_any_instance_of(Users::DossiersController).to receive(:update_brouillon).and_raise("Server is busy")
|
||||
allow_any_instance_of(Users::DossiersController).to receive(:update).and_raise("Server is busy")
|
||||
fill_in('texte obligatoire', with: 'a valid user input')
|
||||
blur
|
||||
expect(page).to have_css('span', text: 'Impossible d’enregistrer le brouillon', visible: true)
|
||||
|
||||
# Test that retrying after a failure works
|
||||
allow_any_instance_of(Users::DossiersController).to receive(:update_brouillon).and_call_original
|
||||
click_on 'réessayer'
|
||||
allow_any_instance_of(Users::DossiersController).to receive(:update).and_call_original
|
||||
click_on 'Réessayer'
|
||||
wait_for_autosave
|
||||
|
||||
visit current_path
|
||||
|
|
|
@ -12,6 +12,7 @@ RSpec.shared_examples 'the user can edit the submitted demande' do
|
|||
fill_in('Texte obligatoire', with: 'Nouveau texte')
|
||||
wait_for_autosave(false)
|
||||
|
||||
click_on 'Déposer les modifications'
|
||||
click_on 'Demande'
|
||||
expect(page).to have_current_path(demande_dossier_path(dossier))
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ describe "Dossier en_construction" do
|
|||
}
|
||||
|
||||
let(:champ) {
|
||||
dossier.champs_public.find { _1.type_de_champ_id == tdc.id }
|
||||
dossier.find_editing_fork(dossier.user).champs_public.find { _1.type_de_champ_id == tdc.id }
|
||||
}
|
||||
|
||||
scenario 'delete a non mandatory piece justificative', js: true do
|
||||
|
@ -29,7 +29,7 @@ describe "Dossier en_construction" do
|
|||
scenario 'remplace a mandatory piece justificative', js: true do
|
||||
visit_dossier(dossier)
|
||||
|
||||
click_on "Remplacer le fichier toto.txt"
|
||||
click_on "Supprimer le fichier toto.txt"
|
||||
|
||||
input_selector = "#attachment-multiple-empty-#{champ.id}"
|
||||
expect(page).to have_selector(input_selector)
|
||||
|
@ -53,9 +53,9 @@ describe "Dossier en_construction" do
|
|||
scenario 'remplace a mandatory titre identite', js: true do
|
||||
visit_dossier(dossier)
|
||||
|
||||
click_on "Remplacer le fichier toto.png"
|
||||
click_on "Supprimer le fichier toto.png"
|
||||
|
||||
input_selector = ".attachment-input-#{champ.piece_justificative_file.attachments.first.id}"
|
||||
input_selector = "##{champ.input_id}"
|
||||
expect(page).to have_selector(input_selector)
|
||||
find(input_selector).attach_file(Rails.root.join('spec/fixtures/files/file.pdf'))
|
||||
|
||||
|
|
|
@ -115,8 +115,8 @@ describe 'shared/dossiers/edit', type: :view do
|
|||
let(:dossier) { create(:dossier, :en_construction) }
|
||||
before { dossier.champs_public << champ }
|
||||
|
||||
it 'cannot delete a piece justificative' do
|
||||
expect(subject).not_to have_selector("[title='Supprimer le fichier #{champ.piece_justificative_file.attachments[0].filename}']")
|
||||
it 'can delete a piece justificative' do
|
||||
expect(subject).to have_selector("[title='Supprimer le fichier #{champ.piece_justificative_file.attachments[0].filename}']")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ describe 'users/dossiers/show', type: :view do
|
|||
|
||||
it 'renders a summary of the dossier state' do
|
||||
expect(rendered).to have_text("Dossier nº #{dossier.id}")
|
||||
expect(rendered).to have_selector('.status-overview')
|
||||
expect(rendered).to have_text('dossier est en construction')
|
||||
end
|
||||
|
||||
context 'with messages' do
|
||||
|
|
Loading…
Reference in a new issue