Merge branch 'main' into feature/add_rna_type_de_champs

This commit is contained in:
Damien Le Thiec 2022-10-05 12:38:24 +02:00 committed by GitHub
commit 0131a41266
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
253 changed files with 2393 additions and 1301 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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");
}
}

View file

@ -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;
}

View file

@ -7,10 +7,6 @@
}
}
.france-connect-help-link {
text-align: center;
}
a.france-connect-login-button {
display: inline-block;
height: 60px;

View file

@ -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 {

View file

@ -1,13 +0,0 @@
.pj {
th {
vertical-align: middle;
}
.dropdown-items a {
flex-direction: column;
}
.filename {
font-weight: bold;
}
}

View file

@ -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 {

View file

@ -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%;

View file

@ -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

View 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

View file

@ -0,0 +1,5 @@
en:
column: Column
value: Value
add_filter: Add filter

View file

@ -0,0 +1,5 @@
fr:
column: Colonne
value: Valeur
add_filter: Ajouter le filtre

View file

@ -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'

View 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

View file

@ -0,0 +1,2 @@
en:
show_notified_first: Show files with notification first

View file

@ -0,0 +1,2 @@
fr:
show_notified_first: Remonter les dossiers avec une notification

View file

@ -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'

View 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

View file

@ -0,0 +1,4 @@
---
en:
close_notice: Hide message

View file

@ -0,0 +1,4 @@
---
fr:
close_notice: Masquer le message

View file

@ -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')

View file

@ -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 = []

View file

@ -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 didentité (uniquement le recto), passeport, titre de séjour ou autre justificatif didentité. 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)

View file

@ -1,2 +0,0 @@
class EditableChamp::EngagementComponent < EditableChamp::EditableChampBaseComponent
end

View file

@ -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

View file

@ -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

View file

@ -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)
{

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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} »."

View file

@ -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} »."

View file

@ -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

View file

@ -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)

View file

@ -1,20 +1,49 @@
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
end
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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 nont pas pu encore être vérifiées : il nest 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 nest pas possible de le passer #{dossier_display_state(target_state, lower: true)}."
end

View file

@ -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])

View file

@ -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

View file

@ -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?)

View file

@ -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

View file

@ -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)

View file

@ -1450,7 +1450,7 @@ type Entreprise {
"""
capitalSocial: BigInt
codeEffectifEntreprise: String
dateCreation: ISO8601Date!
dateCreation: ISO8601Date
"""
effectif moyen dune année
@ -2135,11 +2135,6 @@ enum TypeDeChamp {
"""
email
"""
Engagement
"""
engagement
"""
Explication
"""

View file

@ -18,7 +18,7 @@ module Types
field :options, [String], "List des options dun 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

View file

@ -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

View file

@ -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 dune 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

View file

@ -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 ||

View file

@ -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

View file

@ -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

View file

@ -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;
}
}

View file

@ -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');
}
}

View file

@ -0,0 +1,8 @@
import { ApplicationController } from './application_controller';
export class CheckboxController extends ApplicationController {
onChange() {
const form = this.element as HTMLFormElement;
form.requestSubmit();
}
}

View 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();
}
}

View 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');
}
}

View file

@ -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

View file

@ -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';

View file

@ -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'));
}

View file

@ -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);

View file

@ -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 });

View 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 {};

View file

@ -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');
});
});

View file

@ -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
}
});

View 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

View file

@ -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/',

View file

@ -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
View 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

View file

@ -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?,

View file

@ -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?

View file

@ -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?

View file

@ -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...

View file

@ -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

View file

@ -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?

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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: {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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: {

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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