commit
4faec089e9
56 changed files with 1255 additions and 332 deletions
3
Gemfile
3
Gemfile
|
@ -13,12 +13,9 @@ gem 'bcrypt'
|
|||
gem 'bootstrap-sass', '>= 3.4.1'
|
||||
gem 'bootstrap-wysihtml5-rails', '~> 0.3.3.8'
|
||||
gem 'browser'
|
||||
gem 'carrierwave'
|
||||
gem 'carrierwave-i18n'
|
||||
gem 'chartkick'
|
||||
gem 'chunky_png'
|
||||
gem 'clamav-client', require: 'clamav/client'
|
||||
gem 'copy_carrierwave_file'
|
||||
gem 'daemons'
|
||||
gem 'deep_cloneable' # Enable deep clone of active record models
|
||||
gem 'delayed_cron_job' # Cron jobs
|
||||
|
|
104
Gemfile.lock
104
Gemfile.lock
|
@ -20,25 +20,25 @@ GEM
|
|||
specs:
|
||||
aasm (5.0.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
actioncable (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
actioncable (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activejob (= 5.2.2.1)
|
||||
actionmailer (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
actionpack (5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
actionview (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -51,25 +51,25 @@ GEM
|
|||
activemodel (>= 4.1, < 6)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
activejob (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
activerecord (5.2.2.1)
|
||||
activemodel (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
activemodel (5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
activerecord (5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
arel (>= 9.0)
|
||||
activestorage (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
activerecord (= 5.2.2.1)
|
||||
activestorage (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
marcel (~> 0.3.1)
|
||||
activestorage-openstack (1.0.0)
|
||||
activestorage-openstack (1.2.0)
|
||||
fog-openstack (~> 1.0)
|
||||
marcel
|
||||
mime-types
|
||||
rails (<= 6)
|
||||
activesupport (5.2.2.1)
|
||||
rails (>= 5.2.2)
|
||||
activesupport (5.2.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
|
@ -136,11 +136,6 @@ GEM
|
|||
capybara-selenium (0.0.6)
|
||||
capybara
|
||||
selenium-webdriver
|
||||
carrierwave (1.3.1)
|
||||
activemodel (>= 4.0.0)
|
||||
activesupport (>= 4.0.0)
|
||||
mime-types (>= 1.16)
|
||||
carrierwave-i18n (0.2.0)
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
chartkick (3.2.0)
|
||||
|
@ -158,8 +153,6 @@ GEM
|
|||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.1.5)
|
||||
connection_pool (2.2.2)
|
||||
copy_carrierwave_file (1.3.0)
|
||||
carrierwave (>= 0.9)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.5)
|
||||
|
@ -203,7 +196,7 @@ GEM
|
|||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
erubi (1.8.0)
|
||||
erubi (1.9.0)
|
||||
erubis (2.7.0)
|
||||
ethon (0.11.0)
|
||||
ffi (>= 1.3.0)
|
||||
|
@ -311,7 +304,7 @@ GEM
|
|||
domain_name (~> 0.5)
|
||||
http_parser.rb (0.6.0)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.6.0)
|
||||
i18n (1.7.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ipaddress (0.8.3)
|
||||
jaro_winkler (1.5.2)
|
||||
|
@ -320,7 +313,7 @@ GEM
|
|||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (2.2.0)
|
||||
json-jwt (1.10.0)
|
||||
json-jwt (1.11.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
|
@ -375,7 +368,7 @@ GEM
|
|||
mimemagic (0.3.3)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
minitest (5.11.3)
|
||||
minitest (5.13.0)
|
||||
momentjs-rails (2.20.1)
|
||||
railties (>= 3.1)
|
||||
multi_json (1.14.1)
|
||||
|
@ -384,7 +377,7 @@ GEM
|
|||
mustermann (1.0.3)
|
||||
nenv (0.3.0)
|
||||
netrc (0.11.0)
|
||||
nio4r (2.3.1)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.5)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
notiffany (0.1.1)
|
||||
|
@ -469,18 +462,18 @@ GEM
|
|||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.2.2.1)
|
||||
actioncable (= 5.2.2.1)
|
||||
actionmailer (= 5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activejob (= 5.2.2.1)
|
||||
activemodel (= 5.2.2.1)
|
||||
activerecord (= 5.2.2.1)
|
||||
activestorage (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
rails (5.2.3)
|
||||
actioncable (= 5.2.3)
|
||||
actionmailer (= 5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
actionview (= 5.2.3)
|
||||
activejob (= 5.2.3)
|
||||
activemodel (= 5.2.3)
|
||||
activerecord (= 5.2.3)
|
||||
activestorage (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.2.2.1)
|
||||
railties (= 5.2.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.4)
|
||||
actionpack (>= 5.0.1.x)
|
||||
|
@ -489,14 +482,14 @@ GEM
|
|||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.2.0)
|
||||
loofah (~> 2.2, >= 2.2.2)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
rails-i18n (5.1.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 5.0, < 6)
|
||||
railties (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
railties (5.2.3)
|
||||
actionpack (= 5.2.3)
|
||||
activesupport (= 5.2.3)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
|
@ -693,9 +686,9 @@ GEM
|
|||
activesupport (>= 4.2)
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 4.2)
|
||||
websocket-driver (0.7.0)
|
||||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.3)
|
||||
websocket-extensions (0.1.4)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
xray-rails (0.3.1)
|
||||
|
@ -730,12 +723,9 @@ DEPENDENCIES
|
|||
capybara-email
|
||||
capybara-screenshot
|
||||
capybara-selenium
|
||||
carrierwave
|
||||
carrierwave-i18n
|
||||
chartkick
|
||||
chunky_png
|
||||
clamav-client
|
||||
copy_carrierwave_file
|
||||
daemons
|
||||
database_cleaner
|
||||
deep_cloneable
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
|
||||
%horizontal-list {
|
||||
|
@ -17,3 +18,10 @@
|
|||
animation-fill-mode: forwards;
|
||||
animation-duration: 0.3s;
|
||||
}
|
||||
|
||||
%outline {
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 3px solid $blue;
|
||||
}
|
||||
}
|
||||
|
|
38
app/assets/stylesheets/new_design/add_instructeur.scss
Normal file
38
app/assets/stylesheets/new_design/add_instructeur.scss
Normal file
|
@ -0,0 +1,38 @@
|
|||
@import "constants";
|
||||
@import "colors";
|
||||
|
||||
.instructeur-wrapper {
|
||||
.select-instructeurs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.select2-container--default {
|
||||
.select2-selection--multiple {
|
||||
border: solid 1px $border-grey;
|
||||
|
||||
.select2-selection__choice, // scss-lint:disable SelectorFormat
|
||||
.select2-search--inline {
|
||||
padding: $default-spacer;
|
||||
}
|
||||
}
|
||||
|
||||
&.select2-container--focus {
|
||||
.select2-selection--multiple {
|
||||
border: 1px solid $blue;
|
||||
box-shadow: 0px 0px 2px 1px $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-results__option { // scss-lint:disable SelectorFormat
|
||||
padding: $default-spacer;
|
||||
}
|
||||
|
||||
.custom-select2-option {
|
||||
.icon {
|
||||
margin-right: $default-spacer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
@import "colors";
|
||||
@import "constants";
|
||||
@import "placeholders";
|
||||
|
||||
.button {
|
||||
@extend %outline;
|
||||
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
border-radius: 30px;
|
||||
|
@ -20,11 +23,6 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
filter: saturate(50%);
|
||||
|
|
|
@ -40,6 +40,10 @@
|
|||
.card-title {
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
p:not(:last-of-type) {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
@import "colors";
|
||||
@import "placeholders";
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
|
@ -14,5 +17,7 @@ html {
|
|||
}
|
||||
|
||||
a {
|
||||
@extend %outline;
|
||||
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@import "constants";
|
||||
@import "colors";
|
||||
@import "placeholders";
|
||||
|
||||
.form {
|
||||
h1 {
|
||||
|
@ -177,6 +178,8 @@
|
|||
|
||||
input[type=checkbox],
|
||||
input[type=radio] {
|
||||
@extend %outline;
|
||||
|
||||
margin-left: 5px;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 2 * $default-padding;
|
||||
|
|
|
@ -365,6 +365,11 @@ $cta-panel-button-border-size: 2px;
|
|||
color: #FFFFFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 3px solid #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.cta-panel-button-blue {
|
||||
|
|
|
@ -141,6 +141,8 @@ class ApplicationController < ActionController::Base
|
|||
Raven.user_context(sentry_user)
|
||||
end
|
||||
|
||||
# private method called by rails fwk
|
||||
# see https://github.com/roidrage/lograge
|
||||
def append_info_to_payload(payload)
|
||||
super
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def add_instructeur
|
||||
@instructeur = Instructeur.find_by(email: instructeur_email) ||
|
||||
@instructeur = Instructeur.by_email(instructeur_email) ||
|
||||
create_instructeur(instructeur_email)
|
||||
|
||||
if groupe_instructeur.instructeurs.include?(@instructeur)
|
||||
|
@ -24,7 +24,7 @@ module Instructeurs
|
|||
groupe_instructeur.instructeurs << @instructeur
|
||||
flash[:notice] = "L’instructeur « #{instructeur_email} » a été affecté au groupe."
|
||||
GroupeInstructeurMailer
|
||||
.add_instructeur(groupe_instructeur, @instructeur, current_user.email)
|
||||
.add_instructeurs(groupe_instructeur, [@instructeur], current_user.email)
|
||||
.deliver_later
|
||||
end
|
||||
|
||||
|
|
|
@ -19,5 +19,29 @@ module Manager
|
|||
redirect_to manager_sign_in_path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# private method called by rails fwk
|
||||
# see https://github.com/roidrage/lograge
|
||||
def append_info_to_payload(payload)
|
||||
super
|
||||
|
||||
payload.merge!({
|
||||
user_agent: request.user_agent,
|
||||
user_id: current_user&.id,
|
||||
user_email: current_user&.email
|
||||
}.compact)
|
||||
|
||||
if browser.known?
|
||||
payload.merge!({
|
||||
browser: browser.name,
|
||||
browser_version: browser.version.to_s,
|
||||
platform: browser.platform.name
|
||||
})
|
||||
end
|
||||
|
||||
payload
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ module NewAdministrateur
|
|||
@procedure = procedure
|
||||
@groupe_instructeur = groupe_instructeur
|
||||
@instructeurs = paginated_instructeurs
|
||||
@available_instructeur_emails = available_instructeur_emails
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -40,6 +41,7 @@ module NewAdministrateur
|
|||
else
|
||||
@procedure = procedure
|
||||
@instructeurs = paginated_instructeurs
|
||||
@available_instructeur_emails = available_instructeur_emails
|
||||
|
||||
flash[:alert] = "le nom « #{label} » est déjà pris par un autre groupe."
|
||||
render :show
|
||||
|
@ -47,18 +49,35 @@ module NewAdministrateur
|
|||
end
|
||||
|
||||
def add_instructeur
|
||||
@instructeur = Instructeur.find_by(email: instructeur_email) ||
|
||||
emails = params['emails'].map(&:strip).map(&:downcase)
|
||||
|
||||
correct_emails, bad_emails = emails
|
||||
.partition { |email| URI::MailTo::EMAIL_REGEXP.match?(email) }
|
||||
|
||||
if bad_emails.present?
|
||||
flash[:alert] = t('.wrong_address',
|
||||
count: bad_emails.count,
|
||||
value: bad_emails.join(', '))
|
||||
end
|
||||
|
||||
email_to_adds = correct_emails - groupe_instructeur.instructeurs.pluck(:email)
|
||||
|
||||
if email_to_adds.present?
|
||||
instructeurs = email_to_adds.map do |instructeur_email|
|
||||
Instructeur.by_email(instructeur_email) ||
|
||||
create_instructeur(instructeur_email)
|
||||
end
|
||||
|
||||
if groupe_instructeur.instructeurs.include?(@instructeur)
|
||||
flash[:alert] = "L’instructeur « #{instructeur_email} » est déjà dans le groupe."
|
||||
|
||||
else
|
||||
groupe_instructeur.instructeurs << @instructeur
|
||||
flash[:notice] = "L’instructeur « #{instructeur_email} » a été affecté au groupe."
|
||||
GroupeInstructeurMailer
|
||||
.add_instructeur(groupe_instructeur, @instructeur, current_user.email)
|
||||
.add_instructeurs(groupe_instructeur, instructeurs, current_user.email)
|
||||
.deliver_later
|
||||
|
||||
groupe_instructeur.instructeurs << instructeurs
|
||||
|
||||
flash[:notice] = t('.assignment',
|
||||
count: email_to_adds.count,
|
||||
value: email_to_adds.join(', '),
|
||||
groupe: groupe_instructeur.label)
|
||||
end
|
||||
|
||||
redirect_to procedure_groupe_instructeur_path(procedure, groupe_instructeur)
|
||||
|
@ -110,10 +129,6 @@ module NewAdministrateur
|
|||
procedure.groupe_instructeurs.find(params[:id])
|
||||
end
|
||||
|
||||
def instructeur_email
|
||||
params[:instructeur][:email].strip.downcase
|
||||
end
|
||||
|
||||
def instructeur_id
|
||||
params[:instructeur][:id]
|
||||
end
|
||||
|
@ -141,5 +156,11 @@ module NewAdministrateur
|
|||
def routing_criteria_name
|
||||
params[:procedure][:routing_criteria_name]
|
||||
end
|
||||
|
||||
def available_instructeur_emails
|
||||
all = current_administrateur.instructeurs.pluck(:email)
|
||||
assigned = groupe_instructeur.instructeurs.pluck(:email)
|
||||
(all - assigned).sort
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,8 +18,7 @@ class ServiceDashboard < Administrate::BaseDashboard
|
|||
email: Field::String,
|
||||
telephone: Field::String,
|
||||
horaires: Field::String,
|
||||
adresse: Field::String,
|
||||
siret: Field::String
|
||||
adresse: Field::String
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
|
@ -45,8 +44,7 @@ class ServiceDashboard < Administrate::BaseDashboard
|
|||
:email,
|
||||
:telephone,
|
||||
:horaires,
|
||||
:adresse,
|
||||
:siret
|
||||
:adresse
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
|
|
29
app/graphql/mutations/dossier_accepter.rb
Normal file
29
app/graphql/mutations/dossier_accepter.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
module Mutations
|
||||
class DossierAccepter < Mutations::BaseMutation
|
||||
include DossierHelper
|
||||
|
||||
description "Accepter le dossier."
|
||||
|
||||
argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType
|
||||
argument :instructeur_id, ID, "Instructeur qui prend la décision sur le dossier.", required: true, loads: Types::ProfileType
|
||||
argument :motivation, String, required: false
|
||||
argument :justificatif, ID, required: false
|
||||
|
||||
field :dossier, Types::DossierType, null: true
|
||||
field :errors, [Types::ValidationErrorType], null: true
|
||||
|
||||
def resolve(dossier:, instructeur:, motivation: nil, justificatif: nil)
|
||||
if dossier.en_instruction?
|
||||
dossier.accepter!(instructeur, motivation, justificatif)
|
||||
|
||||
{ dossier: dossier }
|
||||
else
|
||||
{ errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?(dossier:, instructeur:, motivation: nil)
|
||||
instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id)
|
||||
end
|
||||
end
|
||||
end
|
29
app/graphql/mutations/dossier_classer_sans_suite.rb
Normal file
29
app/graphql/mutations/dossier_classer_sans_suite.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
module Mutations
|
||||
class DossierClasserSansSuite < Mutations::BaseMutation
|
||||
include DossierHelper
|
||||
|
||||
description "Classer le dossier sans suite."
|
||||
|
||||
argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType
|
||||
argument :instructeur_id, ID, "Instructeur qui prend la décision sur le dossier.", required: true, loads: Types::ProfileType
|
||||
argument :motivation, String, required: true
|
||||
argument :justificatif, ID, required: false
|
||||
|
||||
field :dossier, Types::DossierType, null: true
|
||||
field :errors, [Types::ValidationErrorType], null: true
|
||||
|
||||
def resolve(dossier:, instructeur:, motivation:, justificatif: nil)
|
||||
if dossier.en_instruction?
|
||||
dossier.classer_sans_suite!(instructeur, motivation, justificatif)
|
||||
|
||||
{ dossier: dossier }
|
||||
else
|
||||
{ errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?(dossier:, instructeur:, motivation:)
|
||||
instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id)
|
||||
end
|
||||
end
|
||||
end
|
27
app/graphql/mutations/dossier_envoyer_message.rb
Normal file
27
app/graphql/mutations/dossier_envoyer_message.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module Mutations
|
||||
class DossierEnvoyerMessage < Mutations::BaseMutation
|
||||
description "Envoyer un message à l'usager du dossier."
|
||||
|
||||
argument :dossier_id, ID, required: true, loads: Types::DossierType
|
||||
argument :instructeur_id, ID, required: true, loads: Types::ProfileType
|
||||
argument :body, String, required: true
|
||||
argument :attachment, ID, required: false
|
||||
|
||||
field :message, Types::MessageType, null: true
|
||||
field :errors, [Types::ValidationErrorType], null: true
|
||||
|
||||
def resolve(dossier:, instructeur:, body:, attachment: nil)
|
||||
message = CommentaireService.build(instructeur, dossier, body: body, piece_jointe: attachment)
|
||||
|
||||
if message.save
|
||||
{ message: message }
|
||||
else
|
||||
{ errors: message.errors.full_messages }
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?(dossier:, instructeur:, body:)
|
||||
instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id)
|
||||
end
|
||||
end
|
||||
end
|
27
app/graphql/mutations/dossier_passer_en_instruction.rb
Normal file
27
app/graphql/mutations/dossier_passer_en_instruction.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
module Mutations
|
||||
class DossierPasserEnInstruction < Mutations::BaseMutation
|
||||
include DossierHelper
|
||||
|
||||
description "Passer le dossier en instruction."
|
||||
|
||||
argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType
|
||||
argument :instructeur_id, ID, "Instructeur qui prend la décision sur le dossier.", required: true, loads: Types::ProfileType
|
||||
|
||||
field :dossier, Types::DossierType, null: true
|
||||
field :errors, [Types::ValidationErrorType], null: true
|
||||
|
||||
def resolve(dossier:, instructeur:)
|
||||
if dossier.en_construction?
|
||||
dossier.passer_en_instruction!(instructeur)
|
||||
|
||||
{ dossier: dossier }
|
||||
else
|
||||
{ errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?(dossier:, instructeur:)
|
||||
instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id)
|
||||
end
|
||||
end
|
||||
end
|
29
app/graphql/mutations/dossier_refuser.rb
Normal file
29
app/graphql/mutations/dossier_refuser.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
module Mutations
|
||||
class DossierRefuser < Mutations::BaseMutation
|
||||
include DossierHelper
|
||||
|
||||
description "Refuser le dossier."
|
||||
|
||||
argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType
|
||||
argument :instructeur_id, ID, "Instructeur qui prend la décision sur le dossier.", required: true, loads: Types::ProfileType
|
||||
argument :motivation, String, required: true
|
||||
argument :justificatif, ID, required: false
|
||||
|
||||
field :dossier, Types::DossierType, null: true
|
||||
field :errors, [Types::ValidationErrorType], null: true
|
||||
|
||||
def resolve(dossier:, instructeur:, motivation:, justificatif: nil)
|
||||
if dossier.en_instruction?
|
||||
dossier.refuser!(instructeur, motivation, justificatif)
|
||||
|
||||
{ dossier: dossier }
|
||||
else
|
||||
{ errors: ["Le dossier est déjà #{dossier_display_state(dossier, lower: true)}"] }
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?(dossier:, instructeur:, motivation:)
|
||||
instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,27 +10,66 @@ type Avis {
|
|||
type CarteChamp implements Champ {
|
||||
geoAreas: [GeoArea!]!
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
}
|
||||
|
||||
interface Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
}
|
||||
|
||||
type ChampDescriptor {
|
||||
"""
|
||||
Description du champ.
|
||||
"""
|
||||
description: String
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
Est-ce que le champ est obligatoire ?
|
||||
"""
|
||||
required: Boolean!
|
||||
|
||||
"""
|
||||
Type de la valeur du champ.
|
||||
"""
|
||||
type: TypeDeChamp!
|
||||
}
|
||||
|
||||
type CheckboxChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
value: Boolean!
|
||||
}
|
||||
|
@ -88,14 +127,30 @@ type CreateDirectUploadPayload {
|
|||
|
||||
type DateChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
value: ISO8601DateTime
|
||||
}
|
||||
|
||||
type DecimalNumberChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
value: Float
|
||||
}
|
||||
|
@ -164,7 +219,11 @@ type Demarche {
|
|||
"""
|
||||
Le numero de la démarche.
|
||||
"""
|
||||
number: ID!
|
||||
number: Int!
|
||||
|
||||
"""
|
||||
L'état de la démarche.
|
||||
"""
|
||||
state: DemarcheState!
|
||||
title: String!
|
||||
updatedAt: ISO8601DateTime!
|
||||
|
@ -244,7 +303,7 @@ type Dossier {
|
|||
"""
|
||||
Le numero du dossier.
|
||||
"""
|
||||
number: ID!
|
||||
number: Int!
|
||||
|
||||
"""
|
||||
L'état du dossier.
|
||||
|
@ -258,6 +317,74 @@ type Dossier {
|
|||
usager: Profile!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DossierAccepter
|
||||
"""
|
||||
input DossierAccepterInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Dossier ID
|
||||
"""
|
||||
dossierId: ID!
|
||||
|
||||
"""
|
||||
Instructeur qui prend la décision sur le dossier.
|
||||
"""
|
||||
instructeurId: ID!
|
||||
justificatif: ID
|
||||
motivation: String
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DossierAccepter
|
||||
"""
|
||||
type DossierAccepterPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
dossier: Dossier
|
||||
errors: [ValidationError!]
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DossierClasserSansSuite
|
||||
"""
|
||||
input DossierClasserSansSuiteInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Dossier ID
|
||||
"""
|
||||
dossierId: ID!
|
||||
|
||||
"""
|
||||
Instructeur qui prend la décision sur le dossier.
|
||||
"""
|
||||
instructeurId: ID!
|
||||
justificatif: ID
|
||||
motivation: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DossierClasserSansSuite
|
||||
"""
|
||||
type DossierClasserSansSuitePayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
dossier: Dossier
|
||||
errors: [ValidationError!]
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for Dossier.
|
||||
"""
|
||||
|
@ -293,13 +420,114 @@ type DossierEdge {
|
|||
node: Dossier
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DossierEnvoyerMessage
|
||||
"""
|
||||
input DossierEnvoyerMessageInput {
|
||||
attachment: ID
|
||||
body: String!
|
||||
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
dossierId: ID!
|
||||
instructeurId: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DossierEnvoyerMessage
|
||||
"""
|
||||
type DossierEnvoyerMessagePayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
errors: [ValidationError!]
|
||||
message: Message
|
||||
}
|
||||
|
||||
type DossierLinkChamp implements Champ {
|
||||
dossier: Dossier
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DossierPasserEnInstruction
|
||||
"""
|
||||
input DossierPasserEnInstructionInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Dossier ID
|
||||
"""
|
||||
dossierId: ID!
|
||||
|
||||
"""
|
||||
Instructeur qui prend la décision sur le dossier.
|
||||
"""
|
||||
instructeurId: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DossierPasserEnInstruction
|
||||
"""
|
||||
type DossierPasserEnInstructionPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
dossier: Dossier
|
||||
errors: [ValidationError!]
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DossierRefuser
|
||||
"""
|
||||
input DossierRefuserInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Dossier ID
|
||||
"""
|
||||
dossierId: ID!
|
||||
|
||||
"""
|
||||
Instructeur qui prend la décision sur le dossier.
|
||||
"""
|
||||
instructeurId: ID!
|
||||
justificatif: ID
|
||||
motivation: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DossierRefuser
|
||||
"""
|
||||
type DossierRefuserPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
dossier: Dossier
|
||||
errors: [ValidationError!]
|
||||
}
|
||||
|
||||
enum DossierState {
|
||||
"""
|
||||
Accepté
|
||||
|
@ -335,22 +563,17 @@ interface GeoArea {
|
|||
|
||||
enum GeoAreaSource {
|
||||
"""
|
||||
translation missing: fr.activerecord.attributes.geo_area.source.cadastre
|
||||
Parcelle cadastrale
|
||||
"""
|
||||
cadastre
|
||||
|
||||
"""
|
||||
translation missing: fr.activerecord.attributes.geo_area.source.parcelle_agricole
|
||||
"""
|
||||
parcelle_agricole
|
||||
|
||||
"""
|
||||
translation missing: fr.activerecord.attributes.geo_area.source.quartier_prioritaire
|
||||
Quartier prioritaire
|
||||
"""
|
||||
quartier_prioritaire
|
||||
|
||||
"""
|
||||
translation missing: fr.activerecord.attributes.geo_area.source.selection_utilisateur
|
||||
Sélection utilisateur
|
||||
"""
|
||||
selection_utilisateur
|
||||
}
|
||||
|
@ -376,16 +599,32 @@ scalar ISO8601DateTime
|
|||
|
||||
type IntegerNumberChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
value: Int
|
||||
}
|
||||
|
||||
type LinkedDropDownListChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
primaryValue: String
|
||||
secondaryValue: String
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
}
|
||||
|
||||
|
@ -399,7 +638,15 @@ type Message {
|
|||
|
||||
type MultipleDropDownListChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
values: [String!]!
|
||||
}
|
||||
|
@ -409,6 +656,31 @@ type Mutation {
|
|||
File information required to prepare a direct upload
|
||||
"""
|
||||
createDirectUpload(input: CreateDirectUploadInput!): CreateDirectUploadPayload
|
||||
|
||||
"""
|
||||
Accepter le dossier.
|
||||
"""
|
||||
dossierAccepter(input: DossierAccepterInput!): DossierAccepterPayload
|
||||
|
||||
"""
|
||||
Classer le dossier sans suite.
|
||||
"""
|
||||
dossierClasserSansSuite(input: DossierClasserSansSuiteInput!): DossierClasserSansSuitePayload
|
||||
|
||||
"""
|
||||
Envoyer un message à l'usager du dossier.
|
||||
"""
|
||||
dossierEnvoyerMessage(input: DossierEnvoyerMessageInput!): DossierEnvoyerMessagePayload
|
||||
|
||||
"""
|
||||
Passer le dossier en instruction.
|
||||
"""
|
||||
dossierPasserEnInstruction(input: DossierPasserEnInstructionInput!): DossierPasserEnInstructionPayload
|
||||
|
||||
"""
|
||||
Refuser le dossier.
|
||||
"""
|
||||
dossierRefuser(input: DossierRefuserInput!): DossierRefuserPayload
|
||||
}
|
||||
|
||||
enum Order {
|
||||
|
@ -480,7 +752,15 @@ type PersonneMorale {
|
|||
|
||||
type PieceJustificativeChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
url: URL
|
||||
}
|
||||
|
@ -507,7 +787,7 @@ type Query {
|
|||
"""
|
||||
Numéro de la démarche.
|
||||
"""
|
||||
number: ID!
|
||||
number: Int!
|
||||
): Demarche!
|
||||
|
||||
"""
|
||||
|
@ -517,14 +797,22 @@ type Query {
|
|||
"""
|
||||
Numéro du dossier.
|
||||
"""
|
||||
number: ID!
|
||||
number: Int!
|
||||
): Dossier!
|
||||
}
|
||||
|
||||
type RepetitionChamp implements Champ {
|
||||
champs: [Champ!]!
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
}
|
||||
|
||||
|
@ -537,13 +825,29 @@ type SelectionUtilisateur implements GeoArea {
|
|||
type SiretChamp implements Champ {
|
||||
etablissement: PersonneMorale
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
}
|
||||
|
||||
type TextChamp implements Champ {
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Libellé du champ.
|
||||
"""
|
||||
label: String!
|
||||
|
||||
"""
|
||||
La valeur du champ sous forme texte.
|
||||
"""
|
||||
stringValue: String
|
||||
value: String
|
||||
}
|
||||
|
@ -689,3 +993,13 @@ enum TypeDeChamp {
|
|||
A valid URL, transported as a string
|
||||
"""
|
||||
scalar URL
|
||||
|
||||
"""
|
||||
Éreur de validation
|
||||
"""
|
||||
type ValidationError {
|
||||
"""
|
||||
A description of the error
|
||||
"""
|
||||
message: String!
|
||||
}
|
|
@ -9,9 +9,9 @@ module Types
|
|||
end
|
||||
|
||||
global_id_field :id
|
||||
field :type, TypeDeChampType, null: false, method: :type_champ
|
||||
field :label, String, null: false, method: :libelle
|
||||
field :description, String, null: true
|
||||
field :required, Boolean, null: false, method: :mandatory?
|
||||
field :type, TypeDeChampType, "Type de la valeur du champ.", null: false, method: :type_champ
|
||||
field :label, String, "Libellé du champ.", null: false, method: :libelle
|
||||
field :description, String, "Description du champ.", null: true
|
||||
field :required, Boolean, "Est-ce que le champ est obligatoire ?", null: false, method: :mandatory?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,8 @@ module Types
|
|||
include Types::BaseInterface
|
||||
|
||||
global_id_field :id
|
||||
field :label, String, null: false, method: :libelle
|
||||
field :string_value, String, null: true, method: :for_api_v2
|
||||
field :label, String, "Libellé du champ.", null: false, method: :libelle
|
||||
field :string_value, String, "La valeur du champ sous forme texte.", null: true, method: :for_api_v2
|
||||
|
||||
definition_methods do
|
||||
def resolve_type(object, context)
|
||||
|
|
|
@ -9,10 +9,10 @@ module Types
|
|||
description "Une demarche"
|
||||
|
||||
global_id_field :id
|
||||
field :number, ID, "Le numero de la démarche.", null: false, method: :id
|
||||
field :number, Int, "Le numero de la démarche.", null: false, method: :id
|
||||
field :title, String, null: false, method: :libelle
|
||||
field :description, String, "Déscription de la démarche.", null: false
|
||||
field :state, DemarcheState, null: false
|
||||
field :state, DemarcheState, "L'état de la démarche.", null: false
|
||||
|
||||
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
|
||||
|
|
|
@ -9,7 +9,7 @@ module Types
|
|||
description "Un dossier"
|
||||
|
||||
global_id_field :id
|
||||
field :number, ID, "Le numero du dossier.", null: false, method: :id
|
||||
field :number, Int, "Le numero du dossier.", null: false, method: :id
|
||||
field :state, DossierState, "L'état du dossier.", null: false
|
||||
field :updated_at, GraphQL::Types::ISO8601DateTime, "Date de dernière mise à jour.", null: false
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ module Types
|
|||
|
||||
class GeoAreaSource < Types::BaseEnum
|
||||
GeoArea.sources.each do |symbol_name, string_name|
|
||||
if string_name != "parcelle_agricole"
|
||||
value(string_name,
|
||||
I18n.t(symbol_name, scope: [:activerecord, :attributes, :geo_area, :source]),
|
||||
value: symbol_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
global_id_field :id
|
||||
field :source, GeoAreaSource, null: false
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
module Types
|
||||
class MutationType < Types::BaseObject
|
||||
field :create_direct_upload, mutation: Mutations::CreateDirectUpload
|
||||
|
||||
field :dossier_envoyer_message, mutation: Mutations::DossierEnvoyerMessage
|
||||
field :dossier_passer_en_instruction, mutation: Mutations::DossierPasserEnInstruction
|
||||
field :dossier_classer_sans_suite, mutation: Mutations::DossierClasserSansSuite
|
||||
field :dossier_refuser, mutation: Mutations::DossierRefuser
|
||||
field :dossier_accepter, mutation: Mutations::DossierAccepter
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
module Types
|
||||
class QueryType < Types::BaseObject
|
||||
field :demarche, DemarcheType, null: false, description: "Informations concernant une démarche." do
|
||||
argument :number, ID, "Numéro de la démarche.", required: true
|
||||
argument :number, Int, "Numéro de la démarche.", required: true
|
||||
end
|
||||
|
||||
field :dossier, DossierType, null: false, description: "Informations sur un dossier d'une démarche." do
|
||||
argument :number, ID, "Numéro du dossier.", required: true
|
||||
argument :number, Int, "Numéro du dossier.", required: true
|
||||
end
|
||||
|
||||
def demarche(number:)
|
||||
|
|
11
app/graphql/types/validation_error_type.rb
Normal file
11
app/graphql/types/validation_error_type.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Types
|
||||
class ValidationErrorType < Types::BaseObject
|
||||
description "Éreur de validation"
|
||||
|
||||
field :message, String, "A description of the error", null: false
|
||||
|
||||
def message
|
||||
object
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,13 @@
|
|||
import $ from 'jquery';
|
||||
import 'select2';
|
||||
|
||||
const optionTemplate = email =>
|
||||
$(
|
||||
'<span class="custom-select2-option"><span class="icon person"></span>' +
|
||||
email.text +
|
||||
'</span>'
|
||||
);
|
||||
|
||||
addEventListener('ds:page:update', () => {
|
||||
$('select.select2').select2({
|
||||
language: 'fr',
|
||||
|
@ -20,4 +27,20 @@ addEventListener('ds:page:update', () => {
|
|||
maximumSelectionLength: '30',
|
||||
width: '300px'
|
||||
});
|
||||
|
||||
$('select.select2-limited.select-instructeurs').select2({
|
||||
language: 'fr',
|
||||
dropdownParent: $('.instructeur-wrapper'),
|
||||
placeholder: 'Saisir l’adresse email de l’instructeur',
|
||||
tags: true,
|
||||
tokenSeparators: [',', ' '],
|
||||
templateResult: optionTemplate,
|
||||
templateSelection: function(email) {
|
||||
return $(
|
||||
'<span class="custom-select2-option"><span class="icon person"></span>' +
|
||||
email.text +
|
||||
'</span>'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
module ActiveStorage
|
||||
# activestorage-openstack uses ActiveStorage::FileNotFoundError which only exists in rails 6
|
||||
class FileNotFoundError < StandardError; end
|
||||
end
|
|
@ -1,9 +1,9 @@
|
|||
class GroupeInstructeurMailer < ApplicationMailer
|
||||
layout 'mailers/layout'
|
||||
|
||||
def add_instructeur(group, instructeur, current_instructeur_email)
|
||||
@email = instructeur.email
|
||||
@group = group
|
||||
def add_instructeurs(group, instructeurs, current_instructeur_email)
|
||||
@new_instructeur_emails = instructeurs.map(&:email)
|
||||
@group = Procedure.last.defaut_groupe_instructeur
|
||||
@current_instructeur_email = current_instructeur_email
|
||||
|
||||
subject = "Ajout d’un instructeur dans le groupe \"#{group.label}\""
|
||||
|
|
|
@ -10,7 +10,7 @@ class Commentaire < ApplicationRecord
|
|||
|
||||
has_one_attached :piece_jointe
|
||||
|
||||
validates :body, presence: { message: "Votre message ne peut être vide" }
|
||||
validates :body, presence: { message: "ne peut être vide" }
|
||||
|
||||
default_scope { order(created_at: :asc) }
|
||||
scope :updated_since?, -> (date) { where('commentaires.updated_at > ?', date) }
|
||||
|
|
|
@ -10,7 +10,7 @@ class Individual < ApplicationRecord
|
|||
GENDER_FEMALE = 'Mme'
|
||||
|
||||
def self.create_from_france_connect(fc_information)
|
||||
create(
|
||||
create!(
|
||||
nom: fc_information.family_name,
|
||||
prenom: fc_information.given_name,
|
||||
gender: fc_information.gender == 'female' ? GENDER_FEMALE : GENDER_MALE
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
%p
|
||||
Bonjour,
|
||||
|
||||
%p
|
||||
L’instructeur « #{@email} » a été affecté au groupe « #{@group.label} » par « #{@current_instructeur_email} », en charge de la démarche « #{@group.procedure.libelle} ».
|
||||
|
||||
%p
|
||||
Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe :
|
||||
= link_to(@group.label, procedure_groupe_instructeur_url(@group.procedure, @group))
|
||||
|
||||
= render partial: "layouts/mailers/signature"
|
|
@ -0,0 +1,11 @@
|
|||
%p
|
||||
Bonjour,
|
||||
|
||||
%p
|
||||
#{t('new_administrateur.groupe_instructeurs.add_instructeur.assignment', count: @new_instructeur_emails.count, value: @new_instructeur_emails.join(', '), groupe: @group.label).chomp('.')} par « #{@current_instructeur_email} », en charge de la démarche « #{@group.procedure.libelle} ».
|
||||
|
||||
%p
|
||||
Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe :
|
||||
= link_to(@group.label, procedure_groupe_instructeur_url(@group.procedure, @group))
|
||||
|
||||
= render partial: "layouts/mailers/signature"
|
|
@ -12,7 +12,7 @@
|
|||
= f.label :routing_criteria_name do
|
||||
Libellé du routage
|
||||
%span.notice Ce texte apparaitra sur le formulaire usager comme le libellé d'une liste
|
||||
= f.text_field :routing_criteria_name, placeholder: 'Votre ville', required: true
|
||||
= f.text_field :routing_criteria_name, placeholder: 'ex. Votre ville', required: true
|
||||
= f.submit 'Renommer', class: 'button primary send'
|
||||
|
||||
.card
|
||||
|
@ -22,13 +22,13 @@
|
|||
= f.label :label do
|
||||
Ajouter un groupe
|
||||
%span.notice Ce groupe sera un choix de la liste « #{@procedure.routing_criteria_name} » .
|
||||
= f.text_field :label, placeholder: 'Ville de Bordeaux', required: true
|
||||
= f.text_field :label, placeholder: 'ex. Ville de Bordeaux', required: true
|
||||
= f.submit 'Ajouter le groupe', class: 'button primary send'
|
||||
|
||||
%table.table.mt-2
|
||||
%thead
|
||||
%tr
|
||||
%th{ colspan: 2 } Liste des groupes
|
||||
%th{ colspan: 2 }= t(".existing_groupe", count: @groupes_instructeurs.count)
|
||||
%tbody
|
||||
- @groupes_instructeurs.each do |group|
|
||||
%tr
|
||||
|
|
|
@ -17,24 +17,29 @@
|
|||
= f.submit 'Renommer', class: 'button primary send'
|
||||
|
||||
.card
|
||||
.card-title Gestion des instructeurs
|
||||
.card-title Affectation des instructeurs
|
||||
= form_for :instructeur,
|
||||
url: { action: :add_instructeur },
|
||||
html: { class: 'form' } do |f|
|
||||
|
||||
= f.label :email do
|
||||
Affecter un nouvel instructeur
|
||||
= f.email_field :email, placeholder: 'marie.dupont@exemple.fr', required: true
|
||||
.instructeur-wrapper
|
||||
= select_tag :emails,
|
||||
options_for_select(@available_instructeur_emails),
|
||||
multiple: true,
|
||||
class: 'select-instructeurs select2-limited'
|
||||
|
||||
= f.submit 'Affecter', class: 'button primary send'
|
||||
|
||||
%table.table.mt-2
|
||||
%thead
|
||||
%tr
|
||||
%th{ colspan: 2 } Instructeurs affectés
|
||||
%th{ colspan: 2 }= t('.assigned_instructeur', count: @instructeurs.count)
|
||||
%tbody
|
||||
- @instructeurs.each do |instructeur|
|
||||
%tr
|
||||
%td= instructeur.email
|
||||
%td
|
||||
%span.icon.person
|
||||
#{instructeur.email}
|
||||
%td.actions= button_to 'retirer',
|
||||
{ action: :remove_instructeur },
|
||||
{ method: :delete,
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
- if dossier.messagerie_available?
|
||||
= render partial: "shared/dossiers/messages/form", locals: { commentaire: new_commentaire, form_url: form_url }
|
||||
- else
|
||||
= render partial: "shared/dossiers/messages/messagerie_disabled", locals: { service: dossier.procedure.service }
|
||||
= render partial: "shared/dossiers/messages/messagerie_disabled", locals: { service: dossier.procedure.service, dossier: dossier }
|
||||
|
|
|
@ -12,4 +12,11 @@
|
|||
- horaires = "Horaires : #{formatted_horaires(service.horaires)}"
|
||||
= simple_format(horaires)
|
||||
%p
|
||||
= link_to service.email, "mailto:#{service.email}"
|
||||
= mail_to service.email,
|
||||
service.email,
|
||||
subject: "[demarches-simplifiees.fr] Question sur le dossier Nº #{dossier.id} de la démarche Nº #{dossier.procedure.id}",
|
||||
rel: "noopener noreferrer",
|
||||
target: '_blank'
|
||||
|
||||
%p
|
||||
Penser bien à préciser que votre demande concerne le <b>dossier Nº #{dossier.id}</b>.
|
||||
|
|
|
@ -23,7 +23,6 @@ FOG_OPENSTACK_IDENTITY_API_VERSION=""
|
|||
FOG_OPENSTACK_REGION=""
|
||||
FOG_DIRECTORY=""
|
||||
FOG_ENABLED=""
|
||||
CARRIERWAVE_CACHE_DIR="/tmp/tps-local-cache"
|
||||
DS_PROXY_URL=""
|
||||
|
||||
FC_PARTICULIER_ID=""
|
||||
|
|
|
@ -40,7 +40,7 @@ features = [
|
|||
def database_exists?
|
||||
ActiveRecord::Base.connection
|
||||
true
|
||||
rescue ActiveRecord::NoDatabaseError
|
||||
rescue ActiveRecord::NoDatabaseError, PG::ConnectionBad
|
||||
false
|
||||
end
|
||||
|
||||
|
|
79
config/initializers/graphiql.rb
Normal file
79
config/initializers/graphiql.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
DEFAULT_QUERY = "# La documentation officielle de la spécification (Anglais) : https://graphql.org/
|
||||
# Une introduction aux concepts et raisons d'être de GraphQL (Français) : https://blog.octo.com/graphql-et-pourquoi-faire/
|
||||
# Le schema GraphQL de demarches-simplifiees.fr : https://demarches-simplifiees-graphql.netlify.com
|
||||
# Le endpoint GraphQL de demarches-simplifiees.fr : https://demarches-simplifiees.fr/api/v2/graphql
|
||||
|
||||
query getDemarche($demarcheNumber: Int!) {
|
||||
demarche(number: $demarcheNumber) {
|
||||
id
|
||||
number
|
||||
title
|
||||
champDescriptors {
|
||||
id
|
||||
type
|
||||
label
|
||||
}
|
||||
dossiers(first: 3) {
|
||||
nodes {
|
||||
id
|
||||
number
|
||||
datePassageEnConstruction
|
||||
datePassageEnInstruction
|
||||
dateTraitement
|
||||
usager {
|
||||
email
|
||||
}
|
||||
champs {
|
||||
id
|
||||
label
|
||||
... on TextChamp {
|
||||
value
|
||||
}
|
||||
... on DecimalNumberChamp {
|
||||
value
|
||||
}
|
||||
... on IntegerNumberChamp {
|
||||
value
|
||||
}
|
||||
... on CheckboxChamp {
|
||||
value
|
||||
}
|
||||
... on DateChamp {
|
||||
value
|
||||
}
|
||||
... on DossierLinkChamp {
|
||||
dossier {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on MultipleDropDownListChamp {
|
||||
values
|
||||
}
|
||||
... on LinkedDropDownListChamp {
|
||||
primaryValue
|
||||
secondaryValue
|
||||
}
|
||||
... on PieceJustificativeChamp {
|
||||
url
|
||||
}
|
||||
... on CarteChamp {
|
||||
geoAreas {
|
||||
source
|
||||
geometry {
|
||||
type
|
||||
coordinates
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}"
|
||||
|
||||
GraphiQL::Rails.config.initial_query = DEFAULT_QUERY
|
||||
GraphiQL::Rails.config.title = 'demarches-simplifiees.fr'
|
|
@ -2,4 +2,5 @@ fr:
|
|||
activerecord:
|
||||
attributes:
|
||||
commentaire:
|
||||
body: 'Votre message'
|
||||
file: fichier
|
||||
|
|
8
config/locales/models/geo_area/fr.yml
Normal file
8
config/locales/models/geo_area/fr.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
fr:
|
||||
activerecord:
|
||||
attributes:
|
||||
geo_area:
|
||||
source:
|
||||
cadastre: Parcelle cadastrale
|
||||
quartier_prioritaire: Quartier prioritaire
|
||||
selection_utilisateur: Sélection utilisateur
|
|
@ -0,0 +1,18 @@
|
|||
fr:
|
||||
new_administrateur:
|
||||
groupe_instructeurs:
|
||||
index:
|
||||
existing_groupe:
|
||||
one: "%{count} groupe existe"
|
||||
other: "%{count} groupes existent"
|
||||
show:
|
||||
assigned_instructeur:
|
||||
one: "%{count} instructeur est affecté"
|
||||
other: "%{count} instructeurs sont affectés"
|
||||
add_instructeur:
|
||||
wrong_address:
|
||||
one: "%{value} n'est pas une adresse email valide"
|
||||
other: "%{value} ne sont pas des adresses emails valides"
|
||||
assignment:
|
||||
one: "L’instructeur %{value} a été affecté au groupe « %{groupe} »."
|
||||
other: "Les instructeurs %{value} ont été affectés au groupe « %{groupe} »."
|
|
@ -42,8 +42,6 @@ defaults: &defaults
|
|||
openstack_identity_api_version: "<%= ENV['FOG_OPENSTACK_IDENTITY_API_VERSION'] %>"
|
||||
openstack_region: <%= ENV['FOG_OPENSTACK_REGION'] %>
|
||||
directory: <%= ENV['FOG_DIRECTORY'] %>
|
||||
carrierwave:
|
||||
cache_dir: <%= ENV['CARRIERWAVE_CACHE_DIR'] %>
|
||||
mailtrap:
|
||||
username: <%= ENV['MAILTRAP_USERNAME'] %>
|
||||
password: <%= ENV['MAILTRAP_PASSWORD'] %>
|
||||
|
@ -84,8 +82,6 @@ test:
|
|||
key: api_entreprise_test_key
|
||||
fog:
|
||||
directory: tps_dev
|
||||
carrierwave:
|
||||
cache_dir: /tmp/tps-test-cache
|
||||
pipedrive:
|
||||
key: pipedrive_test_key
|
||||
france_connect_particulier:
|
||||
|
|
5
db/migrate/20191113142816_instructeurs_remove_email.rb
Normal file
5
db/migrate/20191113142816_instructeurs_remove_email.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class InstructeursRemoveEmail < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
remove_column :instructeurs, :email
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_10_24_150452) do
|
||||
ActiveRecord::Schema.define(version: 2019_11_13_142816) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -414,12 +414,10 @@ ActiveRecord::Schema.define(version: 2019_10_24_150452) do
|
|||
end
|
||||
|
||||
create_table "instructeurs", id: :serial, force: :cascade do |t|
|
||||
t.string "email", default: "", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.text "encrypted_login_token"
|
||||
t.datetime "login_token_created_at"
|
||||
t.index ["email"], name: "index_instructeurs_on_email"
|
||||
end
|
||||
|
||||
create_table "invites", id: :serial, force: :cascade do |t|
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
require Rails.root.join("lib", "tasks", "task_helper")
|
||||
|
||||
namespace :'2017_10_30_copy_commentaire_piece_justificative_to_file' do
|
||||
task set: :environment do
|
||||
commentaires_to_process = Commentaire.where(file: nil).where.not(piece_justificative_id: nil).reorder(id: :desc)
|
||||
|
||||
rake_puts "#{commentaires_to_process.count} commentaires to process..."
|
||||
|
||||
commentaires_to_process.each do |c|
|
||||
process_commentaire(c)
|
||||
end
|
||||
end
|
||||
|
||||
task fix: :environment do
|
||||
commentaires_to_fix = Commentaire.where.not(file: nil).where.not(piece_justificative_id: nil).reorder(id: :desc)
|
||||
|
||||
rake_puts "#{commentaires_to_fix.count} commentaires to fix..."
|
||||
|
||||
commentaires_to_fix.each do |c|
|
||||
process_commentaire(c)
|
||||
end
|
||||
end
|
||||
|
||||
def sanitize_name(name) # from https://github.com/carrierwaveuploader/carrierwave/blob/master/lib/carrierwave/sanitized_file.rb#L323
|
||||
name = name.gsub(/[^[:word:]\.\-\+]/, "_")
|
||||
name = "_#{name}" if name.match?(/\A\.+\z/)
|
||||
name = "unnamed" if name.empty?
|
||||
return name.mb_chars.to_s
|
||||
end
|
||||
|
||||
def process_commentaire(commentaire)
|
||||
rake_puts "Processing commentaire #{commentaire.id}"
|
||||
if commentaire.piece_justificative.present?
|
||||
# https://github.com/carrierwaveuploader/carrierwave#uploading-files-from-a-remote-location
|
||||
commentaire.remote_file_url = commentaire.piece_justificative.content_url
|
||||
|
||||
if commentaire.piece_justificative.original_filename.present?
|
||||
commentaire.file.define_singleton_method(:filename) { sanitize_name(commentaire.piece_justificative.original_filename) }
|
||||
end
|
||||
|
||||
if commentaire.body.blank?
|
||||
commentaire.body = commentaire.piece_justificative.original_filename || "."
|
||||
end
|
||||
|
||||
commentaire.save
|
||||
if commentaire.file.blank?
|
||||
rake_puts "Failed to save file for commentaire #{commentaire.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,113 +0,0 @@
|
|||
require Rails.root.join("lib", "tasks", "task_helper")
|
||||
|
||||
namespace :cloudstorage do
|
||||
task init: :environment do
|
||||
os_config = (YAML.load_file(Fog.credentials_path))['default']
|
||||
@os = OpenStack::Connection.create(
|
||||
{
|
||||
username: os_config['openstack_username'],
|
||||
api_key: os_config['openstack_api_key'],
|
||||
auth_method: "password",
|
||||
auth_url: "https://auth.cloud.ovh.net/v2.0/",
|
||||
authtenant_name: os_config['openstack_tenant'],
|
||||
service_type: "object-store",
|
||||
region: os_config['openstack_region']
|
||||
}
|
||||
)
|
||||
@cont = @os.container(CarrierWave::Uploader::Base.fog_directory)
|
||||
end
|
||||
|
||||
desc 'Move local attestations on cloud storage'
|
||||
task migrate: :environment do
|
||||
puts 'Starting migration'
|
||||
|
||||
Rake::Task['cloudstorage:init'].invoke
|
||||
|
||||
error_count = 0
|
||||
[Cerfa, PieceJustificative, Procedure].each do |c|
|
||||
c.all.each do |entry|
|
||||
content = (c == Procedure) ? entry.logo : entry.content
|
||||
if !(content.current_path.nil? || File.exist?(File.dirname(content.current_path) + '/uploaded'))
|
||||
secure_token = SecureRandom.uuid
|
||||
filename = "#{entry.class.to_s.underscore}-#{secure_token}#{File.extname(content.current_path)}"
|
||||
rake_puts "Uploading #{content.current_path}"
|
||||
begin
|
||||
@cont.create_object(filename, {}, File.open(content.current_path))
|
||||
|
||||
File.open(File.dirname(content.current_path) + '/uploaded', "w+") { |f| f.write(File.basename(content.current_path)) }
|
||||
File.open(File.dirname(content.current_path) + '/filename_cloudstorage', "w+") { |f| f.write(filename) }
|
||||
File.open(File.dirname(content.current_path) + '/secure_token_cloudstorage', "w+") { |f| f.write(secure_token) }
|
||||
|
||||
entry.update_column(c == Procedure ? :logo : :content, filename)
|
||||
entry.update_column(c == Procedure ? :logo_secure_token : :content_secure_token, secure_token)
|
||||
rescue Errno::ENOENT
|
||||
rake_puts "ERROR: #{content.current_path} does not exist!"
|
||||
File.open('upload_errors.report', "a+") { |f| f.write(content.current_path) }
|
||||
error_count += 1
|
||||
end
|
||||
else
|
||||
if content.current_path.present? && File.exist?(File.dirname(content.current_path) + '/uploaded')
|
||||
filename = File.open(File.dirname(content.current_path) + '/filename_cloudstorage', "r").read
|
||||
secure_token = File.open(File.dirname(content.current_path) + '/secure_token_cloudstorage', "r").read
|
||||
|
||||
entry.update_column(c == Procedure ? :logo : :content, filename)
|
||||
entry.update_column(c == Procedure ? :logo_secure_token : :content_secure_token, secure_token)
|
||||
|
||||
rake_puts "RESTORE IN DATABASE: #{filename} "
|
||||
elsif content.current_path.present?
|
||||
rake_puts "Skipping #{content.current_path}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rake_puts "There were #{error_count} errors while uploading files. See upload_errors.report file for details."
|
||||
puts 'Enf of migration'
|
||||
end
|
||||
|
||||
desc 'Clear documents in tenant and revert file entries in database'
|
||||
task :revert do
|
||||
Rake::Task['cloudstorage:init'].invoke
|
||||
|
||||
[Cerfa, PieceJustificative, Procedure].each do |c|
|
||||
c.all.each do |entry|
|
||||
content = (c == Procedure) ? entry.logo : entry.content
|
||||
if content.current_path.present?
|
||||
if File.exist?(File.dirname(content.current_path) + '/uploaded')
|
||||
previous_filename = File.read(File.dirname(content.current_path) + '/uploaded')
|
||||
|
||||
entry.update_column(c == Procedure ? :logo : :content, previous_filename)
|
||||
entry.update_column(c == Procedure ? :logo_secure_token : :content_secure_token, nil)
|
||||
|
||||
rake_puts "restoring #{content.current_path} db data to #{previous_filename}"
|
||||
|
||||
@cont.delete_object(File.open(File.dirname(content.current_path) + '/filename_cloudstorage', "r").read)
|
||||
|
||||
FileUtils.rm(File.dirname(content.current_path) + '/uploaded')
|
||||
FileUtils.rm(File.dirname(content.current_path) + '/filename_cloudstorage')
|
||||
FileUtils.rm(File.dirname(content.current_path) + '/secure_token_cloudstorage')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Clear old documents in tenant'
|
||||
task :clear do
|
||||
Rake::Task['cloudstorage:init'].invoke
|
||||
|
||||
@cont.objects.each do |object|
|
||||
rake_puts "Removing #{object}"
|
||||
@cont.delete_object(object)
|
||||
end
|
||||
end
|
||||
|
||||
task :clear_old_objects do
|
||||
Rake::Task['cloudstorage:init'].invoke
|
||||
|
||||
@cont.objects_detail.each do |object, details|
|
||||
last_modified = Time.zone.parse(details[:last_modified])
|
||||
@cont.delete_object(object) if last_modified.utc <= (Time.zone.now - 2.years).utc
|
||||
end
|
||||
end
|
||||
end
|
|
@ -76,7 +76,7 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(demarche: {
|
||||
id: procedure.to_typed_id,
|
||||
number: procedure.id.to_s,
|
||||
number: procedure.id,
|
||||
title: procedure.libelle,
|
||||
description: procedure.description,
|
||||
state: 'brouillon',
|
||||
|
@ -123,7 +123,7 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(demarche: {
|
||||
id: procedure.to_typed_id,
|
||||
number: procedure.id.to_s,
|
||||
number: procedure.id,
|
||||
dossiers: {
|
||||
nodes: [{ id: dossier1.to_typed_id }, { id: dossier.to_typed_id }]
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
number: dossier.id.to_s,
|
||||
number: dossier.id,
|
||||
state: 'en_construction',
|
||||
updatedAt: dossier.updated_at.iso8601,
|
||||
datePassageEnConstruction: dossier.en_construction_at.iso8601,
|
||||
|
@ -216,7 +216,310 @@ describe API::V2::GraphqlController do
|
|||
end
|
||||
|
||||
context "mutations" do
|
||||
context 'createDirectUpload' do
|
||||
describe 'dossierEnvoyerMessage' do
|
||||
context 'success' do
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierEnvoyerMessage(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\",
|
||||
body: \"Bonjour\"
|
||||
}) {
|
||||
message {
|
||||
body
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should post a message" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierEnvoyerMessage: {
|
||||
message: {
|
||||
body: "Bonjour"
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'schema error' do
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierEnvoyerMessage(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\"
|
||||
}) {
|
||||
message {
|
||||
body
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should fail" do
|
||||
expect(gql_data).to eq(nil)
|
||||
expect(gql_errors).not_to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'validation error' do
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierEnvoyerMessage(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\",
|
||||
body: \"\"
|
||||
}) {
|
||||
message {
|
||||
body
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should fail" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossierEnvoyerMessage: {
|
||||
errors: [{ message: "Votre message ne peut être vide" }],
|
||||
message: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dossierPasserEnInstruction' do
|
||||
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierPasserEnInstruction(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\"
|
||||
}) {
|
||||
dossier {
|
||||
id
|
||||
state
|
||||
motivation
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
context 'success' do
|
||||
it "should passer en instruction dossier" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierPasserEnInstruction: {
|
||||
dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
state: "en_instruction",
|
||||
motivation: nil
|
||||
},
|
||||
errors: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'validation error' do
|
||||
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
|
||||
it "should fail" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossierPasserEnInstruction: {
|
||||
errors: [{ message: "Le dossier est déjà en instruction" }],
|
||||
dossier: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dossierClasserSansSuite' do
|
||||
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierClasserSansSuite(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\",
|
||||
motivation: \"Parce que\"
|
||||
}) {
|
||||
dossier {
|
||||
id
|
||||
state
|
||||
motivation
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
context 'success' do
|
||||
it "should classer sans suite dossier" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierClasserSansSuite: {
|
||||
dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
state: "sans_suite",
|
||||
motivation: "Parce que"
|
||||
},
|
||||
errors: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'validation error' do
|
||||
let(:dossier) { create(:dossier, :accepte, procedure: procedure) }
|
||||
|
||||
it "should fail" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossierClasserSansSuite: {
|
||||
errors: [{ message: "Le dossier est déjà accepté" }],
|
||||
dossier: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dossierRefuser' do
|
||||
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierRefuser(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\",
|
||||
motivation: \"Parce que\"
|
||||
}) {
|
||||
dossier {
|
||||
id
|
||||
state
|
||||
motivation
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
context 'success' do
|
||||
it "should refuser dossier" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierRefuser: {
|
||||
dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
state: "refuse",
|
||||
motivation: "Parce que"
|
||||
},
|
||||
errors: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'validation error' do
|
||||
let(:dossier) { create(:dossier, :sans_suite, procedure: procedure) }
|
||||
|
||||
it "should fail" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossierRefuser: {
|
||||
errors: [{ message: "Le dossier est déjà sans suite" }],
|
||||
dossier: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dossierAccepter' do
|
||||
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierAccepter(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\",
|
||||
motivation: \"Parce que\"
|
||||
}) {
|
||||
dossier {
|
||||
id
|
||||
state
|
||||
motivation
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
context 'success' do
|
||||
it "should accepter dossier" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierAccepter: {
|
||||
dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
state: "accepte",
|
||||
motivation: "Parce que"
|
||||
},
|
||||
errors: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'success without motivation' do
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierAccepter(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\"
|
||||
}) {
|
||||
dossier {
|
||||
id
|
||||
state
|
||||
motivation
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should accepter dossier" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierAccepter: {
|
||||
dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
state: "accepte",
|
||||
motivation: nil
|
||||
},
|
||||
errors: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'validation error' do
|
||||
let(:dossier) { create(:dossier, :refuse, procedure: procedure) }
|
||||
|
||||
it "should fail" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossierAccepter: {
|
||||
errors: [{ message: "Le dossier est déjà refusé" }],
|
||||
dossier: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'createDirectUpload' do
|
||||
let(:query) do
|
||||
"mutation {
|
||||
createDirectUpload(input: {
|
||||
|
@ -263,5 +566,26 @@ describe API::V2::GraphqlController do
|
|||
expect(gql_errors).not_to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context "mutation" do
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierEnvoyerMessage(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\",
|
||||
body: \"Bonjour\"
|
||||
}) {
|
||||
message {
|
||||
body
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should return error" do
|
||||
expect(gql_data[:dossierEnvoyerMessage]).to eq(nil)
|
||||
expect(gql_errors).not_to eq(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
22
spec/controllers/manager/application_controller_spec.rb
Normal file
22
spec/controllers/manager/application_controller_spec.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
describe Manager::ApplicationController, type: :controller do
|
||||
describe 'append_info_to_payload' do
|
||||
let(:current_user) { create(:administration) }
|
||||
let(:payload) { {} }
|
||||
|
||||
before do
|
||||
allow(@controller).to receive(:current_user).and_return(current_user)
|
||||
@controller.send(:append_info_to_payload, payload)
|
||||
end
|
||||
|
||||
it do
|
||||
[:db_runtime, :view_runtime, :variant, :rendered_format].each do |key|
|
||||
payload.delete(key)
|
||||
end
|
||||
expect(payload).to eq({
|
||||
user_agent: 'Rails Testing',
|
||||
user_id: current_user.id,
|
||||
user_email: current_user.email
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -93,20 +93,26 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do
|
|||
params: {
|
||||
procedure_id: procedure.id,
|
||||
id: gi_1_1.id,
|
||||
instructeur: { email: new_instructeur_email }
|
||||
emails: new_instructeur_emails
|
||||
}
|
||||
end
|
||||
|
||||
context 'of a new instructeur' do
|
||||
let(:new_instructeur_email) { 'new_instructeur@mail.com' }
|
||||
context 'of a news instructeurs' do
|
||||
let(:new_instructeur_emails) { ['new_i1@mail.com', 'new_i2@mail.com'] }
|
||||
|
||||
it { expect(gi_1_1.instructeurs.map(&:email)).to include(new_instructeur_email) }
|
||||
it { expect(gi_1_1.instructeurs.pluck(:email)).to include(*new_instructeur_emails) }
|
||||
it { expect(flash.notice).to be_present }
|
||||
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) }
|
||||
end
|
||||
|
||||
context 'of an instructeur already in the group' do
|
||||
let(:new_instructeur_email) { instructeur.email }
|
||||
let(:new_instructeur_emails) { [instructeur.email] }
|
||||
|
||||
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur)) }
|
||||
end
|
||||
|
||||
context 'of badly formed email' do
|
||||
let(:new_instructeur_emails) { ['badly_formed_email'] }
|
||||
|
||||
it { expect(flash.alert).to be_present }
|
||||
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur)) }
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'The routing' do
|
||||
feature 'The routing', js: true do
|
||||
let(:password) { 'a very complicated password' }
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_service, :for_individual) }
|
||||
let(:administrateur) { create(:administrateur, procedures: [procedure]) }
|
||||
let(:scientifique_user) { create(:user, password: password) }
|
||||
let(:litteraire_user) { create(:user, password: password) }
|
||||
|
||||
before { Flipper.enable_actor(:administrateur_routage, administrateur.user) }
|
||||
before do
|
||||
procedure.defaut_groupe_instructeur.instructeurs << administrateur.instructeur
|
||||
Flipper.enable_actor(:administrateur_routage, administrateur.user)
|
||||
end
|
||||
|
||||
scenario 'works' do
|
||||
login_as administrateur.user, scope: :user
|
||||
|
@ -22,11 +25,15 @@ feature 'The routing' do
|
|||
|
||||
# rename defaut groupe to littéraire
|
||||
click_on 'voir'
|
||||
fill_in 'groupe_instructeur_label', with: 'littéraire'
|
||||
expect(page).to have_css('#groupe_instructeur_label')
|
||||
2.times { find(:css, "#groupe_instructeur_label").set("littéraire") }
|
||||
click_on 'Renommer'
|
||||
|
||||
expect(procedure.defaut_groupe_instructeur.reload.label).to eq('littéraire')
|
||||
|
||||
# add victor to littéraire groupe
|
||||
fill_in 'instructeur_email', with: 'victor@inst.com'
|
||||
try_twice { find('input.select2-search__field').send_keys('victor@inst.com', :enter) }
|
||||
|
||||
perform_enqueued_jobs { click_on 'Affecter' }
|
||||
victor = User.find_by(email: 'victor@inst.com').instructeur
|
||||
|
||||
|
@ -37,13 +44,13 @@ feature 'The routing' do
|
|||
click_on 'Ajouter le groupe'
|
||||
|
||||
# add marie to scientifique groupe
|
||||
fill_in 'instructeur_email', with: 'marie@inst.com'
|
||||
try_twice { find('input.select2-search__field').send_keys('marie@inst.com', :enter) }
|
||||
perform_enqueued_jobs { click_on 'Affecter' }
|
||||
marie = User.find_by(email: 'marie@inst.com').instructeur
|
||||
|
||||
# publish
|
||||
publish_procedure(procedure)
|
||||
log_out
|
||||
log_out(old_layout: true)
|
||||
|
||||
# 2 users fill a dossier in each group
|
||||
user_send_dossier(scientifique_user, 'scientifique')
|
||||
|
@ -165,7 +172,25 @@ feature 'The routing' do
|
|||
expect(page).to have_content 'Mot de passe enregistré'
|
||||
end
|
||||
|
||||
def log_out
|
||||
def log_out(old_layout: false)
|
||||
if old_layout
|
||||
expect(page).to have_content('Se déconnecter')
|
||||
click_on 'Se déconnecter'
|
||||
else
|
||||
try_twice do
|
||||
expect(page).to have_css('[title="Mon compte"]')
|
||||
find('[title="Mon compte"]').click
|
||||
expect(page).to have_content('Se déconnecter')
|
||||
click_on 'Se déconnecter'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def try_twice
|
||||
begin
|
||||
yield
|
||||
rescue Selenium::WebDriver::Error::ElementNotInteractableError, Capybara::ElementNotFound
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
class GroupeInstructeurMailerPreview < ActionMailer::Preview
|
||||
def add_instructeurs
|
||||
groupe = GroupeInstructeur.new(label: 'Val-De-Marne')
|
||||
current_instructeur_email = 'admin@dgfip.com'
|
||||
instructeurs = Instructeur.limit(2)
|
||||
GroupeInstructeurMailer.add_instructeurs(groupe, instructeurs, current_instructeur_email)
|
||||
end
|
||||
end
|
|
@ -149,7 +149,7 @@ describe User, type: :model do
|
|||
it 'keeps the existing instructeurs and adds administrateur' do
|
||||
user = subject
|
||||
expect(user.instructeur).to eq(instructeur)
|
||||
expect(user.instructeur.administrateurs).to eq(old_admins + admins)
|
||||
expect(user.instructeur.administrateurs).to match_array(old_admins + admins)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue