Merge branch 'main' into 8054-a11y-ways-of-navigating

This commit is contained in:
Julie Salha 2023-05-26 10:47:33 +02:00 committed by GitHub
commit b50698a30d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
278 changed files with 4369 additions and 2838 deletions

View file

@ -1,18 +0,0 @@
on:
issue_comment:
types: [created]
name: Automatic Rebase
jobs:
rebase:
name: Rebase
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
runs-on: ubuntu-latest
steps:
- name: Checkout the latest code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@1.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -1,19 +0,0 @@
on:
push:
tags:
- '*'
name: Publish release on Sentry
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Sentry Release
uses: getsentry/action-release@v1
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: rails
with:
environment: production
version: ${{ github.ref }}

View file

@ -11,7 +11,7 @@ inherit_mode:
- Include
AllCops:
TargetRubyVersion: 3.1
TargetRubyVersion: 3.2
DisabledByDefault: true
SuggestExtensions: false
NewCops: enable

View file

@ -1 +1 @@
3.1.2
3.2.2

View file

@ -1,5 +1,7 @@
source 'https://rubygems.org'
gem 'rails', '~> 7.0.4' # allows update to security fixes at any time
gem 'aasm'
gem 'acsv'
gem 'active_link_to' # Automatically set a class on active links
@ -67,7 +69,6 @@ gem 'premailer-rails'
gem 'puma' # Use Puma as the app server
gem 'pundit'
gem 'rack-attack'
gem 'rails'
gem 'rails-i18n' # Locales par défaut
gem 'rake-progressbar', require: false
gem 'redcarpet'

View file

@ -4,40 +4,47 @@ GEM
aasm (5.2.0)
concurrent-ruby (~> 1.0)
acsv (0.0.1)
actioncable (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
actioncable (7.0.4.3)
actionpack (= 7.0.4.3)
activesupport (= 7.0.4.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
actionmailbox (7.0.4.3)
actionpack (= 7.0.4.3)
activejob (= 7.0.4.3)
activerecord (= 7.0.4.3)
activestorage (= 7.0.4.3)
activesupport (= 7.0.4.3)
mail (>= 2.7.1)
actionmailer (6.1.7.1)
actionpack (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activesupport (= 6.1.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.4.3)
actionpack (= 7.0.4.3)
actionview (= 7.0.4.3)
activejob (= 7.0.4.3)
activesupport (= 7.0.4.3)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (6.1.7.1)
actionview (= 6.1.7.1)
activesupport (= 6.1.7.1)
rack (~> 2.0, >= 2.0.9)
actionpack (7.0.4.3)
actionview (= 7.0.4.3)
activesupport (= 7.0.4.3)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.7.1)
actionpack (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
actiontext (7.0.4.3)
actionpack (= 7.0.4.3)
activerecord (= 7.0.4.3)
activestorage (= 7.0.4.3)
activesupport (= 7.0.4.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (6.1.7.1)
activesupport (= 6.1.7.1)
actionview (7.0.4.3)
activesupport (= 7.0.4.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -45,9 +52,9 @@ GEM
active_link_to (1.0.5)
actionpack
addressable
active_model_serializers (0.10.12)
actionpack (>= 4.1, < 6.2)
activemodel (>= 4.1, < 6.2)
active_model_serializers (0.10.13)
actionpack (>= 4.1, < 7.1)
activemodel (>= 4.1, < 7.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_storage_validations (0.9.6)
@ -55,31 +62,30 @@ GEM
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
activesupport (>= 5.2.0)
activejob (6.1.7.1)
activesupport (= 6.1.7.1)
activejob (7.0.4.3)
activesupport (= 7.0.4.3)
globalid (>= 0.3.6)
activemodel (6.1.7.1)
activesupport (= 6.1.7.1)
activerecord (6.1.7.1)
activemodel (= 6.1.7.1)
activesupport (= 6.1.7.1)
activestorage (6.1.7.1)
actionpack (= 6.1.7.1)
activejob (= 6.1.7.1)
activerecord (= 6.1.7.1)
activesupport (= 6.1.7.1)
activemodel (7.0.4.3)
activesupport (= 7.0.4.3)
activerecord (7.0.4.3)
activemodel (= 7.0.4.3)
activesupport (= 7.0.4.3)
activestorage (7.0.4.3)
actionpack (= 7.0.4.3)
activejob (= 7.0.4.3)
activerecord (= 7.0.4.3)
activesupport (= 7.0.4.3)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activestorage-openstack (1.5.1)
fog-openstack (~> 1.0)
marcel
rails (>= 5.2.2)
activesupport (6.1.7.1)
activesupport (7.0.4.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
administrate (0.18.0)
@ -95,12 +101,10 @@ GEM
aes_key_wrap (1.1.0)
after_party (1.11.2)
anchored (1.1.0)
annotate (3.1.1)
activerecord (>= 3.2, < 7.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
attr_required (1.0.1)
axe-core-api (4.2.1)
capybara
@ -119,7 +123,7 @@ GEM
axlsx_styler (1.1.0)
activesupport (>= 3.1)
caxlsx (>= 2.0.2)
bcrypt (3.1.16)
bcrypt (3.1.18)
better_html (1.0.16)
actionview (>= 4.0)
activesupport (>= 4.0)
@ -175,14 +179,15 @@ GEM
css_parser (1.9.0)
addressable
daemons (1.3.1)
deep_cloneable (3.0.0)
activerecord (>= 3.1.0, < 7)
date (3.3.3)
deep_cloneable (3.2.0)
activerecord (>= 3.1.0, < 8)
delayed_cron_job (0.7.4)
delayed_job (>= 4.1)
delayed_job (4.1.11)
activesupport (>= 3.0, < 8.0)
delayed_job_active_record (4.1.5)
activerecord (>= 3.0, < 6.2)
delayed_job_active_record (4.1.7)
activerecord (>= 3.0, < 8.0)
delayed_job (>= 3.0, < 5)
delayed_job_web (1.4.4)
activerecord (> 3.0.0)
@ -191,7 +196,7 @@ GEM
sinatra (>= 1.4.4)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.7.3)
devise (4.9.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
@ -199,16 +204,14 @@ GEM
warden (~> 1.2.3)
devise-i18n (1.9.2)
devise (>= 4.7.1)
devise-two-factor (4.0.2)
activesupport (< 7.1)
attr_encrypted (>= 1.3, < 4, != 2)
devise-two-factor (5.0.0)
activesupport (~> 7.0)
devise (~> 4.0)
railties (< 7.1)
railties (~> 7.0)
rotp (~> 6.0)
diff-lcs (1.4.4)
digest (3.1.0)
discard (1.2.0)
activerecord (>= 4.2, < 7)
diff-lcs (1.5.0)
discard (1.2.1)
activerecord (>= 4.2, < 8)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
@ -224,7 +227,6 @@ GEM
concurrent-ruby (~> 1.0)
http (>= 3.0)
ruby2_keywords
encryptor (3.0.0)
erubi (1.12.0)
et-orbi (1.2.4)
tzinfo
@ -233,25 +235,6 @@ GEM
excon (0.79.0)
factory_bot (6.1.0)
activesupport (>= 5.0.0)
faraday (1.8.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
ffi (1.15.5)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
@ -285,7 +268,7 @@ GEM
raabro (~> 1.4)
geo_coord (0.2.0)
geocoder (1.6.5)
globalid (1.0.1)
globalid (1.1.0)
activesupport (>= 5.0)
gon (6.4.0)
actionpack (>= 3.0.20)
@ -341,7 +324,7 @@ GEM
http-form_data (2.3.0)
http_accept_language (2.1.1)
httpclient (2.8.3)
i18n (1.12.0)
i18n (1.13.0)
concurrent-ruby (~> 1.0)
i18n-tasks (1.0.9)
activesupport (>= 4.0.2)
@ -362,7 +345,6 @@ GEM
ruby-vips (>= 2.0.17, < 3)
invisible_captcha (2.0.0)
rails (>= 5.0)
io-wait (0.2.1)
ipaddress (0.8.3)
jquery-rails (4.5.1)
rails-dom-testing (>= 1, < 3)
@ -412,10 +394,10 @@ GEM
railties (>= 4)
request_store (~> 1.0)
logstash-event (1.2.02)
loofah (2.19.1)
loofah (2.21.3)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.8.0.1)
nokogiri (>= 1.12.0)
mail (2.8.1)
mini_mime (>= 0.1.1)
net-imap
net-pop
@ -436,30 +418,25 @@ GEM
rake
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.8.1)
mini_portile2 (2.8.2)
minitest (5.18.0)
msgpack (1.4.2)
multi_json (1.15.0)
multipart-post (2.1.1)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
net-imap (0.2.3)
digest
net-imap (0.3.4)
date
net-protocol
strscan
net-pop (0.1.1)
digest
net-pop (0.1.2)
net-protocol
net-protocol (0.2.1)
timeout
net-protocol (0.1.1)
io-wait
timeout
net-smtp (0.2.1)
net-smtp (0.3.3)
net-protocol
netrc (0.11.0)
nio4r (2.5.8)
nokogiri (1.14.3)
mini_portile2 (~> 2.8.0)
nio4r (2.5.9)
nokogiri (1.15.2)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
open4 (1.3.4)
openid_connect (1.3.0)
@ -478,7 +455,7 @@ GEM
parser (3.2.2.0)
ast (~> 2.4.1)
pdf-core (0.9.0)
pg (1.2.3)
pg (1.4.6)
phonelib (0.6.53)
prawn (2.4.0)
pdf-core (~> 0.9.0)
@ -497,12 +474,12 @@ GEM
actionmailer (>= 3)
premailer (~> 1.7, >= 1.7.9)
promise.rb (0.7.4)
pry (0.13.1)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.9.0)
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (~> 0.13.0)
pry (>= 0.13, < 0.15)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.1)
@ -512,7 +489,7 @@ GEM
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.6.2)
rack (2.2.6.4)
rack (2.2.7)
rack-attack (6.5.0)
rack (>= 1.0, < 3)
rack-mini-profiler (3.0.0)
@ -532,21 +509,20 @@ GEM
rack_session_access (0.2.0)
builder (>= 2.0.0)
rack (>= 1.0.0)
rails (6.1.7.1)
actioncable (= 6.1.7.1)
actionmailbox (= 6.1.7.1)
actionmailer (= 6.1.7.1)
actionpack (= 6.1.7.1)
actiontext (= 6.1.7.1)
actionview (= 6.1.7.1)
activejob (= 6.1.7.1)
activemodel (= 6.1.7.1)
activerecord (= 6.1.7.1)
activestorage (= 6.1.7.1)
activesupport (= 6.1.7.1)
rails (7.0.4.3)
actioncable (= 7.0.4.3)
actionmailbox (= 7.0.4.3)
actionmailer (= 7.0.4.3)
actionpack (= 7.0.4.3)
actiontext (= 7.0.4.3)
actionview (= 7.0.4.3)
activejob (= 7.0.4.3)
activemodel (= 7.0.4.3)
activerecord (= 7.0.4.3)
activestorage (= 7.0.4.3)
activesupport (= 7.0.4.3)
bundler (>= 1.15.0)
railties (= 6.1.7.1)
sprockets-rails (>= 2.0.0)
railties (= 7.0.4.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@ -564,12 +540,13 @@ GEM
rails-i18n (7.0.3)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (6.1.7.1)
actionpack (= 6.1.7.1)
activesupport (= 6.1.7.1)
railties (7.0.4.3)
actionpack (= 7.0.4.3)
activesupport (= 7.0.4.3)
method_source
rake (>= 12.2)
thor (~> 1.0)
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.0.6)
rake-progressbar (0.0.5)
@ -580,9 +557,9 @@ GEM
regexp_parser (2.8.0)
request_store (1.5.0)
rack (>= 1.4)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
responders (3.1.0)
actionpack (>= 5.2)
railties (>= 5.2)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
@ -596,29 +573,29 @@ GEM
builder (>= 3.0)
dry-inflector (~> 0.1)
rubyzip (>= 1.0)
rotp (6.2.0)
rotp (6.2.2)
rouge (3.30.0)
rqrcode (1.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 0.2)
rqrcode_core (0.2.0)
rspec-core (3.10.1)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.1)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-mocks (3.10.2)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-rails (5.0.0)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
rspec-core (~> 3.10)
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.10.2)
rspec-support (~> 3.12.0)
rspec-rails (6.0.1)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.11)
rspec-expectations (~> 3.11)
rspec-mocks (~> 3.11)
rspec-support (~> 3.11)
rspec-support (3.12.0)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.50.2)
@ -685,19 +662,14 @@ GEM
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
sentry-delayed_job (4.8.1)
sentry-delayed_job (5.9.0)
delayed_job (>= 4.0)
sentry-ruby-core (~> 4.8.1)
sentry-rails (4.8.1)
sentry-ruby (~> 5.9.0)
sentry-rails (5.9.0)
railties (>= 5.0)
sentry-ruby-core (~> 4.8.1)
sentry-ruby (4.8.1)
sentry-ruby (~> 5.9.0)
sentry-ruby (5.9.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
faraday (>= 1.0)
sentry-ruby-core (= 4.8.1)
sentry-ruby-core (4.8.1)
concurrent-ruby
faraday
shoulda-matchers (4.5.1)
activesupport (>= 4.2.0)
sib-api-v3-sdk (7.4.0)
@ -718,7 +690,7 @@ GEM
axlsx_styler (>= 1.0.0, < 2)
caxlsx (>= 2.0.2, < 4)
rodf (>= 1.0.0, < 2)
spring (2.1.1)
spring (4.1.1)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
sprockets (4.2.0)
@ -731,7 +703,6 @@ GEM
stackprof (0.2.21)
strong_migrations (0.8.0)
activerecord (>= 5.2)
strscan (3.0.4)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
@ -740,11 +711,11 @@ GEM
temple (0.8.2)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
thor (1.2.1)
thor (1.2.2)
thread_safe (0.3.6)
tilt (2.0.11)
timecop (0.9.4)
timeout (0.1.1)
timeout (0.3.2)
ttfunk (1.7.0)
turbo-rails (1.3.2)
actionpack (>= 6.0.0)
@ -767,8 +738,9 @@ GEM
activemodel (>= 3.0.0)
public_suffix
vcr (6.1.0)
view_component (2.63.0)
activesupport (>= 5.0.0, < 8.0)
view_component (2.82.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
virtus (2.0.0)
axiom-types (~> 0.1)
@ -777,7 +749,7 @@ GEM
vite_rails (3.0.14)
railties (>= 5.1, < 8)
vite_ruby (~> 3.0, >= 3.2.2)
vite_ruby (3.3.0)
vite_ruby (3.3.1)
dry-cli (>= 0.7, < 2)
rack-proxy (~> 0.6, >= 0.6.1)
zeitwerk (~> 2.2)
@ -815,7 +787,7 @@ GEM
nokogiri (~> 1.11)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.7)
zeitwerk (2.6.8)
zip_tricks (5.6.0)
zipline (1.4.1)
actionpack (>= 6.0, < 8.0)
@ -911,7 +883,7 @@ DEPENDENCIES
rack-attack
rack-mini-profiler
rack_session_access
rails
rails (~> 7.0.4)
rails-controller-testing
rails-erd
rails-i18n

View file

@ -25,4 +25,3 @@ $blue-france-500: #000091;
$blue-france-400: #7F7FC8;
$blue-cumulus-950: #E6EEFE;
$g700: #383838;
$alt-blue-france: rgba(245, 245, 254, 1);

View file

@ -39,6 +39,13 @@ input[type="radio"] {
}
// scss-lint:enable DuplicateProperty
// remove additional calendar icon on date input already handle by Firefox navigator
@-moz-document url-prefix() {
.fr-input[type="date"] {
background-image: none;
}
}
// remove pointer cursor on textarea
textarea {
cursor: auto;
@ -66,3 +73,20 @@ fieldset {
color: $light-red;
box-shadow: 0px 0px 0px 1px $light-red;
}
.fr-table table.hack-to-display-dropdown {
padding-bottom: 300px;
margin-bottom: -300px;
}
// on utilise le dropdown de sélecteur de langue pour un autre usage donc on veut retirer l'icone
.fr-translate .fr-translate__btn.custom-fr-translate-no-icon::before {
display: none;
}
// on veut ferrer à droite le dropdown de sélecteur de langue
@media (min-width: 62em) {
.fr-nav__item.custom-fr-translate-flex-end {
align-items: flex-end;
}
}

View file

@ -142,7 +142,7 @@ $landing-breakpoint: 1040px;
.usagers-panel,
.numbers-panel,
.cta-panel-2 {
background-color: $alt-blue-france;
background-color: var(--background-alt-blue-france);
}
.more-info {

View file

@ -1,9 +1,5 @@
.counter-start-header-section {
counter-reset: h1 h2 h3 h4 h5 h6;
.reset-h1 {
counter-reset: h2;
}
counter-reset: h2;
.reset-h2 {
counter-reset: h3;
@ -21,35 +17,46 @@
counter-reset: h6;
}
.reset-h6 {
counter-reset: h6;
}
.header-section.fr-h1::before {
counter-increment: h1;
content: counter(h1) ". ";
.reset-h5 {
counter-reset: h6;
}
.reset-h6 {
counter-reset: h7;
}
.header-section.fr-h2::before {
counter-increment: h2;
content: counter(h1) "."counter(h2) ". ";
content: counter(h2) ". ";
}
.header-section.fr-h3::before {
counter-increment: h3;
content: counter(h1) "."counter(h2) "." counter(h3) ". ";
content: counter(h2) "." counter(h3) ". ";
}
.header-section.fr-h4::before {
counter-increment: h4;
content: counter(h1) "."counter(h2) "." counter(h3) "." counter(h4) ". ";
content: counter(h2) "." counter(h3) "." counter(h4) ". ";
}
.header-section.fr-h5::before {
counter-increment: h5;
content: counter(h1) "."counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) ". ";
content: counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) ". ";
}
.header-section.fr-h6::before {
counter-increment: h6;
content: counter(h1) "."counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) ". ";
content: counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) ". ";
}
.header-section.fr-h7::before {
counter-increment: h7;
content: counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) "." counter(h7) ". ";
}
.repetition {

View file

@ -1,15 +1,8 @@
@import "colors";
@import "constants";
.status-overview {
text-align: center;
margin-bottom: $default-padding * 2;
}
.status-timeline {
display: inline-block;
margin-top: $default-padding * 2;
margin-bottom: $default-padding * 2;
border: 1px solid #808080;
border-radius: 3px;
@ -46,15 +39,6 @@
}
.status-explanation {
text-align: left;
.brouillon,
.en-construction,
.en-instruction {
max-width: 650px;
margin: auto;
}
p {
margin-bottom: $default-padding;
}

View file

@ -2,7 +2,7 @@
@import "constants";
.sub-header {
background-color: $alt-blue-france;
background-color: var(--background-alt-blue-france);
padding-top: $default-padding;
margin-bottom: $sub-header-bottom-margin;
border-bottom: 1px solid $border-grey;

View file

@ -93,7 +93,7 @@ class Attachment::EditComponent < ApplicationComponent
nil
end
def field_name
def field_name(object_name = nil, method_name = nil, *method_names, multiple: false, index: nil)
helpers.field_name(@form_object_name || ActiveModel::Naming.param_key(@attached_file.record), attribute_name)
end

View file

@ -5,9 +5,9 @@ en:
confirmation: Draft saved
error: Impossible to save the draft
en_construction:
explanation: Your file is automatically saved.
confirmation: File saved
error: Impossible to save the file
explanation: Your modifications are automatically saved. Submit them when youre done.
confirmation: Modifications saved
error: Impossible to save the modifications.
annotations:
explanation: Your annotations are automatically saved.
confirmation: Annotations saved

View file

@ -5,11 +5,11 @@ fr:
confirmation: Brouillon enregistré
error: Impossible denregistrer le brouillon
en_construction:
explanation: Votre dossier est automatiquement enregistré.
confirmation: Dossier enregistré
error: Impossible denregistrer le dossier
explanation: Vos modifications sont automatiquement enregistrées. Déposez-les quand vous aurez terminé.
confirmation: Modifications enregistrées.
error: Impossible denregistrer les modifications
annotations:
explanation: Vos annotations sont automatiquement enregistrées.
confirmation: Annotations enregistré
confirmation: Annotations enregistrées
error: Impossible denregistrer les annotations
more_information: En savoir plus

View file

@ -3,10 +3,10 @@
%span.autosave-explanation-text
- if annotation?
= t('.annotations.explanation')
- elsif dossier.brouillon?
= t('.brouillon.explanation')
- else
- elsif dossier.editing_fork?
= t('.en_construction.explanation')
- else
= t('.brouillon.explanation')
- if !annotation?
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
@ -15,10 +15,10 @@
%span.autosave-label
- if annotation?
= t('.annotations.confirmation')
- elsif dossier.brouillon?
= t('.brouillon.confirmation')
- else
- elsif dossier.editing_fork?
= t('.en_construction.confirmation')
- else
= t('.brouillon.confirmation')
- if !annotation?
= link_to t('.more_information'), t("links.common.faq.autosave_url"), class: 'autosave-more-infos fr-link fr-link--sm', **external_link_attributes
@ -27,11 +27,11 @@
%span.autosave-label
- if annotation?
= t('.annotations.error')
- elsif dossier.brouillon?
= t('.brouillon.error')
- else
- elsif dossier.editing_fork?
= t('.en_construction.error')
%button.button.small.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
%span.autosave-retry-label réessayer
%span.autosave-retrying-label enregistrement en cours…
- else
= t('.brouillon.error')
%button.fr-btn.fr-btn--tertiary.fr-btn--sm.autosave-retry{ type: :button, data: { action: 'autosave-status#onClickRetryButton', autosave_status_target: 'retryButton' } }
%span.autosave-retry-label Réessayer
%span.autosave-retrying-label Enregistrement en cours…

View file

@ -7,7 +7,7 @@ en:
in_progress:
text_success:
one: 1/1 is being archived
other: "%{progress_count}/%{count} files have been archived"
other: "%{success_count}/%{count} files have been archived"
passer_en_instruction:
finish:
text_success:
@ -16,7 +16,7 @@ en:
in_progress:
text_success:
one: 1/1 is being changed to instructing
other: "%{progress_count}/%{count} files have been changed to instructing"
other: "%{success_count}/%{count} files have been changed to instructing"
accepter:
finish:
text_success:
@ -25,7 +25,7 @@ en:
in_progress:
text_success:
one: 1/1 is being accepted
other: "%{progress_count}/%{count} files have been accepted"
other: "%{success_count}/%{count} files have been accepted"
follow:
finish:
text_success:
@ -34,7 +34,7 @@ en:
in_progress:
text_success:
one: 1/1 is being followed
other: "%{progress_count}/%{count} files have been followed"
other: "%{success_count}/%{count} files have been followed"
unfollow:
finish:
text_success:
@ -43,7 +43,7 @@ en:
in_progress:
text_success:
one: 1/1 is being unfollowed
other: "%{progress_count}/%{count} files have been unfollowed"
other: "%{success_count}/%{count} files have been unfollowed"
repasser_en_construction:
finish:
text_success:
@ -52,9 +52,10 @@ en:
in_progress:
text_success:
one: 1/1 is being changed to in progress
other: "%{progress_count}/%{count} files have been changed to in progress"
other: "%{success_count}/%{count} files have been changed to in progress"
title:
finish: The bulk action is finished
in_progress: A bulk action is processing
link_text: Refresh this webpage
after_link_text: to check if the process is over.
context: This bulk action was created by %{mail}, %{time_ago}

View file

@ -7,7 +7,7 @@ fr:
in_progress:
text_success:
one: 1 dossier sera archivé
other: "%{progress_count}/%{count} dossiers ont été archivés"
other: "%{success_count}/%{count} dossiers ont été archivés"
passer_en_instruction:
finish:
text_success:
@ -16,7 +16,7 @@ fr:
in_progress:
text_success:
one: 1 dossier sera passé en instruction
other: "%{progress_count}/%{count} dossiers ont été passés en instruction"
other: "%{success_count}/%{count} dossiers ont été passés en instruction"
accepter:
finish:
text_success:
@ -25,7 +25,7 @@ fr:
in_progress:
text_success:
one: 1 dossier sera accepté
other: "%{progress_count}/%{count} dossiers ont été acceptés"
other: "%{success_count}/%{count} dossiers ont été acceptés"
follow:
finish:
text_success:
@ -34,7 +34,7 @@ fr:
in_progress:
text_success:
one: 1 dossier sera suivi
other: "%{progress_count}/%{count} dossiers ont été suivis"
other: "%{success_count}/%{count} dossiers ont été suivis"
unfollow:
finish:
text_success:
@ -43,7 +43,7 @@ fr:
in_progress:
text_success:
one: 1 dossier ne sera plus suivi
other: "%{progress_count}/%{count} dossiers ne sont plus suivis"
other: "%{success_count}/%{count} dossiers ne sont plus suivis"
repasser_en_construction:
finish:
text_success:
@ -52,9 +52,10 @@ fr:
in_progress:
text_success:
one: 1 dossier sera repassé en construction
other: "%{progress_count}/%{count} dossiers ont été repassés en construction"
other: "%{success_count}/%{count} dossiers ont été repassés en construction"
title:
finish: L'action de masse est terminée
in_progress: Une action de masse est en cours
link_text: Recharger la page
after_link_text: pour voir si l'opération est finie.
context: Cette opération a été lancée par %{mail}, il y a %{time_ago}

View file

@ -9,8 +9,11 @@
- else
= render Dsfr::AlertComponent.new(title: t(".title.in_progress"), state: :info, heading_level: 'h2') do |c|
- c.body do
%p= t(".#{batch.operation}.in_progress.text_success", count: @batch.total_count, progress_count: @batch.progress_count)
%p= t(".#{batch.operation}.in_progress.text_success", count: @batch.total_count, success_count: @batch.success_count)
%p
= link_to t('.link_text'), instructeur_procedure_path(@procedure, statut: params["statut"]), data: { action: 'turbo-poll#refresh' }
= t('.after_link_text')
%p.fr-mt-2w
%small= t('.context', mail: @batch.instructeur.email, time_ago: time_ago_in_words(@batch.created_at))

View file

@ -14,7 +14,7 @@ class Dossiers::EditFooterComponent < ApplicationComponent
@annotation.present?
end
def button_options
def submit_draft_button_options
{
class: 'fr-btn fr-btn--sm',
disabled: !owner?,
@ -23,6 +23,14 @@ class Dossiers::EditFooterComponent < ApplicationComponent
}
end
def submit_en_construction_button_options
{
class: 'fr-btn fr-btn--sm',
method: :post,
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit' }
}
end
def render?
!@dossier.for_procedure_preview?
end

View file

@ -1,5 +1,6 @@
---
en:
submit: Submit the file
submit_changes: Submit file changes
submitting: Submitting…
invite_notice: You are invited to make amendments to this file but only the owner themselves can submit it.

View file

@ -1,5 +1,6 @@
---
fr:
submit: Déposer le dossier
submit_changes: Déposer les modifications
submitting: Envoi en cours…
invite_notice: En tant quinvité, vous pouvez remplir ce formulaire mais le titulaire du dossier doit le déposer lui-même.

View file

@ -3,7 +3,10 @@
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
- if !annotation? && @dossier.can_transition_to_en_construction?
= button_to t('.submit'), brouillon_dossier_url(@dossier), button_options
= button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options
- elsif @dossier.forked_with_changes?
= button_to t('.submit_changes'), modifier_dossier_url(@dossier.editing_fork_origin), submit_en_construction_button_options
- if @dossier.brouillon? && !owner?
.send-notice.invite-cannot-submit

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class Dossiers::EnConstructionNotSubmittedComponent < ApplicationComponent
attr_reader :dossier
attr_reader :user
def initialize(dossier:, user:)
@dossier = dossier
@user = user
@fork = @dossier.find_editing_fork(user, rebase: false)
end
def render?
@fork&.forked_with_changes?
end
end

View file

@ -0,0 +1,9 @@
---
en:
title: Modifications not yet submitted
body: |
You have made changes after submitting your file. Submit them
so that the administration can take them into account.
buttons:
edit: Continue to edit
submit: Submit file changes

View file

@ -0,0 +1,9 @@
---
fr:
title: Des modifications nont pas encore été déposées
body: |
Vous avez apporté des modifications après le dépôt de votre dossier.
Déposez-les afin que ladministration les prenne en compte.
buttons:
edit: Continuer à modifier
submit: Déposer les modifications

View file

@ -0,0 +1,9 @@
= render Dsfr::CalloutComponent.new(title: t(".title"), icon: "fr-fi-information-line") do |c|
- c.body do
= t(".body")
- c.bottom do
%ul.fr-mt-2w.fr-btns-group.fr-btns-group--inline
%li= link_to t(".buttons.edit"), modifier_dossier_path(dossier), class: "fr-btn"
%li= button_to t(".buttons.submit"), modifier_dossier_path(dossier), class: "fr-btn fr-btn--secondary", method: :post

View file

@ -11,7 +11,7 @@
- elsif export.available?
- menu.with_item do
%div
= link_to ready_link_label(export), export.file.service_url, target: "_blank", rel: "noopener", role: 'menuitem'
= link_to ready_link_label(export), export.file.url, target: "_blank", rel: "noopener", role: 'menuitem'
- if export.old?
= button_to download_export_path(export_format: export.format, force_export: true), refresh_button_options(export).merge(role: 'menuitem') do
.icon.retry

View file

@ -1,4 +1,4 @@
class Dossiers::FilterComponent < ApplicationComponent
class Dossiers::InstructeurFilterComponent < ApplicationComponent
def initialize(procedure:, procedure_presentation:, statut:, field_id: nil)
@procedure = procedure
@procedure_presentation = procedure_presentation

View file

@ -0,0 +1,23 @@
class Dossiers::UserFilterComponent < ApplicationComponent
include DossierHelper
def initialize(statut:, filter:)
@statut = statut
@filter = filter
end
attr_reader :statut, :filter
def render?
['en-cours', 'traites'].include?(@statut)
end
def states_collection(statut)
case statut
when 'en-cours'
Dossier.states.values - Dossier::TERMINE
when 'traites'
Dossier::TERMINE
end
end
end

View file

@ -0,0 +1,16 @@
en:
legend:
states: States
created_at: Creation date
depose_at: Submission date
button:
apply_filters: Apply filters
reset_filters: Reset filters
select_filters: Select a filter
tag:
active_filters:
one: "1 active filter"
other: "%{count} active filters"
active_filters_link_title:
one: Remove the active filter
other: "Remove the %{count} active filters"

View file

@ -0,0 +1,16 @@
fr:
legend:
states: États
created_at: Date de création
depose_at: Date de dépot
button:
apply_filters: Appliquer les filtres
reset_filters: Réinitialiser les filtres
select_filters: Sélectionner un filtre
tag:
active_filters:
one: "1 filtre actif"
other: "%{count} filtres actifs"
active_filters_link_title:
one: Retirer le filtre actif
other: "Retirer les %{count} filtres actifs"

View file

@ -0,0 +1,37 @@
.fr-grid-row
.fr-col-12
%nav.fr-translate.fr-nav
.fr-nav__item.custom-fr-translate-flex-end
%button.fr-translate__btn.translate-no-icon.fr-btn.fr-btn--tertiary.custom-fr-translate-no-icon{ "aria-controls" => "filters", "aria-expanded" => "false", title: t('.button.select_filters') }
= t('.button.select_filters')
#filters.fr-collapse.fr-translate__menu.fr-menu
%ul.fr-menu__list.fr-p-3w
= form_with(url: dossiers_path(), method: :get ) do |f|
= f.hidden_field :statut, value: @statut
%fieldset#checkboxes.fr-fieldset{ "aria-labelledby" => "checkboxes-legend" }
%legend#checkboxes-legend.fr-fieldset__legend--regular.fr-fieldset__legend
= t('.legend.states')
= f.collection_check_boxes :states, states_collection(@statut), :to_s, :to_s, include_hidden: false do |b|
.fr-fieldset__element
.fr-checkbox-group.fr-ml-2w.fr-py-1w
= b.check_box(checked: filter.states_filtered?(b.value))
= b.label(class: 'fr-label') { dossier_display_state(b.text) }
.fr-input-group
= f.label 'from_created_at_date', t('.legend.created_at'), class: 'fr-label'
= f.date_field 'from_created_at_date', value: @filter.from_created_at_date, class: 'fr-input'
.fr-input-group
= f.label 'from_depose_at_date', t('.legend.depose_at'), class: 'fr-label'
= f.date_field 'from_depose_at_date', value: @filter.from_depose_at_date, class: 'fr-input'
.fr-my-2w
= f.submit t('.button.apply_filters'), class: 'fr-btn fr-btn--sm'
= link_to t('.button.reset_filters'), dossiers_path(statut: @statut), class: 'fr-btn fr-btn--sm fr-btn--tertiary-no-outline'
- if @filter.filter_params.present?
.fr-col-12.text-right
= link_to t('.tag.active_filters', count: @filter.filter_params_count), dossiers_path(statut: @statut), title: t('.tag.active_filters_link_title', count: @filter.filter_params_count), class: 'fr-tag fr-tag--sm fr-tag--dismiss'

View file

@ -27,7 +27,7 @@ class Dsfr::CalloutComponent < ApplicationComponent
when :success
"fr-callout--green-emeraude"
else
# info is default theme
"fr-background-alt--blue-france"
end
end
end

View file

@ -0,0 +1,7 @@
class Dsfr::RadioButtonListComponent < ApplicationComponent
def initialize(form:, target:, buttons:)
@form = form
@target = target
@buttons = buttons
end
end

View file

@ -0,0 +1,13 @@
%fieldset.fr-fieldset{ 'aria-labelledby': 'radio-hint-element-legend radio-hint-element-messages' }
%legend.fr-fieldset__legend--regular.fr-fieldset__legend
= content
- @buttons.map { _1.values_at(:label, :value, :hint) }.each do |label, value, hint|
.fr-fieldset__element
.fr-radio-group
= @form.radio_button @target, value
= @form.label @target, value: value, class: 'fr-label' do
- capture do
= label
%span.fr-hint-text= hint
.fr-messages-group{ 'aria-live': 'assertive' }

View file

@ -0,0 +1,9 @@
class Dsfr::ToggleComponent < ApplicationComponent
def initialize(form:, target:, title:, hint:, disabled:)
@form = form
@target = target
@title = title
@hint = hint
@disabled = disabled
end
end

View file

@ -0,0 +1,7 @@
.fr-toggle.fr-toggle--border-bottom.fr-toggle--label-left
= @form.check_box @target, class: 'fr-toggle__input', disabled: @disabled
= @form.label @target,
@title,
class: 'fr-toggle__label',
data: { 'fr-checked-label': 'Activé', 'fr-unchecked-label': 'Désactivé' }
%p.fr-hint-text= @hint

View file

@ -3,7 +3,7 @@
= @form.label @champ.main_value_name, id: @champ.labelledby_id, for: @champ.input_id do
- render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
- else
.form-label.mb-4
.form-label.mb-4{ id: @champ.labelledby_id }
= render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
- if @champ.description.present?

View file

@ -0,0 +1,5 @@
---
en:
changes_to_save: "modifications to submit"
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.

View file

@ -0,0 +1,5 @@
---
fr:
changes_to_save: "modification à déposer"
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.

View file

@ -2,10 +2,13 @@
- if @champ.mandatory?
%span.mandatory *
- if @champ.updated_at.present? && @seen_at.present?
- if @champ.forked_with_changes?
%span.updated-at.highlighted
= t('.changes_to_save')
- elsif @champ.updated_at.present? && @seen_at.present?
%span.updated-at{ class: highlight_if_unseen_class }
= "modifié le #{try_format_datetime(@champ.updated_at)}"
= t('.modified_at', datetime: try_format_datetime(@champ.updated_at))
- if @champ.rebased_at.present? && @champ.rebased_at > (@seen_at || @champ.updated_at) && current_user.owns_or_invite?(@champ.dossier)
%span.updated-at.highlighted
Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.
= t('.check_content_rebased')

View file

@ -10,7 +10,7 @@
= render EditableChamp::EditableChampComponent.new form: ,champ:
- else
- if header_section
= render EditableChamp::HeaderSectionComponent.new(champ: header_section)
%div{ class: "reset-#{tag_for_depth}" }= render EditableChamp::HeaderSectionComponent.new(champ: header_section)
- splitted_tail.each do |section, champ|
- if section.present?
= render section

View file

@ -1,3 +1,4 @@
en:
remaining_characters: You have %{remaining_words} characters remaining.
excess_characters: You have %{excess_words} characters too many.
recommended_size: The recommended maximum size is %{size} characters.

View file

@ -1,3 +1,4 @@
fr:
remaining_characters: Il vous reste %{remaining_words} caractères.
excess_characters: Vous avez dépassé la taille conseillée de %{excess_words} caractères.
excess_characters: Vous avez dépassé la taille conseillée de %{excess_words} caractères. Réduire le nombre de caractère.
recommended_size: La taille maximale conseillée est de %{size} caractères.

View file

@ -1,6 +1,4 @@
- character_limit = @champ.textarea_character_limit.to_i if !@champ.textarea_character_limit&.empty?
- character_count = @champ.character_count(@champ.value)
- analyze_character_count = @champ.analyze_character_count(character_count, character_limit)
%span.sr-only= t('.recommended_size', size: @champ.character_limit_base)
~ @form.text_area :value,
id: @champ.input_id,
@ -9,12 +7,10 @@
required: @champ.required?,
value: html_to_string(@champ.value)
- if @champ.textarea_character_limit?
- if @champ.character_limit_info?
%span.fr-icon-information-fill.fr-text-default--info.characters-count
= t('.remaining_characters', remaining_words: @champ.remaining_characters)
- if analyze_character_count == :info
%span.fr-icon-information-fill.fr-text-default--info.characters-count
= t('.remaining_characters', remaining_words: @champ.remaining_characters(character_count, character_limit))
- if analyze_character_count == :warning
%span.fr-icon-close-circle-fill.fr-text-default--error.characters-count
= t('.excess_characters', excess_words: @champ.excess_characters(character_count, character_limit))
- if @champ.character_limit_warning?
%span.fr-icon-warning-fill.fr-text-default--warning.characters-count
= t('.excess_characters', excess_words: @champ.excess_characters)

View file

@ -36,8 +36,8 @@ fr:
add_condition: Une condition a été ajoutée sur le champ « %{label} ». La nouvelle condition est « %{to} ».
remove_condition: La condition du champ « %{label} » a été supprimée.
update_condition: La condition du champ « %{label} » a été modifiée. La nouvelle condition est « %{to} ».
update_textarea_character_limit: La limite de caractères du champ texte « %{label} » a été modifiée. La nouvelle limite est « %{to} ».
remove_textarea_character_limit: La limite de caractères du champ texte « %{label} » a été supprimée.
update_character_limit: La limite de caractères du champ texte « %{label} » a été modifiée. La nouvelle limite est « %{to} ».
remove_character_limit: La limite de caractères du champ texte « %{label} » a été supprimée.
private:
add: Lannotation privée « %{label} » a été ajoutée.
remove: Lannotation privée « %{label} » a été supprimée.

View file

@ -130,14 +130,14 @@
= t(".#{prefix}.update_condition", label: change.label, to: change.to)
- if !total_dossiers.zero? && !change.can_rebase?
.fr-alert.fr-alert--warning.fr-mt-1v
%p= t('.breakigng_change', count: total_dossiers)
- when :textarea_character_limit
- if change.to.nil?
%p= t('.breaking_change', count: total_dossiers)
- when :character_limit
- if change.to.blank?
- list.with_item do
= t(".#{prefix}.remove_textarea_character_limit", label: change.label, to: change.to)
= t(".#{prefix}.remove_character_limit", label: change.label, to: change.to)
- else
- list.with_item do
= t(".#{prefix}.update_textarea_character_limit", label: change.label, to: change.to)
= t(".#{prefix}.update_character_limit", label: change.label, to: change.to)
- if @public_move_changes.present?
- list.with_item do

View file

@ -121,4 +121,14 @@ class TypesDeChampEditor::ChampComponent < ApplicationComponent
def conditional_enabled?
!type_de_champ.private?
end
def options_for_character_limit
[
[t('.character_limit.unlimited'), nil],
[t('.character_limit.limit', limit: '400'), 400],
[t('.character_limit.limit', limit: '1 000'), 1000],
[t('.character_limit.limit', limit: '5 000'), 5000],
[t('.character_limit.limit', limit: '10 000'), 10000]
]
end
end

View file

@ -10,3 +10,6 @@ fr:
natura_2000: Natura 2000
zones_humides: Zones humides dimportance internationale
znieff: ZNIEFF
character_limit:
unlimited: Pas de limite de caractère
limit: Limité à %{limit} caractères

View file

@ -103,9 +103,9 @@
= form.text_area :collapsible_explanation_text, class: "small-margin small", id: dom_id(type_de_champ, :collapsible_explanation_text)
- if type_de_champ.textarea?
.cell
= form.label :textarea_character_limit, for: dom_id(type_de_champ, :textarea_character_limit) do
Spécifier un nombre maximal de caractères (non restrictif) :
= form.number_field :textarea_character_limit, min: 400, value: form.object.textarea_character_limit, id: dom_id(type_de_champ, :textarea_character_limit)
= form.label :character_limit, for: dom_id(type_de_champ, :character_limit) do
Spécifier un nombre maximal conseillé de caractères :
= form.select :character_limit, options_for_character_limit, id: dom_id(type_de_champ, :character_limit)
- if type_de_champ.block?
.flex.justify-start.section.ml-1

View file

@ -15,7 +15,7 @@ module Administrateurs
end
format.html do
redirect_to export.file.service_url
redirect_to url_from(export.file.url)
end
end
else

View file

@ -169,7 +169,7 @@ module Administrateurs
new_procedure = procedure.clone(current_administrateur, cloned_from_library?)
if new_procedure.valid?
flash.notice = 'Démarche clonée, pensez a vérifier la Présentation et choisir le service a laquelle cette procédure est associé.'
flash.notice = 'Démarche clonée. Pensez à vérifier la présentation et choisir le service à laquelle cette démarche est associée.'
redirect_to admin_procedure_path(id: new_procedure.id)
else
if cloned_from_library?

View file

@ -74,7 +74,7 @@ module Administrateurs
def destroy
coordinate, type_de_champ = draft.coordinate_and_tdc(params[:stable_id])
if coordinate.used_by_routing_rules?
if coordinate&.used_by_routing_rules?
errors = "« #{type_de_champ.libelle} » est utilisé pour le routage, vous ne pouvez pas le supprimer."
@morphed = [champ_component_from(coordinate, focused: false, errors:)]
flash.alert = errors
@ -129,7 +129,7 @@ module Administrateurs
:collapsible_explanation_enabled,
:collapsible_explanation_text,
:header_section_level,
:textarea_character_limit,
:character_limit,
editable_options: [
:cadastres,
:unesco,

View file

@ -15,7 +15,7 @@ class AgentConnect::AgentController < ApplicationController
cookies.encrypted[STATE_COOKIE_NAME] = state
cookies.encrypted[NONCE_COOKIE_NAME] = nonce
redirect_to uri
redirect_to uri, allow_other_host: true
end
def callback

View file

@ -287,7 +287,8 @@ class ApplicationController < ActionController::Base
enabled: sentry[:enabled],
environment: sentry[:environment],
browser: { modern: BrowserSupport.supported?(browser) },
user: sentry_user
user: sentry_user,
release: SentryRelease.current
}
end

View file

@ -6,7 +6,7 @@ module TurboChampsConcern
def champs_to_turbo_update(params, champs)
champ_ids = params.keys.map(&:to_i)
to_update = champs.filter { _1.id.in?(champ_ids) && _1.refresh_after_update? }
to_update = champs.filter { _1.id.in?(champ_ids) && (_1.refresh_after_update? || _1.forked_with_changes?) }
to_show, to_hide = champs.filter(&:conditional?)
.partition(&:visible?)
.map { champs_to_one_selector(_1 - to_update) }

View file

@ -13,7 +13,10 @@ module Experts
DONNES_STATUS = 'donnes'
def index
avis = current_expert.avis.not_revoked.not_termine.includes(dossier: [groupe_instructeur: :procedure]).not_hidden_by_administration
avis = current_expert.avis
.not_revoked
.includes(dossier: [groupe_instructeur: :procedure])
.not_hidden_by_administration
@avis_by_procedure = avis.to_a.group_by(&:procedure)
end

View file

@ -4,7 +4,7 @@ class FranceConnect::ParticulierController < ApplicationController
def login
if FranceConnectService.enabled?
redirect_to FranceConnectService.authorization_uri
redirect_to FranceConnectService.authorization_uri, allow_other_host: true
else
redirect_to new_user_session_path
end

View file

@ -19,7 +19,7 @@ module Instructeurs
def attestation
if dossier.attestation.pdf.attached?
redirect_to dossier.attestation.pdf.service_url
redirect_to dossier.attestation.pdf.url, allow_other_host: true
end
end

View file

@ -17,13 +17,12 @@ module Instructeurs
dossiers = current_instructeur.dossiers
.joins(groupe_instructeur: :procedure)
.where(procedures: { hidden_at: nil })
dossiers_visibles = dossiers.visible_by_administration
@dossiers_count_per_procedure = dossiers_visibles.all_state.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_a_suivre_count_per_procedure = dossiers_visibles.without_followers.en_cours.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_archived_count_per_procedure = dossiers_visibles.archived.group('groupe_instructeurs.procedure_id').count
@dossiers_termines_count_per_procedure = dossiers_visibles.termine.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_expirant_count_per_procedure = dossiers_visibles.termine_or_en_construction_close_to_expiration.group('groupe_instructeurs.procedure_id').count
@dossiers_supprimes_recemment_count_per_procedure = dossiers.hidden_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_count_per_procedure = dossiers.by_statut('tous').group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_a_suivre_count_per_procedure = dossiers.by_statut('a-suivre').group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_archived_count_per_procedure = dossiers.by_statut('archives').group('groupe_instructeurs.procedure_id').count
@dossiers_termines_count_per_procedure = dossiers.by_statut('traites').group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_expirant_count_per_procedure = dossiers.by_statut('expirant').group('groupe_instructeurs.procedure_id').count
@dossiers_supprimes_recemment_count_per_procedure = dossiers.by_statut('supprimes_recemment').group('groupe_instructeurs.procedure_id').reorder(nil).count
groupe_ids = current_instructeur.groupe_instructeurs.pluck(:id)
@followed_dossiers_count_per_procedure = current_instructeur
@ -175,7 +174,7 @@ module Instructeurs
end
format.html do
redirect_to export.file.service_url
redirect_to url_from(export.file.url)
end
end
else
@ -315,6 +314,7 @@ module Instructeurs
Procedure
.with_attached_logo
.find(procedure_id)
.tap { Sentry.set_tags(procedure: _1.id) }
end
def ensure_ownership!
@ -343,7 +343,7 @@ module Instructeurs
end
def current_filters
@current_filters ||= procedure_presentation.filters[statut]
@current_filters ||= procedure_presentation.filters.fetch(statut, [])
end
def email_usagers_dossiers

View file

@ -6,15 +6,16 @@ module Users
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new, :transferer_all]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :destroy, :demande, :messagerie, :brouillon, :update_brouillon, :submit_brouillon, :modifier, :update, :create_commentaire, :papertrail, :restore]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :destroy, :demande, :messagerie, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :update, :create_commentaire, :papertrail, :restore]
before_action :ensure_ownership!, except: ACTIONS_ALLOWED_TO_ANY_USER + ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
before_action :ensure_ownership_or_invitation!, only: ACTIONS_ALLOWED_TO_OWNER_OR_INVITE
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_siret, :brouillon, :update_brouillon, :submit_brouillon, :modifier, :update]
before_action :ensure_dossier_can_be_filled, only: [:brouillon, :modifier, :update_brouillon, :submit_brouillon, :update]
before_action :ensure_dossier_can_be_updated, only: [:update_identite, :update_siret, :brouillon, :submit_brouillon, :submit_en_construction, :modifier, :modifier_legacy, :update]
before_action :ensure_dossier_can_be_filled, only: [:brouillon, :modifier, :submit_brouillon, :submit_en_construction, :update]
before_action :ensure_dossier_can_be_viewed, only: [:show]
before_action :forbid_invite_submission!, only: [:submit_brouillon]
before_action :forbid_closed_submission!, only: [:submit_brouillon]
before_action :set_dossier_as_editing_fork, only: [:submit_en_construction]
before_action :show_demarche_en_test_banner
before_action :store_user_location!, only: :new
@ -49,6 +50,9 @@ module Users
end.page(page)
@first_brouillon_recently_updated = current_user.dossiers.visible_by_user.brouillons_recently_updated.first
@filter = DossiersFilter.new(current_user, params)
@dossiers = @filter.filter_procedures(@dossiers).page(page)
end
def show
@ -73,7 +77,7 @@ module Users
def attestation
if dossier.attestation&.pdf&.attached?
redirect_to dossier.attestation.pdf.service_url
redirect_to dossier.attestation.pdf.url, allow_other_host: true
else
flash.notice = t('.no_longer_available')
redirect_to dossier_path(dossier)
@ -171,6 +175,7 @@ module Users
errors = submit_dossier_and_compute_errors
if errors.blank?
RoutingEngine.compute(@dossier)
@dossier.passer_en_construction!
@dossier.process_declarative!
NotificationMailer.send_en_construction_notification(@dossier).deliver_later
@ -199,21 +204,46 @@ module Users
@dossier = dossier_with_champs
end
def update_brouillon
@dossier = dossier_with_champs
update_dossier_and_compute_errors
# Transition to en_construction forks,
# so users editing en_construction dossiers won't completely break their changes.
# TODO: remove me after fork en_construction feature deploy (PR #8790)
def modifier_legacy
respond_to do |format|
format.html { render :brouillon }
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
flash.alert = "Une mise à jour de cette page est nécessaire pour poursuivre, veuillez la recharger (touche F5). Attention: le dernier champ modifié na pas été sauvegardé, vous devrez le ressaisir."
end
end
end
render(:update, layout: false)
def submit_en_construction
@dossier = dossier_with_champs(pj_template: false)
errors = submit_dossier_and_compute_errors
if errors.blank?
editing_fork_origin = @dossier.editing_fork_origin
editing_fork_origin.merge_fork(@dossier)
RoutingEngine.compute(editing_fork_origin)
redirect_to dossier_path(editing_fork_origin)
else
flash.now.alert = errors
respond_to do |format|
format.html do
@dossier = @dossier.editing_fork_origin
render :modifier
end
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
render :update, layout: false
end
end
end
end
def update
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
@dossier = dossier_with_champs(pj_template: false)
errors = update_dossier_and_compute_errors
@ -222,9 +252,9 @@ module Users
end
respond_to do |format|
format.html { render :modifier }
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_params.fetch(:champs_public_all_attributes), dossier.champs_public_all)
render :update, layout: false
end
end
end
@ -422,8 +452,8 @@ module Users
end
def dossier_scope
if action_name == 'update_brouillon'
Dossier.visible_by_user.or(Dossier.for_procedure_preview)
if action_name == 'update'
Dossier.visible_by_user.or(Dossier.for_procedure_preview).or(Dossier.for_editing_fork)
elsif action_name == 'restore'
Dossier.hidden_by_user
else
@ -441,6 +471,15 @@ module Users
DossierPreloader.load_one(dossier, pj_template:)
end
def set_dossier_as_editing_fork
@dossier = dossier.find_editing_fork(dossier.user)
return if @dossier.present?
flash[:alert] = t('users.dossiers.en_construction_submitted')
redirect_to dossier_path(dossier)
end
def should_change_groupe_instructeur?
if params[:dossier].key?(:groupe_instructeur_id)
groupe_instructeur_id = params[:dossier][:groupe_instructeur_id]
@ -469,27 +508,18 @@ module Users
def update_dossier_and_compute_errors
errors = []
@dossier.assign_attributes(champs_public_params)
if @dossier.champs_public_all.any?(&:changed_for_autosave?)
@dossier.last_champ_updated_at = Time.zone.now
end
if !@dossier.save(**validation_options)
errors += @dossier.errors.full_messages
errors += format_errors(errors: @dossier.errors)
end
if should_change_groupe_instructeur?
@dossier.assign_to_groupe_instructeur(groupe_instructeur_from_params)
end
if @dossier.procedure.feature_enabled?(:routing_rules)
RoutingEngine.compute(@dossier)
end
if dossier.en_construction?
errors += @dossier.check_mandatory_and_visible_champs
end
errors
end
@ -497,20 +527,46 @@ module Users
errors = []
@dossier.valid?(**submit_validation_options)
errors += @dossier.errors.full_messages
errors += @dossier.check_mandatory_and_visible_champs
errors += format_errors(errors: @dossier.errors)
errors += format_errors(errors: @dossier.check_mandatory_and_visible_champs)
if should_fill_groupe_instructeur?
@dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur)
end
if @dossier.groupe_instructeur.nil?
errors << "Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"
if !@dossier.procedure.feature_enabled?(:routing_rules) && @dossier.groupe_instructeur.nil?
errors += format_errors(errors: ["Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"])
end
errors
end
def format_errors(errors:)
errors.map do |active_model_error|
case active_model_error.class.name
when "ActiveModel::NestedError"
append_anchor_link(active_model_error.full_message, active_model_error.inner_error.base)
when "ActiveModel::Error"
append_anchor_link(active_model_error.full_message, active_model_error.base)
else # "String"
active_model_error
end
end
end
def append_anchor_link(str_error, model)
return str_error.full_message if !model.is_a?(Champ)
route_helper = @dossier.editing_fork? ? :modifier_dossier_path : :brouillon_dossier_path
[
"Le champ « #{model.libelle.truncate(200)} » #{str_error}",
helpers.link_to(t('views.users.dossiers.fix_champ'), public_send(route_helper, anchor: model.labelledby_id), class: 'error-anchor')
].join(", ")
rescue # case of invalid type de champ on champ
str_error
end
def ensure_ownership!
if !current_user.owns?(dossier)
forbidden!

