feat(Users/Dossiers#update): track changes live and pop modal when ineligibilite_rules matches
This commit is contained in:
parent
2210db3b81
commit
be5f580237
10 changed files with 164 additions and 24 deletions
|
@ -0,0 +1,16 @@
|
||||||
|
class Dossiers::InvalidIneligibiliteRulesComponent < ApplicationComponent
|
||||||
|
delegate :can_passer_en_construction?, :ineligibilite_rules_computable?, to: :@dossier
|
||||||
|
|
||||||
|
def initialize(dossier:)
|
||||||
|
@dossier = dossier
|
||||||
|
@revision = dossier.revision
|
||||||
|
end
|
||||||
|
|
||||||
|
def render?
|
||||||
|
ineligibilite_rules_computable? && !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
|
|
@ -303,10 +303,13 @@ 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
|
@ineligibilite_rules_was_computable = @dossier.ineligibilite_rules_computable?
|
||||||
|
@can_passer_en_construction_was = @dossier.can_passer_en_construction?
|
||||||
@dossier.index_search_terms_later if @errors.empty?
|
update_dossier_and_compute_errors
|
||||||
|
@dossier.index_search_terms_later if @dossier.errors.empty?
|
||||||
|
@ineligibilite_rules_is_computable = @dossier.ineligibilite_rules_computable?
|
||||||
|
@can_passer_en_construction_is = @dossier.can_passer_en_construction?
|
||||||
|
@ineligibilite_rules_computable_changed = !@ineligibilite_rules_was_computable && @ineligibilite_rules_is_computable
|
||||||
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?))
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,4 +25,6 @@
|
||||||
|
|
||||||
= render Dossiers::PendingCorrectionCheckboxComponent.new(dossier: dossier)
|
= render Dossiers::PendingCorrectionCheckboxComponent.new(dossier: dossier)
|
||||||
|
|
||||||
|
= render Dossiers::InvalidIneligibiliteRulesComponent.new(dossier: dossier)
|
||||||
|
|
||||||
= render Dossiers::EditFooterComponent.new(dossier: dossier_for_editing, annotation: false)
|
= render Dossiers::EditFooterComponent.new(dossier: dossier_for_editing, annotation: false)
|
||||||
|
|
|
@ -1 +1,8 @@
|
||||||
= render partial: 'shared/dossiers/update_champs', locals: { to_show: @to_show, to_hide: @to_hide, to_update: @to_update, dossier: @dossier }
|
= render partial: 'shared/dossiers/update_champs', locals: { to_show: @to_show, to_hide: @to_hide, to_update: @to_update, dossier: @dossier }
|
||||||
|
|
||||||
|
- if !params.key?(:validate)
|
||||||
|
- if @ineligibilite_rules_is_computable
|
||||||
|
= turbo_stream.remove(dom_id(@dossier, :ineligibilite_rules_broken))
|
||||||
|
|
||||||
|
- if (@ineligibilite_rules_computable_changed && !@can_passer_en_construction_is) || (@can_passer_en_construction_was && !@can_passer_en_construction_is)
|
||||||
|
= turbo_stream.append('contenu', render(Dossiers::InvalidIneligibiliteRulesComponent.new(dossier: @dossier)))
|
||||||
|
|
|
@ -398,7 +398,9 @@ describe Users::DossiersController, type: :controller do
|
||||||
|
|
||||||
describe '#submit_brouillon' do
|
describe '#submit_brouillon' do
|
||||||
before { sign_in(user) }
|
before { sign_in(user) }
|
||||||
let!(:dossier) { create(:dossier, user: user) }
|
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
|
||||||
|
let(:types_de_champ_public) { [{ type: :text }] }
|
||||||
|
let!(:dossier) { create(:dossier, user:, procedure:) }
|
||||||
let(:first_champ) { dossier.champs_public.first }
|
let(:first_champ) { dossier.champs_public.first }
|
||||||
let(:anchor_to_first_champ) { controller.helpers.link_to first_champ.libelle, brouillon_dossier_path(anchor: first_champ.labelledby_id), class: 'error-anchor' }
|
let(:anchor_to_first_champ) { controller.helpers.link_to first_champ.libelle, brouillon_dossier_path(anchor: first_champ.labelledby_id), class: 'error-anchor' }
|
||||||
let(:value) { 'beautiful value' }
|
let(:value) { 'beautiful value' }
|
||||||
|
@ -439,9 +441,9 @@ describe Users::DossiersController, type: :controller do
|
||||||
render_views
|
render_views
|
||||||
let(:error_message) { 'nop' }
|
let(:error_message) { 'nop' }
|
||||||
before do
|
before do
|
||||||
expect_any_instance_of(Dossier).to receive(:validate).and_return(false)
|
allow_any_instance_of(Dossier).to receive(:validate).and_return(false)
|
||||||
expect_any_instance_of(Dossier).to receive(:errors).and_return(
|
allow_any_instance_of(Dossier).to receive(:errors).and_return(
|
||||||
[double(inner_error: double(base: first_champ), message: 'nop')]
|
[instance_double(ActiveModel::NestedError, inner_error: double(base: first_champ), message: 'nop')]
|
||||||
)
|
)
|
||||||
subject
|
subject
|
||||||
end
|
end
|
||||||
|
@ -461,11 +463,8 @@ describe Users::DossiersController, type: :controller do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:value) { nil }
|
let(:value) { nil }
|
||||||
|
let(:types_de_champ_public) { [{ type: :text, mandatory: true, libelle: 'l' }] }
|
||||||
before do
|
before { subject }
|
||||||
first_champ.type_de_champ.update(mandatory: true, libelle: 'l')
|
|
||||||
subject
|
|
||||||
end
|
|
||||||
|
|
||||||
it { expect(response).to render_template(:brouillon) }
|
it { expect(response).to render_template(:brouillon) }
|
||||||
it { expect(response.body).to have_link(first_champ.libelle, href: "##{first_champ.labelledby_id}") }
|
it { expect(response.body).to have_link(first_champ.libelle, href: "##{first_champ.labelledby_id}") }
|
||||||
|
@ -548,8 +547,8 @@ describe Users::DossiersController, type: :controller do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
before do
|
before do
|
||||||
expect_any_instance_of(Dossier).to receive(:validate).and_return(false)
|
allow_any_instance_of(Dossier).to receive(:validate).and_return(false)
|
||||||
expect_any_instance_of(Dossier).to receive(:errors).and_return(
|
allow_any_instance_of(Dossier).to receive(:errors).and_return(
|
||||||
[double(inner_error: double(base: first_champ), message: 'nop')]
|
[double(inner_error: double(base: first_champ), message: 'nop')]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -661,7 +660,8 @@ describe Users::DossiersController, type: :controller do
|
||||||
describe '#update brouillon' do
|
describe '#update brouillon' do
|
||||||
before { sign_in(user) }
|
before { sign_in(user) }
|
||||||
|
|
||||||
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{}, { type: :piece_justificative }]) }
|
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
|
||||||
|
let(:types_de_champ_public) { [{}, { type: :piece_justificative }] }
|
||||||
let(:dossier) { create(:dossier, user:, procedure:) }
|
let(:dossier) { create(:dossier, user:, procedure:) }
|
||||||
let(:first_champ) { dossier.champs_public.first }
|
let(:first_champ) { dossier.champs_public.first }
|
||||||
let(:piece_justificative_champ) { dossier.champs_public.last }
|
let(:piece_justificative_champ) { dossier.champs_public.last }
|
||||||
|
@ -754,13 +754,65 @@ describe Users::DossiersController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "debounce search terms indexation" do
|
context 'having ineligibilite_rules setup' do
|
||||||
# dossier creation trigger a first indexation and flag,
|
include Logic
|
||||||
# so we we have to remove this flag
|
render_views
|
||||||
dossier.debounce_index_search_terms_flag.remove
|
|
||||||
|
|
||||||
assert_enqueued_jobs(1, only: DossierIndexSearchTermsJob) do
|
let(:types_de_champ_public) { [{ type: :text }, { type: :integer_number }] }
|
||||||
3.times { patch :update, params: payload, format: :turbo_stream }
|
let(:text_champ) { dossier.champs_public.first }
|
||||||
|
let(:number_champ) { dossier.champs_public.last }
|
||||||
|
let(:submit_payload) do
|
||||||
|
{
|
||||||
|
id: dossier.id,
|
||||||
|
dossier: {
|
||||||
|
groupe_instructeur_id: dossier.groupe_instructeur_id,
|
||||||
|
champs_public_attributes: {
|
||||||
|
text_champ.public_id => {
|
||||||
|
with_public_id: true,
|
||||||
|
value: "hello world"
|
||||||
|
},
|
||||||
|
number_champ.public_id => {
|
||||||
|
with_public_id: true,
|
||||||
|
value:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:must_be_greater_than) { 10 }
|
||||||
|
|
||||||
|
before do
|
||||||
|
procedure.published_revision.update(
|
||||||
|
ineligibilite_enabled: true,
|
||||||
|
ineligibilite_message: 'lol',
|
||||||
|
ineligibilite_rules: greater_than(champ_value(number_champ.stable_id), constant(must_be_greater_than))
|
||||||
|
)
|
||||||
|
procedure.published_revision.save!
|
||||||
|
end
|
||||||
|
render_views
|
||||||
|
|
||||||
|
context 'when it pass from undefined to true' do
|
||||||
|
let(:value) { must_be_greater_than + 1 }
|
||||||
|
|
||||||
|
it 'raises popup' do
|
||||||
|
subject
|
||||||
|
dossier.reload
|
||||||
|
expect(dossier.can_passer_en_construction?).to be_falsey
|
||||||
|
expect(assigns(:ineligibilite_rules_was_computable)).to eq(false)
|
||||||
|
expect(assigns(:ineligibilite_rules_is_computable)).to eq(true)
|
||||||
|
expect(response.body).to match(ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context 'when it pass from undefined to false' do
|
||||||
|
let(:value) { must_be_greater_than - 1 }
|
||||||
|
it 'does nothing' do
|
||||||
|
subject
|
||||||
|
dossier.reload
|
||||||
|
expect(dossier.can_passer_en_construction?).to be_truthy
|
||||||
|
expect(assigns(:ineligibilite_rules_was_computable)).to eq(false)
|
||||||
|
expect(assigns(:ineligibilite_rules_is_computable)).to eq(true)
|
||||||
|
expect(response.body).not_to have_selector("##{ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken)}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -868,8 +920,8 @@ describe Users::DossiersController, type: :controller do
|
||||||
|
|
||||||
context 'classic error' do
|
context 'classic error' do
|
||||||
before do
|
before do
|
||||||
expect_any_instance_of(Dossier).to receive(:save).and_return(false)
|
allow_any_instance_of(Dossier).to receive(:save).and_return(false)
|
||||||
expect_any_instance_of(Dossier).to receive(:errors).and_return(
|
allow_any_instance_of(Dossier).to receive(:errors).and_return(
|
||||||
[message: 'nop', inner_error: double(base: first_champ)]
|
[message: 'nop', inner_error: double(base: first_champ)]
|
||||||
)
|
)
|
||||||
subject
|
subject
|
||||||
|
|
|
@ -149,4 +149,18 @@ describe 'shared/dossiers/edit', type: :view do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when dossier transitions rules are computable and passer_en_construction is false' do
|
||||||
|
let(:types_de_champ_public) { [] }
|
||||||
|
let(:dossier) { create(:dossier, procedure:) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Dossiers::InvalidIneligibiliteRulesComponent).to receive(:ineligibilite_rules_computable?).and_return(true)
|
||||||
|
allow(dossier).to receive(:can_passer_en_construction?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders broken transitions rules dialog' do
|
||||||
|
expect(subject).to have_selector("##{ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue