Merge branch 'demarches-simplifiees:main' into poc-self_hosted_runners
This commit is contained in:
commit
48190b85dc
66 changed files with 545 additions and 295 deletions
|
@ -150,7 +150,7 @@ linters:
|
||||||
properties: {}
|
properties: {}
|
||||||
|
|
||||||
PseudoElement:
|
PseudoElement:
|
||||||
enabled: true
|
enabled: false # otherwise rules on ::marker fails
|
||||||
|
|
||||||
# To enable later
|
# To enable later
|
||||||
QualifyingElement:
|
QualifyingElement:
|
||||||
|
|
|
@ -42,11 +42,6 @@ $dossier-actions-bar-border-width: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.characters-count {
|
|
||||||
position: relative;
|
|
||||||
top: -1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
background-color: #f9b91666;
|
background-color: #f9b91666;
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
|
||||||
|
// overwrite DSFR style for SimpleFormatComponent, some user use markdown with
|
||||||
|
// ordered list having paragraph between list item
|
||||||
|
.fr-ol-content--override {
|
||||||
|
list-style-type: decimal;
|
||||||
|
|
||||||
|
li::marker {
|
||||||
|
content: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// override default transparent background on inputs & font-size to 16px by default
|
// override default transparent background on inputs & font-size to 16px by default
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea,
|
||||||
|
|
|
@ -20,6 +20,33 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fr-input-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-2 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 2px solid var(--border-default-grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-2,
|
||||||
|
.section-3 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-4,
|
||||||
|
.section-5,
|
||||||
|
.section-6 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
@ -36,7 +63,7 @@
|
||||||
|
|
||||||
|
|
||||||
.mandatory {
|
.mandatory {
|
||||||
color: $dark-red;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
label:not(.fr-label),
|
label:not(.fr-label),
|
||||||
|
@ -97,7 +124,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.editable-champ-checkbox {
|
&.editable-champ-checkbox {
|
||||||
p,
|
|
||||||
label {
|
label {
|
||||||
padding-left: 28px;
|
padding-left: 28px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
@ -147,7 +173,7 @@
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -184,17 +210,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=text],
|
.fr-label {
|
||||||
input[type=email],
|
// la description d'un champ peut contenir du markup (markdown->html),
|
||||||
|
// on herite donc la fontsize/mrgin/padding du fr-hint-text
|
||||||
|
.fr-hint-text > * {
|
||||||
|
font-size: inherit;
|
||||||
|
margin: inherit;
|
||||||
|
padding: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
input[type=password],
|
input[type=password],
|
||||||
input[type=date],
|
|
||||||
input[type=datetime-local],
|
|
||||||
input[type=number],
|
|
||||||
input[type=tel],
|
|
||||||
textarea,
|
|
||||||
select {
|
select {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: 0;
|
||||||
|
|
||||||
&.small-margin {
|
&.small-margin {
|
||||||
margin-bottom: $default-spacer;
|
margin-bottom: $default-spacer;
|
||||||
|
@ -218,13 +247,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=text]:not([data-address='true']),
|
input[type=text]:not([data-address='true']),
|
||||||
input[type=email],
|
|
||||||
input[type=password],
|
|
||||||
input[type=date],
|
|
||||||
input[type=number],
|
|
||||||
input[type=tel],
|
|
||||||
input[type=datetime-local],
|
|
||||||
textarea,
|
|
||||||
select {
|
select {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: solid 1px $border-grey;
|
border: solid 1px $border-grey;
|
||||||
|
@ -266,8 +288,8 @@
|
||||||
input[type=date],
|
input[type=date],
|
||||||
input[type=number],
|
input[type=number],
|
||||||
input[type=datetime-local],
|
input[type=datetime-local],
|
||||||
input[type=tel],
|
textarea,
|
||||||
textarea {
|
input[type=tel], {
|
||||||
@media (max-width: $two-columns-breakpoint) {
|
@media (max-width: $two-columns-breakpoint) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -326,7 +348,7 @@
|
||||||
|
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-reach-combobox-input] {
|
[data-reach-combobox-input] {
|
||||||
|
@ -413,16 +435,6 @@
|
||||||
color: $dark-grey;
|
color: $dark-grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
.datetime {
|
|
||||||
input[type=date] {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subsection {
|
.header-subsection {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
color: $blue-france-500;
|
color: $blue-france-500;
|
||||||
|
@ -445,20 +457,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.siret-info {
|
|
||||||
margin-top: -$default-fields-spacer;
|
|
||||||
margin-bottom: $default-fields-spacer;
|
|
||||||
// Ensure the bottom-margin is not collapsed when the element is empty
|
|
||||||
min-height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rna-info {
|
|
||||||
margin-top: -$default-fields-spacer;
|
|
||||||
margin-bottom: $default-fields-spacer;
|
|
||||||
// Ensure the bottom-margin is not collapsed when the element is empty
|
|
||||||
min-height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-wrapper {
|
.send-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -532,12 +530,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-react-component-value]:not([data-react-component-value^="ComboMultiple"]) {
|
|
||||||
[data-reach-combobox-input]:not(.no-margin) {
|
|
||||||
margin-bottom: $default-fields-spacer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-react-component-value^="ComboMultiple"] {
|
[data-react-component-value^="ComboMultiple"] {
|
||||||
margin-bottom: $default-fields-spacer;
|
margin-bottom: $default-fields-spacer;
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mandatory {
|
.manager-mandatory {
|
||||||
color: #A10005;
|
color: #A10005;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.attachment.fr-upload-group{ { id: attachment ? dom_id(attachment, :edit) : nil, class: class_names("fr-mb-2w": !(as_multiple? && downloadable?)) }.compact, **replace_controller_attributes }
|
.attachment.fr-upload-group{ { id: attachment ? dom_id(attachment, :edit) : nil, class: class_names("fr-mb-1w": !(as_multiple? && downloadable?)) }.compact, **replace_controller_attributes }
|
||||||
- if persisted?
|
- if persisted?
|
||||||
%div{ id: dom_id(attachment, :persisted_row) }
|
%div{ id: dom_id(attachment, :persisted_row) }
|
||||||
.flex.flex-gap-2{ class: class_names("attachment-error": attachment.virus_scanner_error?) }
|
.flex.flex-gap-2{ class: class_names("attachment-error": attachment.virus_scanner_error?) }
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
.fr-mb-4w.attachment-multiple{ class: class_names("fr-downloads-group": view_as == :download, "destroyable": user_can_destroy?), **replace_controller_attributes }
|
.attachment-multiple{ class: class_names("fr-downloads-group": view_as == :download, "destroyable": user_can_destroy?), **replace_controller_attributes }
|
||||||
= template
|
= template
|
||||||
|
|
||||||
%ul
|
- if @attachments.size >= 1
|
||||||
|
%ul.fr-my-1v
|
||||||
- each_attachment do |attachment, index|
|
- each_attachment do |attachment, index|
|
||||||
%li{ id: dom_id(attachment) }
|
%li{ id: dom_id(attachment) }
|
||||||
= render Attachment::EditComponent.new(champ:, attached_file:, attachment:, index:, as_multiple: true, view_as:, user_can_destroy:, user_can_replace:, form_object_name:)
|
= render Attachment::EditComponent.new(champ:, attached_file:, attachment:, index:, as_multiple: true, view_as:, user_can_destroy:, user_can_replace:, form_object_name:)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
class Dsfr::InputComponent < ApplicationComponent
|
class Dsfr::InputComponent < ApplicationComponent
|
||||||
|
include Dsfr::InputErrorable
|
||||||
|
|
||||||
delegate :object, to: :@form
|
delegate :object, to: :@form
|
||||||
delegate :errors, to: :object
|
delegate :errors, to: :object
|
||||||
|
|
||||||
|
@ -18,10 +20,10 @@ class Dsfr::InputComponent < ApplicationComponent
|
||||||
# and and valid on input only if another input is invalid
|
# and and valid on input only if another input is invalid
|
||||||
def input_group_opts
|
def input_group_opts
|
||||||
opts = {
|
opts = {
|
||||||
class: class_names('fr-input-group': true,
|
class: class_names({
|
||||||
'fr-password': password?,
|
'fr-input-group': true,
|
||||||
"fr-input-group--error": errors_on_attribute?,
|
'fr-password': password?
|
||||||
"fr-input-group--valid": !errors_on_attribute? && errors_on_another_attribute?)
|
}.merge(input_group_error_class_names))
|
||||||
}
|
}
|
||||||
if email?
|
if email?
|
||||||
opts[:data] = { controller: 'email-input' }
|
opts[:data] = { controller: 'email-input' }
|
||||||
|
@ -33,38 +35,7 @@ class Dsfr::InputComponent < ApplicationComponent
|
||||||
{ class: class_names('fr-label': true, 'fr-password__label': password?) }
|
{ class: class_names('fr-label': true, 'fr-password__label': password?) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def input_opts
|
|
||||||
@opts[:class] = class_names(map_array_to_hash_with_true(@opts[:class])
|
|
||||||
.merge('fr-password__input': password?,
|
|
||||||
'fr-input': true,
|
|
||||||
'fr-mb-0': true,
|
|
||||||
'fr-input--error': errors_on_attribute?))
|
|
||||||
|
|
||||||
if errors_on_attribute? || describedby?
|
|
||||||
@opts.deep_merge!(aria: {
|
|
||||||
describedby: describedby_id,
|
|
||||||
invalid: errors_on_attribute?
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
if @required
|
|
||||||
@opts[:required] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if email?
|
|
||||||
@opts.deep_merge!(data: {
|
|
||||||
action: "blur->email-input#checkEmail",
|
|
||||||
'email-input-target': 'input'
|
|
||||||
})
|
|
||||||
end
|
|
||||||
@opts
|
|
||||||
end
|
|
||||||
|
|
||||||
# errors helpers
|
# errors helpers
|
||||||
def errors_on_attribute?
|
|
||||||
errors.has_key?(attribute_or_rich_body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_messages
|
def error_messages
|
||||||
errors.full_messages_for(attribute_or_rich_body)
|
errors.full_messages_for(attribute_or_rich_body)
|
||||||
end
|
end
|
||||||
|
@ -78,10 +49,6 @@ class Dsfr::InputComponent < ApplicationComponent
|
||||||
object.class.human_attribute_name(@attribute)
|
object.class.human_attribute_name(@attribute)
|
||||||
end
|
end
|
||||||
|
|
||||||
def hint
|
|
||||||
I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
|
|
||||||
end
|
|
||||||
|
|
||||||
# kind of input helpers
|
# kind of input helpers
|
||||||
def password?
|
def password?
|
||||||
@input_type == :password_field
|
@input_type == :password_field
|
||||||
|
@ -96,27 +63,4 @@ class Dsfr::InputComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def hint?
|
|
||||||
I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def errors_on_another_attribute?
|
|
||||||
!errors.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
# lookup for edge case from `form.rich_text_area`
|
|
||||||
# rich text uses _rich_#{attribute}, but it is saved on #{attribute}, as well as error messages
|
|
||||||
def attribute_or_rich_body
|
|
||||||
case @input_type
|
|
||||||
when :rich_text_area
|
|
||||||
@attribute.to_s.sub(/\Arich_/, '').to_sym
|
|
||||||
else
|
|
||||||
@attribute
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def map_array_to_hash_with_true(array_or_string_or_nil)
|
|
||||||
Array(array_or_string_or_nil).to_h { [_1, true] }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
108
app/components/dsfr/input_errorable.rb
Normal file
108
app/components/dsfr/input_errorable.rb
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
module Dsfr
|
||||||
|
module InputErrorable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
included do
|
||||||
|
delegate :object, to: :@form
|
||||||
|
delegate :errors, to: :object
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# lookup for edge case from `form.rich_text_area`
|
||||||
|
# rich text uses _rich_#{attribute}, but it is saved on #{attribute}, as well as error messages
|
||||||
|
def attribute_or_rich_body
|
||||||
|
case @input_type
|
||||||
|
when :rich_text_area
|
||||||
|
@attribute.to_s.sub(/\Arich_/, '').to_sym
|
||||||
|
else
|
||||||
|
@attribute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_group_error_class_names
|
||||||
|
{
|
||||||
|
"fr-input-group--error": errors_on_attribute?,
|
||||||
|
"fr-input-group--valid": !errors_on_attribute? && errors_on_another_attribute?
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_error_class_names
|
||||||
|
{ 'fr-input--error': errors_on_attribute? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_error_opts
|
||||||
|
{
|
||||||
|
aria: {
|
||||||
|
describedby: describedby_id,
|
||||||
|
invalid: errors_on_attribute?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_opts(other_opts = {})
|
||||||
|
@opts = @opts.deep_merge!(other_opts)
|
||||||
|
@opts[:class] = class_names(map_array_to_hash_with_true(@opts[:class])
|
||||||
|
.merge({
|
||||||
|
'fr-password__input': password?,
|
||||||
|
'fr-input': true,
|
||||||
|
'fr-mb-0': true
|
||||||
|
}.merge(input_error_class_names)))
|
||||||
|
|
||||||
|
if errors_on_attribute?
|
||||||
|
@opts.deep_merge!(aria: {
|
||||||
|
describedby: describedby_id,
|
||||||
|
invalid: errors_on_attribute?
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if @required
|
||||||
|
@opts[:required] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if email?
|
||||||
|
@opts.deep_merge!(data: {
|
||||||
|
action: "blur->email-input#checkEmail",
|
||||||
|
'email-input-target': 'input'
|
||||||
|
})
|
||||||
|
end
|
||||||
|
@opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def describedby_id
|
||||||
|
dom_id(@champ, :error_full_messages)
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors_on_another_attribute?
|
||||||
|
!errors.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors_on_attribute?
|
||||||
|
errors.has_key?(attribute_or_rich_body)
|
||||||
|
end
|
||||||
|
|
||||||
|
# errors helpers
|
||||||
|
def error_full_messages
|
||||||
|
errors.full_messages_for(attribute_or_rich_body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def map_array_to_hash_with_true(array_or_string_or_nil)
|
||||||
|
Array(array_or_string_or_nil).to_h { [_1, true] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def hint
|
||||||
|
I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def password?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def email?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def hint?
|
||||||
|
I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,8 @@
|
||||||
class EditableChamp::ChampLabelComponent < ApplicationComponent
|
class EditableChamp::ChampLabelComponent < ApplicationComponent
|
||||||
|
include Dsfr::InputErrorable
|
||||||
|
|
||||||
def initialize(form:, champ:, seen_at: nil)
|
def initialize(form:, champ:, seen_at: nil)
|
||||||
@form, @champ, @seen_at = form, champ, seen_at
|
@form, @champ, @seen_at = form, champ, seen_at
|
||||||
|
@attribute = :value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
= # we do this trick because some html elements should use 'label' and some should be plain paragraphs
|
= # we do this trick because some html elements should use 'label' and some should be plain paragraphs
|
||||||
- if @champ.html_label?
|
- if @champ.html_label?
|
||||||
= @form.label @champ.main_value_name, id: @champ.labelledby_id, for: @champ.input_id do
|
= @form.label @champ.main_value_name, id: @champ.labelledby_id, for: @champ.input_id, class: 'fr-label' do
|
||||||
- render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
|
- render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at
|
||||||
- else
|
- else
|
||||||
.form-label.mb-4{ id: @champ.labelledby_id }
|
.fr-label.mb-4{ id: @champ.labelledby_id }
|
||||||
= render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
|
= render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at
|
||||||
|
|
||||||
|
|
||||||
- if @champ.description.present?
|
|
||||||
.notice{ id: @champ.describedby_id }= render SimpleFormatComponent.new(@champ.description, allow_a: true)
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
class EditableChamp::ChampLabelContentComponent < ApplicationComponent
|
class EditableChamp::ChampLabelContentComponent < ApplicationComponent
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
|
include Dsfr::InputErrorable
|
||||||
|
|
||||||
def initialize(champ:, seen_at: nil)
|
def initialize(form:, champ:, seen_at: nil)
|
||||||
@champ, @seen_at = champ, seen_at
|
@form, @champ, @seen_at = form, champ, seen_at
|
||||||
|
@attribute = :value
|
||||||
end
|
end
|
||||||
|
|
||||||
def highlight_if_unseen_class
|
def highlight_if_unseen_class
|
||||||
|
|
|
@ -4,3 +4,5 @@ en:
|
||||||
modified_at: "modified on %{datetime}"
|
modified_at: "modified on %{datetime}"
|
||||||
check_content_rebased: The type of this field or its description has been modified by the administration. Check its content.
|
check_content_rebased: The type of this field or its description has been modified by the administration. Check its content.
|
||||||
optional_champ: (optional)
|
optional_champ: (optional)
|
||||||
|
recommended_size: The recommended maximum size is %{size} characters.
|
||||||
|
titre_identite_notice: National identity card (front side only), passport, residency permit or other proof of identity.
|
||||||
|
|
|
@ -4,3 +4,5 @@ fr:
|
||||||
modified_at: "modifié le %{datetime}"
|
modified_at: "modifié le %{datetime}"
|
||||||
check_content_rebased: Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.
|
check_content_rebased: Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.
|
||||||
optional_champ: (facultatif)
|
optional_champ: (facultatif)
|
||||||
|
recommended_size: La taille maximale conseillée est de %{size} caractères.
|
||||||
|
titre_identite_notice: Carte nationale d’identité (uniquement le recto), passeport, titre de séjour ou autre justificatif d’identité.
|
||||||
|
|
|
@ -18,3 +18,15 @@
|
||||||
- if @champ.rebased_at.present? && @champ.rebased_at > (@seen_at || @champ.updated_at) && current_user.owns_or_invite?(@champ.dossier)
|
- if @champ.rebased_at.present? && @champ.rebased_at > (@seen_at || @champ.updated_at) && current_user.owns_or_invite?(@champ.dossier)
|
||||||
%span.updated-at.highlighted
|
%span.updated-at.highlighted
|
||||||
= t('.check_content_rebased')
|
= t('.check_content_rebased')
|
||||||
|
|
||||||
|
- if @champ.titre_identite?
|
||||||
|
%span.fr-hint-text= t('.titre_identite_notice')
|
||||||
|
|
||||||
|
- if hint?
|
||||||
|
%span.fr-hint-text= hint
|
||||||
|
|
||||||
|
- if @champ.description.present?
|
||||||
|
%span.fr-hint-text{ id: @champ.describedby_id }= render SimpleFormatComponent.new(@champ.description, allow_a: true)
|
||||||
|
|
||||||
|
- if @champ.textarea?
|
||||||
|
%span.sr-only= t('.recommended_size', size: @champ.character_limit_base)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
= @form.date_field :value,
|
= @form.date_field :value,
|
||||||
id: @champ.input_id,
|
input_opts(id: @champ.input_id,
|
||||||
aria: { describedby: @champ.describedby_id },
|
aria: { describedby: @champ.describedby_id },
|
||||||
value: @champ.value,
|
value: @champ.value,
|
||||||
required: @champ.required?,
|
required: @champ.required?,
|
||||||
placeholder: 'aaaa-mm-jj',
|
placeholder: 'aaaa-mm-jj', class: "width-33-desktop")
|
||||||
class: "width-33-desktop"
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
.datetime
|
= @form.datetime_field(:value, input_opts(value: formatted_value_for_datetime_locale, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, data: { controller: 'datetime' }))
|
||||||
= @form.datetime_field(:value, value: formatted_value_for_datetime_locale, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, data: { controller: 'datetime' })
|
|
||||||
|
|
|
@ -1,6 +1 @@
|
||||||
= @form.number_field :value,
|
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, step: :any, placeholder: "3.14", required: @champ.required?))
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
step: :any,
|
|
||||||
placeholder: "3.14",
|
|
||||||
required: @champ.required?
|
|
||||||
|
|
|
@ -1,14 +1,4 @@
|
||||||
.dossier-link
|
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, inputmode: :numeric, min: 1, pattern: "[0-9]{1,12}", placeholder: "Numéro de dossier", autocomplete: 'off', required: @champ.required?, class: "width-33-desktop #{@champ.blank? ? '' : 'small-margin'}"))
|
||||||
= @form.text_field :value,
|
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
inputmode: :numeric,
|
|
||||||
min: 1,
|
|
||||||
pattern: "[0-9]{1,12}",
|
|
||||||
placeholder: "Numéro de dossier",
|
|
||||||
autocomplete: 'off',
|
|
||||||
required: @champ.required?,
|
|
||||||
class: "width-33-desktop #{@champ.blank? ? '' : 'small-margin'}"
|
|
||||||
|
|
||||||
- if !@champ.blank?
|
- if !@champ.blank?
|
||||||
- if dossier.blank?
|
- if dossier.blank?
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
class EditableChamp::EditableChampBaseComponent < ApplicationComponent
|
class EditableChamp::EditableChampBaseComponent < ApplicationComponent
|
||||||
def initialize(form:, champ:, seen_at: nil)
|
include Dsfr::InputErrorable
|
||||||
@form, @champ, @seen_at = form, champ, seen_at
|
|
||||||
|
def initialize(form:, champ:, seen_at: nil, opts: {})
|
||||||
|
@form, @champ, @seen_at, @opts = form, champ, seen_at, opts
|
||||||
|
@attribute = :value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
class EditableChamp::EditableChampComponent < ApplicationComponent
|
class EditableChamp::EditableChampComponent < ApplicationComponent
|
||||||
|
include Dsfr::InputErrorable
|
||||||
|
|
||||||
def initialize(form:, champ:, seen_at: nil)
|
def initialize(form:, champ:, seen_at: nil)
|
||||||
@form, @champ, @seen_at = form, champ, seen_at
|
@form, @champ, @seen_at = form, champ, seen_at
|
||||||
|
@attribute = :value
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -21,8 +24,12 @@ class EditableChamp::EditableChampComponent < ApplicationComponent
|
||||||
def html_options
|
def html_options
|
||||||
{
|
{
|
||||||
class: class_names(
|
class: class_names(
|
||||||
|
{
|
||||||
|
'editable-champ': true,
|
||||||
"editable-champ-#{@champ.type_champ}": true,
|
"editable-champ-#{@champ.type_champ}": true,
|
||||||
"hidden": !@champ.visible?
|
"hidden": !@champ.visible?,
|
||||||
|
"fr-input-group": true
|
||||||
|
}.merge(input_group_error_class_names)
|
||||||
),
|
),
|
||||||
id: @champ.input_group_id,
|
id: @champ.input_group_id,
|
||||||
data: { controller: stimulus_controller, **data_dependent_conditions, **stimulus_values }
|
data: { controller: stimulus_controller, **data_dependent_conditions, **stimulus_values }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
---
|
---
|
||||||
en:
|
en:
|
||||||
titre_identite_notice: National identity card (front side only), passport, residency permit or other proof of identity.
|
titre_identite_notice: National identity card (front side only), passport, residency permit or other proof of identity.
|
||||||
datetime_notice: "Expected format : dd/mm/yyyy hh:mm"
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
---
|
---
|
||||||
fr:
|
fr:
|
||||||
titre_identite_notice: Carte nationale d’identité (uniquement le recto), passeport, titre de séjour ou autre justificatif d’identité.
|
titre_identite_notice: Carte nationale d’identité (uniquement le recto), passeport, titre de séjour ou autre justificatif d’identité.
|
||||||
datetime_notice: "Format attendu : jj/mm/aaaa hh:mm"
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
.editable-champ{ html_options }
|
= content_tag(:div, html_options) do
|
||||||
- if has_label?(@champ)
|
- if has_label?(@champ)
|
||||||
= render EditableChamp::ChampLabelComponent.new form: @form, champ: @champ, seen_at: @seen_at
|
= render EditableChamp::ChampLabelComponent.new form: @form, champ: @champ, seen_at: @seen_at
|
||||||
- if @champ.titre_identite?
|
= render component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
|
||||||
%p.notice= t('.titre_identite_notice')
|
|
||||||
- elsif @champ.datetime?
|
- if errors_on_attribute?
|
||||||
%p.notice= t('.datetime_notice')
|
- if error_full_messages.size == 1
|
||||||
|
%p.fr-error-text{ id: describedby_id }= error_full_messages.first
|
||||||
|
- else
|
||||||
|
.fr-error-text{ id: describedby_id }
|
||||||
|
%ul.list-style-type-none.fr-pl-0
|
||||||
|
- error_full_messages.each do |error_message|
|
||||||
|
%li= error_message
|
||||||
|
|
||||||
= @form.hidden_field :id, value: @champ.id
|
= @form.hidden_field :id, value: @champ.id
|
||||||
= render component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
|
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
class EditableChamp::EmailComponent < EditableChamp::EditableChampBaseComponent
|
class EditableChamp::EmailComponent < EditableChamp::EditableChampBaseComponent
|
||||||
|
def email?
|
||||||
|
true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1 @@
|
||||||
= @form.email_field :value,
|
= @form.email_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), required: @champ.required?))
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
placeholder: t(".placeholder"),
|
|
||||||
required: @champ.required?
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.explication
|
%p.fr-info-text
|
||||||
= raison_sociale_or_name(@etablissement)
|
= raison_sociale_or_name(@etablissement)
|
||||||
= @etablissement.entreprise_forme_juridique
|
= @etablissement.entreprise_forme_juridique
|
||||||
- if @etablissement.entreprise_capital_social.present?
|
- if @etablissement.entreprise_capital_social.present?
|
||||||
|
|
|
@ -1,8 +1 @@
|
||||||
= @form.text_field :value,
|
= @form.text_field(:value, input_opts(id: @champ.input_id, placeholder: t(".placeholder"), required: @champ.required?, aria: { describedby: @champ.describedby_id }, data: { controller: 'format', format: 'iban' }, class: "width-66-desktop", maxlength: 34 + 9)) # count space separator of 4 digits-groups
|
||||||
id: @champ.input_id,
|
|
||||||
placeholder: t(".placeholder"),
|
|
||||||
required: @champ.required?,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
data: { controller: 'format', format: 'iban' },
|
|
||||||
class: "width-66-desktop",
|
|
||||||
maxlength: 34 + 9 # count space separator of 4 digits-groups
|
|
||||||
|
|
|
@ -1,5 +1 @@
|
||||||
= @form.number_field :value,
|
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: 5, required: @champ.required?))
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
placeholder: 5,
|
|
||||||
required: @champ.required?
|
|
||||||
|
|
|
@ -1,5 +1 @@
|
||||||
= @form.number_field :value,
|
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: @champ.libelle, required: @champ.required?))
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
placeholder: @champ.libelle,
|
|
||||||
required: @champ.required?
|
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
-# Allowed @formats:
|
-# Allowed @formats:
|
||||||
-# very light validation is made client-side
|
-# very light validation is made client-side
|
||||||
-# stronger validation is made server-side
|
-# stronger validation is made server-side
|
||||||
= @form.phone_field :value,
|
= @form.phone_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), required: @champ.required?, pattern: "[^a-z^A-Z]+"))
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
placeholder: t(".placeholder"),
|
|
||||||
required: @champ.required?,
|
|
||||||
pattern: "[^a-z^A-Z]+"
|
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
= @form.text_field :value,
|
= @form.text_field(:value, input_opts( id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.data.blank?, turbo_input_url_value: champs_rna_path(@champ.id) }, required: @champ.required?, pattern: "W[0-9]{9}", title: t(".title"), class: "width-33-desktop", maxlength: 10))
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
placeholder: t(".placeholder"),
|
|
||||||
data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.data.blank?, turbo_input_url_value: champs_rna_path(@champ.id) },
|
|
||||||
required: @champ.required?,
|
|
||||||
pattern: "W[0-9]{9}",
|
|
||||||
title: t(".title"),
|
|
||||||
class: "width-33-desktop",
|
|
||||||
maxlength: 10
|
|
||||||
.rna-info{ id: dom_id(@champ, :rna_info) }
|
.rna-info{ id: dom_id(@champ, :rna_info) }
|
||||||
= render 'shared/champs/rna/association', champ: @champ, error: nil
|
= render 'shared/champs/rna/association', champ: @champ, error: nil
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
= @form.text_field :value,
|
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.etablissement.blank?, turbo_input_url_value: champs_siret_path(@champ.id) }, required: @champ.required?, pattern: "[0-9]{14}", title: t(".title"), class: "width-33-desktop", maxlength: 14))
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
placeholder: t(".placeholder"),
|
|
||||||
data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.etablissement.blank?, turbo_input_url_value: champs_siret_path(@champ.id) },
|
|
||||||
required: @champ.required?,
|
|
||||||
pattern: "[0-9]{14}",
|
|
||||||
title: t(".title"),
|
|
||||||
class: "width-33-desktop",
|
|
||||||
maxlength: 14
|
|
||||||
.spinner.right.hidden
|
.spinner.right.hidden
|
||||||
.siret-info{ id: dom_id(@champ, :siret_info) }
|
.siret-info{ id: dom_id(@champ, :siret_info) }
|
||||||
- if @champ.etablissement.present?
|
- if @champ.etablissement.present?
|
||||||
|
|
|
@ -1,4 +1 @@
|
||||||
= @form.text_field :value,
|
= @form.text_field(:value, input_opts(id: @champ.input_id, required: @champ.required?, aria: { describedby: @champ.describedby_id }))
|
||||||
id: @champ.input_id,
|
|
||||||
required: @champ.required?,
|
|
||||||
aria: { describedby: @champ.describedby_id }
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
en:
|
en:
|
||||||
remaining_characters: You have %{remaining_words} characters remaining.
|
remaining_characters: You have %{remaining_words} characters remaining.
|
||||||
excess_characters: You have %{excess_words} characters too many.
|
excess_characters: You have %{excess_words} characters too many.
|
||||||
recommended_size: The recommended maximum size is %{size} characters.
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
fr:
|
fr:
|
||||||
remaining_characters: Il vous reste %{remaining_words} caractères.
|
remaining_characters: Il vous reste %{remaining_words} caractères.
|
||||||
excess_characters: Vous avez dépassé la taille conseillée de %{excess_words} caractères. Réduire le nombre de caractères.
|
excess_characters: Vous avez dépassé la taille conseillée de %{excess_words} caractères. Réduire le nombre de caractères.
|
||||||
recommended_size: La taille maximale conseillée est de %{size} caractères.
|
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
%span.sr-only= t('.recommended_size', size: @champ.character_limit_base)
|
~ @form.text_area(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, rows: 6, required: @champ.required?, value: html_to_string(@champ.value)))
|
||||||
|
|
||||||
~ @form.text_area :value,
|
|
||||||
id: @champ.input_id,
|
|
||||||
aria: { describedby: @champ.describedby_id },
|
|
||||||
rows: 6,
|
|
||||||
required: @champ.required?,
|
|
||||||
value: html_to_string(@champ.value)
|
|
||||||
|
|
||||||
- if @champ.character_limit_info?
|
- if @champ.character_limit_info?
|
||||||
%span.fr-icon-information-fill.fr-text-default--info.characters-count
|
%p.fr-info-text
|
||||||
= t('.remaining_characters', remaining_words: @champ.remaining_characters)
|
= t('.remaining_characters', remaining_words: @champ.remaining_characters)
|
||||||
|
|
||||||
- if @champ.character_limit_warning?
|
- if @champ.character_limit_warning?
|
||||||
%span.fr-icon-warning-fill.fr-text-default--warning.characters-count
|
%p.fr-icon--sm.fr-mt-4v.fr-mb-0.fr-hint-text.fr-icon-warning-fill.fr-text-default--warning.characters-count
|
||||||
= t('.excess_characters', excess_words: @champ.excess_characters)
|
= t('.excess_characters', excess_words: @champ.excess_characters)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
class SimpleFormatComponent < ApplicationComponent
|
class SimpleFormatComponent < ApplicationComponent
|
||||||
# see: https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
|
# see: https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
|
||||||
REDCARPET_EXTENSIONS = {
|
REDCARPET_EXTENSIONS = {
|
||||||
no_intra_emphasis: false,
|
no_intra_emphasis: true,
|
||||||
|
disable_indented_code_blocks: true,
|
||||||
|
space_after_headers: true,
|
||||||
tables: false,
|
tables: false,
|
||||||
fenced_code_blocks: false,
|
fenced_code_blocks: false,
|
||||||
autolink: false,
|
autolink: false,
|
||||||
disable_indented_code_blocks: false,
|
|
||||||
strikethrough: false,
|
strikethrough: false,
|
||||||
lax_spacing: false,
|
lax_spacing: false,
|
||||||
space_after_headers: false,
|
|
||||||
superscript: false,
|
superscript: false,
|
||||||
underline: false,
|
underline: false,
|
||||||
highlight: false,
|
highlight: false,
|
||||||
|
@ -27,18 +27,31 @@ class SimpleFormatComponent < ApplicationComponent
|
||||||
def initialize(text, allow_a: true, class_names_map: {})
|
def initialize(text, allow_a: true, class_names_map: {})
|
||||||
@allow_a = allow_a
|
@allow_a = allow_a
|
||||||
|
|
||||||
@text = (text || "").gsub(/\R/, "\n\n") # force double \n otherwise a single one won't split paragraph
|
list_item = false
|
||||||
.split("\n\n")
|
lines = (text || "")
|
||||||
|
.lines
|
||||||
.map(&:lstrip) # this block prevent redcarpet to consider " text" as block code by lstriping
|
.map(&:lstrip) # this block prevent redcarpet to consider " text" as block code by lstriping
|
||||||
.join("\n\n")
|
|
||||||
.gsub(EMAIL_IN_TEXT_REGEX) { _1.gsub('_', '\\_') } # Workaround for redcarpet bug on autolink email having _. Cf tests
|
|
||||||
|
|
||||||
if !@allow_a
|
@text = lines.map do |line|
|
||||||
@text = @text.gsub(SIMPLE_URL_REGEX) { _1.gsub('_', '\\_') } # Escape underscores in URLs
|
item_number = line.match(/\A(\d+)\./)
|
||||||
|
if item_number.present?
|
||||||
|
list_item = true
|
||||||
|
"\n" + line + "[value:#{item_number[1]}]"
|
||||||
|
elsif line.match?(/\A[-*+]\s/)
|
||||||
|
list_item = true
|
||||||
|
"\n" + line
|
||||||
|
elsif line == ''
|
||||||
|
list_item = false
|
||||||
|
"\n" + line
|
||||||
|
elsif list_item
|
||||||
|
line
|
||||||
|
else
|
||||||
|
"\n" + line
|
||||||
end
|
end
|
||||||
|
end.join.lstrip
|
||||||
|
|
||||||
@renderer = Redcarpet::Markdown.new(
|
@renderer = Redcarpet::Markdown.new(
|
||||||
Redcarpet::BareRenderer.new(class_names_map:),
|
Redcarpet::BareRenderer.new(class_names_map: { list: 'fr-ol-content--override' }),
|
||||||
REDCARPET_EXTENSIONS.merge(autolink: @allow_a)
|
REDCARPET_EXTENSIONS.merge(autolink: @allow_a)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -52,6 +65,6 @@ class SimpleFormatComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
def attributes
|
def attributes
|
||||||
['target', 'rel', 'href', 'class', 'title']
|
['target', 'rel', 'href', 'class', 'title', 'value']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
module Administrateurs
|
module Administrateurs
|
||||||
class AdministrateurController < ApplicationController
|
class AdministrateurController < ApplicationController
|
||||||
before_action :authenticate_administrateur!
|
before_action :authenticate_administrateur!
|
||||||
|
before_action :alert_for_missing_siret_service
|
||||||
|
before_action :alert_for_missing_service
|
||||||
helper_method :administrateur_as_manager?
|
helper_method :administrateur_as_manager?
|
||||||
|
|
||||||
def nav_bar_profile
|
def nav_bar_profile
|
||||||
|
@ -38,5 +40,42 @@ module Administrateurs
|
||||||
current_administrateur.administrateurs_procedures
|
current_administrateur.administrateurs_procedures
|
||||||
.exists?(procedure_id: id, manager: true)
|
.exists?(procedure_id: id, manager: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def alert_for_missing_siret_service
|
||||||
|
procedures = missing_siret_services
|
||||||
|
if procedures.any?
|
||||||
|
errors = []
|
||||||
|
errors << I18n.t('shared.procedures.no_siret')
|
||||||
|
procedures.each do |p|
|
||||||
|
errors << I18n.t('shared.procedures.add_siret_to_service_without_siret_html', link: edit_admin_service_path(p.service, procedure_id: p.id), nom: p.service.nom)
|
||||||
|
end
|
||||||
|
flash.now.alert = errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_siret_services
|
||||||
|
current_administrateur
|
||||||
|
.procedures.publiees
|
||||||
|
.joins(:service)
|
||||||
|
.where(service: { siret: nil })
|
||||||
|
end
|
||||||
|
|
||||||
|
def alert_for_missing_service
|
||||||
|
procedures = missing_service
|
||||||
|
if procedures.any?
|
||||||
|
errors = []
|
||||||
|
errors << I18n.t('shared.procedures.no_service')
|
||||||
|
procedures.each do |p|
|
||||||
|
errors << I18n.t('shared.procedures.add_service_html', link: admin_services_path(procedure_id: p.id), id: p.id)
|
||||||
|
end
|
||||||
|
flash.now.alert = errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_service
|
||||||
|
current_administrateur
|
||||||
|
.procedures.publiees
|
||||||
|
.where(service_id: nil)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
module Administrateurs
|
module Administrateurs
|
||||||
class ServicesController < AdministrateurController
|
class ServicesController < AdministrateurController
|
||||||
|
skip_before_action :alert_for_missing_siret_service, only: :edit
|
||||||
|
skip_before_action :alert_for_missing_service, only: :edit
|
||||||
def index
|
def index
|
||||||
@services = services.ordered
|
@services = services.ordered
|
||||||
@procedure = procedure
|
@procedure = procedure
|
||||||
|
|
|
@ -91,14 +91,16 @@ class APIEntreprise::API
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def recipient
|
def recipient_for(siret_or_siren)
|
||||||
@procedure&.service && @procedure.service.siret.presence || ENV.fetch('API_ENTREPRISE_DEFAULT_SIRET')
|
service_siret = @procedure&.service && @procedure.service.siret.presence
|
||||||
|
return service_siret if service_siret && !service_siret.starts_with?(siret_or_siren)
|
||||||
|
ENV.fetch('API_ENTREPRISE_DEFAULT_SIRET')
|
||||||
end
|
end
|
||||||
|
|
||||||
def call_with_siret(resource_name, siret_or_siren, user_id: nil)
|
def call_with_siret(resource_name, siret_or_siren, user_id: nil)
|
||||||
url = make_url(resource_name, siret_or_siren)
|
url = make_url(resource_name, siret_or_siren)
|
||||||
|
|
||||||
params = build_params(user_id)
|
params = build_params(user_id, siret_or_siren)
|
||||||
|
|
||||||
call(url, params)
|
call(url, params)
|
||||||
end
|
end
|
||||||
|
@ -144,8 +146,8 @@ class APIEntreprise::API
|
||||||
[API_ENTREPRISE_URL, format(resource_name, id: siret_or_siren)].compact.join("/")
|
[API_ENTREPRISE_URL, format(resource_name, id: siret_or_siren)].compact.join("/")
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_params(user_id)
|
def build_params(user_id, siret_or_siren)
|
||||||
params = base_params
|
params = base_params(siret_or_siren)
|
||||||
|
|
||||||
params[:object] = if api_object.present?
|
params[:object] = if api_object.present?
|
||||||
api_object
|
api_object
|
||||||
|
@ -158,10 +160,10 @@ class APIEntreprise::API
|
||||||
params
|
params
|
||||||
end
|
end
|
||||||
|
|
||||||
def base_params
|
def base_params(siret_or_siren)
|
||||||
{
|
{
|
||||||
context: APPLICATION_NAME,
|
context: APPLICATION_NAME,
|
||||||
recipient: recipient,
|
recipient: recipient_for(siret_or_siren),
|
||||||
non_diffusables: true
|
non_diffusables: true
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,14 @@ module Redcarpet
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_item(content, list_type)
|
def list_item(content, list_type)
|
||||||
content_tag(:li, content.strip.gsub(/<\/?p>/, ''), {}, false)
|
item_number = content.match(/\[value:(\d+)\]/)
|
||||||
|
text = content.strip
|
||||||
|
.gsub(/<\/?p>/, '')
|
||||||
|
.gsub(/\[value:\d+\]/, '')
|
||||||
|
.gsub(/\n/, '<br>')
|
||||||
|
attributes = item_number.present? ? { value: item_number[1] } : {}
|
||||||
|
|
||||||
|
content_tag(:li, text, attributes, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
def paragraph(text)
|
def paragraph(text)
|
||||||
|
|
|
@ -57,6 +57,7 @@ class Champ < ApplicationRecord
|
||||||
:dossier_link?,
|
:dossier_link?,
|
||||||
:departement?,
|
:departement?,
|
||||||
:region?,
|
:region?,
|
||||||
|
:textarea?,
|
||||||
:titre_identite?,
|
:titre_identite?,
|
||||||
:header_section?,
|
:header_section?,
|
||||||
:checkbox?,
|
:checkbox?,
|
||||||
|
@ -246,7 +247,7 @@ class Champ < ApplicationRecord
|
||||||
value_attributes = fork || !private? ? [:value, :value_json, :data, :external_id] : []
|
value_attributes = fork || !private? ? [:value, :value_json, :data, :external_id] : []
|
||||||
relationships = fork || !private? ? [:etablissement, :geo_areas] : []
|
relationships = fork || !private? ? [:etablissement, :geo_areas] : []
|
||||||
|
|
||||||
deep_clone(only: champ_attributes + value_attributes, include: relationships) do |original, kopy|
|
deep_clone(only: champ_attributes + value_attributes, include: relationships, validate: !fork) do |original, kopy|
|
||||||
PiecesJustificativesService.clone_attachments(original, kopy)
|
PiecesJustificativesService.clone_attachments(original, kopy)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -79,7 +79,7 @@ class APIGeoService
|
||||||
private
|
private
|
||||||
|
|
||||||
def communes_by_postal_code_map
|
def communes_by_postal_code_map
|
||||||
Rails.cache.fetch('api_geo_communes_v2', expires_in: 1.week) do
|
Rails.cache.fetch('api_geo_communes', expires_in: 1.day, version: 3) do
|
||||||
departements
|
departements
|
||||||
.filter { _1[:code] != '99' }
|
.filter { _1[:code] != '99' }
|
||||||
.flat_map { communes(_1[:code]) }
|
.flat_map { communes(_1[:code]) }
|
||||||
|
@ -88,7 +88,7 @@ class APIGeoService
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_from_api_geo(scope)
|
def get_from_api_geo(scope)
|
||||||
Rails.cache.fetch("api_geo_#{scope}_v2", expires_in: 1.week) do
|
Rails.cache.fetch("api_geo_#{scope}", expires_in: 1.day, version: 3) do
|
||||||
JSON.parse(Rails.root.join('lib', 'data', 'api_geo', "#{scope}.json").read, symbolize_names: true)
|
JSON.parse(Rails.root.join('lib', 'data', 'api_geo', "#{scope}.json").read, symbolize_names: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
%td.cell-data
|
%td.cell-data
|
||||||
= type_de_champ.libelle
|
= type_de_champ.libelle
|
||||||
- if type_de_champ.mandatory?
|
- if type_de_champ.mandatory?
|
||||||
%span.mandatory *
|
%span.manager-mandatory *
|
||||||
%td.cell-data
|
%td.cell-data
|
||||||
= I18n.t("activerecord.attributes.type_de_champ.type_champs.#{type_de_champ.type_champ}")
|
= I18n.t("activerecord.attributes.type_de_champ.type_champs.#{type_de_champ.type_champ}")
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
- case error
|
- case error
|
||||||
- when :invalid
|
- when :invalid
|
||||||
%p.pt-1
|
%p.fr-error-text
|
||||||
Le numéro RNA doit commencer par un W majuscule suivi de 9 chiffres
|
Le numéro RNA doit commencer par un W majuscule suivi de 9 chiffres
|
||||||
- when :not_found
|
- when :not_found
|
||||||
%p.pt-1= t('.not_found')
|
%p.fr-error-text= t('.not_found')
|
||||||
- when :network_error
|
- when :network_error
|
||||||
%p.pt-1= t('.network_error')
|
%p.fr-error-text= t('.network_error')
|
||||||
- else
|
- else
|
||||||
- if champ.value.present?
|
- if champ.value.present?
|
||||||
%p.pt-1= t('.data_fetched', title: champ.title)
|
%p.fr-info-text= t('.data_fetched', title: champ.title)
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
- case siret
|
- case siret
|
||||||
- when :invalid_length
|
- when :invalid_length
|
||||||
= t('errors.messages.invalid_siret_length')
|
%p.fr-error-text= t('errors.messages.invalid_siret_length')
|
||||||
|
|
||||||
- when :invalid_checksum
|
- when :invalid_checksum
|
||||||
= t('errors.messages.invalid_siret_checksum')
|
%p.fr-error-text= t('errors.messages.invalid_siret_checksum')
|
||||||
|
|
||||||
- when :not_found
|
- when :not_found
|
||||||
|
%p.fr-error-text
|
||||||
Nous n’avons pas trouvé d’établissement correspondant à ce numéro de SIRET.
|
Nous n’avons pas trouvé d’établissement correspondant à ce numéro de SIRET.
|
||||||
= link_to('Plus d’informations', t("links.common.faq.erreur_siret_url"), **external_link_attributes)
|
= link_to('Plus d’informations', t("links.common.faq.erreur_siret_url"), **external_link_attributes)
|
||||||
|
|
||||||
- when :network_error
|
- when :network_error
|
||||||
= t('errors.messages.siret_network_error')
|
%p.fr-error-text= t('errors.messages.siret_network_error')
|
||||||
|
|
||||||
- when :api_entreprise_down
|
- when :api_entreprise_down
|
||||||
= t('errors.messages.api_entreprise_down')
|
%p.fr-error-text= t('errors.messages.api_entreprise_down')
|
||||||
|
|
||||||
- when :empty
|
- when :empty
|
||||||
// NOOP
|
// NOOP
|
||||||
|
@ -22,4 +23,4 @@
|
||||||
- if siret == etablissement&.siret && raison_sociale_or_name(etablissement).present?
|
- if siret == etablissement&.siret && raison_sociale_or_name(etablissement).present?
|
||||||
= render EditableChamp::EtablissementTitreComponent.new(etablissement: etablissement)
|
= render EditableChamp::EtablissementTitreComponent.new(etablissement: etablissement)
|
||||||
- else
|
- else
|
||||||
Ce SIRET existe, nous en récupérons les informations.
|
%p.fr-info-text Ce SIRET existe, nous en récupérons les informations.
|
||||||
|
|
|
@ -49,6 +49,7 @@ prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], p
|
||||||
pdf.fill_color grey
|
pdf.fill_color grey
|
||||||
pdf.text "#{Individual.human_attribute_name(:prenom)} : #{@dossier.individual.prenom}", size: 10, character_spacing: -0.2, align: :justify
|
pdf.text "#{Individual.human_attribute_name(:prenom)} : #{@dossier.individual.prenom}", size: 10, character_spacing: -0.2, align: :justify
|
||||||
pdf.text "#{Individual.human_attribute_name(:nom)} : #{@dossier.individual.nom.upcase}", size: 10, character_spacing: -0.2, align: :justify
|
pdf.text "#{Individual.human_attribute_name(:nom)} : #{@dossier.individual.nom.upcase}", size: 10, character_spacing: -0.2, align: :justify
|
||||||
|
pdf.text "#{User.human_attribute_name(:email)} : #{@dossier.user.email}", size: 10, character_spacing: -0.2, align: :justify
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
= render EditableChamp::EditableChampComponent.new champ:, form:
|
= render EditableChamp::EditableChampComponent.new champ:, form:
|
||||||
- else
|
- else
|
||||||
= turbo_stream.update champ.labelledby_id do
|
= turbo_stream.update champ.labelledby_id do
|
||||||
= render EditableChamp::ChampLabelContentComponent.new champ:
|
= render EditableChamp::ChampLabelContentComponent.new champ:, form:
|
||||||
|
|
||||||
= turbo_stream.remove_all(".editable-champ .spinner-removable");
|
= turbo_stream.remove_all(".editable-champ .spinner-removable");
|
||||||
= turbo_stream.hide_all(".editable-champ .spinner");
|
= turbo_stream.hide_all(".editable-champ .spinner");
|
||||||
|
|
|
@ -822,6 +822,11 @@ en:
|
||||||
merger_token_expired: "Le delay to merge your FranceConnect and %{application_name} accounts is expired. Please retry."
|
merger_token_expired: "Le delay to merge your FranceConnect and %{application_name} accounts is expired. Please retry."
|
||||||
shared:
|
shared:
|
||||||
procedures:
|
procedures:
|
||||||
|
no_siret: "Some procedures have service without siret. Please update."
|
||||||
|
add_siret_to_service_without_siret_html: Add siret for service <a href="%{link}">%{nom}</a>
|
||||||
|
no_service: "Some procedures have no service."
|
||||||
|
add_service_html: <a href="%{link}">Add service</a> to procedure %{id}
|
||||||
|
|
||||||
stats:
|
stats:
|
||||||
usual_processing_time: "Usual processing time"
|
usual_processing_time: "Usual processing time"
|
||||||
processing_time_description: "%{percentile}% of submitted files in the last %{span} days were processed in less than %{days}."
|
processing_time_description: "%{percentile}% of submitted files in the last %{span} days were processed in less than %{days}."
|
||||||
|
|
|
@ -876,6 +876,11 @@ fr:
|
||||||
merger_token_expired: "Le délai pour fusionner les comptes FranceConnect et %{application_name} est expirée. Veuillez recommencer la procédure pour vous fusionner les comptes."
|
merger_token_expired: "Le délai pour fusionner les comptes FranceConnect et %{application_name} est expirée. Veuillez recommencer la procédure pour vous fusionner les comptes."
|
||||||
shared:
|
shared:
|
||||||
procedures:
|
procedures:
|
||||||
|
no_siret: "Vous n’avez pas renseigné le siret du service pour certaines de vos démarches. Merci de les modifier."
|
||||||
|
add_siret_to_service_without_siret_html: Ajouter le siret du service <a href="%{link}">%{nom}</a>
|
||||||
|
no_service: "Certaines de vos démarches n’ont pas de service associé."
|
||||||
|
add_service_html: <a href="%{link}">Ajouter un service</a> à la démarche %{id}
|
||||||
|
|
||||||
stats:
|
stats:
|
||||||
usual_processing_time: "Temps de traitement usuel"
|
usual_processing_time: "Temps de traitement usuel"
|
||||||
processing_time_description: "%{percentile}% des demandes des %{span} derniers jours ont été traitées en moins de %{days}."
|
processing_time_description: "%{percentile}% des demandes des %{span} derniers jours ont été traitées en moins de %{days}."
|
||||||
|
|
7
config/locales/models/champs/datetime_champ/en.yml
Normal file
7
config/locales/models/champs/datetime_champ/en.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
en:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
champs/datetime_champ:
|
||||||
|
hints:
|
||||||
|
value: "Expected format : dd/mm/yyyy hh:mm"
|
||||||
|
|
7
config/locales/models/champs/datetime_champ/fr.yml
Normal file
7
config/locales/models/champs/datetime_champ/fr.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
fr:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
champs/datetime_champ:
|
||||||
|
hints:
|
||||||
|
value: "Format attendu : jj/mm/aaaa hh:mm"
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
||||||
[{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75001"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75002"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75003"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75004"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75005"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75006"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75007"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75008"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75009"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75010"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75011"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75012"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75013"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75014"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75015"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75016"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75017"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75018"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75019"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75020"},{"name":"Paris","code":"75056","epci_code":"200054781","departement_code":"75","postal_code":"75116"},{"name":"Paris 1er Arrondissement","code":"75101","departement_code":"75","postal_code":"75001"},{"name":"Paris 2e Arrondissement","code":"75102","departement_code":"75","postal_code":"75002"},{"name":"Paris 3e Arrondissement","code":"75103","departement_code":"75","postal_code":"75003"},{"name":"Paris 4e Arrondissement","code":"75104","departement_code":"75","postal_code":"75004"},{"name":"Paris 5e Arrondissement","code":"75105","departement_code":"75","postal_code":"75005"},{"name":"Paris 6e Arrondissement","code":"75106","departement_code":"75","postal_code":"75006"},{"name":"Paris 7e Arrondissement","code":"75107","departement_code":"75","postal_code":"75007"},{"name":"Paris 8e Arrondissement","code":"75108","departement_code":"75","postal_code":"75008"},{"name":"Paris 9e Arrondissement","code":"75109","departement_code":"75","postal_code":"75009"},{"name":"Paris 10e Arrondissement","code":"75110","departement_code":"75","postal_code":"75010"},{"name":"Paris 11e Arrondissement","code":"75111","departement_code":"75","postal_code":"75011"},{"name":"Paris 12e Arrondissement","code":"75112","departement_code":"75","postal_code":"75012"},{"name":"Paris 13e Arrondissement","code":"75113","departement_code":"75","postal_code":"75013"},{"name":"Paris 14e Arrondissement","code":"75114","departement_code":"75","postal_code":"75014"},{"name":"Paris 15e Arrondissement","code":"75115","departement_code":"75","postal_code":"75015"},{"name":"Paris 16e Arrondissement","code":"75116","departement_code":"75","postal_code":"75016"},{"name":"Paris 16e Arrondissement","code":"75116","departement_code":"75","postal_code":"75116"},{"name":"Paris 17e Arrondissement","code":"75117","departement_code":"75","postal_code":"75017"},{"name":"Paris 18e Arrondissement","code":"75118","departement_code":"75","postal_code":"75018"},{"name":"Paris 19e Arrondissement","code":"75119","departement_code":"75","postal_code":"75019"},{"name":"Paris 20e Arrondissement","code":"75120","departement_code":"75","postal_code":"75020"}]
|
[{"name":"Paris 1er Arrondissement","code":"75101","departement_code":"75","postal_code":"75001"},{"name":"Paris 2e Arrondissement","code":"75102","departement_code":"75","postal_code":"75002"},{"name":"Paris 3e Arrondissement","code":"75103","departement_code":"75","postal_code":"75003"},{"name":"Paris 4e Arrondissement","code":"75104","departement_code":"75","postal_code":"75004"},{"name":"Paris 5e Arrondissement","code":"75105","departement_code":"75","postal_code":"75005"},{"name":"Paris 6e Arrondissement","code":"75106","departement_code":"75","postal_code":"75006"},{"name":"Paris 7e Arrondissement","code":"75107","departement_code":"75","postal_code":"75007"},{"name":"Paris 8e Arrondissement","code":"75108","departement_code":"75","postal_code":"75008"},{"name":"Paris 9e Arrondissement","code":"75109","departement_code":"75","postal_code":"75009"},{"name":"Paris 10e Arrondissement","code":"75110","departement_code":"75","postal_code":"75010"},{"name":"Paris 11e Arrondissement","code":"75111","departement_code":"75","postal_code":"75011"},{"name":"Paris 12e Arrondissement","code":"75112","departement_code":"75","postal_code":"75012"},{"name":"Paris 13e Arrondissement","code":"75113","departement_code":"75","postal_code":"75013"},{"name":"Paris 14e Arrondissement","code":"75114","departement_code":"75","postal_code":"75014"},{"name":"Paris 15e Arrondissement","code":"75115","departement_code":"75","postal_code":"75015"},{"name":"Paris 16e Arrondissement","code":"75116","departement_code":"75","postal_code":"75016"},{"name":"Paris 16e Arrondissement","code":"75116","departement_code":"75","postal_code":"75116"},{"name":"Paris 17e Arrondissement","code":"75117","departement_code":"75","postal_code":"75017"},{"name":"Paris 18e Arrondissement","code":"75118","departement_code":"75","postal_code":"75018"},{"name":"Paris 19e Arrondissement","code":"75119","departement_code":"75","postal_code":"75019"},{"name":"Paris 20e Arrondissement","code":"75120","departement_code":"75","postal_code":"75020"}]
|
|
@ -21,7 +21,9 @@ namespace :api_geo_data do
|
||||||
data = []
|
data = []
|
||||||
PATH.join("#{filename}.json").open('w') do |f|
|
PATH.join("#{filename}.json").open('w') do |f|
|
||||||
response = Typhoeus.get("#{API_GEO_URL}/#{query}")
|
response = Typhoeus.get("#{API_GEO_URL}/#{query}")
|
||||||
json = JSON.parse(response.body).map(&:symbolize_keys).flat_map do |result|
|
json = JSON.parse(response.body).map(&:symbolize_keys).filter do |result|
|
||||||
|
!result[:code].in?(['75056', '13055', '69123'])
|
||||||
|
end.flat_map do |result|
|
||||||
item = {
|
item = {
|
||||||
name: result[:nom].tr("'", '’'),
|
name: result[:nom].tr("'", '’'),
|
||||||
code: result[:code],
|
code: result[:code],
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
namespace :after_party do
|
||||||
|
desc 'Deployment task: remove_toplevel_communes'
|
||||||
|
task remove_toplevel_communes: :environment do
|
||||||
|
puts "Running deploy task 'remove_toplevel_communes'"
|
||||||
|
|
||||||
|
communes = Champs::CommuneChamp.where(external_id: ['75056', '13055', '69123'])
|
||||||
|
progress = ProgressReport.new(communes.count)
|
||||||
|
|
||||||
|
communes.find_each do |commune|
|
||||||
|
external_id = case commune.external_id
|
||||||
|
when '75056'
|
||||||
|
'75101'
|
||||||
|
when '13055'
|
||||||
|
'13201'
|
||||||
|
when '69123'
|
||||||
|
'69381'
|
||||||
|
end
|
||||||
|
commune.update_columns(external_id:, value: APIGeoService.commune_name(commune.code_departement, external_id))
|
||||||
|
progress.inc
|
||||||
|
end
|
||||||
|
progress.finish
|
||||||
|
|
||||||
|
# Update task as completed. If you remove the line below, the task will
|
||||||
|
# run with every deploy (or every time you call after_party:run).
|
||||||
|
AfterParty::TaskRecord
|
||||||
|
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
|
||||||
|
end
|
||||||
|
end
|
|
@ -47,11 +47,59 @@ TEXT
|
||||||
<<~TEXT
|
<<~TEXT
|
||||||
1. 1er paragraphe
|
1. 1er paragraphe
|
||||||
2. paragraphe
|
2. paragraphe
|
||||||
|
4. 4eme paragraphe
|
||||||
TEXT
|
TEXT
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(page).to have_selector("ol", count: 1) }
|
it { expect(page).to have_selector("ol", count: 1) }
|
||||||
it { expect(page).to have_selector("li", count: 2) }
|
it { expect(page).to have_selector("li", count: 3) }
|
||||||
|
it { expect(page.native.inner_html).to match('value="1"') }
|
||||||
|
it { expect(page.native.inner_html).to match('value="4"') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'multi line lists' do
|
||||||
|
let(:text) do
|
||||||
|
<<~TEXT
|
||||||
|
Lorsque nous souhaitons envoyer ce message :
|
||||||
|
|
||||||
|
1. Premier point de la recette
|
||||||
|
Commentaire 1
|
||||||
|
2. Deuxième point de la recette
|
||||||
|
Commentaire 2
|
||||||
|
|
||||||
|
4. Troisième point de la recette
|
||||||
|
Commentaire 3
|
||||||
|
|
||||||
|
trois nouveaux paragraphes
|
||||||
|
sur plusieures
|
||||||
|
lignes
|
||||||
|
|
||||||
|
- 1er point de la recette
|
||||||
|
* 2eme point de la recette
|
||||||
|
avec des détailles
|
||||||
|
+ 3eme point de la recette
|
||||||
|
beaucoup
|
||||||
|
de détails
|
||||||
|
|
||||||
|
conclusion
|
||||||
|
TEXT
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(page).to have_selector("ol", count: 1) }
|
||||||
|
it { expect(page).to have_selector("ul", count: 1) }
|
||||||
|
it { expect(page).to have_selector("li", count: 6) }
|
||||||
|
it { expect(page).to have_selector("p", count: 5) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'strong' do
|
||||||
|
let(:text) do
|
||||||
|
<<~TEXT
|
||||||
|
1er paragraphe **fort** un_mot_pas_italic
|
||||||
|
TEXT
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(page).to have_selector("strong", count: 1) }
|
||||||
|
it { expect(page).not_to have_selector("em") }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'auto-link' do
|
context 'auto-link' do
|
||||||
|
|
|
@ -295,7 +295,7 @@ describe Administrateurs::ProceduresController, type: :controller do
|
||||||
let(:service_2) { create(:service, nom: 'DDT du Loiret') }
|
let(:service_2) { create(:service, nom: 'DDT du Loiret') }
|
||||||
let!(:procedure_with_service_1) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_1, dossiers_count: 2) }
|
let!(:procedure_with_service_1) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_1, dossiers_count: 2) }
|
||||||
let!(:procedure_with_service_2) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_2, dossiers_count: 2) }
|
let!(:procedure_with_service_2) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_2, dossiers_count: 2) }
|
||||||
let!(:procedure_without_service) { create(:procedure_with_dossiers, :published, organisation: 'DDT du Loiret', dossiers_count: 2) }
|
let!(:procedure_without_service) { create(:procedure_with_dossiers, :published, service: nil, organisation: 'DDT du Loiret', dossiers_count: 2) }
|
||||||
|
|
||||||
it 'groups procedures with services as well as procedures with organisations' do
|
it 'groups procedures with services as well as procedures with organisations' do
|
||||||
expect(grouped_procedures.length).to eq 2
|
expect(grouped_procedures.length).to eq 2
|
||||||
|
|
|
@ -157,4 +157,36 @@ describe Administrateurs::ServicesController, type: :controller do
|
||||||
it { expect(procedure.reload.service_id).to be_nil }
|
it { expect(procedure.reload.service_id).to be_nil }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#index" do
|
||||||
|
let(:admin) { create(:administrateur) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(admin.user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when admin has service without siret' do
|
||||||
|
let(:service) { create(:service) }
|
||||||
|
let(:procedure) { create(:procedure, :published, service: service, administrateur: admin) }
|
||||||
|
|
||||||
|
it 'display alert when admin has service without siret' do
|
||||||
|
service.siret = nil
|
||||||
|
service.save(validate: false)
|
||||||
|
get :index, params: { procedure_id: procedure.id }
|
||||||
|
expect(flash.alert.first).to eq "Vous n’avez pas renseigné le siret du service pour certaines de vos démarches. Merci de les modifier."
|
||||||
|
expect(flash.alert.last).to include(service.nom)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when admin has procedure without service' do
|
||||||
|
let(:procedure) { create(:procedure, :published, service: nil, administrateur: admin) }
|
||||||
|
|
||||||
|
it 'display alert' do
|
||||||
|
get :index, params: { procedure_id: procedure.id }
|
||||||
|
expect(procedure.service).to be nil
|
||||||
|
expect(flash.alert.first).to eq "Certaines de vos démarches n’ont pas de service associé."
|
||||||
|
expect(flash.alert.last).to include "démarche #{procedure.id}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -315,6 +315,7 @@ FactoryBot.define do
|
||||||
unpublished_at { nil }
|
unpublished_at { nil }
|
||||||
closed_at { nil }
|
closed_at { nil }
|
||||||
zones { [association(:zone, strategy: :build)] }
|
zones { [association(:zone, strategy: :build)] }
|
||||||
|
service { association :service, administrateur: administrateurs.first }
|
||||||
end
|
end
|
||||||
|
|
||||||
trait :closed do
|
trait :closed do
|
||||||
|
|
|
@ -95,12 +95,24 @@ describe APIEntreprise::API do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a service with siret' do
|
context 'with a service with siret' do
|
||||||
|
context 'with a siren entreprise not equivalent to siret service' do
|
||||||
let(:procedure) { create(:procedure, :with_service) }
|
let(:procedure) { create(:procedure, :with_service) }
|
||||||
it 'send default recipient' do
|
it 'send default recipient' do
|
||||||
subject
|
subject
|
||||||
expect(WebMock).to have_requested(:get, /https:\/\/entreprise.api.gouv.fr\/v3\/insee\/sirene\/unites_legales\/#{siren}/).with(query: hash_including({ recipient: procedure.service.siret }))
|
expect(WebMock).to have_requested(:get, /https:\/\/entreprise.api.gouv.fr\/v3\/insee\/sirene\/unites_legales\/#{siren}/).with(query: hash_including({ recipient: procedure.service.siret }))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a siren entreprise equivalent to siret service' do
|
||||||
|
let(:procedure) { create(:procedure, :with_service) }
|
||||||
|
let(:siren) { procedure.service.siret[0..8] }
|
||||||
|
let(:dinum_siret) { "13002526500013" }
|
||||||
|
it 'send default recipient' do
|
||||||
|
subject
|
||||||
|
expect(WebMock).to have_requested(:get, /https:\/\/entreprise.api.gouv.fr\/v3\/insee\/sirene\/unites_legales\/#{siren}/).with(query: hash_including({ recipient: dinum_siret }))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,26 @@ RSpec.describe DossierCloneConcern do
|
||||||
|
|
||||||
# rubocop:enable Lint/BooleanSymbol
|
# rubocop:enable Lint/BooleanSymbol
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when associated record is invalid' do
|
||||||
|
let(:procedure) do
|
||||||
|
create(:procedure, types_de_champ_public: [
|
||||||
|
{ type: :carte, libelle: "Carte", stable_id: 992, mandatory: true }
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
champ = dossier.champs.find { _1.stable_id == 992 }
|
||||||
|
geo_area = build(:geo_area, champ:, geometry: { "i'm" => "invalid" })
|
||||||
|
geo_area.save!(validate: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can still fork' do
|
||||||
|
new_dossier.champs.load # load relation so champs are validated below
|
||||||
|
|
||||||
|
expect(new_dossier.champs.find { _1.stable_id == 992 }.geo_areas.first).not_to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,9 +47,9 @@ describe APIGeoService do
|
||||||
describe 'communes_by_postal_code' do
|
describe 'communes_by_postal_code' do
|
||||||
it 'return results' do
|
it 'return results' do
|
||||||
expect(APIGeoService.communes_by_postal_code('01500').size).to eq(8)
|
expect(APIGeoService.communes_by_postal_code('01500').size).to eq(8)
|
||||||
expect(APIGeoService.communes_by_postal_code('75019').size).to eq(2)
|
expect(APIGeoService.communes_by_postal_code('75019').size).to eq(1)
|
||||||
expect(APIGeoService.communes_by_postal_code('69005').size).to eq(2)
|
expect(APIGeoService.communes_by_postal_code('69005').size).to eq(1)
|
||||||
expect(APIGeoService.communes_by_postal_code('13006').size).to eq(2)
|
expect(APIGeoService.communes_by_postal_code('13006').size).to eq(1)
|
||||||
expect(APIGeoService.communes_by_postal_code('73480').size).to eq(3)
|
expect(APIGeoService.communes_by_postal_code('73480').size).to eq(3)
|
||||||
expect(APIGeoService.communes_by_postal_code('20000').first[:code]).to eq('2A004')
|
expect(APIGeoService.communes_by_postal_code('20000').first[:code]).to eq('2A004')
|
||||||
expect(APIGeoService.communes_by_postal_code('37160').size).to eq(7)
|
expect(APIGeoService.communes_by_postal_code('37160').size).to eq(7)
|
||||||
|
|
|
@ -419,7 +419,7 @@ describe 'The user' do
|
||||||
let(:procedure) do
|
let(:procedure) do
|
||||||
create(:procedure, :published, :for_individual,
|
create(:procedure, :published, :for_individual,
|
||||||
types_de_champ_public: [
|
types_de_champ_public: [
|
||||||
{ type: :integer_number, libelle: 'age', stable_id: age_stable_id },
|
{ type: :integer_number, libelle: 'age du candidat', stable_id: age_stable_id },
|
||||||
{ type: :yes_no, libelle: 'permis de conduire', stable_id: permis_stable_id, condition: permis_condition },
|
{ type: :yes_no, libelle: 'permis de conduire', stable_id: permis_stable_id, condition: permis_condition },
|
||||||
{ type: :header_section, libelle: 'info voiture', condition: permis_condition },
|
{ type: :header_section, libelle: 'info voiture', condition: permis_condition },
|
||||||
{ type: :integer_number, libelle: 'tonnage', stable_id: tonnage_stable_id, condition: tonnage_condition },
|
{ type: :integer_number, libelle: 'tonnage', stable_id: tonnage_stable_id, condition: tonnage_condition },
|
||||||
|
@ -432,12 +432,12 @@ describe 'The user' do
|
||||||
|
|
||||||
fill_individual
|
fill_individual
|
||||||
|
|
||||||
expect(page).to have_css('label', text: 'age', visible: true)
|
expect(page).to have_css('label', text: 'age du candidat', visible: true)
|
||||||
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
|
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
|
||||||
expect(page).to have_no_css('legend h2', text: 'info voiture', visible: true)
|
expect(page).to have_no_css('legend h2', text: 'info voiture', visible: true)
|
||||||
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
||||||
|
|
||||||
fill_in('age (facultatif)', with: '18')
|
fill_in('age du candidat (facultatif)', with: '18')
|
||||||
expect(page).to have_css('label', text: 'permis de conduire', visible: true)
|
expect(page).to have_css('label', text: 'permis de conduire', visible: true)
|
||||||
expect(page).to have_css('legend h2', text: 'info voiture', visible: true)
|
expect(page).to have_css('legend h2', text: 'info voiture', visible: true)
|
||||||
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
||||||
|
@ -453,7 +453,7 @@ describe 'The user' do
|
||||||
fill_in('tonnage (facultatif)', with: 'a')
|
fill_in('tonnage (facultatif)', with: 'a')
|
||||||
expect(page).to have_no_css('label', text: 'parking', visible: true)
|
expect(page).to have_no_css('label', text: 'parking', visible: true)
|
||||||
|
|
||||||
fill_in('age (facultatif)', with: '2')
|
fill_in('age du candidat (facultatif)', with: '2')
|
||||||
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
|
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
|
||||||
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
||||||
|
|
||||||
|
@ -461,11 +461,11 @@ describe 'The user' do
|
||||||
click_on 'Accéder à votre dossier'
|
click_on 'Accéder à votre dossier'
|
||||||
click_on 'Modifier mon dossier'
|
click_on 'Modifier mon dossier'
|
||||||
|
|
||||||
expect(page).to have_css('label', text: 'age', visible: true)
|
expect(page).to have_css('label', text: 'age du candidat', visible: true)
|
||||||
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
|
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
|
||||||
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
|
||||||
|
|
||||||
fill_in('age (facultatif)', with: '18')
|
fill_in('age du candidat (facultatif)', with: '18')
|
||||||
wait_for_autosave(false)
|
wait_for_autosave(false)
|
||||||
|
|
||||||
# the champ keeps their previous value so they are all displayed
|
# the champ keeps their previous value so they are all displayed
|
||||||
|
|
Loading…
Reference in a new issue