View file

@ -36,7 +36,7 @@ class Users::SessionsController < Devise::SessionsController
case connected_with_france_connect
when User.loged_in_with_france_connects.fetch(:particulier)
redirect_to FRANCE_CONNECT[:particulier][:logout_endpoint]
redirect_to FRANCE_CONNECT[:particulier][:logout_endpoint], allow_other_host: true
return
end
end

View file

@ -134,7 +134,6 @@ class API::V2::Schema < GraphQL::Schema
class Timeout < GraphQL::Schema::Timeout
def handle_timeout(error, query)
error.extensions = { code: :timeout }
Sentry.capture_exception(error, extra: query.context.query_info)
end
end

View file

@ -263,6 +263,7 @@ class API::V2::StoredQuery
usager {
email
}
connectionUsager
groupeInstructeur {
...GroupeInstructeurFragment
}

View file

@ -42,7 +42,10 @@ module Loaders
end
def preload_association(records)
::ActiveRecord::Associations::Preloader.new.preload(records, @association_schema)
::ActiveRecord::Associations::Preloader.new(
records: records,
associations: @association_schema
).call
end
def read_association(record)

View file

@ -11,7 +11,7 @@ module Mutations
field :errors, [Types::ValidationErrorType], null: true
def resolve(dossier:, groupe_instructeur:)
dossier.update!(groupe_instructeur:)
dossier.assign_to_groupe_instructeur(groupe_instructeur)
{ dossier: }
end

View file

@ -28,7 +28,9 @@ module Mutations
end
def authorized?(dossier:, instructeur:, **args)
if !dossier.en_instruction?
if dossier.en_instruction? && dossier.any_etablissement_as_degraded_mode?
return false, { errors: ["Les informations du SIRET du dossier ne sont pas complètes. Veuillez réessayer plus tard."] }
elsif !dossier.en_instruction? || !dossier.can_terminer?
return false, { errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
end
dossier_authorized_for?(dossier, instructeur)

View file

@ -28,7 +28,9 @@ module Mutations
end
def authorized?(dossier:, instructeur:, **args)
if !dossier.en_instruction?
if dossier.en_instruction? && dossier.any_etablissement_as_degraded_mode?
return false, { errors: ["Les informations du SIRET du dossier ne sont pas complètes. Veuillez réessayer plus tard."] }
elsif !dossier.en_instruction? || !dossier.can_terminer?
return false, { errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
end
dossier_authorized_for?(dossier, instructeur)

View file

@ -462,6 +462,23 @@ type CommuneChampDescriptor implements ChampDescriptor {
type: TypeDeChamp! @deprecated(reason: "Utilisez le champ `__typename` à la place.")
}
enum ConnectionUsager {
"""
Compte supprimé
"""
deleted
"""
Connexion via FranceConnect
"""
france_connect
"""
Connexion via mot de passe
"""
password
}
"""
GeoJSON coordinates
"""
@ -1199,6 +1216,7 @@ type Dossier {
attestation: File
avis(id: ID): [Avis!]!
champs(id: ID): [Champ!]!
connectionUsager: ConnectionUsager!
"""
Date de dépôt.

View file

@ -6,6 +6,12 @@ module Types
end
end
class ConnectionUsager < Types::BaseEnum
value(:france_connect, "Connexion via FranceConnect", value: :france_connect)
value(:password, "Connexion via mot de passe", value: :password)
value(:deleted, "Compte supprimé", value: :deleted)
end
description "Un dossier"
global_id_field :id
@ -26,6 +32,8 @@ module Types
field :archived, Boolean, null: false
field :connection_usager, ConnectionUsager, null: false
field :motivation, String, null: true
field :motivation_attachment, Types::File, null: true, extensions: [
{ Extensions::Attachment => { attachment: :justificatif_motivation } }
@ -67,11 +75,25 @@ module Types
end
end
def connection_usager
if object.user_deleted?
:deleted
else
user_loader.then do |user|
if user.france_connect_information.present?
:france_connect
else
:password
end
end
end
end
def usager
if object.user_deleted?
{ email: object.user_email_for(:display), id: '<deleted>' }
else
Loaders::Record.for(User).load(object.user_id)
user_loader
end
end
@ -173,5 +195,11 @@ module Types
def self.authorized?(object, context)
context.authorized_demarche?(object.revision.procedure)
end
private
def user_loader
Loaders::Record.for(User, includes: :france_connect_information).load(object.user_id)
end
end
end

View file

@ -11,7 +11,7 @@ module Types
if object.is_a?(Hash)
object[:url]
else
object.service_url
object.url
end
end
end

View file

@ -1,39 +0,0 @@
module FormTagHelper
# from Rails 7 ActionView::Helpers::FormTagHelper
# https://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-field_id
# Should be removed when we upgrade to Rails 7
def field_id(object_name, method_name, *suffixes, index: nil, namespace: nil)
if object_name.respond_to?(:model_name)
object_name = object_name.model_name.singular
end
sanitized_object_name = object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
sanitized_method_name = method_name.to_s.delete_suffix("?")
[
namespace,
sanitized_object_name.presence,
(index unless sanitized_object_name.empty?),
sanitized_method_name,
*suffixes
].tap(&:compact!).join("_")
end
# from Rails 7 ActionView::Helpers::FormTagHelper
# https://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-field_name
# Should be removed when we upgrade to Rails 7
def field_name(object_name, method_name, *method_names, multiple: false, index: nil)
names = method_names.map! { |name| "[#{name}]" }.join
# a little duplication to construct fewer strings
case
when object_name.blank?
"#{method_name}#{names}#{multiple ? "[]" : ""}"
when index
"#{object_name}[#{index}][#{method_name}]#{names}#{multiple ? "[]" : ""}"
else
"#{object_name}[#{method_name}]#{names}#{multiple ? "[]" : ""}"
end
end
end

View file

@ -1,3 +1,4 @@
import 'core-js/proposals/relative-indexing-method';
import Rails from '@rails/ujs';
import * as ActiveStorage from '@rails/activestorage';
import * as Turbo from '@hotwired/turbo';

View file

@ -35,3 +35,4 @@
@import '@gouvfr/dsfr/dist/component/pagination/pagination.css';
@import '@gouvfr/dsfr/dist/component/skiplink/skiplink.css';
@import '@gouvfr/dsfr/dist/component/password/password.css';
@import '@gouvfr/dsfr/dist/component/accordion/accordion.css';

View file

@ -2,13 +2,14 @@ import * as Sentry from '@sentry/browser';
import { getConfig } from '@utils';
const {
sentry: { key, enabled, user, environment, browser }
sentry: { key, enabled, user, environment, browser, release }
} = getConfig();
// We need to check for key presence here as we do not have a dsn for browser yet
if (enabled && key) {
Sentry.init({
dsn: key,
release: release ?? undefined,
environment,
tracesSampleRate: 0.1,
ignoreErrors: [

View file

@ -32,7 +32,8 @@ const Gon = z
enabled: z.boolean().default(false),
environment: z.string().optional(),
user: z.object({ id: z.string() }).default({ id: '' }),
browser: z.object({ modern: z.boolean() }).default({ modern: false })
browser: z.object({ modern: z.boolean() }).default({ modern: false }),
release: z.string().nullish()
})
.default({}),
crisp: z

View file

@ -5,7 +5,7 @@ class Cron::Datagouv::ExportAndPublishDemarchesPubliquesJob < Cron::CronJob
def perform(*args)
gzip_filepath = [
'tmp/',
Time.zone.now.to_formatted_s(:number),
Time.zone.now.to_fs(:number),
'-demarches.json.gz'
].join

View file

@ -1,5 +1,5 @@
class Cron::InstructeurEmailNotificationJob < Cron::CronJob
self.schedule_expression = "from monday through friday at 10 am"
self.schedule_expression = "from monday through friday at 9 am"
def perform(*args)
NotificationService.send_instructeur_email_notification

View file

@ -0,0 +1,7 @@
class DestroyRecordLaterJob < ApplicationJob
discard_on ActiveRecord::RecordNotFound
def perform(record)
record.destroy
end
end

View file

@ -0,0 +1,8 @@
class DossierUpdateSearchTermsJob < ApplicationJob
discard_on ActiveRecord::RecordNotFound
def perform(dossier)
dossier.update_search_terms
dossier.save!(touch: false)
end
end

View file

@ -1,14 +1,14 @@
class APIEntreprise::API
ENTREPRISE_RESOURCE_NAME = "entreprises"
ETABLISSEMENT_RESOURCE_NAME = "etablissements"
EXERCICES_RESOURCE_NAME = "exercices"
RNA_RESOURCE_NAME = "associations"
EFFECTIFS_RESOURCE_NAME = "effectifs_mensuels_acoss_covid"
EFFECTIFS_ANNUELS_RESOURCE_NAME = "effectifs_annuels_acoss_covid"
ATTESTATION_SOCIALE_RESOURCE_NAME = "attestations_sociales_acoss"
ATTESTATION_FISCALE_RESOURCE_NAME = "attestations_fiscales_dgfip"
BILANS_BDF_RESOURCE_NAME = "bilans_entreprises_bdf"
PRIVILEGES_RESOURCE_NAME = "privileges"
ENTREPRISE_RESOURCE_NAME = "v2/entreprises/%{id}"
ETABLISSEMENT_RESOURCE_NAME = "v2/etablissements/%{id}"
EXERCICES_RESOURCE_NAME = "v2/exercices/%{id}"
RNA_RESOURCE_NAME = "v2/associations/%{id}"
EFFECTIFS_RESOURCE_NAME = "v2/effectifs_mensuels_acoss_covid"
EFFECTIFS_ANNUELS_RESOURCE_NAME = "v2/effectifs_annuels_acoss_covid/%{id}"
ATTESTATION_SOCIALE_RESOURCE_NAME = "v4/urssaf/unites_legales/%{id}/attestation_vigilance"
ATTESTATION_FISCALE_RESOURCE_NAME = "v2/attestations_fiscales_dgfip/%{id}"
BILANS_BDF_RESOURCE_NAME = "v2/bilans_entreprises_bdf/%{id}"
PRIVILEGES_RESOURCE_NAME = "v2/privileges"
TIMEOUT = 20
DEFAULT_API_ENTREPRISE_DELAY = 0.0
@ -41,7 +41,7 @@ class APIEntreprise::API
end
def effectifs(siren, annee, mois)
endpoint = [EFFECTIFS_RESOURCE_NAME, annee, mois, "entreprise"].join('/')
endpoint = [EFFECTIFS_RESOURCE_NAME, annee, mois, "entreprise", "%{id}"].join('/')
call_with_siret(endpoint, siren)
end
@ -73,7 +73,7 @@ class APIEntreprise::API
end
def current_status
status_url = "https://entreprise.api.gouv.fr/watchdoge/dashboard/current_status"
status_url = "https://status.entreprise.api.gouv.fr/summary.json"
response = Typhoeus.get(status_url, timeout: 1)
handle_response(response)
@ -127,7 +127,7 @@ class APIEntreprise::API
end
def make_url(resource_name, siret_or_siren = nil)
[API_ENTREPRISE_URL, resource_name, siret_or_siren].compact.join("/")
[API_ENTREPRISE_URL, format(resource_name, id: siret_or_siren)].compact.join("/")
end
def build_params(user_id)

View file

@ -11,9 +11,9 @@ class APIEntreprise::AttestationSocialeAdapter < APIEntreprise::Adapter
end
def process_params
if data_source[:url].present?
if data_source[:data] && data_source[:data][:document_url].present?
{
entreprise_attestation_sociale_url: data_source[:url]
entreprise_attestation_sociale_url: data_source[:data][:document_url]
}
else
{}

View file

@ -50,7 +50,7 @@ class APIEntreprise::EntrepriseAdapter < APIEntreprise::Adapter
case raw_value
when 'A' then 'actif'
when 'F' then 'fermé'
when 'F', 'C' then 'fermé'
end
end

View file

@ -152,7 +152,18 @@ module Dolist
}
}.to_json
post(url, body)["FieldList"].find { _1['ID'] == 72 }['Value']
fields = post(url, body).fetch("FieldList", [])
if fields.empty?
Sentry.with_scope do |scope|
scope.set_extra(:contact, email_address)
Sentry.capture_message("Dolist::API: contact not found")
end
return nil
end
fields.find { _1['ID'] == 72 }.fetch('Value')
end
def ignorable_error?(response, mail)

View file

@ -8,7 +8,7 @@ module DownloadManager
def initialize(attachments, destination)
@attachments = attachments
@destination = destination
@destination = Pathname.new(destination)
end
def download_all
@ -28,26 +28,40 @@ module DownloadManager
# can't be used with typhoeus, otherwise block is closed before the request is run by hydra
def download_one(attachment:, path_in_download_dir:, http_client:)
attachment_path = File.join(destination, path_in_download_dir)
attachment_dir = File.dirname(attachment_path)
path = Pathname.new(path_in_download_dir)
attachment_path = destination.join(path.dirname, sanitize_filename(path.basename.to_s))
attachment_path.dirname.mkpath # defensive, do not write in undefined dir
FileUtils.mkdir_p(attachment_dir) if !Dir.exist?(attachment_dir) # defensive, do not write in undefined dir
if attachment.is_a?(ActiveStorage::FakeAttachment)
File.write(attachment_path, attachment.file.read, mode: 'wb')
attachment_path.write(attachment.file.read, mode: 'wb')
else
request = Typhoeus::Request.new(attachment.url)
request.on_complete do |response|
if response.success?
File.open(attachment_path, mode: "wb") do |fd|
attachment_path.open(mode: "wb") do |fd|
fd.write(response.body)
end
else
File.delete(attachment_path) if File.exist?(attachment_path) # -> case of retries failed, must cleanup partialy downloaded file
attachment_path.delete if attachment_path.exist? # -> case of retries failed, must cleanup partialy downloaded file
on_error.call(attachment, path_in_download_dir, response.code)
end
end
http_client.queue(request)
end
end
private
def sanitize_filename(original_filename)
filename = ActiveStorage::Filename.new(original_filename).sanitized
return filename if filename.bytesize <= 255
ext = File.extname(filename)
basename = File.basename(filename, ext).byteslice(0, 255 - ext.bytesize)
basename + ext
end
end
end

View file

@ -0,0 +1,69 @@
class Recovery::AlignChampWithDossierRevision
def initialize(dossiers, progress: nil)
@dossiers = dossiers
@progress = progress
@logs = []
end
attr_reader :logs
def run(destroy_extra_champs: false)
@logs = []
bad_dossier_ids = find_broken_dossier_ids
Dossier
.where(id: bad_dossier_ids)
.includes(:procedure, champs: { type_de_champ: :revisions })
.find_each do |dossier|
bad_champs = dossier.champs.filter { !dossier.revision_id.in?(_1.type_de_champ.revisions.ids) }
bad_champs.each do |champ|
type_de_champ = dossier.revision.types_de_champ.find { _1.stable_id == champ.stable_id }
state = {
champ_id: champ.id,
champ_type_de_champ_id: champ.type_de_champ_id,
dossier_id: dossier.id,
dossier_revision_id: dossier.revision_id,
procedure_id: dossier.procedure.id
}
if type_de_champ.present?
logs << state.merge(status: :updated, type_de_champ_id: type_de_champ.id)
champ.update_column(:type_de_champ_id, type_de_champ.id)
else
logs << state.merge(status: :not_found)
champ.destroy! if destroy_extra_champs
end
end
end
end
def find_broken_dossier_ids
bad_dossier_ids = []
@dossiers.in_batches(of: 15_000) do |dossiers|
dossier_ids_revision_ids = dossiers.pluck(:id, :revision_id)
dossier_ids = dossier_ids_revision_ids.map(&:first)
dossier_ids_type_de_champ_ids = Champ.where(dossier_id: dossier_ids).pluck(:dossier_id, :type_de_champ_id)
type_de_champ_ids = dossier_ids_type_de_champ_ids.map(&:second).uniq
revision_ids_by_type_de_champ_id = ProcedureRevisionTypeDeChamp
.where(type_de_champ_id: type_de_champ_ids)
.pluck(:type_de_champ_id, :revision_id)
.group_by(&:first).transform_values { _1.map(&:second).uniq }
type_de_champ_ids_by_dossier_id = dossier_ids_type_de_champ_ids
.group_by(&:first)
.transform_values { _1.map(&:second).uniq }
bad_dossier_ids += dossier_ids_revision_ids.filter do |(dossier_id, revision_id)|
type_de_champ_ids_by_dossier_id.fetch(dossier_id, []).any? do |type_de_champ_id|
!revision_id.in?(revision_ids_by_type_de_champ_id.fetch(type_de_champ_id, []))
end
end.map(&:first)
@progress.inc(dossiers.count) if @progress
end
@progress.finish if @progress
bad_dossier_ids
end
end

View file

@ -0,0 +1,30 @@
module Recovery
class Exporter
FILE_PATH = Rails.root.join('lib', 'data', 'export.dump')
attr_reader :dossiers, :file_path
def initialize(dossier_ids:, file_path: FILE_PATH)
dossier_with_data = Dossier.where(id: dossier_ids)
.preload(:user,
:individual,
:invites,
:traitements,
:transfer_logs,
commentaires: { piece_jointe_attachment: :blob },
avis: { introduction_file_attachment: :blob, piece_justificative_file_attachment: :blob },
dossier_operation_logs: { serialized_attachment: :blob },
attestation: { pdf_attachment: :blob },
justificatif_motivation_attachment: :blob,
etablissement: :exercices,
revision: :procedure)
@dossiers = DossierPreloader.new(dossier_with_data,
includes_for_dossier: [:geo_areas, etablissement: :exercices],
includes_for_etablissement: [:exercices]).all
@file_path = file_path
end
def dump
@file_path.binwrite(Marshal.dump(@dossiers))
end
end
end

View file

@ -0,0 +1,120 @@
module Recovery
class Importer
attr_reader :dossiers
def initialize(file_path: Recovery::Exporter::FILE_PATH)
# rubocop:disable Security/MarshalLoad
@dossiers = Marshal.load(File.read(file_path))
# rubocop:enable Security/MarshalLoad
end
def load
@dossiers.each do |dossier|
if Dossier.exists?(dossier.id)
puts "Dossier #{dossier.id} already exists"
next
end
dossier.instance_variable_set :@new_record, true
dossier_attributes = dossier.attributes.dup
parent_dossier_id = dossier_attributes['parent_dossier_id']
if parent_dossier_id && !Dossier.exists?(id: parent_dossier_id)
dossier_attributes.delete('parent_dossier_id')
end
Dossier.insert(dossier_attributes)
if dossier.etablissement.present?
Etablissement.insert(dossier.etablissement.attributes)
if dossier.etablissement.present?
APIEntreprise::EntrepriseJob.perform_later(dossier.etablissement.id, dossier.procedure.id)
end
dossier.etablissement.exercices.each do |exercice|
Exercice.insert(exercice.attributes)
end
end
if dossier.individual.present?
Individual.insert(dossier.individual.attributes)
end
dossier.invites.each do |invite|
Invite.insert(invite.attributes)
end
dossier.traitements.each do |traitement|
Traitement.insert(traitement.attributes)
end
dossier.transfer_logs.each do |transfer|
DossierTransferLog.insert(transfer.attributes)
end
dossier.commentaires.each do |commentaire|
Commentaire.insert(commentaire.attributes)
if commentaire.piece_jointe.attached?
import(commentaire.piece_jointe)
end
end
dossier.avis.each do |avis|
Avis.insert(avis.attributes)
if avis.introduction_file.attached?
import(avis.introduction_file)
end
if avis.piece_justificative_file.attached?
import(avis.piece_justificative_file)
end
end
dossier.dossier_operation_logs.each do |dol|
if dol.operation.nil?
puts "dol nil: #{dol.id}"
next
end
DossierOperationLog.insert(dol.attributes)
if dol.serialized.attached?
import(dol.serialized)
end
end
if dossier.attestation.present?
Attestation.insert(dossier.attestation.attributes)
import(dossier.attestation.pdf)
end
if dossier.justificatif_motivation.attached?
import(dossier.justificatif_motivation)
end
dossier.champs.each do |champ|
champ.piece_justificative_file.each { |pj| import(pj) }
if champ.etablissement.present?
APIEntreprise::EntrepriseJob.perform_later(champ.etablissement.id, dossier.procedure.id)
champ.etablissement.exercices.each do |exercice|
Exercice.insert(exercice.attributes)
end
Etablissement.insert(champ.etablissement.attributes)
end
Champ.insert(champ.attributes)
if champ.geo_areas.present?
champ.geo_areas.each { GeoArea.insert(_1.attributes) }
end
end
puts "imported dossier #{dossier.id}: #{Dossier.exists?(dossier.id)}"
end
end
def import(pj)
ActiveStorage::Blob.insert(pj.blob.attributes)
ActiveStorage::Attachment.insert(pj.attributes)
end
end
end

View file

@ -0,0 +1,17 @@
module Recovery
class RevisionExporter
FILE_PATH = Rails.root.join('lib', 'data', 'revision', 'export.dump')
attr_reader :revisions, :file_path
def initialize(revision_ids:, file_path: FILE_PATH)
@revisions = ProcedureRevision.where(id: revision_ids)
.preload(revision_types_de_champ: :type_de_champ)
.to_a
@file_path = file_path
end
def dump
@file_path.binwrite(Marshal.dump(@revisions))
end
end
end

View file

@ -0,0 +1,22 @@
module Recovery
class RevisionImporter
attr_reader :revisions
def initialize(file_path: Recovery::RevisionExporter::FILE_PATH)
# rubocop:disable Security/MarshalLoad
@revisions = Marshal.load(File.read(file_path))
# rubocop:enable Security/MarshalLoad
end
def load
@revisions.each do |revision|
ProcedureRevisionTypeDeChamp.transaction do
revision.revision_types_de_champ.each do |coordinate|
ProcedureRevisionTypeDeChamp.upsert(coordinate.attributes)
TypeDeChamp.upsert(coordinate.type_de_champ.attributes.except('type_champs'))
end
end
end
end
end
end

View file

@ -128,10 +128,6 @@ class BatchOperation < ApplicationRecord
dossier_operations.size
end
def progress_count
dossier_operations.pending.size
end
def success_count
dossier_operations.success.size
end

View file

@ -5,7 +5,7 @@
# id :integer not null, primary key
# data :jsonb
# fetch_external_data_exceptions :string is an Array
# prefilled :boolean default(FALSE)
# prefilled :boolean
# private :boolean default(FALSE), not null
# rebased_at :datetime
# type :string
@ -75,8 +75,8 @@ class Champ < ApplicationRecord
:mandatory?,
:prefillable?,
:refresh_after_update?,
:textarea_character_limit?,
:textarea_character_limit,
:character_limit?,
:character_limit,
to: :type_de_champ
delegate :to_typed_id, :to_typed_id_for_query, to: :type_de_champ, prefix: true
@ -111,6 +111,10 @@ class Champ < ApplicationRecord
parent_id.present?
end
def stable_id_with_row
[row_id, stable_id].compact
end
def sections
@sections ||= dossier.sections_for(self)
end
@ -226,10 +230,10 @@ class Champ < ApplicationRecord
update!(data: data)
end
def clone
def clone(fork = false)
champ_attributes = [:parent_id, :private, :row_id, :type, :type_de_champ_id]
value_attributes = private? ? [] : [:value, :value_json, :data, :external_id]
relationships = private? ? [] : [:etablissement, :geo_areas]
value_attributes = fork || !private? ? [:value, :value_json, :data, :external_id] : []
relationships = fork || !private? ? [:etablissement, :geo_areas] : []
deep_clone(only: champ_attributes + value_attributes, include: relationships) do |original, kopy|
PiecesJustificativesService.clone_attachments(original, kopy)
@ -240,6 +244,10 @@ class Champ < ApplicationRecord
input_id
end
def forked_with_changes?
public? && dossier.champ_forked_with_changes?(self)
end
private
def html_id

View file

@ -17,6 +17,7 @@
# etablissement_id :integer
# external_id :string
# parent_id :bigint
# row_id :string
# type_de_champ_id :integer
#
class Champs::BooleanChamp < Champ

View file

@ -21,11 +21,4 @@
# type_de_champ_id :integer
#
class Champs::EmailChamp < Champs::TextChamp
validates :value,
format: {
with: Devise.email_regexp,
message: I18n.t('invalid', scope: 'activerecord.errors.models.email_champ.attributes.value')
},
allow_nil: true,
if: -> { validation_context != :brouillon }
end

View file

@ -55,7 +55,7 @@ class Champs::PieceJustificativeChamp < Champ
return nil if attachment.nil?
if attachment.virus_scanner.safe? || attachment.virus_scanner.pending?
attachment.service_url
attachment.url
end
end
end

View file

@ -13,12 +13,12 @@
# 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
# row_id :string
# type_de_champ_id :integer
#
class Champs::RNAChamp < Champ
include RNAChampAssociationFetchableConcern

Some files were not shown because too many files have changed in this diff Show more