Merge branch 'main' into feature/add_rna_type_de_champs
This commit is contained in:
commit
0131a41266
253 changed files with 2393 additions and 1301 deletions
|
@ -166,7 +166,8 @@ linters:
|
|||
|
||||
SelectorFormat:
|
||||
enabled: true
|
||||
convention: hyphenated_lowercase
|
||||
# hyphenated_lowercase + any dsfr selector which are not hyphenated
|
||||
convention: ^(?:fr-[^A-Z]+|[^_A-Z]+)$
|
||||
|
||||
Shorthand:
|
||||
enabled: false
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -59,6 +59,7 @@ gem 'net-imap', require: false # See https://github.com/mikel/mail/pull/1439
|
|||
gem 'net-pop', require: false # same
|
||||
gem 'net-smtp', require: false # same
|
||||
gem 'openid_connect'
|
||||
gem 'parsby'
|
||||
gem 'pg'
|
||||
gem 'phonelib'
|
||||
gem 'prawn-rails' # PDF Generation
|
||||
|
|
|
@ -467,6 +467,7 @@ GEM
|
|||
webfinger (>= 1.0.1)
|
||||
orm_adapter (0.5.0)
|
||||
parallel (1.22.1)
|
||||
parsby (1.1.1)
|
||||
parser (3.1.2.0)
|
||||
ast (~> 2.4.1)
|
||||
pdf-core (0.9.0)
|
||||
|
@ -877,6 +878,7 @@ DEPENDENCIES
|
|||
net-pop
|
||||
net-smtp
|
||||
openid_connect
|
||||
parsby
|
||||
pg
|
||||
phonelib
|
||||
prawn-rails
|
||||
|
|
|
@ -69,7 +69,7 @@ On lance le serveur d'application ainsi :
|
|||
|
||||
bin/dev
|
||||
|
||||
L'application tourne alors à l'adresse `http://localhost:3000` en parralel avec un worker pour les jobs et le bundler vitejs.
|
||||
L'application tourne alors à l'adresse `http://localhost:3000` avec en parallèle un worker pour les jobs et le bundler vitejs.
|
||||
|
||||
### Utilisateurs de test
|
||||
|
||||
|
|
|
@ -304,9 +304,19 @@
|
|||
select {
|
||||
width: 200px;
|
||||
display: inline-block;
|
||||
background-color: $light-grey;
|
||||
border: 1px solid $border-grey;
|
||||
}
|
||||
|
||||
[disabled] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $blue-france-500;
|
||||
}
|
||||
|
||||
.account-btn::before {
|
||||
content: none !important;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
.conditionnel {
|
||||
form.form > .conditionnel {
|
||||
|
||||
.condition-error {
|
||||
background: $background-red;
|
||||
|
@ -64,7 +64,7 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
input[type=text] {
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -81,8 +81,7 @@
|
|||
}
|
||||
|
||||
// Move checkbox to the top-left side of the label
|
||||
&.editable-champ-checkbox,
|
||||
&.editable-champ-engagement {
|
||||
&.editable-champ-checkbox {
|
||||
p,
|
||||
label {
|
||||
padding-left: 28px;
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
.france-connect-agent-login-button {
|
||||
background-image: image-url("agentconnect-btn-principal.svg"), image-url("agentconnect-btn-principal-hover.svg");
|
||||
display: block;
|
||||
height: 60px;
|
||||
width: 206px;
|
||||
margin: 20px auto;
|
||||
font-size: 0;
|
||||
|
||||
&:hover {
|
||||
background-image: image-url("agentconnect-btn-principal-hover.svg");
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
@import "constants";
|
||||
|
||||
.france-connect-informations.card {
|
||||
width: 100%;
|
||||
padding-top: $default-spacer;
|
||||
padding-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
.france-connect-informations-logo img {
|
||||
width: 100px;
|
||||
margin-right: $default-padding;
|
||||
}
|
|
@ -7,10 +7,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.france-connect-help-link {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a.france-connect-login-button {
|
||||
display: inline-block;
|
||||
height: 60px;
|
||||
|
|
|
@ -12,8 +12,12 @@ $header-mobile-breakpoint: 550px;
|
|||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.new-header-with-border {
|
||||
border-bottom: 1px solid $border-grey;
|
||||
// No drop shadow when a notice is shown.
|
||||
.fr-header.fr-header__with-notice-info {
|
||||
&,
|
||||
.fr-header__brand {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-inner-content {
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
.pj {
|
||||
th {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.dropdown-items a {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filename {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
|
@ -103,6 +103,11 @@
|
|||
border-color: $blue-france-500;
|
||||
}
|
||||
}
|
||||
|
||||
// fix/dsfr
|
||||
.fr-checkbox-group.fix-dsfr-notified-toggle-component {
|
||||
margin-top: -7px;
|
||||
}
|
||||
}
|
||||
|
||||
ul.revision-changes {
|
||||
|
|
|
@ -18,6 +18,18 @@
|
|||
display: inline;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: none;
|
||||
}
|
||||
|
||||
// text
|
||||
.text-center,
|
||||
.center {
|
||||
|
@ -74,6 +86,14 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.visible-on-previous-hover {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
:hover + .visible-on-previous-hover {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
// sizing
|
||||
.width-100 {
|
||||
width: 100%;
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
Veuillez télécharger, remplir et joindre
|
||||
= link_to('le modèle suivant', url_for(template), target: '_blank', rel: 'noopener')
|
||||
|
||||
- if helpers.administrateur_signed_in?
|
||||
%em.fr-text-mention--grey.fr-text--xs.visible-on-previous-hover
|
||||
= t('shared.ephemeral_link')
|
||||
|
||||
- if persisted?
|
||||
.attachment-actions{ id: dom_id(attachment, :actions) }
|
||||
.attachment-action
|
||||
|
|
23
app/components/dossiers/filter_component.rb
Normal file
23
app/components/dossiers/filter_component.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
class Dossiers::FilterComponent < ApplicationComponent
|
||||
def initialize(procedure:, procedure_presentation:, statut:, field_id: nil)
|
||||
@procedure = procedure
|
||||
@procedure_presentation = procedure_presentation
|
||||
@statut = statut
|
||||
@field_id = field_id
|
||||
end
|
||||
|
||||
attr_reader :procedure, :procedure_presentation, :statut, :field_id
|
||||
|
||||
def filterable_fields_for_select
|
||||
procedure_presentation.filterable_fields_options
|
||||
end
|
||||
|
||||
def field_type
|
||||
return :text if field_id.nil?
|
||||
procedure_presentation.field_type(field_id)
|
||||
end
|
||||
|
||||
def options_for_select_of_field
|
||||
I18n.t(procedure_presentation.field_enum(field_id)).map(&:to_a).map(&:reverse)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
en:
|
||||
column: Column
|
||||
value: Value
|
||||
add_filter: Add filter
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
fr:
|
||||
column: Colonne
|
||||
value: Valeur
|
||||
add_filter: Ajouter le filtre
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
= form_tag add_filter_instructeur_procedure_url(procedure), method: :post, class: 'dropdown-form large', id: 'filter-component', data: { controller: 'dossier-filter' } do
|
||||
= label_tag :field, t('.column')
|
||||
= select_tag :field, options_for_select(filterable_fields_for_select, field_id), include_blank: field_id.nil?, data: {action: "dossier-filter#onChange"}
|
||||
%br
|
||||
= label_tag :value, t('.value'), for: 'value'
|
||||
- if field_type == :enum
|
||||
= select_tag :value, options_for_select(options_for_select_of_field), id: 'value', name: 'value'
|
||||
- else
|
||||
%input#value{ type: field_type, name: :value, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH, disabled: field_id.nil? ? true : false }
|
||||
|
||||
= hidden_field_tag :statut, statut
|
||||
%br
|
||||
= submit_tag t('.add_filter'), class: 'button'
|
41
app/components/dossiers/notified_toggle_component.rb
Normal file
41
app/components/dossiers/notified_toggle_component.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
class Dossiers::NotifiedToggleComponent < ApplicationComponent
|
||||
def initialize(procedure:, procedure_presentation:)
|
||||
@procedure = procedure
|
||||
@procedure_presentation = procedure_presentation
|
||||
@current_sort = procedure_presentation.sort
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def opposite_order
|
||||
@procedure_presentation.opposite_order_for(current_table, current_column)
|
||||
end
|
||||
|
||||
def active?
|
||||
sorted_by_notifications? && order_asc?
|
||||
end
|
||||
|
||||
def icon_class_name
|
||||
active? ? 'fr-fi-checkbox' : 'fr-fi-checkbox-blank'
|
||||
end
|
||||
|
||||
def order_asc?
|
||||
current_order == 'asc'
|
||||
end
|
||||
|
||||
def current_order
|
||||
@current_sort['order']
|
||||
end
|
||||
|
||||
def current_table
|
||||
@current_sort['table']
|
||||
end
|
||||
|
||||
def current_column
|
||||
@current_sort['column']
|
||||
end
|
||||
|
||||
def sorted_by_notifications?
|
||||
current_table == 'notifications' && current_column == 'notifications'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
en:
|
||||
show_notified_first: Show files with notification first
|
|
@ -0,0 +1,2 @@
|
|||
fr:
|
||||
show_notified_first: Remonter les dossiers avec une notification
|
|
@ -0,0 +1,7 @@
|
|||
= form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, table: 'notifications', column: 'notifications', order: opposite_order), method: 'GET', data: {controller: 'checkbox'} do
|
||||
.fr-form-group
|
||||
.fr-fieldset__content
|
||||
.fr-checkbox-group.fix-dsfr-notified-toggle-component
|
||||
= check_box_tag :order, opposite_order, active?, data: {action: 'change->checkbox#onChange'}
|
||||
= label_tag :order, t('.show_notified_first'), class: 'fr-label'
|
||||
= submit_tag t('.show_notified_first'), data: {"checkbox-target": 'submit' }, class: 'visually-hidden'
|
12
app/components/dsfr/notice_component.rb
Normal file
12
app/components/dsfr/notice_component.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# see: https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/bandeau-d-information-importante/
|
||||
class Dsfr::NoticeComponent < ApplicationComponent
|
||||
renders_one :title
|
||||
|
||||
def initialize(closable: false)
|
||||
@closable = closable
|
||||
end
|
||||
|
||||
def closable?
|
||||
!!@closable
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
en:
|
||||
close_notice: Hide message
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
fr:
|
||||
close_notice: Masquer le message
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
.fr-notice.fr-notice--info{ "data-dsfr-header-target": "notice" }
|
||||
.fr-container
|
||||
.fr-notice__body
|
||||
%p.fr-notice__title
|
||||
= title
|
||||
|
||||
- if closable?
|
||||
%button.fr-btn--close.fr-btn{ title: t('.close_notice'), data: { action: "dsfr-header#closeNotice" } }
|
||||
= t('.close_notice')
|
|
@ -20,12 +20,12 @@ class EditableChamp::EditableChampComponent < ApplicationComponent
|
|||
{
|
||||
class: "editable-champ-#{@champ.type_champ} #{'hidden' if !@champ.visible?}",
|
||||
id: @champ.input_group_id,
|
||||
data: { controller: stimulus_controller }
|
||||
data: { controller: stimulus_controller, block: @champ.block? }
|
||||
}
|
||||
end
|
||||
|
||||
def stimulus_controller
|
||||
if !@champ.repetition? && @champ.fillable?
|
||||
if !@champ.block? && @champ.fillable?
|
||||
# This is an editable champ. Lets find what controllers it might need.
|
||||
controllers = []
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.editable-champ{ html_options }
|
||||
- if @champ.repetition?
|
||||
- if @champ.block?
|
||||
%h3.header-subsection= @champ.libelle
|
||||
- if @champ.description.present?
|
||||
%p.notice= string_to_html(@champ.description, false)
|
||||
|
@ -9,5 +9,5 @@
|
|||
- if @champ.type_champ == "titre_identite"
|
||||
%p.notice Carte nationale d’identité (uniquement le recto), passeport, titre de séjour ou autre justificatif d’identité. Formats acceptés : jpg/png
|
||||
|
||||
= @form.hidden_field :id, value: @champ.id, data: @champ.repetition? ? { id: true } : {}
|
||||
= @form.hidden_field :id, value: @champ.id, data: @champ.block? ? { id: true } : {}
|
||||
= render component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
class EditableChamp::EngagementComponent < EditableChamp::EditableChampBaseComponent
|
||||
end
|
|
@ -1,7 +1,6 @@
|
|||
class Procedure::Card::AnnotationsComponent < ApplicationComponent
|
||||
def initialize(procedure:)
|
||||
@procedure = procedure
|
||||
@procedure.validate(:publication)
|
||||
@count = @procedure.draft_revision.types_de_champ.private_only.size
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
class Procedure::Card::ChampsComponent < ApplicationComponent
|
||||
def initialize(procedure:)
|
||||
@procedure = procedure
|
||||
@procedure.validate(:publication)
|
||||
@count = @procedure.draft_revision.types_de_champ.public_only.size
|
||||
end
|
||||
|
||||
|
|
|
@ -56,6 +56,24 @@ class TypesDeChampEditor::ChampComponent < ApplicationComponent
|
|||
end
|
||||
|
||||
def types_of_type_de_champ
|
||||
if Flipper.enabled?(:categories_type_de_champ, controller.current_user)
|
||||
cat_scope = "activerecord.attributes.type_de_champ.categorie"
|
||||
tdc_scope = "activerecord.attributes.type_de_champ.type_champs"
|
||||
|
||||
TypeDeChamp.type_champs
|
||||
.keys
|
||||
.filter(&method(:filter_type_champ))
|
||||
.filter(&method(:filter_featured_type_champ))
|
||||
.filter(&method(:filter_block_type_champ))
|
||||
.group_by { TypeDeChamp::TYPE_DE_CHAMP_TO_CATEGORIE.fetch(_1.to_sym) }
|
||||
.sort_by { |k, _v| TypeDeChamp::CATEGORIES.find_index(k) }
|
||||
.to_h do |cat, tdc|
|
||||
[
|
||||
t(cat, scope: cat_scope),
|
||||
tdc.map { [t(_1, scope: tdc_scope), _1] }
|
||||
]
|
||||
end
|
||||
else
|
||||
TypeDeChamp.type_champs
|
||||
.keys
|
||||
.filter(&method(:filter_type_champ))
|
||||
|
@ -64,6 +82,7 @@ class TypesDeChampEditor::ChampComponent < ApplicationComponent
|
|||
.map { |type_champ| [t("activerecord.attributes.type_de_champ.type_champs.#{type_champ}"), type_champ] }
|
||||
.sort_by(&:first)
|
||||
end
|
||||
end
|
||||
|
||||
def piece_justificative_options(form)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
%span.sr-only Déplacer le champ vers le bas
|
||||
.cell.flex.justify-start.column.flex-grow
|
||||
= form.label :type_champ, "Type de champ", for: dom_id(type_de_champ, :type_champ)
|
||||
- if Flipper.enabled?(:categories_type_de_champ, controller.current_user)
|
||||
= form.select :type_champ, grouped_options_for_select(types_of_type_de_champ, type_de_champ.type_champ), {}, class: 'small-margin small inline width-100', id: dom_id(type_de_champ, :type_champ)
|
||||
- else
|
||||
= form.select :type_champ, types_of_type_de_champ, {}, class: 'small-margin small inline width-100', id: dom_id(type_de_champ, :type_champ)
|
||||
.flex.column.justify-start.flex-grow
|
||||
.cell
|
||||
|
@ -71,7 +74,7 @@
|
|||
= form.label name, for: dom_id(type_de_champ, "layer_#{name}") do
|
||||
= form.check_box name, checked: checked, class: 'small-margin small', id: dom_id(type_de_champ, "layer_#{name}")
|
||||
= t(".layers.#{name}")
|
||||
- if type_de_champ.repetition?
|
||||
- if type_de_champ.block?
|
||||
.flex.justify-start.section.ml-1
|
||||
.editor-block.flex-grow.cell
|
||||
= render TypesDeChampEditor::BlockComponent.new(block: coordinate, coordinates: coordinate.revision_types_de_champ)
|
||||
|
|
|
@ -105,7 +105,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
|||
end
|
||||
|
||||
def compatibles_operators_for_select(left)
|
||||
case left.type
|
||||
case left.type(@upper_tdcs)
|
||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:boolean)
|
||||
[
|
||||
[t('is', scope: 'logic'), Eq.name]
|
||||
|
@ -119,6 +119,10 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
|||
[t('is', scope: 'logic'), Eq.name],
|
||||
[t('is_not', scope: 'logic'), NotEq.name]
|
||||
]
|
||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:enums)
|
||||
[
|
||||
[t(IncludeOperator.name, scope: 'logic.operators'), IncludeOperator.name]
|
||||
]
|
||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:number)
|
||||
[Eq, LessThan, GreaterThan, LessThanEq, GreaterThanEq]
|
||||
.map(&:name)
|
||||
|
@ -131,7 +135,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
|||
def right_operand_tag(left, right, row_index)
|
||||
right_invalid = !current_right_valid?(left, right)
|
||||
|
||||
case left.type
|
||||
case left.type(@upper_tdcs)
|
||||
when :boolean
|
||||
booleans_for_select = [[t('utils.yes'), constant(true).to_json], [t('utils.no'), constant(false).to_json]]
|
||||
|
||||
|
@ -151,8 +155,8 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
|||
options_for_select(empty_target_for_select),
|
||||
id: input_id_for('value', row_index)
|
||||
)
|
||||
when :enum
|
||||
enums_for_select = left.options
|
||||
when :enum, :enums
|
||||
enums_for_select = left.options(@upper_tdcs)
|
||||
|
||||
if right_invalid
|
||||
enums_for_select = empty_target_for_select + enums_for_select
|
||||
|
@ -165,7 +169,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
|||
class: { alert: right_invalid }
|
||||
)
|
||||
when :number
|
||||
number_field_tag(
|
||||
text_field_tag(
|
||||
input_name_for('value'),
|
||||
right.value,
|
||||
required: true,
|
||||
|
@ -173,12 +177,12 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
|||
class: { alert: right_invalid }
|
||||
)
|
||||
else
|
||||
number_field_tag(input_name_for('value'), '', id: input_id_for('value', row_index))
|
||||
text_field_tag(input_name_for('value'), '', id: input_id_for('value', row_index))
|
||||
end
|
||||
end
|
||||
|
||||
def current_right_valid?(left, right)
|
||||
Logic.compatible_type?(left, right)
|
||||
Logic.compatible_type?(left, right, @upper_tdcs)
|
||||
end
|
||||
|
||||
def add_condition_tag
|
||||
|
|
|
@ -6,35 +6,63 @@ class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
|
|||
private
|
||||
|
||||
def errors
|
||||
@conditions
|
||||
.filter { |condition| condition.errors(@upper_tdcs.map(&:stable_id)).present? }
|
||||
.map { |condition| row_error(Logic.split_condition(condition)) }
|
||||
errors = @conditions
|
||||
.flat_map { |condition| condition.errors(@upper_tdcs) }
|
||||
.uniq
|
||||
|
||||
# if a tdc is not available (has been removed for example)
|
||||
# it causes a lot of errors (incompatible type for example)
|
||||
# only the root cause is displayed
|
||||
messages = if errors.include?({ type: :not_available })
|
||||
[t('not_available', scope: '.errors')]
|
||||
else
|
||||
errors.map { |error| humanize(error) }
|
||||
end
|
||||
|
||||
to_html_list(messages)
|
||||
end
|
||||
|
||||
def to_html_list(messages)
|
||||
messages
|
||||
.map { |message| tag.li(message) }
|
||||
.then { |lis| tag.ul(lis.reduce(&:+)) }
|
||||
end
|
||||
|
||||
def row_error((left, operator_name, right))
|
||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == left.stable_id }
|
||||
|
||||
if targeted_champ.nil?
|
||||
def humanize(error)
|
||||
case error
|
||||
in { type: :not_available }
|
||||
t('not_available', scope: '.errors')
|
||||
elsif left.type == :unmanaged
|
||||
t('unmanaged', scope: '.errors',
|
||||
in { type: :unmanaged, stable_id: stable_id }
|
||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
||||
t('unmanaged',
|
||||
scope: '.errors',
|
||||
libelle: targeted_champ.libelle,
|
||||
type_champ: t(targeted_champ.type_champ, scope: 'activerecord.attributes.type_de_champ.type_champs')&.downcase)
|
||||
else
|
||||
in { type: :incompatible, stable_id: stable_id, right: right, operator_name: operator_name }
|
||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
||||
t('incompatible', scope: '.errors',
|
||||
libelle: targeted_champ.libelle,
|
||||
type_champ: t(targeted_champ.type_champ, scope: 'activerecord.attributes.type_de_champ.type_champs')&.downcase,
|
||||
operator: t(operator_name, scope: 'logic.operators').downcase,
|
||||
right: right.to_s.downcase)
|
||||
in { type: :required_number, operator_name: operator_name }
|
||||
t('required_number', scope: '.errors',
|
||||
operator: t(operator_name, scope: 'logic.operators'))
|
||||
in { type: :not_included, stable_id: stable_id, right: right }
|
||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
||||
t('not_included', scope: '.errors',
|
||||
libelle: targeted_champ.libelle,
|
||||
right: right.to_s.downcase)
|
||||
in { type: :required_list }
|
||||
t('required_list', scope: '.errors')
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def render?
|
||||
@conditions
|
||||
.filter { |condition| condition.errors(@upper_tdcs.map(&:stable_id)).present? }
|
||||
.filter { |condition| condition.errors(@upper_tdcs).present? }
|
||||
.present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
fr:
|
||||
errors:
|
||||
not_available: "A targeted field is not available."
|
||||
unmanaged: "The field « %{libelle} » is a « %{type_champ} » and cannot be used as conditional source."
|
||||
incompatible: "The field « %{libelle} » is a « %{type_champ} ». It cannot be %{operator} « %{right} »."
|
||||
required_number: "« %{operator} » applies only to number."
|
||||
required_list: "The « include » operator only applies to simple or multiple choice."
|
||||
not_included: "« %{right} » is not included in « %{libelle} »."
|
|
@ -3,4 +3,7 @@ fr:
|
|||
errors:
|
||||
not_available: "Un champ cible n'est plus disponible. Il est soit supprimé, soit déplacé en dessous de ce champ."
|
||||
unmanaged: "Le champ « %{libelle} » est de type « %{type_champ} » et ne peut pas être utilisé comme champ cible."
|
||||
incompatible: "Le champ « %{libelle} » est de type « %{type_champ} ». Il ne peut pas être %{operator} %{right}."
|
||||
incompatible: "Le champ « %{libelle} » est de type « %{type_champ} ». Il ne peut pas être %{operator} « %{right} »."
|
||||
required_number: "« %{operator} » ne s'applique qu'à des nombres."
|
||||
required_list: "Lʼopérateur « inclus » ne s'applique qu'au choix simple ou multiple."
|
||||
not_included: "« %{right} » ne fait pas partie de « %{libelle} »."
|
||||
|
|
|
@ -49,7 +49,7 @@ module Administrateurs
|
|||
end
|
||||
|
||||
def condition_form
|
||||
ConditionForm.new(condition_params)
|
||||
ConditionForm.new(condition_params.merge({ upper_tdcs: @upper_tdcs }))
|
||||
end
|
||||
|
||||
def retrieve_coordinate_and_uppers
|
||||
|
|
|
@ -93,11 +93,13 @@ module Administrateurs
|
|||
@procedure = current_administrateur
|
||||
.procedures
|
||||
.includes(
|
||||
published_revision: { revision_types_de_champ: :type_de_champ },
|
||||
draft_revision: { revision_types_de_champ: :type_de_champ }
|
||||
published_revision: :types_de_champ,
|
||||
draft_revision: :types_de_champ
|
||||
)
|
||||
.find(params[:id])
|
||||
|
||||
@procedure.validate(:publication)
|
||||
|
||||
@current_administrateur = current_administrateur
|
||||
@procedure_lien = commencer_url(path: @procedure.path)
|
||||
@procedure_lien_test = commencer_test_url(path: @procedure.path)
|
||||
|
@ -233,6 +235,13 @@ module Administrateurs
|
|||
end
|
||||
|
||||
def publication
|
||||
@procedure = current_administrateur
|
||||
.procedures
|
||||
.includes(
|
||||
published_revision: :types_de_champ,
|
||||
draft_revision: :types_de_champ
|
||||
).find(params[:procedure_id])
|
||||
|
||||
@procedure_lien = commencer_url(path: @procedure.path)
|
||||
@procedure_lien_test = commencer_test_url(path: @procedure.path)
|
||||
@procedure.path = @procedure.suggested_path(current_administrateur)
|
||||
|
|
|
@ -1,16 +1,31 @@
|
|||
class API::V2::BaseController < ApplicationController
|
||||
protect_from_forgery with: :null_session
|
||||
# Disable forgery protection for API controllers when the request is authenticated
|
||||
# with a bearer token. Otherwise the session will be nullified and we'll lose curent_user
|
||||
protect_from_forgery with: :null_session, unless: :token?
|
||||
skip_before_action :setup_tracking
|
||||
prepend_before_action :authenticate_administrateur_from_token
|
||||
|
||||
private
|
||||
|
||||
def context
|
||||
{
|
||||
administrateur_id: current_administrateur&.id,
|
||||
token: authorization_bearer_token
|
||||
}
|
||||
# new token give administrateur_id
|
||||
if api_token.administrateur?
|
||||
{ administrateur_id: api_token.administrateur_id }
|
||||
# web interface (/graphql) give current_administrateur
|
||||
elsif current_administrateur.present?
|
||||
{ administrateur_id: current_administrateur.id }
|
||||
# old token
|
||||
else
|
||||
{ token: api_token.token }
|
||||
end
|
||||
end
|
||||
|
||||
def token?
|
||||
authorization_bearer_token.present?
|
||||
end
|
||||
|
||||
def authorization_bearer_token
|
||||
@authorization_bearer_token ||= begin
|
||||
received_token = nil
|
||||
authenticate_with_http_token do |token, _options|
|
||||
received_token = token
|
||||
|
@ -18,3 +33,17 @@ class API::V2::BaseController < ApplicationController
|
|||
received_token
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_administrateur_from_token
|
||||
if api_token.administrateur?
|
||||
administrateur = Administrateur.includes(:user).find_by(id: api_token.administrateur_id)
|
||||
if administrateur.valid_api_token?(api_token.token)
|
||||
@current_user = administrateur.user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def api_token
|
||||
@api_token ||= APIToken.new(authorization_bearer_token)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ class APIController < ApplicationController
|
|||
|
||||
def find_administrateur_for_token(procedure)
|
||||
procedure.administrateurs.find do |administrateur|
|
||||
administrateur.valid_api_token?(token)
|
||||
administrateur.valid_api_token?(api_token.token)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -15,7 +15,11 @@ class APIController < ApplicationController
|
|||
request.format = "json" if !request.params[:format]
|
||||
end
|
||||
|
||||
def token
|
||||
def api_token
|
||||
@api_token ||= APIToken.new(authorization_bearer_token)
|
||||
end
|
||||
|
||||
def authorization_bearer_token
|
||||
params_token.presence || header_token
|
||||
end
|
||||
|
||||
|
|
|
@ -263,7 +263,13 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def sentry_user
|
||||
{ id: user_signed_in? ? "User##{current_user.id}" : 'Guest' }
|
||||
if user_signed_in?
|
||||
{ id: "User##{current_user.id}" }
|
||||
elsif administrateur_signed_in?
|
||||
{ id: "Administrateur##{current_administrateur.id}" }
|
||||
else
|
||||
{ id: 'Guest' }
|
||||
end
|
||||
end
|
||||
|
||||
def sentry_config
|
||||
|
|
|
@ -53,7 +53,7 @@ class FranceConnect::ParticulierController < ApplicationController
|
|||
if !user.can_france_connect?
|
||||
flash.alert = t('errors.messages.france_connect.forbidden_html', reset_link: new_user_password_path)
|
||||
|
||||
render js: ajax_redirect(root_path)
|
||||
redirect_to root_path
|
||||
else
|
||||
@fci.update(user: user)
|
||||
@fci.delete_merge_token!
|
||||
|
@ -63,8 +63,6 @@ class FranceConnect::ParticulierController < ApplicationController
|
|||
end
|
||||
else
|
||||
flash.alert = t('france_connect.particulier.flash.invalid_password')
|
||||
|
||||
render js: helpers.render_flash
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -112,10 +110,7 @@ class FranceConnect::ParticulierController < ApplicationController
|
|||
if @fci.nil? || !@fci.valid_for_merge?
|
||||
flash.alert = t('france_connect.particulier.flash.merger_token_expired', application_name: APPLICATION_NAME)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to root_path }
|
||||
format.js { render js: ajax_redirect(root_path) }
|
||||
end
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -134,12 +129,7 @@ class FranceConnect::ParticulierController < ApplicationController
|
|||
|
||||
user.update_attribute('loged_in_with_france_connect', User.loged_in_with_france_connects.fetch(:particulier))
|
||||
|
||||
redirection_location = stored_location_for(current_user) || root_path(current_user)
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to redirection_location }
|
||||
format.js { render js: ajax_redirect(root_path) }
|
||||
end
|
||||
redirect_to stored_location_for(current_user) || root_path(current_user)
|
||||
end
|
||||
|
||||
def redirect_france_connect_error_connection
|
||||
|
|
|
@ -308,6 +308,8 @@ module Instructeurs
|
|||
def aasm_error_message(exception, target_state:)
|
||||
if exception.originating_state == target_state
|
||||
"Le dossier est déjà #{dossier_display_state(target_state, lower: true)}."
|
||||
elsif exception.failures.include?(:can_terminer?)
|
||||
"Les données relatives au SIRET de ce dossier n’ont pas pu encore être vérifiées : il n’est pas possible de le passer #{dossier_display_state(target_state, lower: true)}."
|
||||
else
|
||||
"Le dossier est en ce moment #{dossier_display_state(exception.originating_state, lower: true)} : il n’est pas possible de le passer #{dossier_display_state(target_state, lower: true)}."
|
||||
end
|
||||
|
|
|
@ -121,16 +121,26 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def update_sort
|
||||
procedure_presentation.update_sort(params[:table], params[:column])
|
||||
procedure_presentation.update_sort(params[:table], params[:column], params[:order])
|
||||
|
||||
redirect_back(fallback_location: instructeur_procedure_url(procedure))
|
||||
end
|
||||
|
||||
def add_filter
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
procedure_presentation.add_filter(statut, params[:field], params[:value])
|
||||
|
||||
redirect_back(fallback_location: instructeur_procedure_url(procedure))
|
||||
end
|
||||
format.turbo_stream do
|
||||
@statut = statut
|
||||
@procedure = procedure
|
||||
@procedure_presentation = procedure_presentation
|
||||
@field = params[:field]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_filter
|
||||
procedure_presentation.remove_filter(statut, params[:field], params[:value])
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
class TargetedUserLinksController < ApplicationController
|
||||
before_action :set_targeted_user_link, only: [:show]
|
||||
def show
|
||||
if @targeted_user_link.invalid_signed_in_user?(current_user)
|
||||
if targeted_user_link.invalid_signed_in_user?(current_user)
|
||||
render
|
||||
else
|
||||
redirect_to @targeted_user_link.redirect_url(Rails.application.routes.url_helpers)
|
||||
redirect_to targeted_user_link.redirect_url(Rails.application.routes.url_helpers)
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to root_path, flash: { error: t('errors.messages.targeted_user_link_expired') }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_targeted_user_link
|
||||
@targeted_user_link = TargetedUserLink.find(params[:id])
|
||||
def targeted_user_link
|
||||
@targeted_user_link ||= TargetedUserLink.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -428,7 +428,7 @@ module Users
|
|||
@dossier.assign_attributes(champs_params[:dossier])
|
||||
# FIXME: in some cases a removed repetition bloc row is submitted.
|
||||
# In this case it will be treated as a new record, and the action will fail.
|
||||
@dossier.champs.filter(&:repetition?).each do |champ|
|
||||
@dossier.champs.filter(&:block?).each do |champ|
|
||||
champ.champs = champ.champs.filter(&:persisted?)
|
||||
end
|
||||
if @dossier.champs.any?(&:changed_for_autosave?)
|
||||
|
|
|
@ -48,6 +48,6 @@ class ZoneDashboard < Administrate::BaseDashboard
|
|||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(zone)
|
||||
"Zone #{zone.label}"
|
||||
"Zone #{zone.current_label}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,8 +59,7 @@ module Mutations
|
|||
when :checkbox
|
||||
[
|
||||
TypeDeChamp.type_champs.fetch(:checkbox),
|
||||
TypeDeChamp.type_champs.fetch(:yes_no),
|
||||
TypeDeChamp.type_champs.fetch(:engagement)
|
||||
TypeDeChamp.type_champs.fetch(:yes_no)
|
||||
]
|
||||
when :date
|
||||
TypeDeChamp.type_champs.fetch(:date)
|
||||
|
|
|
@ -1450,7 +1450,7 @@ type Entreprise {
|
|||
"""
|
||||
capitalSocial: BigInt
|
||||
codeEffectifEntreprise: String
|
||||
dateCreation: ISO8601Date!
|
||||
dateCreation: ISO8601Date
|
||||
|
||||
"""
|
||||
effectif moyen d’une année
|
||||
|
@ -2135,11 +2135,6 @@ enum TypeDeChamp {
|
|||
"""
|
||||
email
|
||||
|
||||
"""
|
||||
Engagement
|
||||
"""
|
||||
engagement
|
||||
|
||||
"""
|
||||
Explication
|
||||
"""
|
||||
|
|
|
@ -18,7 +18,7 @@ module Types
|
|||
field :options, [String], "List des options d’un champ avec selection.", null: true
|
||||
|
||||
def champ_descriptors
|
||||
if object.type_de_champ.repetition?
|
||||
if object.type_de_champ.block?
|
||||
Loaders::Association.for(object.class, revision_types_de_champ: :type_de_champ).load(object)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ module Types
|
|||
else
|
||||
Types::Champs::TextChampType
|
||||
end
|
||||
when ::Champs::EngagementChamp, ::Champs::YesNoChamp, ::Champs::CheckboxChamp
|
||||
when ::Champs::YesNoChamp, ::Champs::CheckboxChamp
|
||||
Types::Champs::CheckboxChampType
|
||||
when ::Champs::DateChamp
|
||||
Types::Champs::DateChampType
|
||||
|
|
|
@ -22,7 +22,7 @@ module Types
|
|||
field :code_effectif_entreprise, String, null: true
|
||||
field :effectif_mensuel, EffectifType, null: true, description: "effectif pour un mois donné"
|
||||
field :effectif_annuel, EffectifType, null: true, description: "effectif moyen d’une année"
|
||||
field :date_creation, GraphQL::Types::ISO8601Date, null: false
|
||||
field :date_creation, GraphQL::Types::ISO8601Date, null: true
|
||||
field :etat_administratif, EntrepriseEtatAdministratifType, null: true
|
||||
field :nom, String, null: true
|
||||
field :prenom, String, null: true
|
||||
|
|
|
@ -40,77 +40,6 @@ module ApplicationHelper
|
|||
tag.div(**html.merge(data: { controller: 'react', react_component_value: name, react_props_value: props.to_json }))
|
||||
end
|
||||
|
||||
def render_to_element(selector, partial:, outer: false, locals: {})
|
||||
method = outer ? 'outerHTML' : 'innerHTML'
|
||||
html = escape_javascript(render partial: partial, locals: locals)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').#{method} = \"#{html}\";")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def append_to_element(selector, partial:, locals: {})
|
||||
html = escape_javascript(render partial: partial, locals: locals)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').insertAdjacentHTML('beforeend', \"#{html}\");")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def render_flash(timeout: false, sticky: false, fixed: false)
|
||||
if flash.any?
|
||||
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky, fixed: fixed }, outer: true)
|
||||
flash.clear
|
||||
if timeout
|
||||
html += remove_element('#flash_messages', timeout: timeout, inner: true)
|
||||
end
|
||||
html
|
||||
end
|
||||
end
|
||||
|
||||
def remove_element(selector, timeout: 0, inner: false)
|
||||
script = "(function() {";
|
||||
script << "var el = document.querySelector('#{selector}');"
|
||||
method = (inner ? "el.innerHTML = ''" : "el.parentNode.removeChild(el)")
|
||||
if timeout.present? && timeout > 0
|
||||
script << "if (el) { setTimeout(function() { #{method}; }, #{timeout}); }"
|
||||
else
|
||||
script << "if (el) { #{method} };"
|
||||
end
|
||||
script << "})();"
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw(script);
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def show_element(selector)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').classList.remove('hidden');")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def focus_element(selector)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').focus();")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def disable_element(selector)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').disabled = true;")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def enable_element(selector)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("document.querySelector('#{selector}').disabled = false;")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def fire_event(event_name, data)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
raw("DS.fire('#{event_name}', #{raw(data)});")
|
||||
# rubocop:enable Rails/OutputSafety
|
||||
end
|
||||
|
||||
def current_email
|
||||
current_user&.email ||
|
||||
current_instructeur&.email ||
|
||||
|
|
|
@ -115,4 +115,16 @@ module DossierHelper
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
def france_connect_informations(user_information)
|
||||
if user_information.full_name.empty?
|
||||
t("shared.dossiers.france_connect_informations.details_no_name")
|
||||
elsif user_information.updated_at.present?
|
||||
t("shared.dossiers.france_connect_informations.details_updated",
|
||||
name: user_information.full_name,
|
||||
date: l(user_information.updated_at.to_date, format: :default))
|
||||
else
|
||||
t("shared.dossiers.france_connect_informations.details", name: user_information.full_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
module ZoneHelper
|
||||
def grouped_options_for_zone
|
||||
def grouped_options_for_zone(date)
|
||||
collectivite = Zone.find_by(acronym: "COLLECTIVITE")
|
||||
{
|
||||
"--" => [[I18n.t('i_dont_know', scope: 'utils'), nil], [collectivite.label, collectivite.id]],
|
||||
I18n.t('ministeres', scope: 'zones') => (Zone.order(:label) - [collectivite]).map { |m| [m.label, m.id] }
|
||||
"--" => [
|
||||
[I18n.t('i_dont_know', scope: 'utils'), nil],
|
||||
[collectivite.label, collectivite.id]
|
||||
],
|
||||
I18n.t('ministeres', scope: 'zones') => (Zone.available_at(date) - [collectivite]).map { |m| [m.label_at(date), m.id] }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -193,18 +193,20 @@ export class AutosaveController extends ApplicationController {
|
|||
|
||||
private get inputs() {
|
||||
const element = this.element as HTMLElement;
|
||||
|
||||
const inputs = [
|
||||
...element.querySelectorAll<HTMLInputElement>(
|
||||
'input:not([type=file]), textarea, select'
|
||||
)
|
||||
];
|
||||
const parent = this.element.closest('.editable-champ-repetition');
|
||||
].filter((element) => !element.disabled);
|
||||
|
||||
const parent = this.element.closest('[data-block]');
|
||||
if (parent) {
|
||||
return [
|
||||
...inputs,
|
||||
...parent.querySelectorAll<HTMLInputElement>('input[data-id]')
|
||||
];
|
||||
}
|
||||
return inputs.filter((element) => !element.disabled);
|
||||
return inputs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
import {
|
||||
httpRequest,
|
||||
isSelectElement,
|
||||
isCheckboxOrRadioInputElement,
|
||||
isTextInputElement,
|
||||
getConfig
|
||||
} from '@utils';
|
||||
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
const {
|
||||
autosave: { debounce_delay }
|
||||
} = getConfig();
|
||||
|
||||
const AUTOSAVE_DEBOUNCE_DELAY = debounce_delay;
|
||||
const AUTOSAVE_TIMEOUT_DELAY = 60000;
|
||||
|
||||
export class CheckConditionsController extends ApplicationController {
|
||||
#abortController?: AbortController;
|
||||
#latestPromise = Promise.resolve();
|
||||
|
||||
connect() {
|
||||
this.#latestPromise = Promise.resolve();
|
||||
this.on('change', (event) => this.onChange(event));
|
||||
this.on('input', (event) => this.onInput(event));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.#abortController?.abort();
|
||||
this.#latestPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
private onChange(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.disabled) {
|
||||
if (target.type == 'hidden') {
|
||||
this.debounce(this.enqueueCheckRequest, AUTOSAVE_DEBOUNCE_DELAY);
|
||||
} else if (
|
||||
isSelectElement(target) ||
|
||||
isCheckboxOrRadioInputElement(target)
|
||||
) {
|
||||
this.enqueueCheckRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.disabled) {
|
||||
if (
|
||||
target.getAttribute('role') != 'combobox' &&
|
||||
isTextInputElement(target)
|
||||
) {
|
||||
this.debounce(this.enqueueCheckRequest, AUTOSAVE_DEBOUNCE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enqueueCheckRequest() {
|
||||
this.#latestPromise = this.#latestPromise.finally(() =>
|
||||
this.sendCheckRequest().catch(() => null)
|
||||
);
|
||||
}
|
||||
|
||||
private sendCheckRequest(): Promise<void> {
|
||||
this.#abortController = new AbortController();
|
||||
const form = this.form;
|
||||
|
||||
if (!form) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const fileInputs = form.querySelectorAll('input[type="file"]');
|
||||
for (const input of fileInputs) {
|
||||
input.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
const formData = new FormData(form);
|
||||
for (const input of fileInputs) {
|
||||
input.removeAttribute('disabled');
|
||||
}
|
||||
formData.set('check_conditions', 'true');
|
||||
|
||||
return httpRequest(form.action, {
|
||||
method: 'patch',
|
||||
body: formData,
|
||||
signal: this.#abortController.signal,
|
||||
timeout: AUTOSAVE_TIMEOUT_DELAY
|
||||
}).turbo();
|
||||
}
|
||||
|
||||
private get form() {
|
||||
return this.element.closest('form');
|
||||
}
|
||||
}
|
8
app/javascript/controllers/checkbox_controller.ts
Normal file
8
app/javascript/controllers/checkbox_controller.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class CheckboxController extends ApplicationController {
|
||||
onChange() {
|
||||
const form = this.element as HTMLFormElement;
|
||||
form.requestSubmit();
|
||||
}
|
||||
}
|
13
app/javascript/controllers/dossier_filter_controller.ts
Normal file
13
app/javascript/controllers/dossier_filter_controller.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { httpRequest } from '@utils';
|
||||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class DossierFilterController extends ApplicationController {
|
||||
onChange() {
|
||||
const element = this.element as HTMLFormElement;
|
||||
|
||||
httpRequest(element.action, {
|
||||
method: element.getAttribute('method') ?? '',
|
||||
body: new FormData(element)
|
||||
}).turbo();
|
||||
}
|
||||
}
|
13
app/javascript/controllers/dsfr_header_controller.ts
Normal file
13
app/javascript/controllers/dsfr_header_controller.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { ApplicationController } from './application_controller';
|
||||
|
||||
export class DSFRHeaderController extends ApplicationController {
|
||||
static targets = ['notice'];
|
||||
|
||||
declare readonly noticeTarget: HTMLElement;
|
||||
|
||||
closeNotice() {
|
||||
this.noticeTarget.parentNode?.removeChild(this.noticeTarget);
|
||||
|
||||
this.element.classList.remove('fr-header__with-notice-info');
|
||||
}
|
||||
}
|
|
@ -5,17 +5,14 @@ import { Application } from '@hotwired/stimulus';
|
|||
import '@gouvfr/dsfr/dist/dsfr.module.js';
|
||||
|
||||
import '../shared/activestorage/ujs';
|
||||
import '../shared/remote-poller';
|
||||
import '../shared/safari-11-file-xhr-workaround';
|
||||
import '../shared/safari-11-empty-file-workaround';
|
||||
import '../shared/toggle-target';
|
||||
import '../shared/ujs-error-handling';
|
||||
|
||||
import { registerControllers } from '../shared/stimulus-loader';
|
||||
|
||||
import '../new_design/form-validation';
|
||||
import '../new_design/procedure-context';
|
||||
import '../new_design/procedure-form';
|
||||
import '../new_design/spinner';
|
||||
import '../new_design/support';
|
||||
|
||||
import {
|
||||
|
@ -31,11 +28,7 @@ import {
|
|||
acceptEmailSuggestion,
|
||||
discardEmailSuggestionBox
|
||||
} from '../new_design/user-sign_up';
|
||||
import {
|
||||
showFusion,
|
||||
showNewAccount,
|
||||
showNewAccountPasswordConfirmation
|
||||
} from '../new_design/fc-fusion';
|
||||
import { showFusion, showNewAccount } from '../new_design/fc-fusion';
|
||||
|
||||
const application = Application.start();
|
||||
registerControllers(application);
|
||||
|
@ -49,7 +42,6 @@ const DS = {
|
|||
showImportJustificatif,
|
||||
showFusion,
|
||||
showNewAccount,
|
||||
showNewAccountPasswordConfirmation,
|
||||
replaceSemicolonByComma,
|
||||
acceptEmailSuggestion,
|
||||
discardEmailSuggestionBox
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
@import '@gouvfr/dsfr/dist/core/core.css';
|
||||
@import '@gouvfr/dsfr/dist/utility/utility.css';
|
||||
|
||||
/* These base components may be dependencies of other components */
|
||||
@import '@gouvfr/dsfr/dist/component/link/link.css';
|
||||
@import '@gouvfr/dsfr/dist/component/logo/logo.css';
|
||||
@import '@gouvfr/dsfr/dist/component/form/form.css';
|
||||
@import '@gouvfr/dsfr/dist/component/badge/badge.css';
|
||||
@import '@gouvfr/dsfr/dist/component/card/card.css';
|
||||
@import '@gouvfr/dsfr/dist/component/navigation/navigation.css';
|
||||
@import '@gouvfr/dsfr/dist/component/button/button.css';
|
||||
|
||||
/* Verify README of each component to insert them in the expected order. */
|
||||
@import '@gouvfr/dsfr/dist/component/alert/alert.css';
|
||||
@import '@gouvfr/dsfr/dist/component/badge/badge.css';
|
||||
@import '@gouvfr/dsfr/dist/component/breadcrumb/breadcrumb.css';
|
||||
@import '@gouvfr/dsfr/dist/component/callout/callout.css';
|
||||
@import '@gouvfr/dsfr/dist/component/connect/connect.css';
|
||||
@import '@gouvfr/dsfr/dist/component/breadcrumb/breadcrumb.css';
|
||||
@import '@gouvfr/dsfr/dist/component/table/table.css';
|
||||
@import '@gouvfr/dsfr/dist/component/modal/modal.css';
|
||||
@import '@gouvfr/dsfr/dist/component/highlight/highlight.css';
|
||||
@import '@gouvfr/dsfr/dist/component/input/input.css';
|
||||
@import '@gouvfr/dsfr/dist/component/search/search.css';
|
||||
@import '@gouvfr/dsfr/dist/component/translate/translate.css';
|
||||
@import '@gouvfr/dsfr/dist/component/checkbox/checkbox.css';
|
||||
@import '@gouvfr/dsfr/dist/component/logo/logo.css';
|
||||
@import '@gouvfr/dsfr/dist/component/modal/modal.css';
|
||||
@import '@gouvfr/dsfr/dist/component/navigation/navigation.css';
|
||||
@import '@gouvfr/dsfr/dist/component/notice/notice.css';
|
||||
@import '@gouvfr/dsfr/dist/component/table/table.css';
|
||||
@import '@gouvfr/dsfr/dist/component/tag/tag.css';
|
||||
@import '@gouvfr/dsfr/dist/component/card/card.css';
|
||||
@import '@gouvfr/dsfr/dist/component/header/header.css';
|
||||
@import '@gouvfr/dsfr/dist/component/footer/footer.css';
|
||||
@import '@gouvfr/dsfr/dist/component/search/search.css';
|
||||
@import '@gouvfr/dsfr/dist/component/translate/translate.css';
|
||||
|
|
|
@ -3,17 +3,11 @@ import { show, hide } from '@utils';
|
|||
export function showFusion() {
|
||||
show(document.querySelector('.fusion'));
|
||||
hide(document.querySelector('.new-account'));
|
||||
hide(document.querySelector('.new-account-password-confirmation'));
|
||||
hide(document.querySelector('#new-account-password-confirmation'));
|
||||
}
|
||||
|
||||
export function showNewAccount() {
|
||||
hide(document.querySelector('.fusion'));
|
||||
show(document.querySelector('.new-account'));
|
||||
hide(document.querySelector('.new-account-password-confirmation'));
|
||||
}
|
||||
|
||||
export function showNewAccountPasswordConfirmation() {
|
||||
hide(document.querySelector('.fusion'));
|
||||
hide(document.querySelector('.new-account'));
|
||||
show(document.querySelector('.new-account-password-confirmation'));
|
||||
hide(document.querySelector('#new-account-password-confirmation'));
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { show, hide, delegate } from '@utils';
|
||||
|
||||
function showSpinner() {
|
||||
[...document.querySelectorAll('.spinner')].forEach(show);
|
||||
}
|
||||
|
||||
function hideSpinner() {
|
||||
[...document.querySelectorAll('.spinner')].forEach(hide);
|
||||
}
|
||||
|
||||
delegate('ajax:complete', '[data-spinner]', hideSpinner);
|
||||
delegate('ajax:stopped', '[data-spinner]', hideSpinner);
|
||||
delegate('ajax:send', '[data-spinner]', showSpinner);
|
|
@ -1,101 +0,0 @@
|
|||
import { httpRequest, delegate } from '@utils';
|
||||
|
||||
addEventListener('DOMContentLoaded', () => {
|
||||
attachementPoller.deactivate();
|
||||
exportPoller.deactivate();
|
||||
|
||||
const attachments = document.querySelectorAll('[data-attachment-poll-url]');
|
||||
const exports = document.querySelectorAll('[data-export-poll-url]');
|
||||
|
||||
for (let { dataset } of attachments) {
|
||||
attachementPoller.add(dataset.attachmentPollUrl);
|
||||
}
|
||||
|
||||
for (let { dataset } of exports) {
|
||||
exportPoller.add(dataset.exportPollUrl);
|
||||
}
|
||||
});
|
||||
|
||||
addEventListener('attachment:update', ({ detail: { url } }) => {
|
||||
attachementPoller.add(url);
|
||||
});
|
||||
|
||||
addEventListener('export:update', ({ detail: { url } }) => {
|
||||
exportPoller.add(url);
|
||||
});
|
||||
|
||||
delegate('click', '[data-attachment-refresh]', (event) => {
|
||||
event.preventDefault();
|
||||
attachementPoller.check();
|
||||
});
|
||||
|
||||
// Periodically check the state of a set of URLs.
|
||||
//
|
||||
// Each time the given URL is requested, the matching `show.js.erb` view is rendered,
|
||||
// causing the state to be refreshed.
|
||||
//
|
||||
// This is used mainly to refresh attachments during the anti-virus check,
|
||||
// but also to refresh the state of a pending spreadsheet export.
|
||||
class RemotePoller {
|
||||
urls = new Set();
|
||||
timeout;
|
||||
checks = 0;
|
||||
|
||||
constructor(settings = {}) {
|
||||
this.interval = settings.interval;
|
||||
this.maxChecks = settings.maxChecks;
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this.checks <= this.maxChecks;
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
return this.timeout !== undefined;
|
||||
}
|
||||
|
||||
add(url) {
|
||||
if (this.isEnabled) {
|
||||
if (!this.isActive) {
|
||||
this.activate();
|
||||
}
|
||||
this.urls.add(url);
|
||||
}
|
||||
}
|
||||
|
||||
check() {
|
||||
let urls = this.urls;
|
||||
this.reset();
|
||||
for (let url of urls) {
|
||||
// Start the request. The JS payload in the response will update the page.
|
||||
// (Errors are ignored, because background tasks shouldn't report errors to the user.)
|
||||
httpRequest(url)
|
||||
.js()
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
activate() {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => {
|
||||
this.checks++;
|
||||
this.currentInterval = this.interval * 1.5;
|
||||
this.check();
|
||||
}, this.currentInterval);
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.checks = 0;
|
||||
this.currentInterval = this.interval;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
clearTimeout(this.timeout);
|
||||
this.urls = new Set();
|
||||
this.timeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const attachementPoller = new RemotePoller({ interval: 3000, maxChecks: 5 });
|
||||
const exportPoller = new RemotePoller({ interval: 6000, maxChecks: 10 });
|
36
app/javascript/shared/safari-11-empty-file-workaround.ts
Normal file
36
app/javascript/shared/safari-11-empty-file-workaround.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
// iOS 11.3 Safari / macOS Safari 11.1 empty <input type="file"> XHR bug workaround.
|
||||
// This should work with every modern browser which supports ES5 (including IE9).
|
||||
// https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty
|
||||
// https://github.com/rails/rails/issues/32440
|
||||
|
||||
document.documentElement.addEventListener(
|
||||
'turbo:before-fetch-request',
|
||||
(event) => {
|
||||
const target = event.target as Element;
|
||||
const inputs = target.querySelectorAll<HTMLInputElement>(
|
||||
'input[type="file"]:not([disabled])'
|
||||
);
|
||||
for (const input of inputs) {
|
||||
if (input.files?.length == 0) {
|
||||
input.setAttribute('data-safari-temp-disabled', 'true');
|
||||
input.setAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
document.documentElement.addEventListener(
|
||||
'turbo:before-fetch-response',
|
||||
(event) => {
|
||||
const target = event.target as Element;
|
||||
const inputs = target.querySelectorAll(
|
||||
'input[type="file"][data-safari-temp-disabled]'
|
||||
);
|
||||
for (const input of inputs) {
|
||||
input.removeAttribute('data-safari-temp-disabled');
|
||||
input.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export {};
|
|
@ -1,26 +0,0 @@
|
|||
// iOS 11.3 Safari / macOS Safari 11.1 empty <input type="file"> XHR bug workaround.
|
||||
// This should work with every modern browser which supports ES5 (including IE9).
|
||||
// https://stackoverflow.com/questions/49614091/safari-11-1-ajax-xhr-form-submission-fails-when-inputtype-file-is-empty
|
||||
// https://github.com/rails/rails/issues/32440
|
||||
|
||||
document.addEventListener('ajax:before', function (e) {
|
||||
let inputs = e.target.querySelectorAll('input[type="file"]:not([disabled])');
|
||||
inputs.forEach(function (input) {
|
||||
if (input.files.length > 0) {
|
||||
return;
|
||||
}
|
||||
input.setAttribute('data-safari-temp-disabled', 'true');
|
||||
input.setAttribute('disabled', '');
|
||||
});
|
||||
});
|
||||
|
||||
// You should call this by yourself when you aborted an ajax request by stopping a event in ajax:before hook.
|
||||
document.addEventListener('ajax:beforeSend', function (e) {
|
||||
let inputs = e.target.querySelectorAll(
|
||||
'input[type="file"][data-safari-temp-disabled]'
|
||||
);
|
||||
inputs.forEach(function (input) {
|
||||
input.removeAttribute('data-safari-temp-disabled');
|
||||
input.removeAttribute('disabled');
|
||||
});
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
// For links and requests done through rails-ujs (mostly data-remote links),
|
||||
// redirect to the sign-in page when the server responds '401 Unauthorized'.
|
||||
document.addEventListener('ajax:error', (event) => {
|
||||
const [, , xhr] = event.detail;
|
||||
if (xhr && xhr.status == 401) {
|
||||
location.reload(); // reload whole page so Devise will redirect to sign-in
|
||||
}
|
||||
});
|
32
app/jobs/cron/backfill_siret_degraded_mode_job.rb
Normal file
32
app/jobs/cron/backfill_siret_degraded_mode_job.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
class Cron::BackfillSiretDegradedModeJob < Cron::CronJob
|
||||
self.schedule_expression = "every 2 hour"
|
||||
|
||||
def perform(*args)
|
||||
fix_etablissement_with_dossier
|
||||
fix_etablissement_with_champs
|
||||
end
|
||||
|
||||
def fix_etablissement_with_dossier
|
||||
Etablissement.joins(:dossier).where(adresse: nil).find_each do |etablissement|
|
||||
begin
|
||||
procedure_id = etablissement.dossier.procedure.id
|
||||
|
||||
APIEntrepriseService.update_etablissement_from_degraded_mode(etablissement, procedure_id)
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def fix_etablissement_with_champs
|
||||
Etablissement.joins(:champ).where(adresse: nil).find_each do |etablissement|
|
||||
begin
|
||||
procedure_id = etablissement.champ.procedure.id
|
||||
|
||||
APIEntrepriseService.update_etablissement_from_degraded_mode(etablissement, procedure_id)
|
||||
rescue => e
|
||||
Sentry.capture_exception(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,10 @@
|
|||
class Cron::Datagouv::ExportAndPublishDemarchesPubliquesJob < Cron::CronJob
|
||||
include DatagouvCronSchedulableConcern
|
||||
self.schedule_expression = "every month at 3:00"
|
||||
|
||||
def self.schedulable?
|
||||
false
|
||||
end
|
||||
|
||||
def perform(*args)
|
||||
gzip_filepath = [
|
||||
'tmp/',
|
||||
|
|
|
@ -54,7 +54,7 @@ class Administrateur < ApplicationRecord
|
|||
api_token = Administrateur.generate_unique_secure_token
|
||||
encrypted_token = BCrypt::Password.create(api_token)
|
||||
update(encrypted_token: encrypted_token)
|
||||
api_token
|
||||
APIToken.signe(id, api_token)
|
||||
end
|
||||
|
||||
def valid_api_token?(api_token)
|
||||
|
|
27
app/models/api_token.rb
Normal file
27
app/models/api_token.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class APIToken
|
||||
attr_reader :administrateur_id, :token
|
||||
|
||||
def initialize(token)
|
||||
@token = token
|
||||
verify!
|
||||
end
|
||||
|
||||
def administrateur?
|
||||
administrateur_id.present?
|
||||
end
|
||||
|
||||
def self.message_verifier
|
||||
Rails.application.message_verifier('api_v2_token')
|
||||
end
|
||||
|
||||
def self.signe(administrateur_id, token)
|
||||
message_verifier.generate([administrateur_id, token])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify!
|
||||
@administrateur_id, @token = self.class.message_verifier.verified(@token) || [nil, @token]
|
||||
rescue
|
||||
end
|
||||
end
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champ < ApplicationRecord
|
||||
belongs_to :dossier, inverse_of: false, touch: true, optional: false
|
||||
|
@ -48,6 +48,7 @@ class Champ < ApplicationRecord
|
|||
:exclude_from_export?,
|
||||
:exclude_from_view?,
|
||||
:repetition?,
|
||||
:block?,
|
||||
:dossier_link?,
|
||||
:titre_identite?,
|
||||
:header_section?,
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::AddressChamp < Champs::TextChamp
|
||||
def full_address?
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::AnnuaireEducationChamp < Champs::TextChamp
|
||||
def fetch_external_data?
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::CarteChamp < Champ
|
||||
# Default map location. Center of the World, ahm, France...
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::CheckboxChamp < Champs::YesNoChamp
|
||||
def true?
|
||||
|
@ -27,4 +27,8 @@ class Champs::CheckboxChamp < Champs::YesNoChamp
|
|||
def for_export
|
||||
true? ? 'on' : 'off'
|
||||
end
|
||||
|
||||
def mandatory_blank_and_visible?
|
||||
mandatory? && (blank? || !true?)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::CiviliteChamp < Champ
|
||||
def html_label?
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::CnafChamp < Champs::TextChamp
|
||||
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/cnaf-input-validation.middleware.ts
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::CommuneChamp < Champs::TextChamp
|
||||
store_accessor :value_json, :departement, :code_departement
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::DateChamp < Champ
|
||||
before_save :format_before_save
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::DatetimeChamp < Champ
|
||||
before_save :format_before_save
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::DecimalNumberChamp < Champ
|
||||
validates :value, numericality: {
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::DepartementChamp < Champs::TextChamp
|
||||
end
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::DgfipChamp < Champs::TextChamp
|
||||
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/dgfip-input-validation.middleware.ts
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::DossierLinkChamp < Champ
|
||||
end
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::DropDownListChamp < Champ
|
||||
THRESHOLD_NB_OPTIONS_AS_RADIO = 5
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::EmailChamp < Champs::TextChamp
|
||||
end
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: champs
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# data :jsonb
|
||||
# fetch_external_data_exceptions :string is an Array
|
||||
# private :boolean default(FALSE), not null
|
||||
# rebased_at :datetime
|
||||
# row :integer
|
||||
# type :string
|
||||
# value :string
|
||||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
#
|
||||
class Champs::EngagementChamp < Champs::CheckboxChamp
|
||||
end
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::ExplicationChamp < Champs::TextChamp
|
||||
def search_terms
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::HeaderSectionChamp < Champ
|
||||
def search_terms
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::IbanChamp < Champ
|
||||
validates_with IbanValidator
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::IntegerNumberChamp < Champ
|
||||
validates :value, numericality: {
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::LinkedDropDownListChamp < Champ
|
||||
delegate :primary_options, :secondary_options, to: 'type_de_champ.dynamic_type'
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::MesriChamp < Champs::TextChamp
|
||||
# see https://github.com/betagouv/api-particulier/blob/master/src/presentation/middlewares/mesri-input-validation.middleware.ts
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::MultipleDropDownListChamp < Champ
|
||||
before_save :format_before_save
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::NumberChamp < Champ
|
||||
end
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
# value_json :jsonb
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# dossier_id :integer not null
|
||||
# dossier_id :integer
|
||||
# etablissement_id :integer
|
||||
# external_id :string
|
||||
# parent_id :bigint
|
||||
# type_de_champ_id :integer not null
|
||||
# type_de_champ_id :integer
|
||||
#
|
||||
class Champs::PaysChamp < Champs::TextChamp
|
||||
def localized_value
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue