Merge branch 'main' into etq-usager-bouton-jdma
This commit is contained in:
commit
97ef01b075
170 changed files with 4076 additions and 1251 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -81,7 +81,8 @@ jobs:
|
||||||
- name: Install build dependancies
|
- name: Install build dependancies
|
||||||
# - fonts pickable by ImageMagick
|
# - fonts pickable by ImageMagick
|
||||||
# - rust for YJIT support
|
# - rust for YJIT support
|
||||||
run: sudo apt-get update && sudo apt-get install -y gsfonts rustc redis-server
|
# - poppler-utils for pdf previews
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y gsfonts rustc redis-server poppler-utils
|
||||||
|
|
||||||
- name: Setup the app runtime and dependencies
|
- name: Setup the app runtime and dependencies
|
||||||
uses: ./.github/actions/ci-setup-rails
|
uses: ./.github/actions/ci-setup-rails
|
||||||
|
|
1
Gemfile
1
Gemfile
|
@ -95,6 +95,7 @@ gem 'sidekiq'
|
||||||
gem 'sidekiq-cron'
|
gem 'sidekiq-cron'
|
||||||
gem 'skylight'
|
gem 'skylight'
|
||||||
gem 'spreadsheet_architect'
|
gem 'spreadsheet_architect'
|
||||||
|
gem 'string-similarity'
|
||||||
gem 'strong_migrations' # lint database migrations
|
gem 'strong_migrations' # lint database migrations
|
||||||
gem 'sys-proctable'
|
gem 'sys-proctable'
|
||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
|
|
118
Gemfile.lock
118
Gemfile.lock
|
@ -12,47 +12,47 @@ GEM
|
||||||
aasm (5.5.0)
|
aasm (5.5.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
acsv (0.0.1)
|
acsv (0.0.1)
|
||||||
actioncable (7.0.8.3)
|
actioncable (7.0.8.4)
|
||||||
actionpack (= 7.0.8.3)
|
actionpack (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.8.3)
|
actionmailbox (7.0.8.4)
|
||||||
actionpack (= 7.0.8.3)
|
actionpack (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.3)
|
activejob (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.3)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8.3)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.8.3)
|
actionmailer (7.0.8.4)
|
||||||
actionpack (= 7.0.8.3)
|
actionpack (= 7.0.8.4)
|
||||||
actionview (= 7.0.8.3)
|
actionview (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.3)
|
activejob (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (7.0.8.3)
|
actionpack (7.0.8.4)
|
||||||
actionview (= 7.0.8.3)
|
actionview (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
rack (~> 2.0, >= 2.2.4)
|
rack (~> 2.0, >= 2.2.4)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (7.0.8.3)
|
actiontext (7.0.8.4)
|
||||||
actionpack (= 7.0.8.3)
|
actionpack (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.3)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8.3)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.8.3)
|
actionview (7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
|
@ -67,26 +67,26 @@ GEM
|
||||||
activemodel (>= 5.2.0)
|
activemodel (>= 5.2.0)
|
||||||
activestorage (>= 5.2.0)
|
activestorage (>= 5.2.0)
|
||||||
activesupport (>= 5.2.0)
|
activesupport (>= 5.2.0)
|
||||||
activejob (7.0.8.3)
|
activejob (7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.8.3)
|
activemodel (7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
activerecord (7.0.8.3)
|
activerecord (7.0.8.4)
|
||||||
activemodel (= 7.0.8.3)
|
activemodel (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
activestorage (7.0.8.3)
|
activestorage (7.0.8.4)
|
||||||
actionpack (= 7.0.8.3)
|
actionpack (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.3)
|
activejob (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.3)
|
activerecord (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activestorage-openstack (1.6.0)
|
activestorage-openstack (1.6.0)
|
||||||
fog-openstack (>= 1.0.9)
|
fog-openstack (>= 1.0.9)
|
||||||
marcel
|
marcel
|
||||||
rails (>= 5.2.2)
|
rails (>= 5.2.2)
|
||||||
activesupport (7.0.8.3)
|
activesupport (7.0.8.4)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
@ -174,7 +174,7 @@ GEM
|
||||||
clamav-client (3.2.0)
|
clamav-client (3.2.0)
|
||||||
coercible (1.0.0)
|
coercible (1.0.0)
|
||||||
descendants_tracker (~> 0.0.1)
|
descendants_tracker (~> 0.0.1)
|
||||||
concurrent-ruby (1.2.3)
|
concurrent-ruby (1.3.1)
|
||||||
connection_pool (2.4.1)
|
connection_pool (2.4.1)
|
||||||
content_disposition (1.0.0)
|
content_disposition (1.0.0)
|
||||||
crack (1.0.0)
|
crack (1.0.0)
|
||||||
|
@ -438,15 +438,15 @@ GEM
|
||||||
rake
|
rake
|
||||||
mini_magick (4.12.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.6)
|
mini_portile2 (2.8.7)
|
||||||
minitest (5.23.0)
|
minitest (5.23.1)
|
||||||
msgpack (1.7.2)
|
msgpack (1.7.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
mustermann (3.0.0)
|
mustermann (3.0.0)
|
||||||
ruby2_keywords (~> 0.0.1)
|
ruby2_keywords (~> 0.0.1)
|
||||||
net-http (0.4.1)
|
net-http (0.4.1)
|
||||||
uri
|
uri
|
||||||
net-imap (0.4.11)
|
net-imap (0.4.12)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
|
@ -531,20 +531,20 @@ GEM
|
||||||
rack_session_access (0.2.0)
|
rack_session_access (0.2.0)
|
||||||
builder (>= 2.0.0)
|
builder (>= 2.0.0)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rails (7.0.8.3)
|
rails (7.0.8.4)
|
||||||
actioncable (= 7.0.8.3)
|
actioncable (= 7.0.8.4)
|
||||||
actionmailbox (= 7.0.8.3)
|
actionmailbox (= 7.0.8.4)
|
||||||
actionmailer (= 7.0.8.3)
|
actionmailer (= 7.0.8.4)
|
||||||
actionpack (= 7.0.8.3)
|
actionpack (= 7.0.8.4)
|
||||||
actiontext (= 7.0.8.3)
|
actiontext (= 7.0.8.4)
|
||||||
actionview (= 7.0.8.3)
|
actionview (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.3)
|
activejob (= 7.0.8.4)
|
||||||
activemodel (= 7.0.8.3)
|
activemodel (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.3)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8.3)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.8.3)
|
railties (= 7.0.8.4)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
actionview (>= 5.0.1.rc1)
|
actionview (>= 5.0.1.rc1)
|
||||||
|
@ -567,9 +567,9 @@ GEM
|
||||||
rails-pg-extras (5.3.1)
|
rails-pg-extras (5.3.1)
|
||||||
rails
|
rails
|
||||||
ruby-pg-extras (= 5.3.1)
|
ruby-pg-extras (= 5.3.1)
|
||||||
railties (7.0.8.3)
|
railties (7.0.8.4)
|
||||||
actionpack (= 7.0.8.3)
|
actionpack (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.3)
|
activesupport (= 7.0.8.4)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
|
@ -765,6 +765,7 @@ GEM
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
stackprof (0.2.26)
|
stackprof (0.2.26)
|
||||||
|
string-similarity (2.1.0)
|
||||||
stringio (3.1.0)
|
stringio (3.1.0)
|
||||||
strong_migrations (1.8.0)
|
strong_migrations (1.8.0)
|
||||||
activerecord (>= 5.2)
|
activerecord (>= 5.2)
|
||||||
|
@ -872,7 +873,7 @@ GEM
|
||||||
anyway_config (>= 1.3, < 3)
|
anyway_config (>= 1.3, < 3)
|
||||||
sidekiq
|
sidekiq
|
||||||
yabeda (~> 0.6)
|
yabeda (~> 0.6)
|
||||||
zeitwerk (2.6.14)
|
zeitwerk (2.6.15)
|
||||||
zip_tricks (5.6.0)
|
zip_tricks (5.6.0)
|
||||||
zipline (1.5.0)
|
zipline (1.5.0)
|
||||||
actionpack (>= 6.0, < 8.0)
|
actionpack (>= 6.0, < 8.0)
|
||||||
|
@ -1013,6 +1014,7 @@ DEPENDENCIES
|
||||||
spring
|
spring
|
||||||
spring-commands-rspec
|
spring-commands-rspec
|
||||||
stackprof
|
stackprof
|
||||||
|
string-similarity
|
||||||
strong_migrations
|
strong_migrations
|
||||||
sys-proctable
|
sys-proctable
|
||||||
timecop
|
timecop
|
||||||
|
|
|
@ -57,5 +57,14 @@ form.form > .conditionnel {
|
||||||
select.alert {
|
select.alert {
|
||||||
border-color: $dark-red;
|
border-color: $dark-red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class Conditions::ConditionsComponent < ApplicationComponent
|
||||||
|
|
||||||
def available_targets_for_select
|
def available_targets_for_select
|
||||||
@source_tdcs
|
@source_tdcs
|
||||||
.filter { |tdc| ChampValue::MANAGED_TYPE_DE_CHAMP.values.include?(tdc.type_champ) }
|
.filter(&:conditionable?)
|
||||||
.map { |tdc| [tdc.libelle, champ_value(tdc.stable_id).to_json] }
|
.map { |tdc| [tdc.libelle, champ_value(tdc.stable_id).to_json] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
34
app/components/conditions/ineligibilite_rules_component.rb
Normal file
34
app/components/conditions/ineligibilite_rules_component.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
class Conditions::IneligibiliteRulesComponent < Conditions::ConditionsComponent
|
||||||
|
include Logic
|
||||||
|
|
||||||
|
def initialize(draft_revision:)
|
||||||
|
@draft_revision = draft_revision
|
||||||
|
@published_revision = draft_revision.procedure.published_revision
|
||||||
|
@condition = draft_revision.ineligibilite_rules
|
||||||
|
@source_tdcs = draft_revision.types_de_champ_for(scope: :public)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pending_changes?
|
||||||
|
return false if !@published_revision
|
||||||
|
|
||||||
|
!@published_revision.compare_ineligibilite_rules(@draft_revision).empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def input_prefix
|
||||||
|
'procedure_revision[condition_form]'
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_id_for(name, row_index)
|
||||||
|
"#{@draft_revision.id}-#{name}-#{row_index}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_condition_path(row_index)
|
||||||
|
delete_row_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id, revision_id: @draft_revision.id, row_index:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_condition_path
|
||||||
|
add_row_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id, revision_id: @draft_revision.id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
display_if: Bloquer si
|
||||||
|
select: Sélectionner
|
||||||
|
add_condition: Ajouter une règle d’inéligibilité
|
||||||
|
remove_a_row: Supprimer une règle
|
|
@ -0,0 +1,49 @@
|
||||||
|
%div{ id: dom_id(@draft_revision, :ineligibilite_rules) }
|
||||||
|
= render Procedure::PendingRepublishComponent.new(procedure: @draft_revision.procedure, render_if: pending_changes?)
|
||||||
|
= render Conditions::ConditionsErrorsComponent.new(conditions: condition_per_row, source_tdcs: @source_tdcs)
|
||||||
|
.fr-fieldset
|
||||||
|
= form_for(@draft_revision, url: change_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id), html: { id: 'ineligibilite_form', class: 'width-100' }) do |f|
|
||||||
|
.fr-fieldset__element
|
||||||
|
.fr-toggle.fr-toggle--label-left
|
||||||
|
= f.check_box :ineligibilite_enabled, class: 'fr-toggle__input', data: @opt
|
||||||
|
= f.label :ineligibilite_enabled, "Bloquer le dépôt des dossiers répondant à des conditions d’inéligibilité", data: { 'fr-checked-label': "Activé", 'fr-unchecked-label': "Désactivé" }, class: 'fr-toggle__label'
|
||||||
|
|
||||||
|
.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :ineligibilite_message, input_type: :text_area, opts: {rows: 5})
|
||||||
|
|
||||||
|
.fr-mx-1w.fr-label.fr-py-0.fr-mb-1w.fr-mt-2w
|
||||||
|
Conditions d’inéligibilité
|
||||||
|
%span.fr-hint-text Vous pouvez utiliser une ou plusieurs condtions pour bloquer le dépot.
|
||||||
|
.fr-fieldset__element
|
||||||
|
= form_tag admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id), method: :patch, data: { turbo: true, controller: 'autosave' }, class: 'form width-100' do
|
||||||
|
.conditionnel.width-100
|
||||||
|
%table.condition-table
|
||||||
|
- if rows.size > 0
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th.fr-pt-0.far-left
|
||||||
|
%th.fr-pt-0.target Champ Cible
|
||||||
|
%th.fr-pt-0.operator Opérateur
|
||||||
|
%th.fr-pt-0.value Valeur
|
||||||
|
%th.fr-pt-0.delete-column
|
||||||
|
%tbody
|
||||||
|
- rows.each.with_index do |(targeted_champ, operator_name, value), row_index|
|
||||||
|
%tr
|
||||||
|
%td.far-left= far_left_tag(row_index)
|
||||||
|
%td.target= left_operand_tag(targeted_champ, row_index)
|
||||||
|
%td.operator= operator_tag(operator_name, targeted_champ, row_index)
|
||||||
|
%td.value= right_operand_tag(targeted_champ, value, row_index, operator_name)
|
||||||
|
%td.delete-column= delete_condition_tag(row_index)
|
||||||
|
%tfoot
|
||||||
|
%tr
|
||||||
|
%td.text-right{ colspan: 5 }= add_condition_tag
|
||||||
|
|
||||||
|
.padded-fixed-footer
|
||||||
|
.fixed-footer
|
||||||
|
.fr-container
|
||||||
|
.fr-grid-row.fr-col-offset-md-2.fr-col-md-8
|
||||||
|
.fr-col-12
|
||||||
|
%ul.fr-btns-group.fr-btns-group--inline-md
|
||||||
|
%li
|
||||||
|
= link_to "Annuler et revenir à l'écran de gestion", admin_procedure_path(id: @draft_revision.procedure), class: 'fr-btn fr-btn--secondary', data: { confirm: 'Si vous avez fait des modifications elles ne seront pas sauvegardées.'}
|
||||||
|
%li
|
||||||
|
= button_tag "Enregistrer", class: "fr-btn", form: 'ineligibilite_form'
|
|
@ -1,4 +1,6 @@
|
||||||
class Dossiers::EditFooterComponent < ApplicationComponent
|
class Dossiers::EditFooterComponent < ApplicationComponent
|
||||||
|
delegate :can_passer_en_construction?, to: :@dossier
|
||||||
|
|
||||||
def initialize(dossier:, annotation:)
|
def initialize(dossier:, annotation:)
|
||||||
@dossier = dossier
|
@dossier = dossier
|
||||||
@annotation = annotation
|
@annotation = annotation
|
||||||
|
@ -14,20 +16,29 @@ class Dossiers::EditFooterComponent < ApplicationComponent
|
||||||
@annotation.present?
|
@annotation.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def disabled_submit_buttons_options
|
||||||
|
{
|
||||||
|
class: 'fr-text--sm fr-mb-0 fr-mr-2w',
|
||||||
|
data: { 'fr-opened': "true" },
|
||||||
|
aria: { controls: 'modal-eligibilite-rules-dialog' }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def submit_draft_button_options
|
def submit_draft_button_options
|
||||||
{
|
{
|
||||||
class: 'fr-btn fr-btn--sm',
|
class: 'fr-btn fr-btn--sm',
|
||||||
disabled: !owner?,
|
disabled: !owner? || !can_passer_en_construction?,
|
||||||
method: :post,
|
method: :post,
|
||||||
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit' }
|
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def submit_en_construction_button_options
|
def submit_en_construction_button_options
|
||||||
{
|
{
|
||||||
class: 'fr-btn fr-btn--sm',
|
class: 'fr-btn fr-btn--sm',
|
||||||
|
disabled: !can_passer_en_construction?,
|
||||||
method: :post,
|
method: :post,
|
||||||
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit' },
|
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server },
|
||||||
form: { id: "form-submit-en-construction" }
|
form: { id: "form-submit-en-construction" }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
en:
|
en:
|
||||||
submit: Submit the file
|
submit: Submit the file
|
||||||
submit_changes: Submit file changes
|
submit_changes: Submit file changes
|
||||||
|
submit_disabled: File submission disabled
|
||||||
submitting: Submitting…
|
submitting: Submitting…
|
||||||
invite_notice: You are invited to make amendments to this file but <strong>only the owner themselves can submit it</strong>.
|
invite_notice: You are invited to make amendments to this file but <strong>only the owner themselves can submit it</strong>.
|
||||||
|
|
|
@ -2,5 +2,6 @@
|
||||||
fr:
|
fr:
|
||||||
submit: Déposer le dossier
|
submit: Déposer le dossier
|
||||||
submit_changes: Déposer les modifications
|
submit_changes: Déposer les modifications
|
||||||
|
submit_disabled: Pourquoi je ne peux pas déposer mon dossier ?
|
||||||
submitting: Envoi en cours…
|
submitting: Envoi en cours…
|
||||||
invite_notice: En tant qu’invité, vous pouvez remplir ce formulaire – mais <strong>le titulaire du dossier doit le déposer lui-même</strong>.
|
invite_notice: En tant qu’invité, vous pouvez remplir ce formulaire – mais <strong>le titulaire du dossier doit le déposer lui-même</strong>.
|
||||||
|
|
|
@ -3,8 +3,13 @@
|
||||||
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
|
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
|
||||||
|
|
||||||
- if !annotation? && @dossier.can_transition_to_en_construction?
|
- if !annotation? && @dossier.can_transition_to_en_construction?
|
||||||
|
- if !can_passer_en_construction?
|
||||||
|
= link_to t('.submit_disabled'), "#", disabled_submit_buttons_options
|
||||||
= button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options
|
= button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options
|
||||||
- elsif @dossier.forked_with_changes?
|
|
||||||
|
- if @dossier.forked_with_changes?
|
||||||
|
- if !can_passer_en_construction?
|
||||||
|
= link_to t('.submit_disabled'), "#", disabled_submit_buttons_options
|
||||||
= button_to t('.submit_changes'), modifier_dossier_url(@dossier.editing_fork_origin), submit_en_construction_button_options
|
= button_to t('.submit_changes'), modifier_dossier_url(@dossier.editing_fork_origin), submit_en_construction_button_options
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,15 @@
|
||||||
class Dossiers::ErrorsFullMessagesComponent < ApplicationComponent
|
class Dossiers::ErrorsFullMessagesComponent < ApplicationComponent
|
||||||
ErrorDescriptor = Data.define(:anchor, :label, :error_message)
|
ErrorDescriptor = Data.define(:anchor, :label, :error_message)
|
||||||
|
|
||||||
def initialize(dossier:, errors:)
|
def initialize(dossier:)
|
||||||
@dossier = dossier
|
@dossier = dossier
|
||||||
@errors = errors
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def dedup_and_partitioned_errors
|
def dedup_and_partitioned_errors
|
||||||
formated_errors = @errors.to_enum # ActiveModel::Errors.to_a is an alias to full_messages, we don't want that
|
@dossier.errors.to_enum # ActiveModel::Errors.to_a is an alias to full_messages, we don't want that
|
||||||
.to_a # but enum.to_a gives back an array
|
.to_a # but enum.to_a gives back an array
|
||||||
.uniq { |error| [error.inner_error.base] } # dedup cumulated errors from dossier.champs, dossier.champs_public, dossier.champs_private which run the validator one time per association
|
.uniq { |error| [error.inner_error.base] } # dedup cumulated errors from dossier.champs, dossier.champs_public, dossier.champs_private which run the validator one time per association
|
||||||
.map { |error| to_error_descriptor(error) }
|
.map { |error| to_error_descriptor(error) }
|
||||||
yield(Array(formated_errors[0..2]), Array(formated_errors[3..]))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_error_descriptor(error)
|
def to_error_descriptor(error)
|
||||||
|
@ -27,6 +25,6 @@ class Dossiers::ErrorsFullMessagesComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
def render?
|
def render?
|
||||||
!@errors.empty?
|
!@dossier.errors.empty?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,4 +5,3 @@ en:
|
||||||
Your file has 1 error. <a href="%{url}">Fix-it</a> to continue :
|
Your file has 1 error. <a href="%{url}">Fix-it</a> to continue :
|
||||||
other: |
|
other: |
|
||||||
Your file has %{count} errors. <a href="%{url}">Fix-them</a> to continue :
|
Your file has %{count} errors. <a href="%{url}">Fix-them</a> to continue :
|
||||||
see_more: Show all errors
|
|
||||||
|
|
|
@ -5,4 +5,3 @@ fr:
|
||||||
Votre dossier contient 1 champ en erreur. <a href="%{url}">Corrigez-la</a> pour poursuivre :
|
Votre dossier contient 1 champ en erreur. <a href="%{url}">Corrigez-la</a> pour poursuivre :
|
||||||
other: |
|
other: |
|
||||||
Votre dossier contient %{count} champs en erreurs. <a href="%{url}">Corrigez-les</a> pour poursuivre :
|
Votre dossier contient %{count} champs en erreurs. <a href="%{url}">Corrigez-les</a> pour poursuivre :
|
||||||
see_more: Afficher toutes les erreurs
|
|
||||||
|
|
|
@ -1,15 +1,4 @@
|
||||||
.fr-alert.fr-alert--error.fr-mb-3w{ role: "alertdialog" }
|
.fr-alert.fr-alert--error.fr-mb-3w{ role: "alertdialog" }
|
||||||
- dedup_and_partitioned_errors do |head, tail|
|
- if dedup_and_partitioned_errors.size > 0
|
||||||
%p#sumup-errors= t('.sumup_html', count: head.size + tail.size, url: head.first.anchor)
|
%p#sumup-errors= t('.sumup_html', count: dedup_and_partitioned_errors.size, url: dedup_and_partitioned_errors.first.anchor)
|
||||||
%ul.fr-mb-0#head-errors
|
= render ExpandableErrorList.new(errors: dedup_and_partitioned_errors)
|
||||||
- head.each do |error_descriptor|
|
|
||||||
%li
|
|
||||||
= link_to error_descriptor.label, error_descriptor.anchor, class: 'error-anchor'
|
|
||||||
= error_descriptor.error_message
|
|
||||||
- if tail.size > 0
|
|
||||||
%button{ type: "button", "aria-controls": 'tail-errors', "aria-expanded": "false", class: "fr-btn fr-btn--sm fr-btn--tertiary-no-outline" }= t('.see_more')
|
|
||||||
%ul#tail-errors.fr-collapse.fr-mt-0
|
|
||||||
- tail.each do |error_descriptor|
|
|
||||||
%li
|
|
||||||
= link_to error_descriptor.label, error_descriptor.anchor, class: 'error-anchor'
|
|
||||||
= "(#{error_descriptor.error_message})"
|
|
||||||
|
|
|
@ -15,12 +15,12 @@
|
||||||
= link_to download_export_path(export_format: format), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
|
= link_to download_export_path(export_format: format), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
|
||||||
= t(".everything_#{format}_html")
|
= t(".everything_#{format}_html")
|
||||||
|
|
||||||
- if export_templates.present?
|
- if @procedure.feature_enabled?(:export_template)
|
||||||
- export_templates.each do |export_template|
|
- if export_templates.present?
|
||||||
|
- export_templates.each do |export_template|
|
||||||
|
- menu.with_item do
|
||||||
|
= link_to download_export_path(export_template_id: export_template.id), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
|
||||||
|
= "Exporter à partir du modèle #{export_template.name}"
|
||||||
- menu.with_item do
|
- menu.with_item do
|
||||||
= link_to download_export_path(export_template_id: export_template.id), role: 'menuitem', data: { turbo_method: :post, turbo: true } do
|
= link_to new_instructeur_export_template_path(procedure_id: params[:procedure_id]), role: 'menuitem' do
|
||||||
= "Exporter à partir du modèle #{export_template.name}"
|
Ajouter un modèle d'export
|
||||||
- if feature_enabled?(:export_template)
|
|
||||||
- menu.with_item do
|
|
||||||
= link_to new_instructeur_export_template_path(procedure_id: params[:procedure_id]), role: 'menuitem' do
|
|
||||||
Ajouter un modèle d'export
|
|
||||||
|
|
|
@ -11,9 +11,10 @@ class Dossiers::ExportLinkComponent < ApplicationComponent
|
||||||
@export_url = export_url
|
@export_url = export_url
|
||||||
end
|
end
|
||||||
|
|
||||||
def download_export_path(export_format:, statut:, no_progress_notification: nil)
|
def download_export_path(export_format:, statut:, export_template_id: nil, no_progress_notification: nil)
|
||||||
@export_url.call(@procedure,
|
@export_url.call(@procedure,
|
||||||
export_format: export_format,
|
export_format: export_format,
|
||||||
|
export_template_id:,
|
||||||
statut: statut,
|
statut: statut,
|
||||||
no_progress_notification: no_progress_notification)
|
no_progress_notification: no_progress_notification)
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
= export_title(export)
|
= export_title(export)
|
||||||
%span.fr-text-mention--grey.fr-mb-1w
|
%span.fr-text-mention--grey.fr-mb-1w
|
||||||
= time_info(export)
|
= time_info(export)
|
||||||
|
- if export.export_template
|
||||||
|
%span.fr-tag.fr-tag--sm.fr-ml-1w
|
||||||
|
= export.export_template.name
|
||||||
.fr-ml-auto
|
.fr-ml-auto
|
||||||
= badge(export)
|
= badge(export)
|
||||||
|
|
||||||
|
@ -14,4 +17,4 @@
|
||||||
= export_button(export)
|
= export_button(export)
|
||||||
|
|
||||||
- if export.failed?
|
- if export.failed?
|
||||||
= button_to refresh_button_options(export)[:title], download_export_path(export_format: export.format, statut: export.statut), refresh_button_options(export)
|
= button_to refresh_button_options(export)[:title], download_export_path(export_template_id: export.export_template&.id, export_format: export.format, statut: export.statut), refresh_button_options(export)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
class Dossiers::InvalidIneligibiliteRulesComponent < ApplicationComponent
|
||||||
|
delegate :can_passer_en_construction?, to: :@dossier
|
||||||
|
|
||||||
|
def initialize(dossier:)
|
||||||
|
@dossier = dossier
|
||||||
|
@revision = dossier.revision
|
||||||
|
end
|
||||||
|
|
||||||
|
def render?
|
||||||
|
!can_passer_en_construction?
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_message
|
||||||
|
@dossier.revision.ineligibilite_message
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
fr:
|
||||||
|
modal:
|
||||||
|
title: "Your file does not match submission criteria"
|
||||||
|
close: "Close"
|
||||||
|
close_alt: "Close this modal"
|
||||||
|
body: "The procedure « %{procedure_libelle} » have submission criteria, unfortunately your file does not match them. You can not submit your file"
|
|
@ -0,0 +1,5 @@
|
||||||
|
fr:
|
||||||
|
modal:
|
||||||
|
title: "Vous ne pouvez pas déposer votre dossier"
|
||||||
|
close: "Fermer"
|
||||||
|
close_alt: "Fermer la fenêtre modale"
|
|
@ -0,0 +1,16 @@
|
||||||
|
%div{ id: dom_id(@dossier, :ineligibilite_rules_broken), data: { controller: 'ineligibilite-rules-match', turbo_force: :server } }
|
||||||
|
%button.fr-sr-only{ aria: {controls: 'modal-eligibilite-rules-dialog' }, data: {'fr-opened': "false" } }
|
||||||
|
show modal
|
||||||
|
|
||||||
|
%dialog.fr-modal{ "aria-labelledby" => "fr-modal-title-modal-1", role: "dialog", id: 'modal-eligibilite-rules-dialog', data: { 'ineligibilite-rules-match-target' => 'dialog' } }
|
||||||
|
.fr-container.fr-container--fluid.fr-container-md
|
||||||
|
.fr-grid-row.fr-grid-row--center
|
||||||
|
.fr-col-12.fr-col-md-8.fr-col-lg-6
|
||||||
|
.fr-modal__body
|
||||||
|
.fr-modal__header
|
||||||
|
%button.fr-btn--close.fr-btn{ aria: { controls: 'modal-eligibilite-rules-dialog' }, title: t('.modal.close_alt') }= t('.modal.close')
|
||||||
|
.fr-modal__content
|
||||||
|
%h1#fr-modal-title-modal-1.fr-modal__title
|
||||||
|
%span.fr-icon-arrow-right-line.fr-icon--lg>
|
||||||
|
= t('.modal.title')
|
||||||
|
%p= error_message
|
|
@ -32,7 +32,7 @@ class Dsfr::InputComponent < ApplicationComponent
|
||||||
}.merge(input_group_error_class_names))
|
}.merge(input_group_error_class_names))
|
||||||
}
|
}
|
||||||
if email?
|
if email?
|
||||||
opts[:data] = { controller: 'email-input' }
|
opts[:data] = { controller: 'email-input', email_input_url_value: show_email_suggestions_path }
|
||||||
end
|
end
|
||||||
opts
|
opts
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Dsfr::ToggleComponent < ApplicationComponent
|
class Dsfr::ToggleComponent < ApplicationComponent
|
||||||
def initialize(form:, target:, title:, disabled: nil, hint: nil, toggle_labels: { checked: 'Activé', unchecked: 'Désactivé' }, opt: nil)
|
def initialize(form:, target:, title:, disabled: nil, hint: nil, toggle_labels: { checked: 'Activé', unchecked: 'Désactivé' }, opt: nil, extra_class_names: nil)
|
||||||
@form = form
|
@form = form
|
||||||
@target = target
|
@target = target
|
||||||
@title = title
|
@title = title
|
||||||
|
@ -7,7 +7,8 @@ class Dsfr::ToggleComponent < ApplicationComponent
|
||||||
@disabled = disabled
|
@disabled = disabled
|
||||||
@toggle_labels = toggle_labels
|
@toggle_labels = toggle_labels
|
||||||
@opt = opt
|
@opt = opt
|
||||||
|
@extra_class_names = extra_class_names
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :toggle_labels
|
attr_reader :toggle_labels, :extra_class_names
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.fr-toggle.fr-toggle--label-left
|
%div{ class: "fr-toggle fr-toggle--label-left #{extra_class_names}" }
|
||||||
= @form.check_box @target, class: 'fr-toggle__input', disabled: @disabled,
|
= @form.check_box @target, class: 'fr-toggle__input', disabled: @disabled,
|
||||||
data: @opt
|
data: @opt
|
||||||
= @form.label @target,
|
= @form.label @target,
|
||||||
|
|
9
app/components/expandable_error_list.rb
Normal file
9
app/components/expandable_error_list.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class ExpandableErrorList < ApplicationComponent
|
||||||
|
def initialize(errors:)
|
||||||
|
@errors = errors
|
||||||
|
end
|
||||||
|
|
||||||
|
def splitted_errors
|
||||||
|
yield(Array(@errors[0..2]), Array(@errors[3..]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
en:
|
||||||
|
see_more: Show all errors
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
see_more: Afficher toutes les erreurs
|
|
@ -0,0 +1,14 @@
|
||||||
|
- splitted_errors do |head, tail|
|
||||||
|
%ul#head-errors.fr-mb-0
|
||||||
|
- head.each do |error_descriptor|
|
||||||
|
%li
|
||||||
|
= link_to error_descriptor.label, error_descriptor.anchor, class: 'error-anchor'
|
||||||
|
= error_descriptor.error_message
|
||||||
|
|
||||||
|
- if tail.size > 0
|
||||||
|
%button.fr-mt-0.fr-btn.fr-btn--sm.fr-btn--tertiary-no-outline{ type: "button", "aria-controls": 'tail-errors', "aria-expanded": "false", class: "" }= t('see_more')
|
||||||
|
%ul#tail-errors.fr-collapse.fr-mt-0
|
||||||
|
- tail.each do |error_descriptor|
|
||||||
|
%li
|
||||||
|
= link_to error_descriptor.label, error_descriptor.anchor, class: 'error-anchor'
|
||||||
|
= error_descriptor.error_message
|
|
@ -7,9 +7,6 @@ class Procedure::Card::ChampsComponent < ApplicationComponent
|
||||||
private
|
private
|
||||||
|
|
||||||
def error_messages
|
def error_messages
|
||||||
[
|
@procedure.errors.messages_for(:draft_types_de_champ_public).to_sentence
|
||||||
@procedure.errors.messages_for(:draft_types_de_champ_public),
|
|
||||||
@procedure.errors.messages_for(:draft_revision)
|
|
||||||
].flatten.to_sentence
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
class Procedure::Card::IneligibiliteDossierComponent < ApplicationComponent
|
||||||
|
def initialize(procedure:)
|
||||||
|
@procedure = procedure
|
||||||
|
end
|
||||||
|
|
||||||
|
def ready?
|
||||||
|
@procedure.draft_revision
|
||||||
|
.conditionable_types_de_champ
|
||||||
|
.present? && @procedure.draft_revision.ineligibilite_enabled
|
||||||
|
end
|
||||||
|
|
||||||
|
def error?
|
||||||
|
!@procedure.draft_revision.validate(:ineligibilite_rules_editor)
|
||||||
|
end
|
||||||
|
|
||||||
|
def completed?
|
||||||
|
@procedure.draft_revision.ineligibilite_enabled
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
title: Inéligibilité des dossiers
|
||||||
|
state:
|
||||||
|
pending: Désactivé
|
||||||
|
ready: À configurer
|
||||||
|
completed: Activé
|
||||||
|
subtitle: Gérez vos conditions d’inéligibilité en fonction des champs du formulaire
|
|
@ -0,0 +1,13 @@
|
||||||
|
.fr-col-6.fr-col-md-4.fr-col-lg-3
|
||||||
|
= link_to edit_admin_procedure_ineligibilite_rules_path(@procedure), class: 'fr-tile fr-enlarge-link' do
|
||||||
|
.fr-tile__body.flex.column.align-center.justify-between
|
||||||
|
- if !ready?
|
||||||
|
%p.fr-badge.fr-badge= t('.state.pending')
|
||||||
|
- elsif error?
|
||||||
|
%p.fr-badge.fr-badge--error À modifier
|
||||||
|
- else
|
||||||
|
%p.fr-badge.fr-badge--success= t('.state.completed')
|
||||||
|
%div
|
||||||
|
%h3.fr-h6.fr-mt-10v= t('.title')
|
||||||
|
%p.fr-tile-subtitle= t('.subtitle')
|
||||||
|
%p.fr-btn.fr-btn--tertiary= t('views.shared.actions.edit')
|
|
@ -1,9 +0,0 @@
|
||||||
class Procedure::Card::ModificationsComponent < ApplicationComponent
|
|
||||||
def initialize(procedure:)
|
|
||||||
@procedure = procedure
|
|
||||||
end
|
|
||||||
|
|
||||||
def render?
|
|
||||||
@procedure.revised?
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
fr:
|
|
||||||
title:
|
|
||||||
one: Modification du formulaire
|
|
||||||
other: Modifications du formulaire
|
|
|
@ -1,12 +0,0 @@
|
||||||
.fr-col-6.fr-col-md-4.fr-col-lg-3
|
|
||||||
= link_to modifications_admin_procedure_path(@procedure), id: 'modifications', class: 'fr-tile fr-enlarge-link' do
|
|
||||||
.fr-tile__body.flex.column.align-center.justify-between
|
|
||||||
%p.fr-badge.fr-badge--success Activée
|
|
||||||
%div
|
|
||||||
.line-count.fr-my-1w
|
|
||||||
%p.fr-tag= @procedure.revisions_count
|
|
||||||
%h3.fr-h6
|
|
||||||
= t('.title', count: @procedure.revisions_count)
|
|
||||||
|
|
||||||
%p.fr-tile-subtitle Historique des modifications apportées au formulaire
|
|
||||||
%p.fr-btn.fr-btn--tertiary Voir
|
|
60
app/components/procedure/errors_summary.rb
Normal file
60
app/components/procedure/errors_summary.rb
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
class Procedure::ErrorsSummary < ApplicationComponent
|
||||||
|
ErrorDescriptor = Data.define(:anchor, :label, :error_message)
|
||||||
|
|
||||||
|
def initialize(procedure:, validation_context:)
|
||||||
|
@procedure = procedure
|
||||||
|
@validation_context = validation_context
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
case @validation_context
|
||||||
|
when :types_de_champ_private_editor
|
||||||
|
"Les annotations privées contiennent des erreurs"
|
||||||
|
when :types_de_champ_public_editor
|
||||||
|
"Les champs formulaire contiennent des erreurs"
|
||||||
|
when :publication
|
||||||
|
if @procedure.publiee?
|
||||||
|
"Des problèmes empêchent la publication des modifications"
|
||||||
|
else
|
||||||
|
"Des problèmes empêchent la publication de la démarche"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def invalid?
|
||||||
|
@procedure.validate(@validation_context)
|
||||||
|
@procedure.errors.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors
|
||||||
|
@procedure.errors.map { to_error_descriptor(_1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_correction_page(error)
|
||||||
|
case error.attribute
|
||||||
|
when :ineligibilite_rules
|
||||||
|
edit_admin_procedure_ineligibilite_rules_path(@procedure)
|
||||||
|
when :draft_types_de_champ_public
|
||||||
|
tdc = error.options[:type_de_champ]
|
||||||
|
champs_admin_procedure_path(@procedure, anchor: dom_id(tdc.stable_self, :editor_error))
|
||||||
|
when :draft_types_de_champ_private
|
||||||
|
tdc = error.options[:type_de_champ]
|
||||||
|
annotations_admin_procedure_path(@procedure, anchor: dom_id(tdc.stable_self, :editor_error))
|
||||||
|
when :attestation_template
|
||||||
|
edit_admin_procedure_attestation_template_path(@procedure)
|
||||||
|
when :initiated_mail, :received_mail, :closed_mail, :refused_mail, :without_continuation_mail, :re_instructed_mail
|
||||||
|
klass = "Mails::#{error.attribute.to_s.classify}".constantize
|
||||||
|
edit_admin_procedure_mail_template_path(@procedure, klass.const_get(:SLUG))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_error_descriptor(error)
|
||||||
|
libelle = case error.attribute
|
||||||
|
when :draft_types_de_champ_public, :draft_types_de_champ_private
|
||||||
|
error.options[:type_de_champ].libelle.truncate(200)
|
||||||
|
else
|
||||||
|
error.base.class.human_attribute_name(error.attribute)
|
||||||
|
end
|
||||||
|
ErrorDescriptor.new(error_correction_page(error), libelle, error.message)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
#errors-summary
|
||||||
|
- if invalid?
|
||||||
|
= render Dsfr::AlertComponent.new(state: :error, title: , extra_class_names: 'fr-mb-2w') do |c|
|
||||||
|
- c.with_body do
|
||||||
|
= render ExpandableErrorList.new(errors:)
|
10
app/components/procedure/pending_republish_component.rb
Normal file
10
app/components/procedure/pending_republish_component.rb
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
class Procedure::PendingRepublishComponent < ApplicationComponent
|
||||||
|
def initialize(procedure:, render_if:)
|
||||||
|
@procedure = procedure
|
||||||
|
@render_if = render_if
|
||||||
|
end
|
||||||
|
|
||||||
|
def render?
|
||||||
|
@render_if
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
pending_republish_html: |
|
||||||
|
Ces modifications ne seront appliquées qu'à la prochaine publication. Vous pouvez vérifier puis publier les modifications sur l'écran de <a href="%{href}">gestion de la démarche</a>
|
|
@ -0,0 +1,3 @@
|
||||||
|
= render Dsfr::AlertComponent.new(state: :warning) do |c|
|
||||||
|
- c.with_body do
|
||||||
|
= t('.pending_republish_html', href: admin_procedure_path(@procedure.id))
|
|
@ -1,39 +0,0 @@
|
||||||
class Procedure::PublicationWarningComponent < ApplicationComponent
|
|
||||||
def initialize(procedure:)
|
|
||||||
@procedure = procedure
|
|
||||||
end
|
|
||||||
|
|
||||||
def title
|
|
||||||
return "Des problèmes empêchent la publication des modifications" if @procedure.publiee?
|
|
||||||
|
|
||||||
"Des problèmes empêchent la publication de la démarche"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def render?
|
|
||||||
@procedure.validate(:publication)
|
|
||||||
@procedure.errors.delete(:path)
|
|
||||||
@procedure.errors.any?
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_messages
|
|
||||||
@procedure.errors
|
|
||||||
.to_hash(full_messages: true)
|
|
||||||
.map do |attribute, messages|
|
|
||||||
[messages, error_correction_page(attribute)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_correction_page(attribute)
|
|
||||||
case attribute
|
|
||||||
when :draft_revision
|
|
||||||
champs_admin_procedure_path(@procedure)
|
|
||||||
when :attestation_template
|
|
||||||
edit_admin_procedure_attestation_template_path(@procedure)
|
|
||||||
when :initiated_mail, :received_mail, :closed_mail, :refused_mail, :without_continuation_mail, :re_instructed_mail
|
|
||||||
klass = "Mails::#{attribute.to_s.classify}".constantize
|
|
||||||
edit_admin_procedure_mail_template_path(@procedure, klass.const_get(:SLUG))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
= render Dsfr::AlertComponent.new(state: :warning, title:) do |c|
|
|
||||||
- c.with_body do
|
|
||||||
- error_messages.each do |(messages, path)|
|
|
||||||
%p.mt-2
|
|
||||||
= messages.to_sentence
|
|
||||||
- if path.present?
|
|
||||||
= "(#{link_to 'corriger', path, class: 'fr-link'})"
|
|
|
@ -1,9 +1,13 @@
|
||||||
class Procedure::RevisionChangesComponent < ApplicationComponent
|
class Procedure::RevisionChangesComponent < ApplicationComponent
|
||||||
def initialize(changes:, previous_revision:)
|
def initialize(new_revision:, previous_revision:)
|
||||||
@changes = changes
|
|
||||||
@previous_revision = previous_revision
|
@previous_revision = previous_revision
|
||||||
@public_move_changes, @private_move_changes = changes.filter { _1.op == :move }.partition { !_1.private? }
|
@new_revision = new_revision
|
||||||
@delete_champ_warning = !total_dossiers.zero? && !@changes.all?(&:can_rebase?)
|
|
||||||
|
@tdc_changes = previous_revision.compare_types_de_champ(new_revision)
|
||||||
|
@public_move_changes, @private_move_changes = @tdc_changes.filter { _1.op == :move }.partition { !_1.private? }
|
||||||
|
@delete_champ_warning = !total_dossiers.zero? && !@tdc_changes.all?(&:can_rebase?)
|
||||||
|
|
||||||
|
@ineligibilite_rules_changes = previous_revision.compare_ineligibilite_rules(new_revision)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -80,3 +80,10 @@ fr:
|
||||||
update_expression_reguliere_exemple_text: L’exemple d’expression régulière de l’annotation privée « %{label} » a été modifiée. Le nouvel exemple est « %{to} ».
|
update_expression_reguliere_exemple_text: L’exemple d’expression régulière de l’annotation privée « %{label} » a été modifiée. Le nouvel exemple est « %{to} ».
|
||||||
remove_expression_reguliere_error_message: Le message d’erreur de l’expression régulière de l’annotation privée « %{label} » a été supprimé.
|
remove_expression_reguliere_error_message: Le message d’erreur de l’expression régulière de l’annotation privée « %{label} » a été supprimé.
|
||||||
update_expression_reguliere_error_message: Le message d’erreur de l’expression régulière de l’annotation privée « %{label} » a été modifiée. Le nouveau message est « %{to} ».
|
update_expression_reguliere_error_message: Le message d’erreur de l’expression régulière de l’annotation privée « %{label} » a été modifiée. Le nouveau message est « %{to} ».
|
||||||
|
ineligibilite_rules:
|
||||||
|
add: La condition d’inéligibilité « %{new_condition} » a été ajoutée.
|
||||||
|
remove: La condition d’inéligibilité « %{previous_condition} » a été supprimée
|
||||||
|
update: La conditon d’inéligibilité « %{previous_condition} » a été changée pour « %{new_condition} »
|
||||||
|
enabled: "L’inéligibilité des dossiers a été activée"
|
||||||
|
disabled: "L’inéligibilité des dossiers a été désactivée"
|
||||||
|
message_updated: "Le message d’inéligibilité a été changé pour « %{ineligibilite_message} »"
|
|
@ -2,7 +2,7 @@
|
||||||
- list.with_empty do
|
- list.with_empty do
|
||||||
= t('.no_changes')
|
= t('.no_changes')
|
||||||
|
|
||||||
- @changes.each do |change|
|
- @tdc_changes.each do |change|
|
||||||
- prefix = change.private? ? 'private' : 'public'
|
- prefix = change.private? ? 'private' : 'public'
|
||||||
- case change.op
|
- case change.op
|
||||||
- when :add
|
- when :add
|
||||||
|
@ -176,3 +176,7 @@
|
||||||
- list.with_item do
|
- list.with_item do
|
||||||
.fr-alert.fr-alert--warning.fr-mt-1v
|
.fr-alert.fr-alert--warning.fr-mt-1v
|
||||||
= t(".invalid_routing_rules_alert")
|
= t(".invalid_routing_rules_alert")
|
||||||
|
|
||||||
|
- @ineligibilite_rules_changes.each do |change|
|
||||||
|
- list.with_item do
|
||||||
|
= t(".ineligibilite_rules.#{change.op}", **change.i18n_params)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%li.type-de-champ.flex.column.justify-start.fr-mb-5v{ html_options }
|
%li.type-de-champ.flex.column.justify-start.fr-mb-5v{ html_options }
|
||||||
.type-de-champ-container
|
.type-de-champ-container{ id: dom_id(type_de_champ.stable_self, :editor_error) }
|
||||||
- if @errors.present?
|
- if @errors.present?
|
||||||
.types-de-champ-errors
|
.types-de-champ-errors
|
||||||
= @errors
|
= @errors
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
.flex.justify-start.width-33
|
.flex.justify-start.width-33
|
||||||
.cell.flex.justify-start.column.flex-grow
|
.cell.flex.justify-start.column.flex-grow
|
||||||
= form.label :type_champ, "Type de champ", for: dom_id(type_de_champ, :type_champ)
|
= form.label :type_champ, "Type de champ", for: dom_id(type_de_champ, :type_champ)
|
||||||
= form.select :type_champ, grouped_options_for_select(types_of_type_de_champ, type_de_champ.type_champ), {}, class: 'fr-select small-margin small inline width-100', id: dom_id(type_de_champ, :type_champ), disabled: coordinate.used_by_routing_rules?
|
= form.select :type_champ, grouped_options_for_select(types_of_type_de_champ, type_de_champ.type_champ), {}, class: 'fr-select small-margin small inline width-100', id: dom_id(type_de_champ, :type_champ), disabled: coordinate.used_by_routing_rules? || coordinate.used_by_ineligibilite_rules?
|
||||||
|
|
||||||
.flex.column.justify-start.flex-grow
|
.flex.column.justify-start.flex-grow
|
||||||
.cell
|
.cell
|
||||||
|
@ -136,6 +136,10 @@
|
||||||
%span
|
%span
|
||||||
utilisé pour
|
utilisé pour
|
||||||
= link_to('le routage', admin_procedure_groupe_instructeurs_path(revision.procedure_id, anchor: 'routing-rules'))
|
= link_to('le routage', admin_procedure_groupe_instructeurs_path(revision.procedure_id, anchor: 'routing-rules'))
|
||||||
|
- elsif coordinate.used_by_ineligibilite_rules?
|
||||||
|
%span
|
||||||
|
utilisé pour
|
||||||
|
= link_to('l’eligibilité des dossiers', edit_admin_procedure_ineligibilite_rules_path(revision.procedure_id))
|
||||||
- else
|
- else
|
||||||
= button_to type_de_champ_path, class: 'fr-btn fr-btn--tertiary-no-outline fr-icon-delete-line', title: "Supprimer le champ", method: :delete, form: { data: { turbo_confirm: 'Êtes vous sûr de vouloir supprimer ce champ ?' } } do
|
= button_to type_de_champ_path, class: 'fr-btn fr-btn--tertiary-no-outline fr-icon-delete-line', title: "Supprimer le champ", method: :delete, form: { data: { turbo_confirm: 'Êtes vous sûr de vouloir supprimer ce champ ?' } } do
|
||||||
%span.sr-only Supprimer
|
%span.sr-only Supprimer
|
||||||
|
|
|
@ -17,4 +17,12 @@ class TypesDeChampEditor::EditorComponent < ApplicationComponent
|
||||||
@revision.revision_types_de_champ_public
|
@revision.revision_types_de_champ_public
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validation_context
|
||||||
|
if annotations?
|
||||||
|
:types_de_champ_private_editor
|
||||||
|
else
|
||||||
|
:types_de_champ_public_editor
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.fr-pb-12w{ 'data-turbo': 'true', id: dom_id(@revision, :types_de_champ_editor) }
|
.fr-pb-12w{ 'data-turbo': 'true', id: dom_id(@revision, :types_de_champ_editor) }
|
||||||
.types-de-champ-editor.editor-root
|
.types-de-champ-editor.editor-root
|
||||||
= render TypesDeChampEditor::ErrorsSummary.new(revision: @revision)
|
= render Procedure::ErrorsSummary.new(procedure: @revision.procedure, validation_context:)
|
||||||
= render TypesDeChampEditor::BlockComponent.new(block: @revision, coordinates: coordinates)
|
= render TypesDeChampEditor::BlockComponent.new(block: @revision, coordinates: coordinates)
|
||||||
#empty-coordinates{ hidden: coordinates.present? }
|
#empty-coordinates{ hidden: coordinates.present? }
|
||||||
= render TypesDeChampEditor::AddChampButtonComponent.new(revision: @revision, is_annotation: annotations?)
|
= render TypesDeChampEditor::AddChampButtonComponent.new(revision: @revision, is_annotation: annotations?)
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
class TypesDeChampEditor::ErrorsSummary < ApplicationComponent
|
|
||||||
def initialize(revision:)
|
|
||||||
@revision = revision
|
|
||||||
end
|
|
||||||
|
|
||||||
def invalid?
|
|
||||||
@revision.invalid?
|
|
||||||
end
|
|
||||||
|
|
||||||
def condition_errors?
|
|
||||||
@revision.errors.include?(:condition)
|
|
||||||
end
|
|
||||||
|
|
||||||
def header_section_errors?
|
|
||||||
@revision.errors.include?(:header_section)
|
|
||||||
end
|
|
||||||
|
|
||||||
def expression_reguliere_errors?
|
|
||||||
@revision.errors.include?(:expression_reguliere)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def errors_for(key)
|
|
||||||
@revision.errors.filter { _1.attribute == key }
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_message_for(key)
|
|
||||||
errors_for(key)
|
|
||||||
.map { |error| error.options[:type_de_champ] }
|
|
||||||
.map { |tdc| tag.li(tdc_anchor(tdc, key)) }
|
|
||||||
.then { |lis| tag.ul(lis.reduce(&:+)) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def tdc_anchor(tdc, key)
|
|
||||||
tag.a(tdc.libelle, href: champs_admin_procedure_path(@revision.procedure_id, anchor: dom_id(tdc.stable_self, key)), data: { turbo: false })
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,12 +0,0 @@
|
||||||
fr:
|
|
||||||
fix_conditional:
|
|
||||||
one: 'La logique conditionnelle du champ suivant est invalide, veuillez la corriger :'
|
|
||||||
other: 'La logique conditionnelle des champs suivants sont invalides, veuillez les corriger :'
|
|
||||||
|
|
||||||
fix_header_section:
|
|
||||||
one: 'Le titre de section suivant est invalide, veuillez le corriger :'
|
|
||||||
other: 'Les titres de section suivants sont invalides, veuillez les corriger :'
|
|
||||||
|
|
||||||
fix_expressions_regulieres:
|
|
||||||
one: "L'expression régulière suivante est invalide, veuillez la corriger :"
|
|
||||||
other: 'Les expressions régulières suivantes sont invalides, veuillez les corriger :'
|
|
|
@ -1,15 +0,0 @@
|
||||||
#errors-summary
|
|
||||||
- if invalid?
|
|
||||||
= render Dsfr::AlertComponent.new(state: :warning, title: "Le formulaire contient des erreurs", extra_class_names: 'fr-mb-2w') do |c|
|
|
||||||
- c.with_body do
|
|
||||||
- if condition_errors?
|
|
||||||
%p= t('.fix_conditional', count: errors_for(:condition).size)
|
|
||||||
= error_message_for(:condition)
|
|
||||||
|
|
||||||
- if header_section_errors?
|
|
||||||
%p= t('.fix_header_section', count: errors_for(:header_section).size)
|
|
||||||
= error_message_for(:header_section)
|
|
||||||
|
|
||||||
- if expression_reguliere_errors?
|
|
||||||
%p= t('.fix_expressions_regulieres', count: errors_for(:expression_reguliere).size)
|
|
||||||
= error_message_for(:expression_reguliere)
|
|
|
@ -31,7 +31,7 @@ class TypesDeChampEditor::HeaderSectionComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
def errors?
|
def errors?
|
||||||
!errors.empty?
|
errors.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_html_list(messages)
|
def to_html_list(messages)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%div{ id: dom_id(@tdc.stable_self, :header_section) }
|
%div{ id: dom_id(@tdc.stable_self, :header_section) }
|
||||||
- if errors?
|
- if errors?
|
||||||
.errors-summary= to_html_list(errors)
|
.errors-summary= errors
|
||||||
= @form.label :header_section_level, "Niveau du titre", for: dom_id(@tdc, :header_section_level)
|
= @form.label :header_section_level, "Niveau du titre", for: dom_id(@tdc, :header_section_level)
|
||||||
= @form.select :header_section_level, header_section_options_for_select, {}, id: dom_id(@tdc, :header_section_level), class: 'fr-select width-33'
|
= @form.select :header_section_level, header_section_options_for_select, {}, id: dom_id(@tdc, :header_section_level), class: 'fr-select width-33'
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
module Administrateurs
|
||||||
|
class IneligibiliteRulesController < AdministrateurController
|
||||||
|
before_action :retrieve_procedure
|
||||||
|
|
||||||
|
def edit
|
||||||
|
end
|
||||||
|
|
||||||
|
def change
|
||||||
|
if draft_revision.update(procedure_revision_params)
|
||||||
|
redirect_to edit_admin_procedure_ineligibilite_rules_path(@procedure)
|
||||||
|
else
|
||||||
|
flash[:alert] = draft_revision.errors.full_messages
|
||||||
|
render :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_row
|
||||||
|
condition = Logic.add_empty_condition_to(draft_revision.ineligibilite_rules)
|
||||||
|
draft_revision.update!(ineligibilite_rules: condition)
|
||||||
|
@ineligibilite_rules_component = build_ineligibilite_rules_component
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_row
|
||||||
|
condition = condition_form.delete_row(row_index).to_condition
|
||||||
|
draft_revision.update!(ineligibilite_rules: condition)
|
||||||
|
|
||||||
|
@ineligibilite_rules_component = build_ineligibilite_rules_component
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
condition = condition_form.to_condition
|
||||||
|
draft_revision.update!(ineligibilite_rules: condition)
|
||||||
|
|
||||||
|
@ineligibilite_rules_component = build_ineligibilite_rules_component
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_targeted_champ
|
||||||
|
condition = condition_form.change_champ(row_index).to_condition
|
||||||
|
draft_revision.update!(ineligibilite_rules: condition)
|
||||||
|
@ineligibilite_rules_component = build_ineligibilite_rules_component
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_ineligibilite_rules_component
|
||||||
|
Conditions::IneligibiliteRulesComponent.new(draft_revision: draft_revision)
|
||||||
|
end
|
||||||
|
|
||||||
|
def draft_revision
|
||||||
|
@procedure.draft_revision
|
||||||
|
end
|
||||||
|
|
||||||
|
def condition_form
|
||||||
|
ConditionForm.new(ineligibilite_rules_params.merge(source_tdcs: draft_revision.types_de_champ_for(scope: :public)))
|
||||||
|
end
|
||||||
|
|
||||||
|
def ineligibilite_rules_params
|
||||||
|
params
|
||||||
|
.require(:procedure_revision)
|
||||||
|
.require(:condition_form)
|
||||||
|
.permit(:top_operator_name, rows: [:targeted_champ, :operator_name, :value])
|
||||||
|
end
|
||||||
|
|
||||||
|
def row_index
|
||||||
|
params[:row_index].to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def procedure_revision_params
|
||||||
|
params
|
||||||
|
.require(:procedure_revision)
|
||||||
|
.permit(:ineligibilite_message, :ineligibilite_enabled)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,8 +1,12 @@
|
||||||
class API::Public::V1::BaseController < APIController
|
class API::Public::V1::BaseController < ApplicationController
|
||||||
skip_forgery_protection
|
skip_forgery_protection
|
||||||
|
|
||||||
before_action :check_content_type_is_json, if: -> { request.post? || request.patch? || request.put? }
|
before_action :check_content_type_is_json, if: -> { request.post? || request.patch? || request.put? }
|
||||||
|
|
||||||
|
before_action do
|
||||||
|
Current.browser = 'api'
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def render_missing_param(param_name)
|
def render_missing_param(param_name)
|
||||||
|
|
5
app/controllers/email_checker_controller.rb
Normal file
5
app/controllers/email_checker_controller.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class EmailCheckerController < ApplicationController
|
||||||
|
def show
|
||||||
|
render json: EmailChecker.new.check(email: params[:email])
|
||||||
|
end
|
||||||
|
end
|
|
@ -231,9 +231,9 @@ module Users
|
||||||
|
|
||||||
def submit_brouillon
|
def submit_brouillon
|
||||||
@dossier = dossier_with_champs(pj_template: false)
|
@dossier = dossier_with_champs(pj_template: false)
|
||||||
@errors = submit_dossier_and_compute_errors
|
submit_dossier_and_compute_errors
|
||||||
|
|
||||||
if @errors.blank?
|
if @dossier.errors.blank? && @dossier.can_passer_en_construction?
|
||||||
@dossier.passer_en_construction!
|
@dossier.passer_en_construction!
|
||||||
@dossier.process_declarative!
|
@dossier.process_declarative!
|
||||||
@dossier.process_sva_svr!
|
@dossier.process_sva_svr!
|
||||||
|
@ -278,9 +278,9 @@ module Users
|
||||||
editing_fork_origin.resolve_pending_correction
|
editing_fork_origin.resolve_pending_correction
|
||||||
end
|
end
|
||||||
|
|
||||||
@errors = submit_dossier_and_compute_errors
|
submit_dossier_and_compute_errors
|
||||||
|
|
||||||
if @errors.blank?
|
if @dossier.errors.blank? && @dossier.can_passer_en_construction?
|
||||||
editing_fork_origin.merge_fork(@dossier)
|
editing_fork_origin.merge_fork(@dossier)
|
||||||
editing_fork_origin.submit_en_construction!
|
editing_fork_origin.submit_en_construction!
|
||||||
|
|
||||||
|
@ -288,7 +288,6 @@ module Users
|
||||||
else
|
else
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
@dossier = editing_fork_origin
|
|
||||||
render :modifier
|
render :modifier
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -303,10 +302,10 @@ module Users
|
||||||
def update
|
def update
|
||||||
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
|
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
|
||||||
@dossier = dossier_with_champs(pj_template: false)
|
@dossier = dossier_with_champs(pj_template: false)
|
||||||
@errors = update_dossier_and_compute_errors
|
@can_passer_en_construction_was = @dossier.can_passer_en_construction?
|
||||||
|
update_dossier_and_compute_errors
|
||||||
@dossier.index_search_terms_later if @errors.empty?
|
@dossier.index_search_terms_later if @dossier.errors.empty?
|
||||||
|
@can_passer_en_construction_is = @dossier.can_passer_en_construction?
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.turbo_stream do
|
format.turbo_stream do
|
||||||
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
|
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
|
||||||
|
@ -567,21 +566,14 @@ module Users
|
||||||
|
|
||||||
def submit_dossier_and_compute_errors
|
def submit_dossier_and_compute_errors
|
||||||
@dossier.validate(:champs_public_value)
|
@dossier.validate(:champs_public_value)
|
||||||
|
@dossier.check_mandatory_and_visible_champs
|
||||||
errors = @dossier.errors
|
|
||||||
@dossier.check_mandatory_and_visible_champs.each do |error_on_champ|
|
|
||||||
errors.import(error_on_champ)
|
|
||||||
end
|
|
||||||
|
|
||||||
if @dossier.editing_fork_origin&.pending_correction?
|
if @dossier.editing_fork_origin&.pending_correction?
|
||||||
@dossier.editing_fork_origin.validate(:champs_public_value)
|
@dossier.editing_fork_origin.validate(:champs_public_value)
|
||||||
@dossier.editing_fork_origin.errors.where(:pending_correction).each do |error|
|
@dossier.editing_fork_origin.errors.where(:pending_correction).each do |error|
|
||||||
errors.import(error)
|
@dossier.errors.import(error)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
errors
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_ownership!
|
def ensure_ownership!
|
||||||
|
|
|
@ -819,6 +819,19 @@ class API::V2::StoredQuery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation dossierSupprimerMessage($input: DossierSupprimerMessageInput!) {
|
||||||
|
dossierSupprimerMessage(input: $input) {
|
||||||
|
message {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
discardedAt
|
||||||
|
}
|
||||||
|
errors {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mutation dossierModifierAnnotationText(
|
mutation dossierModifierAnnotationText(
|
||||||
$input: DossierModifierAnnotationTextInput!
|
$input: DossierModifierAnnotationTextInput!
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -21,6 +21,9 @@ module Mutations
|
||||||
if !dossier.en_construction?
|
if !dossier.en_construction?
|
||||||
return false, { errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
|
return false, { errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
|
||||||
end
|
end
|
||||||
|
if dossier.blocked_with_pending_correction?
|
||||||
|
return false, { errors: ["Le dossier est en attente de correction"] }
|
||||||
|
end
|
||||||
dossier_authorized_for?(dossier, instructeur)
|
dossier_authorized_for?(dossier, instructeur)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
24
app/graphql/mutations/dossier_supprimer_message.rb
Normal file
24
app/graphql/mutations/dossier_supprimer_message.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
module Mutations
|
||||||
|
class DossierSupprimerMessage < Mutations::BaseMutation
|
||||||
|
description "Supprimer un message."
|
||||||
|
|
||||||
|
argument :message_id, ID, required: true, loads: Types::MessageType
|
||||||
|
argument :instructeur_id, ID, required: true, loads: Types::ProfileType
|
||||||
|
|
||||||
|
field :message, Types::MessageType, null: true
|
||||||
|
field :errors, [Types::ValidationErrorType], null: true
|
||||||
|
|
||||||
|
def resolve(message:, **args)
|
||||||
|
message.soft_delete!
|
||||||
|
|
||||||
|
{ message: }
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized?(message:, instructeur:, **args)
|
||||||
|
if !message.soft_deletable?(instructeur)
|
||||||
|
return false, { errors: ["Le message ne peut pas être supprimé"] }
|
||||||
|
end
|
||||||
|
dossier_authorized_for?(message.dossier, instructeur)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2192,6 +2192,30 @@ enum DossierState {
|
||||||
sans_suite
|
sans_suite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Autogenerated input type of DossierSupprimerMessage
|
||||||
|
"""
|
||||||
|
input DossierSupprimerMessageInput {
|
||||||
|
"""
|
||||||
|
A unique identifier for the client performing the mutation.
|
||||||
|
"""
|
||||||
|
clientMutationId: String
|
||||||
|
instructeurId: ID!
|
||||||
|
messageId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Autogenerated return type of DossierSupprimerMessage.
|
||||||
|
"""
|
||||||
|
type DossierSupprimerMessagePayload {
|
||||||
|
"""
|
||||||
|
A unique identifier for the client performing the mutation.
|
||||||
|
"""
|
||||||
|
clientMutationId: String
|
||||||
|
errors: [ValidationError!]
|
||||||
|
message: Message
|
||||||
|
}
|
||||||
|
|
||||||
type DropDownListChampDescriptor implements ChampDescriptor {
|
type DropDownListChampDescriptor implements ChampDescriptor {
|
||||||
"""
|
"""
|
||||||
Description des champs d’un bloc répétable.
|
Description des champs d’un bloc répétable.
|
||||||
|
@ -3084,6 +3108,7 @@ type Message {
|
||||||
body: String!
|
body: String!
|
||||||
correction: Correction
|
correction: Correction
|
||||||
createdAt: ISO8601DateTime!
|
createdAt: ISO8601DateTime!
|
||||||
|
discardedAt: ISO8601DateTime
|
||||||
email: String!
|
email: String!
|
||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
|
@ -3313,6 +3338,16 @@ type Mutation {
|
||||||
input: DossierRepasserEnInstructionInput!
|
input: DossierRepasserEnInstructionInput!
|
||||||
): DossierRepasserEnInstructionPayload
|
): DossierRepasserEnInstructionPayload
|
||||||
|
|
||||||
|
"""
|
||||||
|
Supprimer un message.
|
||||||
|
"""
|
||||||
|
dossierSupprimerMessage(
|
||||||
|
"""
|
||||||
|
Parameters for DossierSupprimerMessage
|
||||||
|
"""
|
||||||
|
input: DossierSupprimerMessageInput!
|
||||||
|
): DossierSupprimerMessagePayload
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Ajouter des instructeurs à un groupe instructeur.
|
Ajouter des instructeurs à un groupe instructeur.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Types
|
||||||
field :email, String, null: false
|
field :email, String, null: false
|
||||||
field :body, String, null: false
|
field :body, String, null: false
|
||||||
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||||
|
field :discarded_at, GraphQL::Types::ISO8601DateTime, null: true
|
||||||
field :attachment, Types::File, null: true, deprecation_reason: "Utilisez le champ `attachments` à la place.", extensions: [
|
field :attachment, Types::File, null: true, deprecation_reason: "Utilisez le champ `attachments` à la place.", extensions: [
|
||||||
{ Extensions::Attachment => { attachments: :piece_jointe, as: :single } }
|
{ Extensions::Attachment => { attachments: :piece_jointe, as: :single } }
|
||||||
]
|
]
|
||||||
|
@ -19,5 +20,9 @@ module Types
|
||||||
def correction
|
def correction
|
||||||
Loaders::Association.for(object.class, :dossier_correction).load(object)
|
Loaders::Association.for(object.class, :dossier_correction).load(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.authorized?(object, context)
|
||||||
|
context.authorized_demarche?(object.dossier.revision.procedure)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@ module Types
|
||||||
field :create_direct_upload, mutation: Mutations::CreateDirectUpload
|
field :create_direct_upload, mutation: Mutations::CreateDirectUpload
|
||||||
|
|
||||||
field :dossier_envoyer_message, mutation: Mutations::DossierEnvoyerMessage
|
field :dossier_envoyer_message, mutation: Mutations::DossierEnvoyerMessage
|
||||||
|
field :dossier_supprimer_message, mutation: Mutations::DossierSupprimerMessage
|
||||||
field :dossier_passer_en_instruction, mutation: Mutations::DossierPasserEnInstruction
|
field :dossier_passer_en_instruction, mutation: Mutations::DossierPasserEnInstruction
|
||||||
field :dossier_classer_sans_suite, mutation: Mutations::DossierClasserSansSuite
|
field :dossier_classer_sans_suite, mutation: Mutations::DossierClasserSansSuite
|
||||||
field :dossier_refuser, mutation: Mutations::DossierRefuser
|
field :dossier_refuser, mutation: Mutations::DossierRefuser
|
||||||
|
|
27
app/helpers/gallery_helper.rb
Normal file
27
app/helpers/gallery_helper.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module GalleryHelper
|
||||||
|
def displayable_pdf?(blob)
|
||||||
|
blob.previewable? && blob.content_type.in?(AUTHORIZED_PDF_TYPES)
|
||||||
|
end
|
||||||
|
|
||||||
|
def displayable_image?(blob)
|
||||||
|
blob.variable? && blob.content_type.in?(AUTHORIZED_IMAGE_TYPES)
|
||||||
|
end
|
||||||
|
|
||||||
|
def preview_url_for(attachment)
|
||||||
|
attachment.preview(resize_to_limit: [400, 400]).processed.url
|
||||||
|
rescue StandardError
|
||||||
|
'pdf-placeholder.png'
|
||||||
|
end
|
||||||
|
|
||||||
|
def variant_url_for(attachment)
|
||||||
|
attachment.variant(resize_to_limit: [400, 400]).processed.url
|
||||||
|
rescue StandardError
|
||||||
|
'apercu-indisponible.png'
|
||||||
|
end
|
||||||
|
|
||||||
|
def blob_url(attachment)
|
||||||
|
attachment.blob.content_type.in?(RARE_IMAGE_TYPES) ? attachment.variant(resize_to_limit: [2000, 2000]).processed.url : attachment.blob.url
|
||||||
|
rescue StandardError
|
||||||
|
attachment.blob.url
|
||||||
|
end
|
||||||
|
end
|
|
@ -17,7 +17,11 @@ export class ClipboardController extends Controller {
|
||||||
connect(): void {
|
connect(): void {
|
||||||
// some extensions or browsers block clipboard
|
// some extensions or browsers block clipboard
|
||||||
if (!navigator.clipboard) {
|
if (!navigator.clipboard) {
|
||||||
this.element.classList.add('hidden');
|
if (this.hasToHideTarget) {
|
||||||
|
this.toHideTarget.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
this.element.classList.add('hidden');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,43 @@
|
||||||
import { suggest } from 'email-butler';
|
import { httpRequest } from '@utils';
|
||||||
import { show, hide } from '@utils';
|
import { show, hide } from '@utils';
|
||||||
import { ApplicationController } from './application_controller';
|
import { ApplicationController } from './application_controller';
|
||||||
|
|
||||||
|
type checkEmailResponse = {
|
||||||
|
success: boolean;
|
||||||
|
email_suggestions: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export class EmailInputController extends ApplicationController {
|
export class EmailInputController extends ApplicationController {
|
||||||
static targets = ['ariaRegion', 'suggestion', 'input'];
|
static targets = ['ariaRegion', 'suggestion', 'input'];
|
||||||
|
|
||||||
|
static values = {
|
||||||
|
url: String
|
||||||
|
};
|
||||||
|
|
||||||
|
declare readonly urlValue: string;
|
||||||
|
|
||||||
declare readonly ariaRegionTarget: HTMLElement;
|
declare readonly ariaRegionTarget: HTMLElement;
|
||||||
declare readonly suggestionTarget: HTMLElement;
|
declare readonly suggestionTarget: HTMLElement;
|
||||||
declare readonly inputTarget: HTMLInputElement;
|
declare readonly inputTarget: HTMLInputElement;
|
||||||
|
|
||||||
checkEmail() {
|
async checkEmail() {
|
||||||
const suggestion = suggest(this.inputTarget.value);
|
if (
|
||||||
if (suggestion && suggestion.full) {
|
!this.inputTarget.value ||
|
||||||
this.suggestionTarget.innerHTML = suggestion.full;
|
this.inputTarget.value.length < 5 ||
|
||||||
|
!this.inputTarget.value.includes('@')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(this.urlValue, document.baseURI);
|
||||||
|
url.searchParams.append('email', this.inputTarget.value);
|
||||||
|
|
||||||
|
const data: checkEmailResponse | null = await httpRequest(
|
||||||
|
url.toString()
|
||||||
|
).json();
|
||||||
|
|
||||||
|
if (data && data.email_suggestions && data.email_suggestions.length > 0) {
|
||||||
|
this.suggestionTarget.innerHTML = data.email_suggestions[0];
|
||||||
show(this.ariaRegionTarget);
|
show(this.ariaRegionTarget);
|
||||||
this.ariaRegionTarget.setAttribute('aria-live', 'assertive');
|
this.ariaRegionTarget.setAttribute('aria-live', 'assertive');
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { ApplicationController } from './application_controller';
|
||||||
|
declare interface modal {
|
||||||
|
disclose: () => void;
|
||||||
|
}
|
||||||
|
declare interface dsfr {
|
||||||
|
modal: modal;
|
||||||
|
}
|
||||||
|
declare const window: Window &
|
||||||
|
typeof globalThis & { dsfr: (elem: HTMLElement) => dsfr };
|
||||||
|
|
||||||
|
export class InvalidIneligibiliteRulesController extends ApplicationController {
|
||||||
|
static targets = ['dialog'];
|
||||||
|
|
||||||
|
declare dialogTarget: HTMLElement;
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
setTimeout(() => window.dsfr(this.dialogTarget).modal.disclose(), 100);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,10 @@ class ImageProcessorJob < ApplicationJob
|
||||||
# (to avoid modifying the file while it is being scanned).
|
# (to avoid modifying the file while it is being scanned).
|
||||||
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
|
retry_on FileNotScannedYetError, wait: :exponentially_longer, attempts: 10
|
||||||
|
|
||||||
|
rescue_from ActiveStorage::PreviewError do
|
||||||
|
retry_or_discard
|
||||||
|
end
|
||||||
|
|
||||||
def perform(blob)
|
def perform(blob)
|
||||||
return if blob.nil?
|
return if blob.nil?
|
||||||
raise FileNotScannedYetError if blob.virus_scanner.pending?
|
raise FileNotScannedYetError if blob.virus_scanner.pending?
|
||||||
|
@ -38,6 +42,9 @@ class ImageProcessorJob < ApplicationJob
|
||||||
blob.attachments.each do |attachment|
|
blob.attachments.each do |attachment|
|
||||||
next unless attachment&.representable?
|
next unless attachment&.representable?
|
||||||
attachment.representation(resize_to_limit: [400, 400]).processed
|
attachment.representation(resize_to_limit: [400, 400]).processed
|
||||||
|
if attachment.blob.content_type.in?(RARE_IMAGE_TYPES)
|
||||||
|
attachment.variant(resize_to_limit: [2000, 2000]).processed
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -55,4 +62,14 @@ class ImageProcessorJob < ApplicationJob
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def retry_or_discard
|
||||||
|
if executions < max_attempts
|
||||||
|
retry_job wait: 5.minutes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_attempts
|
||||||
|
3
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,7 @@ class ActiveJob::ApplicationLogSubscriber < ::ActiveJob::LogSubscriber
|
||||||
def process_event(event, type)
|
def process_event(event, type)
|
||||||
data = extract_metadata(event)
|
data = extract_metadata(event)
|
||||||
data.merge!(extract_exception(event))
|
data.merge!(extract_exception(event))
|
||||||
|
data[:request_id] = Current.request_id if Current.request_id.present?
|
||||||
|
|
||||||
case type
|
case type
|
||||||
when 'enqueue_at'
|
when 'enqueue_at'
|
||||||
|
|
|
@ -45,6 +45,7 @@ class BalancerDeliveryMethod
|
||||||
|
|
||||||
def prevent_delivery?(mail)
|
def prevent_delivery?(mail)
|
||||||
return false if mail[BYPASS_UNVERIFIED_MAIL_PROTECTION].present?
|
return false if mail[BYPASS_UNVERIFIED_MAIL_PROTECTION].present?
|
||||||
|
return false if mail.to.blank? # bcc list
|
||||||
|
|
||||||
user = User.find_by(email: mail.to.first)
|
user = User.find_by(email: mail.to.first)
|
||||||
return user.unverified_email? if user.present?
|
return user.unverified_email? if user.present?
|
||||||
|
|
652
app/lib/email_checker.rb
Normal file
652
app/lib/email_checker.rb
Normal file
|
@ -0,0 +1,652 @@
|
||||||
|
class EmailChecker
|
||||||
|
# Extracted 100 most used domain on our users table [june 2024]
|
||||||
|
# + all .gouv.fr domain on our users table
|
||||||
|
# + all .ac-xxx on our users table
|
||||||
|
KNOWN_DOMAINS = [
|
||||||
|
'gmail.com',
|
||||||
|
'hotmail.fr',
|
||||||
|
'orange.fr',
|
||||||
|
'yahoo.fr',
|
||||||
|
'hotmail.com',
|
||||||
|
'outlook.fr',
|
||||||
|
'wanadoo.fr',
|
||||||
|
'free.fr',
|
||||||
|
'yahoo.com',
|
||||||
|
'icloud.com',
|
||||||
|
'laposte.net',
|
||||||
|
'live.fr',
|
||||||
|
'sfr.fr',
|
||||||
|
'outlook.com',
|
||||||
|
'neuf.fr',
|
||||||
|
'aol.com',
|
||||||
|
'bbox.fr',
|
||||||
|
'msn.com',
|
||||||
|
'me.com',
|
||||||
|
'gmx.fr',
|
||||||
|
'protonmail.com',
|
||||||
|
'club-internet.fr',
|
||||||
|
'live.com',
|
||||||
|
'ymail.com',
|
||||||
|
'ars.sante.fr',
|
||||||
|
'mail.ru',
|
||||||
|
'cegetel.net',
|
||||||
|
'numericable.fr',
|
||||||
|
'aliceadsl.fr',
|
||||||
|
'comcast.net',
|
||||||
|
'assurance-maladie.fr',
|
||||||
|
'mac.com',
|
||||||
|
'naver.com',
|
||||||
|
'airbus.com',
|
||||||
|
'justice.fr',
|
||||||
|
'pole-emploi.fr',
|
||||||
|
'educagri.fr',
|
||||||
|
'aphp.fr',
|
||||||
|
'netcourrier.com',
|
||||||
|
'dbmail.com',
|
||||||
|
'aol.fr',
|
||||||
|
'qq.com',
|
||||||
|
'hotmail.co.uk',
|
||||||
|
'yahoo.co.uk',
|
||||||
|
'proxima-mail.fr',
|
||||||
|
'yahoo.com.br',
|
||||||
|
'sciencespo.fr',
|
||||||
|
'gmx.com',
|
||||||
|
'etu.univ-st-etienne.fr',
|
||||||
|
'yahoo.ca',
|
||||||
|
'163.com',
|
||||||
|
'francetravail.fr',
|
||||||
|
'mail.pf',
|
||||||
|
'nantesmetropole.fr',
|
||||||
|
'hotmail.it',
|
||||||
|
'sbcglobal.net',
|
||||||
|
'noos.fr',
|
||||||
|
'ird.fr',
|
||||||
|
'safrangroup.com',
|
||||||
|
'croix-rouge.fr',
|
||||||
|
'eiffage.com',
|
||||||
|
'veolia.com',
|
||||||
|
'notaires.fr',
|
||||||
|
'nordnet.fr',
|
||||||
|
'videotron.ca',
|
||||||
|
'paris.fr',
|
||||||
|
'lilo.org',
|
||||||
|
'mfr.asso.fr',
|
||||||
|
'yopmail.com',
|
||||||
|
'ukr.net',
|
||||||
|
'onf.fr',
|
||||||
|
'stellantis.com',
|
||||||
|
'9online.fr',
|
||||||
|
'atmp50.fr',
|
||||||
|
'engie.com',
|
||||||
|
'libertysurf.fr',
|
||||||
|
'mailo.com',
|
||||||
|
'auchan.fr',
|
||||||
|
'verizon.net',
|
||||||
|
'rocketmail.com',
|
||||||
|
'mpsa.com',
|
||||||
|
'entrepreneur.fr',
|
||||||
|
'googlemail.com',
|
||||||
|
'arcelormittal.com',
|
||||||
|
'groupe-sos.org',
|
||||||
|
'proton.me',
|
||||||
|
'att.net',
|
||||||
|
'pm.me',
|
||||||
|
'orange.com',
|
||||||
|
'abv.bg',
|
||||||
|
'yahoo.es',
|
||||||
|
'creditmutuel.fr',
|
||||||
|
'yandex.ru',
|
||||||
|
'essec.edu',
|
||||||
|
'urssaf.fr',
|
||||||
|
'bpifrance.fr',
|
||||||
|
'uol.com.br',
|
||||||
|
'suez.com',
|
||||||
|
'univ-st-etienne.fr',
|
||||||
|
'korian.fr',
|
||||||
|
'developpement-durable.gouv.fr',
|
||||||
|
'modernisation.gouv.fr',
|
||||||
|
'social.gouv.fr',
|
||||||
|
'emploi.gouv.fr',
|
||||||
|
'agriculture.gouv.fr',
|
||||||
|
'intradef.gouv.fr',
|
||||||
|
'interieur.gouv.fr',
|
||||||
|
'oise.gouv.fr',
|
||||||
|
'direccte.gouv.fr',
|
||||||
|
'culture.gouv.fr',
|
||||||
|
'pas-de-calais.gouv.fr',
|
||||||
|
'finances.gouv.fr',
|
||||||
|
'drieets.gouv.fr',
|
||||||
|
'drjscs.gouv.fr',
|
||||||
|
'sg.social.gouv.fr',
|
||||||
|
'martinique.pref.gouv.fr',
|
||||||
|
'beta.gouv.fr',
|
||||||
|
'dieccte.gouv.fr',
|
||||||
|
'cotes-darmor.gouv.fr',
|
||||||
|
'vosges.gouv.fr',
|
||||||
|
'developppement-durable.gouv.fr',
|
||||||
|
'mayenne.gouv.fr',
|
||||||
|
'aviation-civile.gouv.fr',
|
||||||
|
'data.gouv.fr',
|
||||||
|
'recherche.gouv.fr',
|
||||||
|
'sante.gouv.fr',
|
||||||
|
'paris-idf.gouv.fr',
|
||||||
|
'guyane.gouv.fr',
|
||||||
|
'douane.finances.gouv.fr',
|
||||||
|
'cget.gouv.fr',
|
||||||
|
'herault.gouv.fr',
|
||||||
|
'loire-atlantique.gouv.fr',
|
||||||
|
'manche.gouv.fr',
|
||||||
|
'seine-maritime.gouv.fr',
|
||||||
|
'dgccrf.finances.gouv.fr',
|
||||||
|
'tarn-et-garonne.gouv.fr',
|
||||||
|
'dila.gouv.fr',
|
||||||
|
'diplomatie.gouv.fr',
|
||||||
|
'haut-rhin.gouv.fr',
|
||||||
|
'nord.gouv.fr',
|
||||||
|
'bouches-du-rhone.gouv.fr',
|
||||||
|
'alpes-de-haute-provence.gouv.fr',
|
||||||
|
'hautes-alpes.gouv.fr',
|
||||||
|
'alpes-maritimes.gouv.fr',
|
||||||
|
'var.gouv.fr',
|
||||||
|
'vaucluse.gouv.fr',
|
||||||
|
'rhone.gouv.fr',
|
||||||
|
'occitanie.gouv.fr',
|
||||||
|
'ille-et-vilaine.gouv.fr',
|
||||||
|
'finistere.gouv.fr',
|
||||||
|
'aisne.gouv.fr',
|
||||||
|
'indre.gouv.fr',
|
||||||
|
'yvelines.gouv.fr',
|
||||||
|
'bas-rhin.gouv.fr',
|
||||||
|
'landes.gouv.fr',
|
||||||
|
'haute-marne.gouv.fr',
|
||||||
|
'correze.gouv.fr',
|
||||||
|
'val-doise.gouv.fr',
|
||||||
|
'seine-et-marne.gouv.fr',
|
||||||
|
'essonne.gouv.fr',
|
||||||
|
'calvados.gouv.fr',
|
||||||
|
'charente-maritime.gouv.fr',
|
||||||
|
'corse-du-sud.gouv.fr',
|
||||||
|
'gironde.gouv.fr',
|
||||||
|
'haute-corse.gouv.fr',
|
||||||
|
'morbihan.gouv.fr',
|
||||||
|
'pyrenees-atlantiques.gouv.fr',
|
||||||
|
'pyrenees-orientales.gouv.fr',
|
||||||
|
'somme.gouv.fr',
|
||||||
|
'vendee.gouv.fr',
|
||||||
|
'dgtresor.gouv.fr',
|
||||||
|
'marne.gouv.fr',
|
||||||
|
'auvergne-rhone-alpes.gouv.fr',
|
||||||
|
'meurthe-et-moselle.gouv.fr',
|
||||||
|
'pm.gouv.fr',
|
||||||
|
'oncfs.gouv.fr',
|
||||||
|
'orne.gouv.fr',
|
||||||
|
'charente.gouv.fr',
|
||||||
|
'travail.gouv.fr',
|
||||||
|
'gard.gouv.fr',
|
||||||
|
'maine-et-loire.gouv.fr',
|
||||||
|
'moselle.gouv.fr',
|
||||||
|
'outre-mer.gouv.fr',
|
||||||
|
'jscs.gouv.fr',
|
||||||
|
'haute-garonne.gouv.fr',
|
||||||
|
'vienne.gouv.fr',
|
||||||
|
'dordogne.gouv.fr',
|
||||||
|
'eure.gouv.fr',
|
||||||
|
'meuse.gouv.fr',
|
||||||
|
'savoie.gouv.fr',
|
||||||
|
'doubs.gouv.fr',
|
||||||
|
'bfc.gouv.fr',
|
||||||
|
'education.gouv.fr',
|
||||||
|
'ariege.gouv.fr',
|
||||||
|
'normandie.gouv.fr',
|
||||||
|
'gendarmerie.interieur.gouv.fr',
|
||||||
|
'ain.gouv.fr',
|
||||||
|
'ardennes.gouv.fr',
|
||||||
|
'drome.gouv.fr',
|
||||||
|
'bretagne.gouv.fr',
|
||||||
|
'paca.gouv.fr',
|
||||||
|
'haute-saone.gouv.fr',
|
||||||
|
'lot.gouv.fr',
|
||||||
|
'dgfip.finances.gouv.fr',
|
||||||
|
'aveyron.gouv.fr',
|
||||||
|
'gers.gouv.fr',
|
||||||
|
'tarn.gouv.fr',
|
||||||
|
'aude.gouv.fr',
|
||||||
|
'lozere.gouv.fr',
|
||||||
|
'hautes-pyrenees.gouv.fr',
|
||||||
|
'jeunesse-sports.gouv.fr',
|
||||||
|
'alpes.maritimes.gouv.fr',
|
||||||
|
'dreets.gouv.fr',
|
||||||
|
'justice.gouv.fr',
|
||||||
|
'sports.gouv.fr',
|
||||||
|
'nouvelle-aquitaine.gouv.fr',
|
||||||
|
'jura.gouv.fr',
|
||||||
|
'haute-savoie.gouv.fr',
|
||||||
|
'creuse.gouv.fr',
|
||||||
|
'creps-poitiers.sports.gouv.fr',
|
||||||
|
'equipement-agriculture.gouv.fr',
|
||||||
|
'ira-metz.gouv.fr',
|
||||||
|
'loire.gouv.fr',
|
||||||
|
'defense.gouv.fr',
|
||||||
|
'paris.gouv.fr',
|
||||||
|
'ensm.sports.gouv.fr',
|
||||||
|
'isere.gouv.fr',
|
||||||
|
'haute-loire.gouv.fr',
|
||||||
|
'cantal.gouv.fr',
|
||||||
|
'lot-et-garonne.gouv.fr',
|
||||||
|
'reunion.pref.gouv.fr',
|
||||||
|
'loiret.gouv.fr',
|
||||||
|
'indre-et-loire.gouv.fr',
|
||||||
|
'eleve.ira-metz.gouv.fr',
|
||||||
|
'deux-sevres.gouv.fr',
|
||||||
|
'inao.gouv.fr',
|
||||||
|
'franceconnect.gouv.fr',
|
||||||
|
'essone.gouv.fr',
|
||||||
|
'workinfrance.beta.gouv.fr',
|
||||||
|
'seine-saint-denis.gouv.fr',
|
||||||
|
'val-de-marne.gouv.fr',
|
||||||
|
'morbihan.pref.gouv.fr',
|
||||||
|
'externes.justice.gouv.fr',
|
||||||
|
'haute-vienne.gouv.fr',
|
||||||
|
'territoire-de-belfort.gouv.fr',
|
||||||
|
'creps-reunion.sports.gouv.fr',
|
||||||
|
'creps-centre.sports.gouv.fr',
|
||||||
|
'creps-rhonealpes.sports.gouv.fr',
|
||||||
|
'creps-montpellier.sports.gouv.fr',
|
||||||
|
'nord.pref.gouv.fr',
|
||||||
|
'charente-maritime.pref.gouv.fr',
|
||||||
|
'cher.gouv.fr',
|
||||||
|
'cote-dor.gouv.fr',
|
||||||
|
'ssi.gouv.fr',
|
||||||
|
'ira.gouv.fr',
|
||||||
|
'pays-de-la-loire.gouv.fr',
|
||||||
|
'loir-et-cher.gouv.fr',
|
||||||
|
'saone-et-loire.gouv.fr',
|
||||||
|
'enseignementsup.gouv.fr',
|
||||||
|
'eure-et-loir.gouv.fr',
|
||||||
|
'yonne.gouv.fr',
|
||||||
|
'guadeloupe.pref.gouv.fr',
|
||||||
|
'centre-val-de-loire.gouv.fr',
|
||||||
|
'entreprise.api.gouv.fr',
|
||||||
|
'grand-est.gouv.fr',
|
||||||
|
'sarthe.gouv.fr',
|
||||||
|
'sarthe.pref.gouv.fr',
|
||||||
|
'puy-de-dome.gouv.fr',
|
||||||
|
'externes.sante.gouv.fr',
|
||||||
|
'allier.gouv.fr',
|
||||||
|
'aube.gouv.fr',
|
||||||
|
'nievre.gouv.fr',
|
||||||
|
'ardeche.gouv.fr',
|
||||||
|
'api.gouv.fr',
|
||||||
|
'hauts-de-seine.gouv.fr',
|
||||||
|
'hauts-de-france.gouv.fr',
|
||||||
|
'temp-beta.gouv.fr',
|
||||||
|
'def.gouv.fr',
|
||||||
|
'particulier.api.gouv.fr',
|
||||||
|
'ira-lille.gouv.fr',
|
||||||
|
'haute-saone.pref.gouv.fr',
|
||||||
|
'yvelines.pref.gouv.fr',
|
||||||
|
'sgg.pm.gouv.fr',
|
||||||
|
'anah.gouv.fr',
|
||||||
|
'corse.gouv.fr',
|
||||||
|
'mayenne.pref.gouv.fr',
|
||||||
|
'cote-dor.pref.gouv.fr',
|
||||||
|
'guyane.pref.gouv.fr',
|
||||||
|
'ira-nantes.gouv.fr',
|
||||||
|
'igas.gouv.fr',
|
||||||
|
'tarn.pref.gouv.fr',
|
||||||
|
'martinique.gouv.fr',
|
||||||
|
'creps-paca.sports.gouv.fr',
|
||||||
|
'ofb.gouv.fr',
|
||||||
|
'loir-et-cher.pref.gouv.fr',
|
||||||
|
'indre-et-loire.pref.gouv.fr',
|
||||||
|
'polynesie-francaise.pref.gouv.fr',
|
||||||
|
'scl.finances.gouv.fr',
|
||||||
|
'numerique.gouv.fr',
|
||||||
|
'cantal.pref.gouv.fr',
|
||||||
|
'territoire-de-belfort.pref.gouv.fr',
|
||||||
|
'creps-wattignies.sports.gouv.fr',
|
||||||
|
'vienne.pref.gouv.fr',
|
||||||
|
'ardennes.pref.gouv.fr',
|
||||||
|
'creps-strasbourg.sports.gouv.fr',
|
||||||
|
'creps-dijon.sports.gouv.fr',
|
||||||
|
'ara.gouv.fr',
|
||||||
|
'sgdsn.gouv.fr',
|
||||||
|
'pays-de-la-loire.pref.gouv.fr',
|
||||||
|
'anct.gouv.fr',
|
||||||
|
'creps-pap.sports.gouv.fr',
|
||||||
|
'sgae.gouv.fr',
|
||||||
|
'esnm.sports.gouv.fr',
|
||||||
|
'nouvelle-caledonie.gouv.fr',
|
||||||
|
'deets.gouv.fr',
|
||||||
|
'mayotte.gouv.fr',
|
||||||
|
'creps-bordeaux.sports.gouv.fr',
|
||||||
|
'civs.gouv.fr',
|
||||||
|
'iga.interieur.gouv.fr',
|
||||||
|
'cab.travail.gouv.fr',
|
||||||
|
'ira-bastia.gouv.fr',
|
||||||
|
'ira-lyon.gouv.fr',
|
||||||
|
'creps-lorraine.sports.gouv.fr',
|
||||||
|
'dihal.gouv.fr',
|
||||||
|
'ofpra.gouv.fr',
|
||||||
|
'mayotte.pref.gouv.fr',
|
||||||
|
'strategie.gouv.fr',
|
||||||
|
'territoires.gouv.fr',
|
||||||
|
'dgcl.gouv.fr',
|
||||||
|
'doubs.pref.gouv.fr',
|
||||||
|
'service-civique.gouv.fr',
|
||||||
|
'maine-et-loire.pref.gouv.fr',
|
||||||
|
'envsn.sports.gouv.fr',
|
||||||
|
'wallis-et-futuna.pref.gouv.fr',
|
||||||
|
'gendarmerie.defense.gouv.fr',
|
||||||
|
'anlci.gouv.fr',
|
||||||
|
'cabinets.finances.gouv.fr',
|
||||||
|
'seine-maritime.pref.gouv.fr',
|
||||||
|
'promo46.ira-metz.gouv.fr',
|
||||||
|
'aisne.pref.gouv.fr',
|
||||||
|
'sportsdenature.gouv.fr',
|
||||||
|
'loire-atlantique.pref.gouv.fr',
|
||||||
|
'aude.pref.gouv.fr',
|
||||||
|
'premier-ministre.gouv.fr',
|
||||||
|
'igf.finances.gouv.fr',
|
||||||
|
'eleves.ira-bastia.gouv.fr',
|
||||||
|
'igesr.gouv.fr',
|
||||||
|
'alpc.gouv.fr',
|
||||||
|
'externes.emploi.gouv.fr',
|
||||||
|
'prestataire.finances.gouv.fr',
|
||||||
|
'gironde.pref.gouv.fr',
|
||||||
|
'premar-atlantique.gouv.fr',
|
||||||
|
'creps-toulouse.sports.gouv.fr',
|
||||||
|
'guadeloupe.gouv.fr',
|
||||||
|
'cybermalveillance.gouv.fr',
|
||||||
|
'dicod.defense.gouv.fr',
|
||||||
|
'creps-vichy.sports.gouv.fr',
|
||||||
|
'aft.gouv.fr',
|
||||||
|
'equipement.gouv.fr',
|
||||||
|
'academie.defense.gouv.fr',
|
||||||
|
'aube.pref.gouv.fr',
|
||||||
|
'seine-et-marne.pref.gouv.fr',
|
||||||
|
'pyrenees-orientales.pref.gouv.fr',
|
||||||
|
'haute-garonne.pref.gouv.fr',
|
||||||
|
'haut-rhin.pref.gouv.fr',
|
||||||
|
'seine-saint-denis.pref.gouv.fr',
|
||||||
|
'dcstep.gouv.fr',
|
||||||
|
'promo47.ira-metz.gouv.fr',
|
||||||
|
'trackdechets.beta.gouv.fr',
|
||||||
|
'val-de-marne.pref.gouv.fr',
|
||||||
|
'fabrique.social.gouv.fr',
|
||||||
|
'agrasc.gouv.fr',
|
||||||
|
'indre.pref.gouv.fr',
|
||||||
|
'tarn-et-garonne.pref.gouv.fr',
|
||||||
|
'corse.pref.gouv.fr',
|
||||||
|
'bas-rhin.pref.gouv.fr',
|
||||||
|
'inclusion.beta.gouv.fr',
|
||||||
|
'hauts-de-seine.pref.gouv.fr',
|
||||||
|
'loiret.pref.gouv.fr',
|
||||||
|
'essonne.pref.gouv.fr',
|
||||||
|
'territoires-industrie.gouv.fr',
|
||||||
|
'spm975.gouv.fr',
|
||||||
|
'saint-barth-saint-martin.gouv.fr',
|
||||||
|
'judiciaire.interieur.gouv.fr',
|
||||||
|
'mer.gouv.fr',
|
||||||
|
'premar-manche.gouv.fr',
|
||||||
|
'haute-normandie.pref.gouv.fr',
|
||||||
|
'prestataire.modernisation.gouv.fr',
|
||||||
|
'covoiturage.beta.gouv.fr',
|
||||||
|
'promo48.ira-metz.gouv.fr',
|
||||||
|
'france-services.gouv.fr',
|
||||||
|
'ddets.gouv.fr',
|
||||||
|
'afa.gouv.fr',
|
||||||
|
'externes.social.gouv.fr',
|
||||||
|
'vosges.pref.gouv.fr',
|
||||||
|
'reunion.gouv.fr',
|
||||||
|
'rhone.pref.gouv.fr',
|
||||||
|
'alpes-maritimes.pref.gouv.fr',
|
||||||
|
'gard.pref.gouv.fr',
|
||||||
|
'oise.pref.gouv.fr',
|
||||||
|
'creps-reims.sports.gouv.fr',
|
||||||
|
'bouches-du-rhone.pref.gouv.fr',
|
||||||
|
'esante.gouv.fr',
|
||||||
|
'rhone-alpes.pref.gouv.fr',
|
||||||
|
'finistere.pref.gouv.fr',
|
||||||
|
'ops-bss.defense.gouv.fr',
|
||||||
|
'orne.pref.gouv.fr',
|
||||||
|
'transformation.gouv.fr',
|
||||||
|
'cbcm.social.gouv.fr',
|
||||||
|
'recosante.beta.gouv.fr',
|
||||||
|
'pas-de-calais.pref.gouv.fr',
|
||||||
|
'promo49.ira-metz.gouv.fr',
|
||||||
|
'paca.pref.gouv.fr',
|
||||||
|
'meurthe-et-moselle.pref.gouv.fr',
|
||||||
|
'externes.sg.social.gouv.fr',
|
||||||
|
'puy-de-dome.pref.gouv.fr',
|
||||||
|
'academie.def.gouv.fr',
|
||||||
|
'tarn.gouv.frd81intranet.ddcspp.tarn.gouv.fr',
|
||||||
|
'agriculture-equipement.gouv.fr',
|
||||||
|
'creps-idf.sports.gouv.fr',
|
||||||
|
'eleve.ira-nantes.gouv.fr',
|
||||||
|
'cohesion-territoires.gouv.fr',
|
||||||
|
'ariege.pref.gouv.fr',
|
||||||
|
'pyrenees-atlantiques.pref.gouv.fr',
|
||||||
|
'hautes-pyrenees.pref.gouv.fr',
|
||||||
|
'lot-et-garonne.pref.gouv.fr',
|
||||||
|
'loire.pref.gouv.fr',
|
||||||
|
'info-routiere.gouv.fr',
|
||||||
|
'diges.gouv.fr',
|
||||||
|
'insp.gouv.fr',
|
||||||
|
'creps-pdl.sports.gouv.fr',
|
||||||
|
'ddc.social.gouv.fr',
|
||||||
|
'eleve.insp.gouv.fr',
|
||||||
|
'val-doise.pref.gouv.fr',
|
||||||
|
'montsaintmichel.gouv.fr',
|
||||||
|
'st-cyr.terre-net.defense.gouv.fr',
|
||||||
|
'.finances.gouv.fr',
|
||||||
|
'logement.gouv.fr',
|
||||||
|
'cotes-darmor.pref.gouv.fr',
|
||||||
|
'marne.pref.gouv.fr',
|
||||||
|
'herault.pref.gouv.fr',
|
||||||
|
'viennne.gouv.fr',
|
||||||
|
'landes.pref.gouv.fr',
|
||||||
|
'moselle.pref.gouv.fr',
|
||||||
|
'saone-et-loire.pref.gouv.fr',
|
||||||
|
'bmpm.gouv.fr',
|
||||||
|
'ecologie-territoires.gouv.fr',
|
||||||
|
'nievre.pref.gouv.fr',
|
||||||
|
'hautes-pyrénées.gouv.fr',
|
||||||
|
'gic.gouv.fr',
|
||||||
|
'industrie.gouv.fr',
|
||||||
|
'lot.pref.gouv.fr',
|
||||||
|
'plan.gouv.fr',
|
||||||
|
'internet.gouv.fr',
|
||||||
|
'mesads.beta.gouv.fr',
|
||||||
|
'gers.pref.gouv.fr',
|
||||||
|
'dordogne.pref.gouv.fr',
|
||||||
|
'somme.pref.gouv.fr',
|
||||||
|
'datasubvention.beta.gouv.fr',
|
||||||
|
'anc.gouv.fr',
|
||||||
|
'premar-mediterranee.gouv.fr',
|
||||||
|
'ille-et-vilaine.pref.gouv.fr',
|
||||||
|
'eure-et-loir.pref.gouv.fr',
|
||||||
|
'prestataires.pm.gouv.fr',
|
||||||
|
'snu.gouv.fr',
|
||||||
|
'code.gouv.fr',
|
||||||
|
'alsace.pref.gouv.fr',
|
||||||
|
'haute-vienne.pref.gouv.fr',
|
||||||
|
'yonne.pref.gouv.fr',
|
||||||
|
'bretagne.pref.gouv.fr',
|
||||||
|
'mastere.insp.gouv.fr',
|
||||||
|
'cada.pm.gouv.fr',
|
||||||
|
'creuse.pref.gouv.fr',
|
||||||
|
'ecologie.gouv.fr',
|
||||||
|
'midi-pyrenees.pref.gouv.fr',
|
||||||
|
'promo54.ira-metz.gouv.fr',
|
||||||
|
'var.pref.gouv.fr',
|
||||||
|
'alpes-de-haute-provence.pref.gouv.fr',
|
||||||
|
'mail.numerique.gouv.fr',
|
||||||
|
'france-identite.gouv.fr',
|
||||||
|
'transport.data.gouv.fr',
|
||||||
|
'allier.pref.gouv.fr',
|
||||||
|
'dilhal.gouv.fr',
|
||||||
|
'ardeche.pref.gouv.fr',
|
||||||
|
'haute-corse.pref.gouv.fr',
|
||||||
|
'intérieur.gouv.fr',
|
||||||
|
'ddfip.gouv.fr',
|
||||||
|
'calvados.pref.gouv.fr',
|
||||||
|
'territoir-de-belfort.gouv.fr',
|
||||||
|
'nor.gouv.fr',
|
||||||
|
'creps-occitanie.sports.gouv.fr',
|
||||||
|
'developpement-durabe.gouv.fr',
|
||||||
|
'educ.nat.gouv.fr',
|
||||||
|
'developpement-duable.gouv.fr',
|
||||||
|
'dgfip.finanes.gouv.fr',
|
||||||
|
'loire-atlantqieu.gouv.fr',
|
||||||
|
'promo55.ira-metz.gouv.fr',
|
||||||
|
'haute-saône.gouv.fr',
|
||||||
|
'developpement.durable.gouv.fr',
|
||||||
|
'dreet.gouv.fr',
|
||||||
|
'miprof.gouv.fr',
|
||||||
|
'pref.guyane.gouv.fr',
|
||||||
|
'developpement.gouv.fr',
|
||||||
|
'gendamrerie.interieur.gouv.fr',
|
||||||
|
'pyrenees-atlantique.gouv.fr',
|
||||||
|
'apprentissage.beta.gouv.fr',
|
||||||
|
'yveliens.gouv.fr',
|
||||||
|
'justiice.gouv.fr',
|
||||||
|
'cutlure.gouv.fr',
|
||||||
|
'aidantsconnect.beta.gouv.fr',
|
||||||
|
'developpement-durbale.gouv.fr',
|
||||||
|
'sine-et-marne.gouv.fr',
|
||||||
|
'sociale.gouv.fr',
|
||||||
|
'develeoppement-durable.gouv.fr',
|
||||||
|
'draaf.gouv.fr',
|
||||||
|
'drets.gouv.fr',
|
||||||
|
'ancli.gouv.fr',
|
||||||
|
'finistrere.gouv.fr',
|
||||||
|
'bourgogne.pref.gouv.fr',
|
||||||
|
'ac-polynesie.pf',
|
||||||
|
'ac-lille.fr',
|
||||||
|
'ac-nantes.fr',
|
||||||
|
'ac-martinique.fr',
|
||||||
|
'ac-creteil.fr',
|
||||||
|
'ac-toulouse.fr',
|
||||||
|
'ac-amiensfr',
|
||||||
|
'ac-amiens.fr',
|
||||||
|
'ac-rennes.fr',
|
||||||
|
'ac-strasbourg.fr',
|
||||||
|
'ac-lyon.fr',
|
||||||
|
'ac-versailles.fr',
|
||||||
|
'ac-audit.fr',
|
||||||
|
'ac-rouen.fr',
|
||||||
|
'ac-reunion.fr',
|
||||||
|
'ac-poitiers.fr',
|
||||||
|
'ac-caen.fr',
|
||||||
|
'ac-montpellier.fr',
|
||||||
|
'ac-paris.fr',
|
||||||
|
'ac-besancon.fr',
|
||||||
|
'ac-nancy-metz.fr',
|
||||||
|
'ac-aix-marseille.fr',
|
||||||
|
'ac-grenoble.fr',
|
||||||
|
'ac-corse.fr',
|
||||||
|
'ac-nice.fr',
|
||||||
|
'ac-orleans-tours.fr',
|
||||||
|
'ac-guadeloupe.fr',
|
||||||
|
'ac-reims.fr',
|
||||||
|
'ac-mayotte.fr',
|
||||||
|
'ac-clermont.fr',
|
||||||
|
'ac-bordeaux.fr',
|
||||||
|
'ac-limoges.fr',
|
||||||
|
'ac-normandie.fr',
|
||||||
|
'ac-dijon.fr',
|
||||||
|
'ac-guyane.fr',
|
||||||
|
'ac-transports.fr',
|
||||||
|
'ac-arpajonnais.com',
|
||||||
|
'ac-cned.fr',
|
||||||
|
'ac-nettoyage.com',
|
||||||
|
'ac-architectes.fr',
|
||||||
|
'ac-ajaccio.corsica',
|
||||||
|
'ac-noumea.nc',
|
||||||
|
'ac-spm.fr',
|
||||||
|
'ac-versailes.fr',
|
||||||
|
'ac-polynesie.fr',
|
||||||
|
'ac-experts.fr',
|
||||||
|
'ac-creteil.com',
|
||||||
|
'ac-smart-relocation.com',
|
||||||
|
'ac-ec.pro',
|
||||||
|
'ac-sas.fr',
|
||||||
|
'ac-derma.de',
|
||||||
|
'ac-or.com',
|
||||||
|
'ac-baugeois.fr',
|
||||||
|
'ac-5.ru',
|
||||||
|
'ac-arles.fr',
|
||||||
|
'ac-holding.net',
|
||||||
|
'ac-mb.fr',
|
||||||
|
'ac-wf.wf',
|
||||||
|
'ac-brest-finistere.fr',
|
||||||
|
'ac-leman.com',
|
||||||
|
'ac-darboussier.fr',
|
||||||
|
'ac-si.fr',
|
||||||
|
'ac-bordeau.fr',
|
||||||
|
'ac-gatinais.com',
|
||||||
|
'ac-cheminots.fr',
|
||||||
|
'ac-seyssinet.com',
|
||||||
|
'ac-cannes.fr',
|
||||||
|
'ac-prev.com',
|
||||||
|
'ac-sologne.fr',
|
||||||
|
'ac-rennes',
|
||||||
|
'ac-courbevoie.com',
|
||||||
|
'ac-ce.fr',
|
||||||
|
'ac-architecte.fr',
|
||||||
|
'ac-tions.org',
|
||||||
|
'ac-pm.fr',
|
||||||
|
'ac-avocats.com',
|
||||||
|
'ac-talents-rh.com',
|
||||||
|
'ac-louis.com',
|
||||||
|
'ac-internet.fr',
|
||||||
|
'ac-toulouse.com',
|
||||||
|
'ac-escial.fr',
|
||||||
|
'ac-environnement.com',
|
||||||
|
'ac-academie.fr',
|
||||||
|
'ac-poiters.fr',
|
||||||
|
'ac-bordeux.fr',
|
||||||
|
'ac-verseilles.fr',
|
||||||
|
'ac-ais-marseille.fr',
|
||||||
|
'ac-horizon.fr',
|
||||||
|
'ac-bordeaux.ft',
|
||||||
|
'ac-toulouses.fr',
|
||||||
|
'ac-toulous.fr'
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
def check(email:)
|
||||||
|
return { success: false } if email.blank?
|
||||||
|
|
||||||
|
parsed_email = Mail::Address.new(EmailSanitizableConcern::EmailSanitizer.sanitize(email))
|
||||||
|
return { success: false } if parsed_email.domain.blank?
|
||||||
|
|
||||||
|
return { success: true } if KNOWN_DOMAINS.any? { _1 == parsed_email.domain }
|
||||||
|
|
||||||
|
similar_domains = closest_domains(domain: parsed_email.domain)
|
||||||
|
return { success: true } if similar_domains.empty?
|
||||||
|
|
||||||
|
{ success: true, email_suggestions: email_suggestions(parsed_email:, similar_domains:) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def closest_domains(domain:)
|
||||||
|
KNOWN_DOMAINS.filter do |known_domain|
|
||||||
|
close_by_distance_of(domain, known_domain, distance: 1) ||
|
||||||
|
with_same_chars_and_close_by_distance_of(domain, known_domain, distance: 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def close_by_distance_of(a, b, distance:)
|
||||||
|
String::Similarity.levenshtein_distance(a, b) == distance
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_same_chars_and_close_by_distance_of(a, b, distance:)
|
||||||
|
close_by_distance_of(a, b, distance: 2) && a.chars.sort == b.chars.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
def email_suggestions(parsed_email:, similar_domains:)
|
||||||
|
similar_domains.map { Mail::Address.new("#{parsed_email.local}@#{_1}").to_s }
|
||||||
|
end
|
||||||
|
end
|
|
@ -88,10 +88,6 @@ class Champ < ApplicationRecord
|
||||||
parent_id.present?
|
parent_id.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def stable_id_with_row
|
|
||||||
[row_id, stable_id].compact
|
|
||||||
end
|
|
||||||
|
|
||||||
# used for the `required` html attribute
|
# used for the `required` html attribute
|
||||||
# check visibility to avoid hidden required input
|
# check visibility to avoid hidden required input
|
||||||
# which prevent the form from being sent.
|
# which prevent the form from being sent.
|
||||||
|
|
|
@ -21,6 +21,10 @@ module ChampConditionalConcern
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset_visible # recompute after a dossier update
|
||||||
|
remove_instance_variable :@visible if instance_variable_defined? :@visible
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def champs_for_condition
|
def champs_for_condition
|
||||||
|
|
|
@ -22,7 +22,7 @@ module DossierRebaseConcern
|
||||||
end
|
end
|
||||||
|
|
||||||
def pending_changes
|
def pending_changes
|
||||||
procedure.published_revision.present? ? revision.compare(procedure.published_revision) : []
|
procedure.published_revision.present? ? revision.compare_types_de_champ(procedure.published_revision) : []
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_rebase_mandatory_change?(stable_id)
|
def can_rebase_mandatory_change?(stable_id)
|
||||||
|
|
|
@ -307,7 +307,8 @@ module TagsSubstitutionConcern
|
||||||
|
|
||||||
def format_date(date)
|
def format_date(date)
|
||||||
if date.present?
|
if date.present?
|
||||||
date.strftime('%d/%m/%Y')
|
format = defined?(self.class::FORMAT_DATE) ? self.class::FORMAT_DATE : '%d/%m/%Y'
|
||||||
|
date.strftime(format)
|
||||||
else
|
else
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,7 +59,7 @@ class Dossier < ApplicationRecord
|
||||||
has_many :previous_follows, -> { inactive }, class_name: 'Follow', inverse_of: :dossier
|
has_many :previous_follows, -> { inactive }, class_name: 'Follow', inverse_of: :dossier
|
||||||
has_many :followers_instructeurs, through: :follows, source: :instructeur
|
has_many :followers_instructeurs, through: :follows, source: :instructeur
|
||||||
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
|
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
|
||||||
has_many :avis, inverse_of: :dossier, dependent: :destroy
|
has_many :avis, -> { order(:created_at) }, inverse_of: :dossier, dependent: :destroy
|
||||||
has_many :experts, through: :avis
|
has_many :experts, through: :avis
|
||||||
has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy do
|
has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy do
|
||||||
def passer_en_construction(instructeur: nil, processed_at: Time.zone.now)
|
def passer_en_construction(instructeur: nil, processed_at: Time.zone.now)
|
||||||
|
@ -156,7 +156,7 @@ class Dossier < ApplicationRecord
|
||||||
state :sans_suite
|
state :sans_suite
|
||||||
|
|
||||||
event :passer_en_construction, after: :after_passer_en_construction, after_commit: :after_commit_passer_en_construction do
|
event :passer_en_construction, after: :after_passer_en_construction, after_commit: :after_commit_passer_en_construction do
|
||||||
transitions from: :brouillon, to: :en_construction
|
transitions from: :brouillon, to: :en_construction, guard: :can_passer_en_construction?
|
||||||
end
|
end
|
||||||
|
|
||||||
event :passer_en_instruction, after: :after_passer_en_instruction, after_commit: :after_commit_passer_en_instruction do
|
event :passer_en_instruction, after: :after_passer_en_instruction, after_commit: :after_commit_passer_en_instruction do
|
||||||
|
@ -558,8 +558,18 @@ class Dossier < ApplicationRecord
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def blocked_with_pending_correction?
|
||||||
|
procedure.feature_enabled?(:blocking_pending_correction) && pending_correction?
|
||||||
|
end
|
||||||
|
|
||||||
|
def can_passer_en_construction?
|
||||||
|
return true if !revision.ineligibilite_enabled
|
||||||
|
|
||||||
|
!revision.ineligibilite_rules.compute(champs_for_revision(scope: :public))
|
||||||
|
end
|
||||||
|
|
||||||
def can_passer_en_instruction?
|
def can_passer_en_instruction?
|
||||||
return false if procedure.feature_enabled?(:blocking_pending_correction) && pending_correction?
|
return false if blocked_with_pending_correction?
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
@ -932,6 +942,7 @@ class Dossier < ApplicationRecord
|
||||||
.map do |champ|
|
.map do |champ|
|
||||||
champ.errors.add(:value, :missing)
|
champ.errors.add(:value, :missing)
|
||||||
end
|
end
|
||||||
|
.each { errors.import(_1) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def demander_un_avis!(avis)
|
def demander_un_avis!(avis)
|
||||||
|
@ -1006,7 +1017,6 @@ class Dossier < ApplicationRecord
|
||||||
else
|
else
|
||||||
columns << ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale]
|
columns << ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale]
|
||||||
end
|
end
|
||||||
|
|
||||||
if procedure.chorusable? && procedure.chorus_configuration.complete?
|
if procedure.chorusable? && procedure.chorus_configuration.complete?
|
||||||
columns += [
|
columns += [
|
||||||
['Domaine Fonctionnel', procedure.chorus_configuration.domaine_fonctionnel&.fetch("code") { '' }],
|
['Domaine Fonctionnel', procedure.chorus_configuration.domaine_fonctionnel&.fetch("code") { '' }],
|
||||||
|
|
|
@ -7,6 +7,7 @@ class ExportTemplate < ApplicationRecord
|
||||||
validates_with ExportTemplateValidator
|
validates_with ExportTemplateValidator
|
||||||
|
|
||||||
DOSSIER_STATE = Dossier.states.fetch(:en_construction)
|
DOSSIER_STATE = Dossier.states.fetch(:en_construction)
|
||||||
|
FORMAT_DATE = "%Y-%m-%d"
|
||||||
|
|
||||||
def set_default_values
|
def set_default_values
|
||||||
content["default_dossier_directory"] = tiptap_json("dossier-")
|
content["default_dossier_directory"] = tiptap_json("dossier-")
|
||||||
|
@ -48,7 +49,7 @@ class ExportTemplate < ApplicationRecord
|
||||||
def attachment_and_path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
def attachment_and_path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
||||||
[
|
[
|
||||||
attachment,
|
attachment,
|
||||||
path(dossier, attachment, index, row_index, champ)
|
path(dossier, attachment, index:, row_index:, champ:)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -116,7 +117,7 @@ class ExportTemplate < ApplicationRecord
|
||||||
"#{render_attributes_for(content["pdf_name"], dossier)}.pdf"
|
"#{render_attributes_for(content["pdf_name"], dossier)}.pdf"
|
||||||
end
|
end
|
||||||
|
|
||||||
def path(dossier, attachment, index, row_index, champ)
|
def path(dossier, attachment, index: 0, row_index: nil, champ: nil)
|
||||||
if attachment.name == 'pdf_export_for_instructeur'
|
if attachment.name == 'pdf_export_for_instructeur'
|
||||||
return export_path(dossier)
|
return export_path(dossier)
|
||||||
end
|
end
|
||||||
|
@ -128,6 +129,8 @@ class ExportTemplate < ApplicationRecord
|
||||||
'messagerie'
|
'messagerie'
|
||||||
when 'Avis'
|
when 'Avis'
|
||||||
'avis'
|
'avis'
|
||||||
|
when 'Attestation', 'Etablissement'
|
||||||
|
'pieces_justificatives'
|
||||||
else
|
else
|
||||||
# for attachment
|
# for attachment
|
||||||
return attachment_path(dossier, attachment, index, row_index, champ)
|
return attachment_path(dossier, attachment, index, row_index, champ)
|
||||||
|
|
|
@ -259,13 +259,19 @@ class Procedure < ApplicationRecord
|
||||||
validates :lien_dpo, url: { no_local: true, allow_blank: true, accept_email: true }
|
validates :lien_dpo, url: { no_local: true, allow_blank: true, accept_email: true }
|
||||||
|
|
||||||
validates :draft_types_de_champ_public,
|
validates :draft_types_de_champ_public,
|
||||||
|
'types_de_champ/condition': true,
|
||||||
|
'types_de_champ/expression_reguliere': true,
|
||||||
|
'types_de_champ/header_section_consistency': true,
|
||||||
'types_de_champ/no_empty_block': true,
|
'types_de_champ/no_empty_block': true,
|
||||||
'types_de_champ/no_empty_drop_down': true,
|
'types_de_champ/no_empty_drop_down': true,
|
||||||
on: :publication
|
on: [:types_de_champ_public_editor, :publication]
|
||||||
|
|
||||||
validates :draft_types_de_champ_private,
|
validates :draft_types_de_champ_private,
|
||||||
|
'types_de_champ/condition': true,
|
||||||
|
'types_de_champ/header_section_consistency': true,
|
||||||
'types_de_champ/no_empty_block': true,
|
'types_de_champ/no_empty_block': true,
|
||||||
'types_de_champ/no_empty_drop_down': true,
|
'types_de_champ/no_empty_drop_down': true,
|
||||||
on: :publication
|
on: [:types_de_champ_private_editor, :publication]
|
||||||
|
|
||||||
validate :check_juridique, on: [:create, :publication]
|
validate :check_juridique, on: [:create, :publication]
|
||||||
|
|
||||||
|
@ -287,7 +293,7 @@ class Procedure < ApplicationRecord
|
||||||
|
|
||||||
validates_with MonAvisEmbedValidator
|
validates_with MonAvisEmbedValidator
|
||||||
|
|
||||||
validates_associated :draft_revision, on: :publication
|
validate :validates_associated_draft_revision_with_context
|
||||||
validates_associated :initiated_mail, on: :publication
|
validates_associated :initiated_mail, on: :publication
|
||||||
validates_associated :received_mail, on: :publication
|
validates_associated :received_mail, on: :publication
|
||||||
validates_associated :closed_mail, on: :publication
|
validates_associated :closed_mail, on: :publication
|
||||||
|
@ -425,11 +431,15 @@ class Procedure < ApplicationRecord
|
||||||
|
|
||||||
def draft_changed?
|
def draft_changed?
|
||||||
preload_draft_and_published_revisions
|
preload_draft_and_published_revisions
|
||||||
!brouillon? && published_revision.different_from?(draft_revision) && revision_changes.present?
|
!brouillon? && (types_de_champ_revision_changes.present? || ineligibilite_rules_revision_changes.present?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def revision_changes
|
def types_de_champ_revision_changes
|
||||||
published_revision.compare(draft_revision)
|
published_revision.compare_types_de_champ(draft_revision)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ineligibilite_rules_revision_changes
|
||||||
|
published_revision.compare_ineligibilite_rules(draft_revision)
|
||||||
end
|
end
|
||||||
|
|
||||||
def preload_draft_and_published_revisions
|
def preload_draft_and_published_revisions
|
||||||
|
@ -553,6 +563,7 @@ class Procedure < ApplicationRecord
|
||||||
procedure.closing_notification_brouillon = false
|
procedure.closing_notification_brouillon = false
|
||||||
procedure.closing_notification_en_cours = false
|
procedure.closing_notification_en_cours = false
|
||||||
procedure.template = false
|
procedure.template = false
|
||||||
|
procedure.monavis_embed = nil
|
||||||
|
|
||||||
if !procedure.valid?
|
if !procedure.valid?
|
||||||
procedure.errors.attribute_names.each do |attribute|
|
procedure.errors.attribute_names.each do |attribute|
|
||||||
|
@ -1014,6 +1025,13 @@ class Procedure < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def validates_associated_draft_revision_with_context
|
||||||
|
return if draft_revision.blank?
|
||||||
|
return if draft_revision.validate(validation_context)
|
||||||
|
|
||||||
|
draft_revision.errors.map { errors.import(_1) }
|
||||||
|
end
|
||||||
|
|
||||||
def validate_auto_archive_on_in_the_future
|
def validate_auto_archive_on_in_the_future
|
||||||
return if auto_archive_on.nil?
|
return if auto_archive_on.nil?
|
||||||
return if auto_archive_on.future?
|
return if auto_archive_on.future?
|
||||||
|
|
|
@ -14,7 +14,7 @@ ProcedureDetail = Struct.new(:id, :libelle, :published_at, :aasm_state, :estimat
|
||||||
end
|
end
|
||||||
|
|
||||||
def parsed_latest_zone_labels
|
def parsed_latest_zone_labels
|
||||||
# Replace curly braces with square brackets to make it a valid JSON array
|
return [] if latest_zone_labels.nil? || latest_zone_labels.strip.empty?
|
||||||
JSON.parse(latest_zone_labels.tr('{', '[').tr('}', ']'))
|
JSON.parse(latest_zone_labels.tr('{', '[').tr('}', ']'))
|
||||||
rescue JSON::ParserError
|
rescue JSON::ParserError
|
||||||
[]
|
[]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class ProcedureRevision < ApplicationRecord
|
class ProcedureRevision < ApplicationRecord
|
||||||
|
include Logic
|
||||||
self.implicit_order_column = :created_at
|
self.implicit_order_column = :created_at
|
||||||
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
|
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
|
||||||
belongs_to :dossier_submitted_message, inverse_of: :revisions, optional: true, dependent: :destroy
|
belongs_to :dossier_submitted_message, inverse_of: :revisions, optional: true, dependent: :destroy
|
||||||
|
@ -17,12 +18,19 @@ class ProcedureRevision < ApplicationRecord
|
||||||
|
|
||||||
scope :ordered, -> { order(:created_at) }
|
scope :ordered, -> { order(:created_at) }
|
||||||
|
|
||||||
validate :conditions_are_valid?
|
validates :ineligibilite_message, presence: true, if: -> { ineligibilite_enabled? }
|
||||||
validate :header_sections_are_valid?
|
|
||||||
validate :expressions_regulieres_are_valid?
|
|
||||||
|
|
||||||
delegate :path, to: :procedure, prefix: true
|
delegate :path, to: :procedure, prefix: true
|
||||||
|
|
||||||
|
validate :ineligibilite_rules_are_valid?,
|
||||||
|
on: [:ineligibilite_rules_editor, :publication]
|
||||||
|
validates :ineligibilite_message,
|
||||||
|
presence: true,
|
||||||
|
if: -> { ineligibilite_enabled? },
|
||||||
|
on: [:ineligibilite_rules_editor, :publication]
|
||||||
|
|
||||||
|
serialize :ineligibilite_rules, LogicSerializer
|
||||||
|
|
||||||
def build_champs_public
|
def build_champs_public
|
||||||
# reload: it can be out of sync in test if some tdcs are added wihtout using add_tdc
|
# reload: it can be out of sync in test if some tdcs are added wihtout using add_tdc
|
||||||
types_de_champ_public.reload.map(&:build_champ)
|
types_de_champ_public.reload.map(&:build_champ)
|
||||||
|
@ -140,16 +148,18 @@ class ProcedureRevision < ApplicationRecord
|
||||||
!draft?
|
!draft?
|
||||||
end
|
end
|
||||||
|
|
||||||
def different_from?(revision)
|
def compare_types_de_champ(revision)
|
||||||
revision_types_de_champ != revision.revision_types_de_champ
|
|
||||||
end
|
|
||||||
|
|
||||||
def compare(revision)
|
|
||||||
changes = []
|
changes = []
|
||||||
changes += compare_revision_types_de_champ(revision_types_de_champ, revision.revision_types_de_champ)
|
changes += compare_revision_types_de_champ(revision_types_de_champ, revision.revision_types_de_champ)
|
||||||
changes
|
changes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def compare_ineligibilite_rules(revision)
|
||||||
|
changes = []
|
||||||
|
changes += compare_revision_ineligibilite_rules(revision)
|
||||||
|
changes
|
||||||
|
end
|
||||||
|
|
||||||
def dossier_for_preview(user)
|
def dossier_for_preview(user)
|
||||||
dossier = Dossier
|
dossier = Dossier
|
||||||
.create_with(autorisation_donnees: true)
|
.create_with(autorisation_donnees: true)
|
||||||
|
@ -255,6 +265,10 @@ class ProcedureRevision < ApplicationRecord
|
||||||
types_de_champ_public.filter(&:routable?)
|
types_de_champ_public.filter(&:routable?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def conditionable_types_de_champ
|
||||||
|
types_de_champ_for(scope: :public).filter(&:conditionable?)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def compute_estimated_fill_duration
|
def compute_estimated_fill_duration
|
||||||
|
@ -322,6 +336,29 @@ class ProcedureRevision < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def compare_revision_ineligibilite_rules(new_revision)
|
||||||
|
from_ineligibilite_rules = ineligibilite_rules
|
||||||
|
to_ineligibilite_rules = new_revision.ineligibilite_rules
|
||||||
|
changes = []
|
||||||
|
|
||||||
|
if from_ineligibilite_rules.present? && to_ineligibilite_rules.blank?
|
||||||
|
changes << ProcedureRevisionChange::RemoveEligibiliteRuleChange
|
||||||
|
end
|
||||||
|
if from_ineligibilite_rules.blank? && to_ineligibilite_rules.present?
|
||||||
|
changes << ProcedureRevisionChange::AddEligibiliteRuleChange
|
||||||
|
end
|
||||||
|
if from_ineligibilite_rules != to_ineligibilite_rules
|
||||||
|
changes << ProcedureRevisionChange::UpdateEligibiliteRuleChange
|
||||||
|
end
|
||||||
|
if ineligibilite_message != new_revision.ineligibilite_message
|
||||||
|
changes << ProcedureRevisionChange::UpdateEligibiliteMessageChange
|
||||||
|
end
|
||||||
|
if ineligibilite_enabled != new_revision.ineligibilite_enabled
|
||||||
|
changes << (new_revision.ineligibilite_enabled ? ProcedureRevisionChange::EligibiliteEnabledChange : ProcedureRevisionChange::EligibiliteDisabledChange)
|
||||||
|
end
|
||||||
|
changes.map { _1.new(self, new_revision) }
|
||||||
|
end
|
||||||
|
|
||||||
def compare_type_de_champ(from_type_de_champ, to_type_de_champ, from_coordinates, to_coordinates)
|
def compare_type_de_champ(from_type_de_champ, to_type_de_champ, from_coordinates, to_coordinates)
|
||||||
changes = []
|
changes = []
|
||||||
if from_type_de_champ.type_champ != to_type_de_champ.type_champ
|
if from_type_de_champ.type_champ != to_type_de_champ.type_champ
|
||||||
|
@ -446,6 +483,13 @@ class ProcedureRevision < ApplicationRecord
|
||||||
changes
|
changes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ineligibilite_rules_are_valid?
|
||||||
|
if ineligibilite_rules
|
||||||
|
ineligibilite_rules.errors(types_de_champ_for(scope: :public).to_a)
|
||||||
|
.each { errors.add(:ineligibilite_rules, :invalid) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def replace_type_de_champ_by_clone(coordinate)
|
def replace_type_de_champ_by_clone(coordinate)
|
||||||
cloned_type_de_champ = coordinate.type_de_champ.deep_clone do |original, kopy|
|
cloned_type_de_champ = coordinate.type_de_champ.deep_clone do |original, kopy|
|
||||||
ClonePiecesJustificativesService.clone_attachments(original, kopy)
|
ClonePiecesJustificativesService.clone_attachments(original, kopy)
|
||||||
|
@ -453,48 +497,4 @@ class ProcedureRevision < ApplicationRecord
|
||||||
coordinate.update!(type_de_champ: cloned_type_de_champ)
|
coordinate.update!(type_de_champ: cloned_type_de_champ)
|
||||||
cloned_type_de_champ
|
cloned_type_de_champ
|
||||||
end
|
end
|
||||||
|
|
||||||
def conditions_are_valid?
|
|
||||||
public_tdcs = types_de_champ_public.to_a
|
|
||||||
.flat_map { _1.repetition? ? children_of(_1) : _1 }
|
|
||||||
|
|
||||||
public_tdcs
|
|
||||||
.map.with_index
|
|
||||||
.filter_map { |tdc, i| tdc.condition? ? [tdc, i] : nil }
|
|
||||||
.map do |tdc, i|
|
|
||||||
[tdc, tdc.condition.errors(public_tdcs.take(i))]
|
|
||||||
end
|
|
||||||
.filter { |_tdc, errors| errors.present? }
|
|
||||||
.each { |tdc, message| errors.add(:condition, message, type_de_champ: tdc) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def header_sections_are_valid?
|
|
||||||
public_tdcs = types_de_champ_public.to_a
|
|
||||||
|
|
||||||
root_tdcs_errors = errors_for_header_sections_order(public_tdcs)
|
|
||||||
repetition_tdcs_errors = public_tdcs
|
|
||||||
.filter_map { _1.repetition? ? children_of(_1) : nil }
|
|
||||||
.map { errors_for_header_sections_order(_1) }
|
|
||||||
|
|
||||||
repetition_tdcs_errors + root_tdcs_errors
|
|
||||||
end
|
|
||||||
|
|
||||||
def expressions_regulieres_are_valid?
|
|
||||||
types_de_champ_public.to_a
|
|
||||||
.flat_map { _1.repetition? ? children_of(_1) : _1 }
|
|
||||||
.each do |tdc|
|
|
||||||
if tdc.expression_reguliere? && tdc.invalid_regexp?
|
|
||||||
errors.add(:expression_reguliere, type_de_champ: tdc)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def errors_for_header_sections_order(tdcs)
|
|
||||||
tdcs
|
|
||||||
.map.with_index
|
|
||||||
.filter_map { |tdc, i| tdc.header_section? ? [tdc, i] : nil }
|
|
||||||
.map { |tdc, i| [tdc, tdc.check_coherent_header_level(tdcs.take(i))] }
|
|
||||||
.filter { |_tdc, errors| errors.present? }
|
|
||||||
.each { |tdc, message| errors.add(:header_section, message, type_de_champ: tdc) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
class ProcedureRevisionChange
|
class ProcedureRevisionChange
|
||||||
attr_reader :type_de_champ
|
class TypeDeChange
|
||||||
def initialize(type_de_champ)
|
attr_reader :type_de_champ
|
||||||
@type_de_champ = type_de_champ
|
def initialize(type_de_champ)
|
||||||
|
@type_de_champ = type_de_champ
|
||||||
|
end
|
||||||
|
|
||||||
|
def label = @type_de_champ.libelle
|
||||||
|
def stable_id = @type_de_champ.stable_id
|
||||||
|
def private? = @type_de_champ.private?
|
||||||
|
def child? = @type_de_champ.child?
|
||||||
|
|
||||||
|
def to_h = { op:, stable_id:, label:, private: private? }
|
||||||
end
|
end
|
||||||
|
|
||||||
def label = @type_de_champ.libelle
|
class AddChamp < TypeDeChange
|
||||||
def stable_id = @type_de_champ.stable_id
|
|
||||||
def private? = @type_de_champ.private?
|
|
||||||
def child? = @type_de_champ.child?
|
|
||||||
|
|
||||||
def to_h = { op:, stable_id:, label:, private: private? }
|
|
||||||
|
|
||||||
class AddChamp < ProcedureRevisionChange
|
|
||||||
def initialize(type_de_champ)
|
def initialize(type_de_champ)
|
||||||
super(type_de_champ)
|
super(type_de_champ)
|
||||||
end
|
end
|
||||||
|
@ -23,7 +25,7 @@ class ProcedureRevisionChange
|
||||||
def to_h = super.merge(mandatory: mandatory?)
|
def to_h = super.merge(mandatory: mandatory?)
|
||||||
end
|
end
|
||||||
|
|
||||||
class RemoveChamp < ProcedureRevisionChange
|
class RemoveChamp < TypeDeChange
|
||||||
def initialize(type_de_champ)
|
def initialize(type_de_champ)
|
||||||
super(type_de_champ)
|
super(type_de_champ)
|
||||||
end
|
end
|
||||||
|
@ -32,7 +34,7 @@ class ProcedureRevisionChange
|
||||||
def can_rebase?(dossier = nil) = true
|
def can_rebase?(dossier = nil) = true
|
||||||
end
|
end
|
||||||
|
|
||||||
class MoveChamp < ProcedureRevisionChange
|
class MoveChamp < TypeDeChange
|
||||||
attr_reader :from, :to
|
attr_reader :from, :to
|
||||||
|
|
||||||
def initialize(type_de_champ, from, to)
|
def initialize(type_de_champ, from, to)
|
||||||
|
@ -46,7 +48,7 @@ class ProcedureRevisionChange
|
||||||
def to_h = super.merge(from:, to:)
|
def to_h = super.merge(from:, to:)
|
||||||
end
|
end
|
||||||
|
|
||||||
class UpdateChamp < ProcedureRevisionChange
|
class UpdateChamp < TypeDeChange
|
||||||
attr_reader :attribute, :from, :to
|
attr_reader :attribute, :from, :to
|
||||||
|
|
||||||
def initialize(type_de_champ, attribute, from, to)
|
def initialize(type_de_champ, attribute, from, to)
|
||||||
|
@ -75,4 +77,48 @@ class ProcedureRevisionChange
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class EligibiliteRulesChange
|
||||||
|
attr_reader :previous_revision, :new_revision
|
||||||
|
def initialize(previous_revision, new_revision)
|
||||||
|
@previous_revision = previous_revision
|
||||||
|
@new_revision = new_revision
|
||||||
|
@previous_ineligibilite_rules = @previous_revision.ineligibilite_rules
|
||||||
|
@new_ineligibilite_rules = @new_revision.ineligibilite_rules
|
||||||
|
end
|
||||||
|
|
||||||
|
def i18n_params
|
||||||
|
{
|
||||||
|
previous_condition: @previous_ineligibilite_rules&.to_s(previous_revision.types_de_champ.filter { @previous_ineligibilite_rules.sources.include? _1.stable_id }),
|
||||||
|
new_condition: @new_ineligibilite_rules&.to_s(new_revision.types_de_champ.filter { @new_ineligibilite_rules.sources.include? _1.stable_id })
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class AddEligibiliteRuleChange < EligibiliteRulesChange
|
||||||
|
def op = :add
|
||||||
|
end
|
||||||
|
|
||||||
|
class RemoveEligibiliteRuleChange < EligibiliteRulesChange
|
||||||
|
def op = :remove
|
||||||
|
end
|
||||||
|
|
||||||
|
class UpdateEligibiliteRuleChange < EligibiliteRulesChange
|
||||||
|
def op = :update
|
||||||
|
end
|
||||||
|
|
||||||
|
class EligibiliteEnabledChange < EligibiliteRulesChange
|
||||||
|
def op = :enabled
|
||||||
|
def i18n_params = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
class EligibiliteDisabledChange < EligibiliteRulesChange
|
||||||
|
def op = :disabled
|
||||||
|
def i18n_params = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
class UpdateEligibiliteMessageChange < EligibiliteRulesChange
|
||||||
|
def op = :message_updated
|
||||||
|
def i18n_params = { ineligibilite_message: @new_revision.ineligibilite_message }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -75,4 +75,8 @@ class ProcedureRevisionTypeDeChamp < ApplicationRecord
|
||||||
def used_by_routing_rules?
|
def used_by_routing_rules?
|
||||||
stable_id.in?(procedure.stable_ids_used_by_routing_rules)
|
stable_id.in?(procedure.stable_ids_used_by_routing_rules)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def used_by_ineligibilite_rules?
|
||||||
|
revision.ineligibilite_enabled? && stable_id.in?(revision.ineligibilite_rules&.sources || [])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -505,15 +505,15 @@ class TypeDeChamp < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_coherent_header_level(upper_tdcs)
|
def check_coherent_header_level(upper_tdcs)
|
||||||
errs = []
|
|
||||||
previous_level = previous_section_level(upper_tdcs)
|
previous_level = previous_section_level(upper_tdcs)
|
||||||
|
|
||||||
current_level = header_section_level_value.to_i
|
current_level = header_section_level_value.to_i
|
||||||
|
|
||||||
difference = current_level - previous_level
|
difference = current_level - previous_level
|
||||||
if current_level > previous_level && difference != 1
|
if current_level > previous_level && difference != 1
|
||||||
errs << I18n.t('activerecord.errors.type_de_champ.attributes.header_section_level.gap_error', level: current_level - previous_level - 1)
|
I18n.t('activerecord.errors.type_de_champ.attributes.header_section_level.gap_error', level: current_level - previous_level - 1)
|
||||||
|
else
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
errs
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_section_level(revision)
|
def current_section_level(revision)
|
||||||
|
@ -657,6 +657,10 @@ class TypeDeChamp < ApplicationRecord
|
||||||
type_champ.in?(ROUTABLE_TYPES)
|
type_champ.in?(ROUTABLE_TYPES)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def conditionable?
|
||||||
|
Logic::ChampValue::MANAGED_TYPE_DE_CHAMP.values.include?(type_champ)
|
||||||
|
end
|
||||||
|
|
||||||
def invalid_regexp?
|
def invalid_regexp?
|
||||||
self.errors.delete(:expression_reguliere)
|
self.errors.delete(:expression_reguliere)
|
||||||
self.errors.delete(:expression_reguliere_exemple_text)
|
self.errors.delete(:expression_reguliere_exemple_text)
|
||||||
|
|
|
@ -2,9 +2,18 @@ class CommentaireSerializer < ActiveModel::Serializer
|
||||||
attributes :email,
|
attributes :email,
|
||||||
:body,
|
:body,
|
||||||
:created_at,
|
:created_at,
|
||||||
:piece_jointe_attachments
|
:piece_jointe_attachments,
|
||||||
|
:attachment
|
||||||
|
|
||||||
def created_at
|
def created_at
|
||||||
object.created_at&.in_time_zone('UTC')
|
object.created_at&.in_time_zone('UTC')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attachment
|
||||||
|
piece_jointe = object.piece_jointe_attachments.first
|
||||||
|
|
||||||
|
if piece_jointe&.virus_scanner&.safe?
|
||||||
|
Rails.application.routes.url_helpers.url_for(piece_jointe)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -169,7 +169,12 @@ class PiecesJustificativesService
|
||||||
.filter { |a| safe_attachment(a) }
|
.filter { |a| safe_attachment(a) }
|
||||||
.map do |a|
|
.map do |a|
|
||||||
dossier_id = commentaire_id_dossier_id[a.record_id]
|
dossier_id = commentaire_id_dossier_id[a.record_id]
|
||||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
if @export_template
|
||||||
|
dossier = dossiers.find { _1.id == dossier_id }
|
||||||
|
@export_template.attachment_and_path(dossier, a)
|
||||||
|
else
|
||||||
|
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -190,7 +195,12 @@ class PiecesJustificativesService
|
||||||
.where(record_type: "Etablissement", record_id: etablissement_id_dossier_id.keys)
|
.where(record_type: "Etablissement", record_id: etablissement_id_dossier_id.keys)
|
||||||
.map do |a|
|
.map do |a|
|
||||||
dossier_id = etablissement_id_dossier_id[a.record_id]
|
dossier_id = etablissement_id_dossier_id[a.record_id]
|
||||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
if @export_template
|
||||||
|
dossier = dossiers.find { _1.id == dossier_id }
|
||||||
|
@export_template.attachment_and_path(dossier, a)
|
||||||
|
else
|
||||||
|
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -201,7 +211,12 @@ class PiecesJustificativesService
|
||||||
.filter { |a| safe_attachment(a) }
|
.filter { |a| safe_attachment(a) }
|
||||||
.map do |a|
|
.map do |a|
|
||||||
dossier_id = a.record_id
|
dossier_id = a.record_id
|
||||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
if @export_template
|
||||||
|
dossier = dossiers.find { _1.id == dossier_id }
|
||||||
|
@export_template.attachment_and_path(dossier, a)
|
||||||
|
else
|
||||||
|
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -217,7 +232,12 @@ class PiecesJustificativesService
|
||||||
.where(record_type: "Attestation", record_id: attestation_id_dossier_id.keys)
|
.where(record_type: "Attestation", record_id: attestation_id_dossier_id.keys)
|
||||||
.map do |a|
|
.map do |a|
|
||||||
dossier_id = attestation_id_dossier_id[a.record_id]
|
dossier_id = attestation_id_dossier_id[a.record_id]
|
||||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
if @export_template
|
||||||
|
dossier = dossiers.find { _1.id == dossier_id }
|
||||||
|
@export_template.attachment_and_path(dossier, a)
|
||||||
|
else
|
||||||
|
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -241,7 +261,12 @@ class PiecesJustificativesService
|
||||||
.filter { |a| safe_attachment(a) }
|
.filter { |a| safe_attachment(a) }
|
||||||
.map do |a|
|
.map do |a|
|
||||||
dossier_id = avis_ids_dossier_id[a.record_id]
|
dossier_id = avis_ids_dossier_id[a.record_id]
|
||||||
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
if @export_template
|
||||||
|
dossier = dossiers.find { _1.id == dossier_id }
|
||||||
|
@export_template.attachment_and_path(dossier, a)
|
||||||
|
else
|
||||||
|
ActiveStorage::DownloadableFile.pj_and_path(dossier_id, a)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
34
app/validators/types_de_champ/condition_validator.rb
Normal file
34
app/validators/types_de_champ/condition_validator.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
class TypesDeChamp::ConditionValidator < ActiveModel::EachValidator
|
||||||
|
# condition are valid when
|
||||||
|
# tdc.condition.left is present in upper tdcs
|
||||||
|
# in case of types_de_champ_private, we should include types_de_champ_publics too
|
||||||
|
def validate_each(procedure, collection, tdcs)
|
||||||
|
return if tdcs.empty?
|
||||||
|
|
||||||
|
tdcs = tdcs_with_children(procedure, tdcs)
|
||||||
|
tdcs.each_with_index do |tdc, tdc_index|
|
||||||
|
next unless tdc.condition?
|
||||||
|
|
||||||
|
upper_tdcs = []
|
||||||
|
if collection == :draft_types_de_champ_private # in case of private tdc validation, we must include public tdcs
|
||||||
|
upper_tdcs += tdcs_with_children(procedure, procedure.draft_types_de_champ_public)
|
||||||
|
end
|
||||||
|
upper_tdcs += tdcs.take(tdc_index) # we take all upper_tdcs of current tdcs
|
||||||
|
|
||||||
|
errors = tdc.condition.errors(upper_tdcs)
|
||||||
|
next if errors.blank?
|
||||||
|
|
||||||
|
procedure.errors.add(
|
||||||
|
collection,
|
||||||
|
procedure.errors.generate_message(collection, :invalid_condition, { value: tdc.libelle }),
|
||||||
|
type_de_champ: tdc
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# find children in repetitions
|
||||||
|
def tdcs_with_children(procedure, tdcs)
|
||||||
|
tdcs.to_a
|
||||||
|
.flat_map { _1.repetition? ? procedure.draft_revision.children_of(_1) : _1 }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
class TypesDeChamp::ExpressionReguliereValidator < ActiveModel::EachValidator
|
||||||
|
def validate_each(procedure, attribute, types_de_champ)
|
||||||
|
types_de_champ.to_a
|
||||||
|
.flat_map { _1.repetition? ? procedure.draft_revision.children_of(_1) : _1 }
|
||||||
|
.each do |tdc|
|
||||||
|
if tdc.expression_reguliere? && tdc.invalid_regexp?
|
||||||
|
procedure.errors.add(:expression_reguliere, type_de_champ: tdc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,29 @@
|
||||||
|
class TypesDeChamp::HeaderSectionConsistencyValidator < ActiveModel::EachValidator
|
||||||
|
def validate_each(procedure, attribute, types_de_champ)
|
||||||
|
public_tdcs = types_de_champ.to_a
|
||||||
|
|
||||||
|
root_tdcs_errors = errors_for_header_sections_order(procedure, attribute, public_tdcs)
|
||||||
|
repetition_tdcs_errors = public_tdcs
|
||||||
|
.filter_map { _1.repetition? ? procedure.draft_revision.children_of(_1) : nil }
|
||||||
|
.map { errors_for_header_sections_order(procedure, attribute, _1) }
|
||||||
|
|
||||||
|
repetition_tdcs_errors + root_tdcs_errors
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def errors_for_header_sections_order(procedure, attribute, types_de_champ)
|
||||||
|
types_de_champ
|
||||||
|
.map.with_index
|
||||||
|
.filter_map { |tdc, i| tdc.header_section? ? [tdc, i] : nil }
|
||||||
|
.map { |tdc, i| [tdc, tdc.check_coherent_header_level(types_de_champ.take(i))] }
|
||||||
|
.filter { |_tdc, errors| errors.present? }
|
||||||
|
.each do |tdc, message|
|
||||||
|
procedure.errors.add(
|
||||||
|
attribute,
|
||||||
|
procedure.errors.generate_message(attribute, :inconsistent_header_section, { value: tdc.libelle, custom_message: message }),
|
||||||
|
type_de_champ: tdc
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,7 +11,8 @@ class TypesDeChamp::NoEmptyBlockValidator < ActiveModel::EachValidator
|
||||||
if procedure.draft_revision.children_of(parent).empty?
|
if procedure.draft_revision.children_of(parent).empty?
|
||||||
procedure.errors.add(
|
procedure.errors.add(
|
||||||
attribute,
|
attribute,
|
||||||
procedure.errors.generate_message(attribute, :empty_repetition, { value: parent.libelle })
|
procedure.errors.generate_message(attribute, :empty_repetition, { value: parent.libelle }),
|
||||||
|
type_de_champ: parent
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,8 @@ class TypesDeChamp::NoEmptyDropDownValidator < ActiveModel::EachValidator
|
||||||
if drop_down.drop_down_list_enabled_non_empty_options.empty?
|
if drop_down.drop_down_list_enabled_non_empty_options.empty?
|
||||||
procedure.errors.add(
|
procedure.errors.add(
|
||||||
attribute,
|
attribute,
|
||||||
procedure.errors.generate_message(attribute, :empty_drop_down, { value: drop_down.libelle })
|
procedure.errors.generate_message(attribute, :empty_drop_down, { value: drop_down.libelle }),
|
||||||
|
type_de_champ: drop_down
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
['Configuration des champs']],
|
['Configuration des champs']],
|
||||||
preview: @procedure.draft_revision.valid? })
|
preview: @procedure.draft_revision.valid? })
|
||||||
|
|
||||||
= turbo_stream.replace 'errors-summary', render(TypesDeChampEditor::ErrorsSummary.new(revision: @procedure.draft_revision))
|
= turbo_stream.replace 'errors-summary', render(Procedure::ErrorsSummary.new(procedure: @procedure, validation_context: @tdc.public? ? :types_de_champ_public_editor : :types_de_champ_private_editor))
|
||||||
|
|
||||||
- rendered = render @condition_component
|
- rendered = render @condition_component
|
||||||
|
|
||||||
|
|
|
@ -7,102 +7,112 @@
|
||||||
%h1.fr-h2
|
%h1.fr-h2
|
||||||
Avis externes
|
Avis externes
|
||||||
|
|
||||||
.groupe-instructeur
|
= render Dsfr::CalloutComponent.new(title: nil) do |c|
|
||||||
.card
|
- c.with_body do
|
||||||
.card-title= t('.titles.allow_invite_experts')
|
Pendant l'instruction d'un dossier, les instructeurs peuvent demander leur avis à un ou plusieurs experts.
|
||||||
%p= t('.descriptions.allow_invite_experts')
|
%p
|
||||||
|
= link_to('Comment gérer les avis externes', t('.experts_doc.url'),
|
||||||
|
title: t('.experts_doc.title'),
|
||||||
|
**external_link_attributes)
|
||||||
|
|
||||||
|
%ul.fr-toggle__list
|
||||||
|
%li
|
||||||
= form_for @procedure,
|
= form_for @procedure,
|
||||||
method: :put,
|
method: :put,
|
||||||
url: allow_expert_review_admin_procedure_path(@procedure),
|
url: allow_expert_review_admin_procedure_path(@procedure),
|
||||||
html: { class: 'form procedure-form__column--form no-background' } do |f|
|
data: { controller: 'autosubmit', turbo: 'true' } do |f|
|
||||||
%label.toggle-switch{ data: { controller: 'autosubmit' } }
|
|
||||||
= f.check_box :allow_expert_review, class: 'toggle-switch-checkbox'
|
= render Dsfr::ToggleComponent.new(form: f,
|
||||||
%span.toggle-switch-control.round
|
target: :allow_expert_review,
|
||||||
%span.toggle-switch-label.on
|
title: t('.titles.allow_invite_experts'),
|
||||||
%span.toggle-switch-label.off
|
hint: t('.descriptions.allow_invite_experts'),
|
||||||
|
disabled: false,
|
||||||
|
extra_class_names: 'fr-toggle--border-bottom')
|
||||||
|
|
||||||
- if @procedure.allow_expert_review?
|
- if @procedure.allow_expert_review?
|
||||||
.card
|
%li
|
||||||
.card-title= t('.titles.manage_procedure_experts')
|
= form_for @procedure,
|
||||||
%p= t('.descriptions.manage_procedure_experts')
|
method: :put,
|
||||||
|
url: allow_expert_messaging_admin_procedure_path(@procedure),
|
||||||
|
data: { controller: 'autosubmit', turbo: 'true' } do |f|
|
||||||
|
|
||||||
|
= render Dsfr::ToggleComponent.new(form: f,
|
||||||
|
target: :allow_expert_messaging,
|
||||||
|
title: t('.titles.allow_expert_messaging'),
|
||||||
|
hint: t('.descriptions.allow_expert_messaging'),
|
||||||
|
disabled: false,
|
||||||
|
extra_class_names: 'fr-toggle--border-bottom')
|
||||||
|
|
||||||
|
%li
|
||||||
= form_for @procedure,
|
= form_for @procedure,
|
||||||
method: :put,
|
method: :put,
|
||||||
url: experts_require_administrateur_invitation_admin_procedure_path(@procedure),
|
url: experts_require_administrateur_invitation_admin_procedure_path(@procedure),
|
||||||
html: { class: 'form procedure-form__column--form no-background' } do |f|
|
data: { controller: 'autosubmit', turbo: 'true' } do |f|
|
||||||
%label.toggle-switch{ data: { controller: 'autosubmit' } }
|
|
||||||
= f.check_box :experts_require_administrateur_invitation, class: 'toggle-switch-checkbox'
|
|
||||||
%span.toggle-switch-control.round
|
|
||||||
%span.toggle-switch-label.on
|
|
||||||
%span.toggle-switch-label.off
|
|
||||||
|
|
||||||
.card
|
= render Dsfr::ToggleComponent.new(form: f,
|
||||||
.card-title= t('.titles.allow_expert_messaging')
|
target: :experts_require_administrateur_invitation,
|
||||||
%p= t('.descriptions.allow_expert_messaging')
|
title: t('.titles.manage_procedure_experts'),
|
||||||
= form_for @procedure,
|
hint: t('.descriptions.manage_procedure_experts'),
|
||||||
method: :put,
|
disabled: false)
|
||||||
url: allow_expert_messaging_admin_procedure_path(@procedure),
|
|
||||||
html: { class: 'form procedure-form__column--form no-background' } do |f|
|
|
||||||
%label.toggle-switch{ data: { controller: 'autosubmit' } }
|
|
||||||
= f.check_box :allow_expert_messaging, class: 'toggle-switch-checkbox'
|
|
||||||
%span.toggle-switch-control.round
|
|
||||||
%span.toggle-switch-label.on
|
|
||||||
%span.toggle-switch-label.off
|
|
||||||
|
|
||||||
- if @procedure.experts_require_administrateur_invitation?
|
|
||||||
.card
|
|
||||||
.card-title Affecter des experts à la démarche
|
|
||||||
= form_for :experts_procedure,
|
|
||||||
url: admin_procedure_experts_path(@procedure),
|
|
||||||
html: { class: 'form' } do |f|
|
|
||||||
|
|
||||||
.instructeur-wrapper
|
- if @procedure.experts_require_administrateur_invitation?
|
||||||
%p Pendant l'instruction d’un dossier, les instructeurs peuvent demander leur avis à un ou plusieurs experts.
|
.card
|
||||||
%p#experts-emails Entrez les adresses email des experts que vous souhaitez affecter à cette démarche
|
= form_for :experts_procedure,
|
||||||
= hidden_field_tag :emails, nil
|
url: admin_procedure_experts_path(@procedure),
|
||||||
= react_component("ComboMultiple",
|
html: { class: 'form' } do |f|
|
||||||
options: [],
|
|
||||||
selected: [], disabled: [],
|
.instructeur-wrapper
|
||||||
group: '.instructeur-wrapper',
|
%p#experts-emails Entrez les adresses emails des experts que vous souhaitez ajouter à la liste prédéfinie
|
||||||
name: 'emails',
|
= hidden_field_tag :emails, nil
|
||||||
label: 'Emails',
|
= react_component("ComboMultiple",
|
||||||
describedby: 'experts-emails',
|
options: [],
|
||||||
acceptNewValues: true)
|
selected: [], disabled: [],
|
||||||
|
group: '.instructeur-wrapper',
|
||||||
|
name: 'emails',
|
||||||
|
label: 'Emails',
|
||||||
|
describedby: 'experts-emails',
|
||||||
|
acceptNewValues: true)
|
||||||
|
|
||||||
|
= f.submit 'Ajouter à la liste', class: 'fr-btn'
|
||||||
|
|
||||||
= f.submit 'Affecter à la démarche', class: 'button primary send'
|
|
||||||
- if @experts_procedure.present?
|
- if @experts_procedure.present?
|
||||||
%table.table.mt-2
|
.fr-table.fr-table--no-caption.fr-table--layout-fixed.fr-mt-3w
|
||||||
%thead
|
%table
|
||||||
%tr
|
%thead
|
||||||
%th Liste des experts
|
|
||||||
%th Nombre d’avis
|
|
||||||
- if @procedure.experts_require_administrateur_invitation
|
|
||||||
%th Notifier des décisions sur les dossiers
|
|
||||||
%tbody
|
|
||||||
- @experts_procedure.each do |expert_procedure|
|
|
||||||
%tr
|
%tr
|
||||||
%td
|
%th Liste des experts
|
||||||
= dsfr_icon('fr-icon-user-fill')
|
%th Nombre d’avis
|
||||||
= expert_procedure.expert.email
|
|
||||||
%td.text-center
|
|
||||||
= expert_procedure.avis.count
|
|
||||||
- if @procedure.experts_require_administrateur_invitation
|
- if @procedure.experts_require_administrateur_invitation
|
||||||
|
%th Notifier des décisions sur les dossiers
|
||||||
|
- if @procedure.experts_require_administrateur_invitation
|
||||||
|
%th Action
|
||||||
|
%tbody
|
||||||
|
- @experts_procedure.each do |expert_procedure|
|
||||||
|
%tr
|
||||||
|
%td
|
||||||
|
= dsfr_icon('fr-icon-user-fill')
|
||||||
|
= expert_procedure.expert.email
|
||||||
%td.text-center
|
%td.text-center
|
||||||
= form_for expert_procedure,
|
= expert_procedure.avis.count
|
||||||
url: admin_procedure_expert_path(id: expert_procedure),
|
- if @procedure.experts_require_administrateur_invitation
|
||||||
method: :put,
|
%td.text-center
|
||||||
data: { turbo: true },
|
= form_for expert_procedure,
|
||||||
html: { class: 'form procedure-form__column--form no-background' } do |f|
|
url: admin_procedure_expert_path(id: expert_procedure),
|
||||||
%label.toggle-switch{ data: { controller: 'autosubmit' } }
|
method: :put,
|
||||||
= f.check_box :allow_decision_access, class: 'toggle-switch-checkbox'
|
data: { turbo: true },
|
||||||
%span.toggle-switch-control.round
|
html: { class: 'form procedure-form__column--form no-background' } do |f|
|
||||||
%span.toggle-switch-label.on
|
%label.toggle-switch{ data: { controller: 'autosubmit' } }
|
||||||
%span.toggle-switch-label.off
|
= f.check_box :allow_decision_access, class: 'toggle-switch-checkbox'
|
||||||
- if @procedure.experts_require_administrateur_invitation
|
%span.toggle-switch-control.round
|
||||||
%td.actions= button_to 'retirer',
|
%span.toggle-switch-label.on
|
||||||
admin_procedure_expert_path(id: expert_procedure, procedure: @procedure),
|
%span.toggle-switch-label.off
|
||||||
method: :delete,
|
- if @procedure.experts_require_administrateur_invitation
|
||||||
data: { confirm: "Êtes-vous sûr de vouloir révoquer l'expert « #{expert_procedure.expert.email} » de la démarche #{expert_procedure.procedure.libelle} ? Les instructeurs ne pourront plus lui demander d’avis" },
|
%td.actions= button_to 'retirer',
|
||||||
class: 'button'
|
admin_procedure_expert_path(id: expert_procedure, procedure: @procedure),
|
||||||
|
method: :delete,
|
||||||
|
data: { confirm: "Êtes-vous sûr de vouloir révoquer l'expert « #{expert_procedure.expert.email} » de la démarche #{expert_procedure.procedure.libelle} ? Les instructeurs ne pourront plus lui demander d’avis" },
|
||||||
|
class: 'fr-btn fr-btn--secondary'
|
||||||
- else
|
- else
|
||||||
.blank-tab
|
.blank-tab
|
||||||
%h2.empty-text Aucun expert invité pour le moment.
|
%h2.empty-text Aucun expert invité pour le moment.
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
- rendered = render @ineligibilite_rules_component
|
||||||
|
|
||||||
|
- if rendered.present?
|
||||||
|
= turbo_stream.replace dom_id(@procedure.draft_revision, :ineligibilite_rules) do
|
||||||
|
- rendered
|
||||||
|
- else
|
||||||
|
= turbo_stream.remove dom_id(@procedure.draft_revision, :ineligibilite_rules)
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
28
app/views/administrateurs/ineligibilite_rules/edit.html.haml
Normal file
28
app/views/administrateurs/ineligibilite_rules/edit.html.haml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
= render partial: 'administrateurs/breadcrumbs',
|
||||||
|
locals: { steps: [['Démarches', admin_procedures_path],
|
||||||
|
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
|
||||||
|
['Inéligibilité des dossiers']] }
|
||||||
|
|
||||||
|
|
||||||
|
.fr-container
|
||||||
|
.fr-grid-row
|
||||||
|
.fr-col-12.fr-col-offset-md-2.fr-col-md-8
|
||||||
|
%h1.fr-h1 Inéligibilité des dossiers
|
||||||
|
|
||||||
|
= render Dsfr::AlertComponent.new(title: nil, size: :sm, state: :info, heading_level: 'h2', extra_class_names: 'fr-my-2w') do |c|
|
||||||
|
- c.with_body do
|
||||||
|
%p
|
||||||
|
Les dossiers répondant à vos conditions d’inéligibilité ne pourront pas être déposés. Plus d’informations sur l’inéligibilité des dossiers dans la
|
||||||
|
= link_to('doc', ELIGIBILITE_URL, title: "Document sur l’inéligibilité des dossiers", **external_link_attributes)
|
||||||
|
|
||||||
|
- if !@procedure.draft_revision.conditionable_types_de_champ.present?
|
||||||
|
%p.fr-mt-2w.fr-mb-2w
|
||||||
|
Pour configurer l’inéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les conditions d’inéligibilité. Il vous faut donc ajouter au moins un des champs suivant à votre formulaire :
|
||||||
|
%ul
|
||||||
|
- Logic::ChampValue::MANAGED_TYPE_DE_CHAMP.values.each do
|
||||||
|
%li= "« #{t(_1, scope: [:activerecord, :attributes, :type_de_champ, :type_champs])} »"
|
||||||
|
%p.fr-mt-2w
|
||||||
|
= link_to 'Ajouter un champ supportant les conditions d’inéligibilité', champs_admin_procedure_path(@procedure), class: 'fr-link fr-icon-arrow-right-line fr-link--icon-right'
|
||||||
|
= render Procedure::FixedFooterComponent.new(procedure: @procedure)
|
||||||
|
- else
|
||||||
|
= render Conditions::IneligibiliteRulesComponent.new(draft_revision: @procedure.draft_revision)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue