Merge pull request #10547 from colinux/mandatory-by-default

ETQ admin, un nouveau champ est obligatoire par défaut
This commit is contained in:
Colin Darie 2024-07-02 14:26:29 +00:00 committed by GitHub
commit b3ccfc16c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 100 additions and 61 deletions

View file

@ -261,7 +261,7 @@ class TypeDeChamp < ApplicationRecord
end
def check_mandatory
if non_fillable?
if non_fillable? || private?
self.mandatory = false
else
true

View file

@ -103,6 +103,7 @@ module TPS
config.active_record.encryption.primary_key = Rails.application.secrets.active_record_encryption.fetch(:primary_key)
config.active_record.encryption.key_derivation_salt = Rails.application.secrets.active_record_encryption.fetch(:key_derivation_salt)
config.active_record.partial_inserts = false
config.exceptions_app = self.routes

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class ChangeTypesDeChampMandatoryDefault < ActiveRecord::Migration[7.0]
def change
change_column_default :types_de_champ, :mandatory, from: false, to: true
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_05_27_090508) do
ActiveRecord::Schema[7.0].define(version: 2024_06_19_205011) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_buffercache"
enable_extension "pg_stat_statements"
@ -1122,7 +1122,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_05_27_090508) do
t.datetime "created_at", precision: nil
t.text "description"
t.string "libelle"
t.boolean "mandatory", default: false
t.boolean "mandatory", default: true
t.jsonb "options"
t.boolean "private", default: false, null: false
t.bigint "stable_id"

View file

@ -399,7 +399,7 @@ describe Users::DossiersController, type: :controller do
describe '#submit_brouillon' do
before { sign_in(user) }
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
let(:types_de_champ_public) { [{ type: :text }] }
let(:types_de_champ_public) { [{ type: :text, mandatory: false }] }
let!(:dossier) { create(:dossier, user:, procedure:) }
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' }
@ -516,7 +516,7 @@ describe Users::DossiersController, type: :controller do
describe '#submit_en_construction' do
before { sign_in(user) }
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
let(:types_de_champ_public) { [{ type: :text }] }
let(:types_de_champ_public) { [{ type: :text, mandatory: false }] }
let(:dossier) { create(:dossier, :en_construction, procedure:, user:) }
let(:first_champ) { dossier.owner_editing_fork.champs_public.first }
let(:anchor_to_first_champ) { controller.helpers.link_to I18n.t('views.users.dossiers.fix_champ'), modifier_dossier_path(anchor: first_champ.labelledby_id), class: 'error-anchor' }
@ -661,7 +661,7 @@ describe Users::DossiersController, type: :controller do
before { sign_in(user) }
let(:procedure) { create(:procedure, :published, types_de_champ_public:) }
let(:types_de_champ_public) { [{}, { type: :piece_justificative }] }
let(:types_de_champ_public) { [{}, { type: :piece_justificative, mandatory: false }] }
let(:dossier) { create(:dossier, user:, procedure:) }
let(:first_champ) { dossier.champs_public.first }
let(:piece_justificative_champ) { dossier.champs_public.last }

View file

@ -3,8 +3,8 @@ FactoryBot.define do
sequence(:libelle) { |n| "Libelle du champ #{n}" }
sequence(:description) { |n| "description du champ #{n}" }
type_champ { TypeDeChamp.type_champs.fetch(:text) }
mandatory { false }
add_attribute(:private) { false }
mandatory { !private }
transient do
procedure { nil }

View file

@ -37,7 +37,7 @@ RSpec.describe ProcedureHelper, type: :helper do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }, { type: :piece_justificative }]) }
it 'rounds up the duration to the minute' do
expect(subject).to eq(2)
expect(subject).to eq(3)
end
end

View file

@ -28,6 +28,10 @@ describe Migrations::BatchUpdateDatetimeValuesJob, type: :job do
end
context "when the value is a date not convertible to IS8061" do
before do
datetime_champ.type_de_champ.update!(mandatory: false)
end
let!(:datetime_champ) { build(:champ_datetime, value: "blabla") }
subject { described_class.perform_now([datetime_champ.id]) }

View file

@ -12,6 +12,10 @@ describe Migrations::BatchUpdatePaysValuesJob, type: :job do
end
context "the value is incorrect" do
before do
pays_champ.type_de_champ.update!(mandatory: false)
end
let(:pays_champ) { create(:champ_pays).tap { _1.update_columns(value: 'Incorrect') } }
it 'updates value to nil' do

View file

@ -3,7 +3,7 @@ describe 'Dossier::Recovery::LifeCycle' do
let(:procedure) do
create(:procedure,
types_de_champ_public: [
{ type: :repetition, children: [{ type: :piece_justificative }] },
{ type: :repetition, children: [{ type: :piece_justificative }], mandatory: false },
{ type: :carte },
{ type: :siret }
])

View file

@ -81,7 +81,7 @@ describe Champs::LinkedDropDownListChamp do
subject { described_class.new(type_de_champ: type_de_champ) }
context 'when the champ is not mandatory' do
let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, drop_down_list_value: value) }
let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, mandatory: false, drop_down_list_value: value) }
it 'blank is fine' do
is_expected.not_to be_mandatory_blank

View file

@ -1,6 +1,6 @@
describe DossierRebaseConcern do
describe '#can_rebase?' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ mandatory: true }, { type: :yes_no }], types_de_champ_private: [{}]) }
let(:procedure) { create(:procedure, types_de_champ_public: [{ mandatory: true }, { type: :yes_no, mandatory: false }], types_de_champ_private: [{}]) }
let(:attestation_template) { procedure.draft_revision.attestation_template.find_or_revise! }
let(:type_de_champ) { procedure.active_revision.types_de_champ_public.find { |tdc| !tdc.mandatory? } }
let(:private_type_de_champ) { procedure.active_revision.types_de_champ_private.first }
@ -26,11 +26,12 @@ describe DossierRebaseConcern do
dossier
end
context 'with added type de champ' do
context 'with added non mandatory type de champ' do
before do
procedure.draft_revision.add_type_de_champ({
type_champ: TypeDeChamp.type_champs.fetch(:text),
libelle: "Un champ text"
libelle: "Un champ text",
mandatory: false
})
procedure.publish_revision!
dossier.reload
@ -125,7 +126,7 @@ describe DossierRebaseConcern do
end
context 'with type de champ regexp and regexp change' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ mandatory: true }, { type: :expression_reguliere }], types_de_champ_private: [{}]) }
let(:procedure) { create(:procedure, types_de_champ_public: [{ mandatory: true }, { type: :expression_reguliere, mandatory: false }], types_de_champ_private: [{}]) }
before do
procedure.draft_revision.find_and_ensure_exclusive_use(type_de_champ.stable_id).update(expression_reguliere: /\d+/)
@ -162,11 +163,12 @@ describe DossierRebaseConcern do
dossier
end
context 'with added type de champ' do
context 'with added non mandatory type de champ' do
before do
procedure.draft_revision.add_type_de_champ({
type_champ: TypeDeChamp.type_champs.fetch(:text),
libelle: "Un champ text"
libelle: "Un champ text",
mandatory: false
})
procedure.publish_revision!
dossier.reload

View file

@ -1552,8 +1552,8 @@ describe Dossier, type: :model do
let(:procedure) { create(:procedure, types_de_champ_public: types_de_champ) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:types_de_champ) { [type_de_champ] }
let(:type_de_champ) { {} }
let(:types_de_champ) { [type_de_champ].compact }
let(:type_de_champ) { nil }
let(:errors) { dossier.check_mandatory_and_visible_champs }
it 'no mandatory champs' do
@ -1575,7 +1575,7 @@ describe Dossier, type: :model do
end
context "conditionaly visible" do
let(:types_de_champ) { [{ type: :yes_no, stable_id: 99 }, type_de_champ] }
let(:types_de_champ) { [{ type: :yes_no, stable_id: 99, mandatory: false }, type_de_champ] }
let(:type_de_champ) { { mandatory: true, condition: ds_eq(champ_value(99), constant(true)) } }
it 'should not have errors' do
@ -1635,7 +1635,7 @@ describe Dossier, type: :model do
context "conditionaly visible" do
let(:champ_with_error) { dossier.champs_public.second.champs.first }
let(:types_de_champ) { [{ type: :yes_no, stable_id: 99 }, type_de_champ] }
let(:types_de_champ) { [{ type: :yes_no, stable_id: 99, mandatory: false }, type_de_champ] }
let(:type_de_champ) { { type: :repetition, mandatory: true, children: [{ mandatory: true }], condition: ds_eq(champ_value(99), constant(true)) } }
it 'should not have errors' do

View file

@ -439,6 +439,7 @@ describe ProcedureRevision do
let(:new_tdc) do
new_draft.add_type_de_champ(
type_champ: TypeDeChamp.type_champs.fetch(:text),
mandatory: false,
libelle: "Un champ text"
)
end
@ -493,8 +494,8 @@ describe ProcedureRevision do
attribute: :mandatory,
label: first_tdc.libelle,
private: false,
from: false,
to: true,
from: true,
to: false,
stable_id: first_tdc.stable_id
}
])

View file

@ -2,12 +2,14 @@ describe Procedure do
describe 'mail templates' do
subject { create(:procedure) }
it { expect(subject.passer_en_construction_email_template).to be_a(Mails::InitiatedMail) }
it { expect(subject.passer_en_instruction_email_template).to be_a(Mails::ReceivedMail) }
it { expect(subject.accepter_email_template).to be_a(Mails::ClosedMail) }
it { expect(subject.refuser_email_template).to be_a(Mails::RefusedMail) }
it { expect(subject.classer_sans_suite_email_template).to be_a(Mails::WithoutContinuationMail) }
it { expect(subject.repasser_en_instruction_email_template).to be_a(Mails::ReInstructedMail) }
it "returns expected classes" do
expect(subject.passer_en_construction_email_template).to be_a(Mails::InitiatedMail)
expect(subject.passer_en_instruction_email_template).to be_a(Mails::ReceivedMail)
expect(subject.accepter_email_template).to be_a(Mails::ClosedMail)
expect(subject.refuser_email_template).to be_a(Mails::RefusedMail)
expect(subject.classer_sans_suite_email_template).to be_a(Mails::WithoutContinuationMail)
expect(subject.repasser_en_instruction_email_template).to be_a(Mails::ReInstructedMail)
end
end
describe 'compute_dossiers_count' do
@ -1660,7 +1662,7 @@ describe Procedure do
children: [
{ libelle: 'Nom', mandatory: true },
{ libelle: 'Prénom', mandatory: true },
{ libelle: 'Age', type: :integer_number }
{ libelle: 'Age', type: :integer_number, mandatory: false }
]
}
]

View file

@ -89,17 +89,35 @@ describe TypesDeChamp::LinkedDropDownListTypeDeChamp do
END_OPTIONS
end
it do
expect(subject.secondary_options).to eq(
{
'' => [],
'Primary 1' => ['', 'secondary 1.1', 'secondary 1.2'],
'Primary 2' => ['', 'secondary 2.1', 'secondary 2.2', 'secondary 2.3']
}
)
context "mandatory tdc" do
it do
expect(subject.secondary_options).to eq(
{
'' => [],
'Primary 1' => ['secondary 1.1', 'secondary 1.2'],
'Primary 2' => ['secondary 2.1', 'secondary 2.2', 'secondary 2.3']
}
)
end
it { expect(subject.primary_options).to eq(['Primary 1', 'Primary 2']) }
end
it { expect(subject.primary_options).to eq(['', 'Primary 1', 'Primary 2']) }
context "not mandatory" do
let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, drop_down_list_value: menu_options, mandatory: false) }
it do
expect(subject.secondary_options).to eq(
{
'' => [],
'Primary 1' => ['', 'secondary 1.1', 'secondary 1.2'],
'Primary 2' => ['', 'secondary 2.1', 'secondary 2.2', 'secondary 2.3']
}
)
end
it { expect(subject.primary_options).to eq(['', 'Primary 1', 'Primary 2']) }
end
end
end
end

View file

@ -33,7 +33,7 @@ describe DemarchesPubliquesExportService do
{
description: procedure.active_revision.types_de_champ_public.first.description,
label: procedure.active_revision.types_de_champ_public.first.libelle,
required: false,
required: true,
__typename: "TextChampDescriptor"
}
]

View file

@ -1,6 +1,6 @@
describe PiecesJustificativesService do
describe 'pjs_for_champs' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative }, { type: :repetition, children: [{ type: :piece_justificative }] }]) }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :piece_justificative, mandatory: false }, { type: :repetition, mandatory: false, children: [{ type: :piece_justificative, mandatory: false }] }]) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:dossiers) { Dossier.where(id: dossier.id) }
let(:witness) { create(:dossier, procedure: procedure) }
@ -477,8 +477,8 @@ describe PiecesJustificativesService do
let(:user_profile) { build(:administrateur) }
let(:types_de_champ_public) do
[
{ type: :repetition, children: [{ type: :piece_justificative }] },
{ type: :repetition, children: [{ type: :piece_justificative }, { type: :piece_justificative }] }
{ type: :repetition, mandatory: false, children: [{ type: :piece_justificative }] },
{ type: :repetition, mandatory: false, children: [{ type: :piece_justificative }, { type: :piece_justificative }] }
]
end

View file

@ -177,11 +177,11 @@ describe 'As an administrateur I can edit types de champ', js: true do
# It displays the estimate when adding a new champ
add_champ
select('Pièce justificative', from: 'Type de champ')
expect(page).to have_content('Durée de remplissage estimée : 2 min')
expect(page).to have_content('Durée de remplissage estimée : 3 min')
# It updates the estimate when updating the champ
check 'Champ obligatoire'
expect(page).to have_content('Durée de remplissage estimée : 3 min')
uncheck 'Champ obligatoire'
expect(page).to have_content('Durée de remplissage estimée : 2 min')
# It updates the estimate when removing the champ
page.accept_alert do

View file

@ -252,10 +252,10 @@ describe 'fetch API Particulier Data', js: true do
instructeurs: [instructeur],
api_particulier_sources: expected_sources,
api_particulier_token: api_particulier_token).tap do |p|
p.active_revision.add_type_de_champ(type_champ: :cnaf, libelle: 'cnaf')
p.active_revision.add_type_de_champ(type_champ: :dgfip, libelle: 'dgfip')
p.active_revision.add_type_de_champ(type_champ: :pole_emploi, libelle: 'pole_emploi')
p.active_revision.add_type_de_champ(type_champ: :mesri, libelle: 'mesri')
p.active_revision.add_type_de_champ(type_champ: :cnaf, libelle: 'cnaf', mandatory: false)
p.active_revision.add_type_de_champ(type_champ: :dgfip, libelle: 'dgfip', mandatory: false)
p.active_revision.add_type_de_champ(type_champ: :pole_emploi, libelle: 'pole_emploi', mandatory: false)
p.active_revision.add_type_de_champ(type_champ: :mesri, libelle: 'mesri', mandatory: false)
end
end

View file

@ -3,8 +3,8 @@ describe 'The routing with rules', js: true do
let(:procedure) do
create(:procedure, :with_service, :for_individual, :with_zone, types_de_champ_public: [
{ type: :text, libelle: 'un premier champ text' },
{ type: :drop_down_list, libelle: 'Spécialité', options: ["", "littéraire", "scientifique", "artistique"] }
{ type: :text, libelle: 'un premier champ text', mandatory: false },
{ type: :drop_down_list, libelle: 'Spécialité', options: ["", "littéraire", "scientifique", "artistique"], mandatory: false }
])
end
let(:administrateur) { create(:administrateur, procedures: [procedure]) }

View file

@ -410,7 +410,7 @@ describe 'The user' do
let(:procedure) do
create(:procedure, :published, :for_individual,
types_de_champ_public: [
{ type: :integer_number, libelle: 'age', stable_id: },
{ type: :integer_number, libelle: 'age', mandatory: false, stable_id: },
{
type: :repetition, libelle: 'repetition', condition:, children: [
{ type: :text, libelle: 'nom', mandatory: true }
@ -439,7 +439,7 @@ describe 'The user' do
let(:procedure) do
create(:procedure, :published, :for_individual,
types_de_champ_public: [
{ type: :checkbox, libelle: 'champ_a', stable_id: a_stable_id },
{ type: :checkbox, libelle: 'champ_a', mandatory: false, stable_id: a_stable_id },
{
type: :repetition, libelle: 'repetition', mandatory: true, children: [
{ type: :checkbox, libelle: 'champ_b', stable_id: b_stable_id },
@ -476,7 +476,7 @@ describe 'The user' do
let(:procedure) do
create(:procedure, :published, :for_individual,
types_de_champ_public: [
{ type: :integer_number, libelle: 'age', stable_id: },
{ type: :integer_number, libelle: 'age', mandatory: false, stable_id: },
{ type: :text, libelle: 'nom', mandatory: true, condition: }
])
end
@ -514,11 +514,11 @@ describe 'The user' do
let(:procedure) do
create(:procedure, :published, :for_individual,
types_de_champ_public: [
{ type: :integer_number, libelle: 'age du candidat', stable_id: age_stable_id },
{ type: :yes_no, libelle: 'permis de conduire', stable_id: permis_stable_id, condition: permis_condition },
{ type: :header_section, libelle: 'info voiture', condition: permis_condition },
{ type: :integer_number, libelle: 'tonnage', stable_id: tonnage_stable_id, condition: tonnage_condition },
{ type: :text, libelle: 'parking', condition: parking_condition }
{ type: :integer_number, libelle: 'age du candidat', stable_id: age_stable_id, mandatory: false },
{ type: :yes_no, libelle: 'permis de conduire', stable_id: permis_stable_id, condition: permis_condition, mandatory: false },
{ type: :header_section, libelle: 'info voiture', condition: permis_condition, mandatory: false },
{ type: :integer_number, libelle: 'tonnage', stable_id: tonnage_stable_id, condition: tonnage_condition, mandatory: false },
{ type: :text, libelle: 'parking', condition: parking_condition, mandatory: false }
])
end

View file

@ -64,7 +64,7 @@ describe 'Dossier Inéligibilité', js: true do
end
describe 'ineligibilite_rules with a Or' do
let(:types_de_champ_public) { [{ type: :yes_no, libelle: 'l1' }, { type: :drop_down_list, libelle: 'l2', options: ['Paris', 'Marseille'] }] }
let(:types_de_champ_public) { [{ type: :yes_no, libelle: 'l1' }, { type: :drop_down_list, libelle: 'l2', options: ['Paris', 'Marseille'], mandatory: false }] }
let(:ineligibilite_rules) do
ds_or([
ds_eq(champ_value(first_tdc.stable_id), constant(true)),
@ -134,7 +134,7 @@ describe 'Dossier Inéligibilité', js: true do
end
describe 'ineligibilite_rules with a And and all visible champs' do
let(:types_de_champ_public) { [{ type: :yes_no, libelle: 'l1' }, { type: :drop_down_list, libelle: 'l2', options: ['Paris', 'Marseille'] }] }
let(:types_de_champ_public) { [{ type: :yes_no, libelle: 'l1' }, { type: :drop_down_list, libelle: 'l2', options: ['Paris', 'Marseille'], mandatory: false }] }
let(:ineligibilite_rules) do
ds_and([
ds_eq(champ_value(first_tdc.stable_id), constant(true)),