Merge pull request #6445 from betagouv/main

2021-09-07-01
This commit is contained in:
LeSim 2021-09-07 12:51:07 +02:00 committed by GitHub
commit 1f1be70763
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 1288 additions and 2416 deletions

View file

@ -42,6 +42,7 @@ gem 'groupdate'
gem 'haml-rails'
gem 'hashie'
gem 'http_accept_language'
gem 'i18n_data'
gem 'i18n-tasks', require: false
gem 'iban-tools'
gem 'image_processing'

View file

@ -11,40 +11,40 @@ GEM
specs:
aasm (5.1.1)
concurrent-ruby (~> 1.0)
actioncable (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
actioncable (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
actionmailbox (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
mail (>= 2.7.1)
actionmailer (6.1.3.2)
actionpack (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activesupport (= 6.1.3.2)
actionmailer (6.1.4.1)
actionpack (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activesupport (= 6.1.4.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.3.2)
actionview (= 6.1.3.2)
activesupport (= 6.1.3.2)
actionpack (6.1.4.1)
actionview (= 6.1.4.1)
activesupport (= 6.1.4.1)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.3.2)
actionpack (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
actiontext (6.1.4.1)
actionpack (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
nokogiri (>= 1.8.5)
actionview (6.1.3.2)
activesupport (= 6.1.3.2)
actionview (6.1.4.1)
activesupport (= 6.1.4.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -59,26 +59,26 @@ GEM
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_storage_validations (0.9.2)
rails (>= 5.2.0)
activejob (6.1.3.2)
activesupport (= 6.1.3.2)
activejob (6.1.4.1)
activesupport (= 6.1.4.1)
globalid (>= 0.3.6)
activemodel (6.1.3.2)
activesupport (= 6.1.3.2)
activerecord (6.1.3.2)
activemodel (= 6.1.3.2)
activesupport (= 6.1.3.2)
activestorage (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activesupport (= 6.1.3.2)
activemodel (6.1.4.1)
activesupport (= 6.1.4.1)
activerecord (6.1.4.1)
activemodel (= 6.1.4.1)
activesupport (= 6.1.4.1)
activestorage (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activesupport (= 6.1.4.1)
marcel (~> 1.0.0)
mini_mime (~> 1.0.2)
mini_mime (>= 1.1.0)
activestorage-openstack (1.5.1)
fog-openstack (~> 1.0)
marcel
rails (>= 5.2.2)
activesupport (6.1.3.2)
activesupport (6.1.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -134,7 +134,7 @@ GEM
bindex (0.8.1)
bootsnap (1.7.2)
msgpack (~> 1.0)
brakeman (5.0.0)
brakeman (5.1.1)
browser (5.3.1)
builder (3.2.4)
byebug (11.1.3)
@ -292,8 +292,8 @@ GEM
raabro (~> 1.4)
geo_coord (0.2.0)
geocoder (1.6.5)
globalid (0.4.2)
activesupport (>= 4.2.0)
globalid (0.5.2)
activesupport (>= 5.0)
gon (6.4.0)
actionpack (>= 3.0.20)
i18n (>= 0.7)
@ -360,6 +360,7 @@ GEM
rails-i18n
rainbow (>= 2.2.2, < 4.0)
terminal-table (>= 1.5.1)
i18n_data (0.13.0)
iban-tools (1.1.0)
ice_nine (0.11.2)
image_processing (1.12.1)
@ -411,7 +412,7 @@ GEM
railties (>= 4)
request_store (~> 1.0)
logstash-event (1.2.02)
loofah (2.9.1)
loofah (2.12.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -426,8 +427,8 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0212)
mini_magick (4.11.0)
mini_mime (1.0.3)
mini_portile2 (2.5.1)
mini_mime (1.1.1)
mini_portile2 (2.6.1)
minitest (5.14.4)
momentjs-rails (2.20.1)
railties (>= 3.1)
@ -437,9 +438,9 @@ GEM
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
netrc (0.11.0)
nio4r (2.5.7)
nokogiri (1.11.6)
mini_portile2 (~> 2.5.0)
nio4r (2.5.8)
nokogiri (1.12.4)
mini_portile2 (~> 2.6.1)
racc (~> 1.4)
open4 (1.3.4)
openid_connect (1.2.0)
@ -509,20 +510,20 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.1.3.2)
actioncable (= 6.1.3.2)
actionmailbox (= 6.1.3.2)
actionmailer (= 6.1.3.2)
actionpack (= 6.1.3.2)
actiontext (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activemodel (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
rails (6.1.4.1)
actioncable (= 6.1.4.1)
actionmailbox (= 6.1.4.1)
actionmailer (= 6.1.4.1)
actionpack (= 6.1.4.1)
actiontext (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activemodel (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
bundler (>= 1.15.0)
railties (= 6.1.3.2)
railties (= 6.1.4.1)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -536,19 +537,19 @@ GEM
activesupport (>= 4.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.3.0)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7)
railties (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
railties (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
method_source
rake (>= 0.8.7)
rake (>= 0.13)
thor (~> 1.0)
rainbow (3.0.0)
rake (13.0.3)
rake (13.0.6)
rake-progressbar (0.0.5)
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
@ -767,7 +768,7 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
websocket-driver (0.7.4)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
@ -837,6 +838,7 @@ DEPENDENCIES
hashie
http_accept_language
i18n-tasks
i18n_data
iban-tools
image_processing
json_schemer

View file

@ -1,63 +0,0 @@
class Admin::InstructeursController < AdminController
include SmartListing::Helper::ControllerExtensions
helper SmartListing::Helper
def index
@instructeurs = smart_listing_create :instructeurs,
current_administrateur.instructeurs,
partial: "admin/instructeurs/list",
array: true
end
def create
email = params[:instructeur][:email].downcase
@instructeur = Instructeur.by_email(email)
procedure_id = params[:procedure_id]
procedure = Procedure.find_by(id: procedure_id)
if @instructeur.nil?
invite_instructeur(email)
else
assign_instructeur!
end
if procedure_id.present?
redirect_to admin_procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur)
else
redirect_to admin_instructeurs_path
end
end
def destroy
Instructeur.find(params[:id]).administrateurs.delete current_administrateur
redirect_to admin_instructeurs_path
end
private
def invite_instructeur(email)
user = User.create_or_promote_to_instructeur(
email,
SecureRandom.hex,
administrateurs: [current_administrateur]
)
if user.valid?
user.invite!
flash.notice = 'Instructeur ajouté'
else
flash.alert = user.errors.full_messages
end
end
def assign_instructeur!
if current_administrateur.instructeurs.include?(@instructeur)
flash.alert = 'Instructeur déjà ajouté'
else
@instructeur.administrateurs.push current_administrateur
flash.notice = 'Instructeur ajouté'
# TODO Mailer no assign_to
end
end
end

View file

@ -1,147 +0,0 @@
class Admin::ProceduresController < AdminController
include SmartListing::Helper::ControllerExtensions
helper SmartListing::Helper
before_action :retrieve_procedure, only: [:show, :delete_logo, :delete_deliberation, :delete_notice, :publish_validate, :publish]
def index
if current_administrateur.procedures.count != 0
@procedures = smart_listing_create :procedures,
current_administrateur.procedures.publiees.order(published_at: :desc),
partial: "admin/procedures/list",
array: true
active_class
else
redirect_to new_from_existing_admin_procedures_path
end
end
def show
if @procedure.brouillon?
@procedure_lien = commencer_test_url(path: @procedure.path)
else
@procedure_lien = commencer_url(path: @procedure.path)
end
@procedure.path = @procedure.suggested_path(current_administrateur)
@current_administrateur = current_administrateur
end
def destroy
procedure = current_administrateur.procedures.find(params[:id])
if procedure.can_be_deleted_by_administrateur?
procedure.discard_and_keep_track!(current_administrateur)
flash.notice = 'Démarche supprimée'
redirect_to admin_procedures_draft_path
else
render json: {}, status: 403
end
end
def archive
procedure = current_administrateur.procedures.find(params[:procedure_id])
procedure.close!
flash.notice = "Démarche close"
redirect_to admin_procedures_path
rescue ActiveRecord::RecordNotFound
flash.alert = 'Démarche inexistante'
redirect_to admin_procedures_path
end
def clone
procedure = Procedure.find(params[:procedure_id])
new_procedure = procedure.clone(current_administrateur, cloned_from_library?)
if new_procedure.valid?
flash.notice = 'Démarche clonée'
redirect_to edit_admin_procedure_path(id: new_procedure.id)
else
if cloned_from_library?
flash.alert = new_procedure.errors.full_messages
redirect_to new_from_existing_admin_procedures_path
else
flash.alert = new_procedure.errors.full_messages
redirect_to admin_procedures_path
end
end
rescue ActiveRecord::RecordNotFound
flash.alert = 'Démarche inexistante'
redirect_to admin_procedures_path
end
SIGNIFICANT_DOSSIERS_THRESHOLD = 30
def new_from_existing
significant_procedure_ids = Procedure
.publiees_ou_closes
.joins(:dossiers)
.group("procedures.id")
.having("count(dossiers.id) >= ?", SIGNIFICANT_DOSSIERS_THRESHOLD)
.pluck('procedures.id')
@grouped_procedures = Procedure
.includes(:administrateurs, :service)
.where(id: significant_procedure_ids)
.group_by(&:organisation_name)
.sort_by { |_, procedures| procedures.first.created_at }
render layout: 'application'
end
def active_class
@active_class = 'active'
end
def archived_class
@archived_class = 'active'
end
def draft_class
@draft_class = 'active'
end
def delete_logo
@procedure.logo.purge_later
flash.notice = 'le logo a bien été supprimé'
redirect_to edit_admin_procedure_path(@procedure)
end
def delete_deliberation
@procedure.deliberation.purge_later
flash.notice = 'la délibération a bien été supprimée'
redirect_to edit_admin_procedure_path(@procedure)
end
def delete_notice
@procedure.notice.purge_later
flash.notice = 'la notice a bien été supprimée'
redirect_to edit_admin_procedure_path(@procedure)
end
private
def cloned_from_library?
params[:from_new_from_existing].present?
end
def publish_params
params.permit(:path, :lien_site_web)
end
def procedure_params
editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :euro_flag, :logo, :auto_archive_on]
permited_params = if @procedure&.locked?
params.require(:procedure).permit(*editable_params)
else
params.require(:procedure).permit(*editable_params, :duree_conservation_dossiers_dans_ds, :duree_conservation_dossiers_hors_ds, :for_individual, :path)
end
permited_params
end
end

View file

@ -0,0 +1,20 @@
class API::PaysController < ApplicationController
before_action :authenticate_logged_user!
def index
countries = I18nData.countries('FR').zip(I18nData.countries(I18n.locale))
countries = countries.map do |(code, value_fr), (localized_code, localized_value)|
if code != localized_code
raise "Countries lists mismatch. It means i18n_data gem has some internal inconsistencies."
end
{
code: code,
value: value_fr,
label: localized_value
}
end
render json: countries
end
end

View file

@ -78,6 +78,9 @@ class ApplicationController < ActionController::Base
def set_locale(locale)
if locale && locale.to_sym.in?(I18n.available_locales)
cookies[:locale] = locale
if user_signed_in?
current_user.update(locale: locale)
end
locale
end
end
@ -322,6 +325,7 @@ class ApplicationController < ActionController::Base
def switch_locale(&action)
locale = extract_locale_from_query_params ||
extract_locale_from_cookie ||
extract_locale_from_user ||
extract_locale_from_accept_language_header ||
I18n.default_locale
@ -332,6 +336,10 @@ class ApplicationController < ActionController::Base
set_locale(request.query_parameters[:locale])
end
def extract_locale_from_user
current_user&.locale
end
def extract_locale_from_cookie
cookies[:locale]
end

View file

@ -52,6 +52,23 @@ module NewAdministrateur
@procedure ||= Procedure.new(for_individual: true)
end
SIGNIFICANT_DOSSIERS_THRESHOLD = 30
def new_from_existing
significant_procedure_ids = Procedure
.publiees_ou_closes
.joins(:dossiers)
.group("procedures.id")
.having("count(dossiers.id) >= ?", SIGNIFICANT_DOSSIERS_THRESHOLD)
.pluck('procedures.id')
@grouped_procedures = Procedure
.includes(:administrateurs, :service)
.where(id: significant_procedure_ids)
.group_by(&:organisation_name)
.sort_by { |_, procedures| procedures.first.created_at }
end
def show
@procedure = current_administrateur.procedures.find(params[:id])
@current_administrateur = current_administrateur
@ -93,6 +110,40 @@ module NewAdministrateur
end
end
def clone
procedure = Procedure.find(params[:procedure_id])
new_procedure = procedure.clone(current_administrateur, cloned_from_library?)
if new_procedure.valid?
flash.notice = 'Démarche clonée'
redirect_to edit_admin_procedure_path(id: new_procedure.id)
else
if cloned_from_library?
flash.alert = new_procedure.errors.full_messages
redirect_to new_from_existing_admin_procedures_path
else
flash.alert = new_procedure.errors.full_messages
redirect_to admin_procedures_path
end
end
rescue ActiveRecord::RecordNotFound
flash.alert = 'Démarche inexistante'
redirect_to admin_procedures_path
end
def archive
procedure = current_administrateur.procedures.find(params[:procedure_id])
procedure.close!
flash.notice = "Démarche close"
redirect_to admin_procedures_path
rescue ActiveRecord::RecordNotFound
flash.alert = 'Démarche inexistante'
redirect_to admin_procedures_path
end
def destroy
procedure = current_administrateur.procedures.find(params[:id])
@ -219,5 +270,9 @@ module NewAdministrateur
def allow_decision_access_params
params.require(:experts_procedure).permit(:allow_decision_access)
end
def cloned_from_library?
params[:from_new_from_existing].present?
end
end
end

View file

@ -12,7 +12,7 @@ function ComboPaysSearch(params) {
hiddenFieldId={params.hiddenFieldId}
scope="pays"
minimumInputLength={0}
transformResult={({ nom }) => [nom, nom]}
transformResult={({ code, value, label }) => [code, value, label]}
/>
</QueryClientProvider>
);

View file

@ -47,6 +47,10 @@ function ComboSearch({
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
const [value, setValue] = useState(initialValue);
const resultsMap = useRef({});
const getLabel = (result) => {
const [, value, label] = transformResult(result);
return label ?? value;
};
const setExternalValue = useCallback(
(value) => {
if (hiddenValueField) {
@ -64,8 +68,8 @@ function ComboSearch({
},
[hiddenIdField]
);
const setExternalValueAndId = useCallback((value) => {
const [key, result] = resultsMap.current[value];
const setExternalValueAndId = useCallback((label) => {
const { key, value, result } = resultsMap.current[label];
setExternalId(key);
setExternalValue(value);
if (onChange) {
@ -107,9 +111,9 @@ function ComboSearch({
const onBlur = useCallback(() => {
if (!allowInputValues && isSuccess && results[0]) {
const [, value] = transformResult(results[0]);
const label = getLabel(results[0]);
awaitFormSubmit(() => {
handleOnSelect(value);
handleOnSelect(label);
});
}
}, [data]);
@ -129,13 +133,14 @@ function ComboSearch({
{results.length > 0 ? (
<ComboboxList>
{results.map((result, index) => {
const [key, str] = transformResult(result);
resultsMap.current[str] = [key, result];
const label = getLabel(result);
const [key, value] = transformResult(result);
resultsMap.current[label] = { key, value, result };
return (
<ComboboxOption
key={`${key}-${index}`}
value={str}
data-option-value={str}
value={label}
data-option-value={value}
/>
);
})}

View file

@ -44,16 +44,11 @@ function MapEditor({ featureCollection, url, options, preview }) {
enabled: !preview,
cadastreEnabled
});
const {
style,
layers,
setStyle,
setLayerEnabled,
setLayerOpacity
} = useMapStyle(options.layers, {
onStyleChange,
cadastreEnabled
});
const { style, layers, setStyle, setLayerEnabled, setLayerOpacity } =
useMapStyle(options.layers, {
onStyleChange,
cadastreEnabled
});
if (!isSupported) {
return (

View file

@ -133,25 +133,20 @@ export function useMapboxEditor(
);
const addEventListeners = useCallback((events) => {
const unsubscribe = Object.entries(
events
).map(([eventName, [target, callback]]) =>
addEventListener(eventName, target, callback)
const unsubscribe = Object.entries(events).map(
([eventName, [target, callback]]) =>
addEventListener(eventName, target, callback)
);
return () => unsubscribe.map((unsubscribe) => unsubscribe());
}, []);
const {
createFeatures,
updateFeatures,
deleteFeatures,
...props
} = useFeatureCollection(featureCollection, {
url,
enabled: isSupported && enabled,
addFeatures,
removeFeatures
});
const { createFeatures, updateFeatures, deleteFeatures, ...props } =
useFeatureCollection(featureCollection, {
url,
enabled: isSupported && enabled,
addFeatures,
removeFeatures
});
const onStyleChange = useCallback(() => {
if (mapRef.current) {

View file

@ -13,20 +13,10 @@ import { useMapbox } from './useMapbox';
const Mapbox = ReactMapboxGl({});
const MapReader = ({ featureCollection, options }) => {
const {
isSupported,
onLoad,
onStyleChange,
onMouseEnter,
onMouseLeave
} = useMapbox(featureCollection);
const {
style,
layers,
setStyle,
setLayerEnabled,
setLayerOpacity
} = useMapStyle(options.layers, { onStyleChange });
const { isSupported, onLoad, onStyleChange, onMouseEnter, onMouseLeave } =
useMapbox(featureCollection);
const { style, layers, setStyle, setLayerEnabled, setLayerOpacity } =
useMapStyle(options.layers, { onStyleChange });
if (!isSupported) {
return (

View file

@ -1,5 +1,6 @@
import React from 'react';
import { createPortal } from 'react-dom';
import PropTypes from 'prop-types';
export function FlashMessage({ message, level, sticky, fixed }) {
return createPortal(
@ -22,3 +23,10 @@ function flashClassName(level, sticky = false, fixed = false) {
}
return className.join(' ');
}
FlashMessage.propTypes = {
message: PropTypes.string,
level: PropTypes.string,
sticky: PropTypes.bool,
fixed: PropTypes.bool
};

View file

@ -198,23 +198,16 @@ export default {
sources: {
'decoupage-administratif': {
type: 'vector',
url:
'https://openmaptiles.geo.data.gouv.fr/data/decoupage-administratif.json'
url: 'https://openmaptiles.geo.data.gouv.fr/data/decoupage-administratif.json'
},
openmaptiles: {
type: 'vector',
url: 'https://openmaptiles.geo.data.gouv.fr/data/france-vector.json'
},
'photographies-aeriennes': {
type: 'raster',
tiles: [
'https://tiles.geo.api.gouv.fr/photographies-aeriennes/tiles/{z}/{x}/{y}'
],
tileSize: 256,
attribution: 'Images aériennes © IGN',
minzoom: 0,
maxzoom: 19
},
'photographies-aeriennes': rasterSource(
[ignServiceURL('ORTHOIMAGERY.ORTHOPHOTOS', 'image/jpeg')],
'IGN-F/Géoportail'
),
cadastre: {
type: 'vector',
url: 'https://openmaptiles.geo.data.gouv.fr/data/cadastre.json'

View file

@ -55,7 +55,7 @@ function buildOptions() {
async function defaultQueryFn({ queryKey: [scope, term] }) {
if (scope == 'pays') {
return matchSorter(await getPays(), term, { keys: ['nom'] });
return matchSorter(await getPays(), term, { keys: ['label'] });
}
const url = buildURL(scope, term);
@ -73,7 +73,7 @@ async function defaultQueryFn({ queryKey: [scope, term] }) {
let paysCache;
async function getPays() {
if (!paysCache) {
paysCache = await fetch('/pays.json').then((response) => response.json());
paysCache = await fetch('/api/pays').then((response) => response.json());
}
return paysCache;
}

View file

@ -64,7 +64,7 @@ addEventListener('autosave:end', () => {
addEventListener('autosave:error', (event) => {
let error = event.detail;
if (error.xhr.status == 401) {
if (error.xhr && error.xhr.status == 401) {
// If we are unauthenticated, reload the page using a GET request.
// This will allow Devise to properly redirect us to sign-in, and then back to this page.
document.location.reload();

View file

@ -99,9 +99,8 @@ export default class AutoUploadController {
}
_hideErrorMessage() {
let errorElement = this.input.parentElement.querySelector(
'.attachment-error'
);
let errorElement =
this.input.parentElement.querySelector('.attachment-error');
if (errorElement) {
hide(errorElement);
}

View file

@ -5,61 +5,73 @@ class DossierMailer < ApplicationMailer
helper ProcedureHelper
layout 'mailers/layout'
default from: NO_REPLY_EMAIL
def notify_new_draft(dossier)
@dossier = dossier
@service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure)
I18n.with_locale(dossier.user_locale) do
@dossier = dossier
@service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure)
@subject = default_i18n_subject(libelle_demarche: dossier.procedure.libelle)
subject = "Retrouvez votre brouillon pour la démarche « #{dossier.procedure.libelle} »"
mail(from: NO_REPLY_EMAIL, to: dossier.user_email_for(:notification), subject: subject) do |format|
format.html { render layout: 'mailers/notifications_layout' }
mail(to: dossier.user_email_for(:notification), subject: @subject) do |format|
format.html { render layout: 'mailers/notifications_layout' }
end
end
end
def notify_new_answer(dossier, body = nil)
@dossier = dossier
@service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure)
@body = body
I18n.with_locale(dossier.user_locale) do
@dossier = dossier
@service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure)
@body = body
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
subject = "Nouveau message pour votre dossier nº #{dossier.id} (#{dossier.procedure.libelle})"
mail(from: NO_REPLY_EMAIL, to: dossier.user_email_for(:notification), subject: subject) do |format|
format.html { render layout: 'mailers/notifications_layout' }
mail(to: dossier.user_email_for(:notification), subject: @subject) do |format|
format.html { render layout: 'mailers/notifications_layout' }
end
end
end
def notify_new_commentaire_to_instructeur(dossier, instructeur_email)
@dossier = dossier
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
mail(from: NO_REPLY_EMAIL, to: instructeur_email, subject: @subject)
I18n.with_locale(dossier.user_locale) do
@dossier = dossier
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
mail(to: instructeur_email, subject: @subject)
end
end
def notify_new_dossier_depose_to_instructeur(dossier, instructeur_email)
@dossier = dossier
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
mail(from: NO_REPLY_EMAIL, to: instructeur_email, subject: @subject)
I18n.with_locale(dossier.user_locale) do
@dossier = dossier
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
mail(to: instructeur_email, subject: @subject)
end
end
def notify_revert_to_instruction(dossier)
@dossier = dossier
@service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure)
I18n.with_locale(dossier.user_locale) do
@dossier = dossier
@service = dossier.procedure.service
@logo_url = attach_logo(dossier.procedure)
@subject = default_i18n_subject(dossier_id: dossier.id, libelle_demarche: dossier.procedure.libelle)
subject = "Votre dossier nº #{@dossier.id} est en train dêtre réexaminé"
mail(from: NO_REPLY_EMAIL, to: dossier.user_email_for(:notification), subject: subject) do |format|
format.html { render layout: 'mailers/notifications_layout' }
mail(to: dossier.user_email_for(:notification), subject: @subject) do |format|
format.html { render layout: 'mailers/notifications_layout' }
end
end
end
def notify_brouillon_near_deletion(dossiers, to_email)
@subject = default_i18n_subject(count: dossiers.count)
@dossiers = dossiers
I18n.with_locale(dossiers.first.user_locale) do
@subject = default_i18n_subject(count: dossiers.count)
@dossiers = dossiers
mail(to: to_email, subject: @subject)
mail(to: to_email, subject: @subject)
end
end
def notify_brouillon_deletion(dossier_hashes, to_email)
@ -70,24 +82,21 @@ class DossierMailer < ApplicationMailer
end
def notify_deletion_to_user(deleted_dossier, to_email)
@subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id)
@deleted_dossier = deleted_dossier
I18n.with_locale(deleted_dossier.user_locale) do
@subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id)
@deleted_dossier = deleted_dossier
mail(to: to_email, subject: @subject)
mail(to: to_email, subject: @subject)
end
end
def notify_instructeur_deletion_to_user(deleted_dossier, to_email)
@subject = default_i18n_subject(libelle_demarche: deleted_dossier.procedure.libelle)
@deleted_dossier = deleted_dossier
I18n.with_locale(deleted_dossier.user_locale) do
@subject = default_i18n_subject(libelle_demarche: deleted_dossier.procedure.libelle)
@deleted_dossier = deleted_dossier
mail(to: to_email, subject: @subject)
end
def notify_instructeur(deleted_dossier, to_email)
@subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id)
@deleted_dossier = deleted_dossier
mail(to: to_email, subject: @subject)
mail(to: to_email, subject: @subject)
end
end
def notify_deletion_to_administration(deleted_dossier, to_email)
@ -98,11 +107,13 @@ class DossierMailer < ApplicationMailer
end
def notify_automatic_deletion_to_user(deleted_dossiers, to_email)
@state = deleted_dossiers.first.state
@subject = default_i18n_subject(count: deleted_dossiers.count)
@deleted_dossiers = deleted_dossiers
I18n.with_locale(deleted_dossiers.first.user_locale) do
@state = deleted_dossiers.first.state
@subject = default_i18n_subject(count: deleted_dossiers.count)
@deleted_dossiers = deleted_dossiers
mail(to: to_email, subject: @subject)
mail(to: to_email, subject: @subject)
end
end
def notify_automatic_deletion_to_administration(deleted_dossiers, to_email)
@ -113,11 +124,13 @@ class DossierMailer < ApplicationMailer
end
def notify_near_deletion_to_user(dossiers, to_email)
@state = dossiers.first.state
@subject = default_i18n_subject(count: dossiers.count, state: @state)
@dossiers = dossiers
I18n.with_locale(dossiers.first.user_locale) do
@state = dossiers.first.state
@subject = default_i18n_subject(count: dossiers.count, state: @state)
@dossiers = dossiers
mail(to: to_email, subject: @subject)
mail(to: to_email, subject: @subject)
end
end
def notify_near_deletion_to_administration(dossiers, to_email)
@ -129,18 +142,19 @@ class DossierMailer < ApplicationMailer
end
def notify_groupe_instructeur_changed(instructeur, dossier)
@subject = "Un dossier a changé de groupe instructeur"
@dossier_id = dossier.id
@demarche = dossier.procedure.libelle
@subject = default_i18n_subject(dossier_id: dossier.id)
@dossier = dossier
mail(from: NO_REPLY_EMAIL, to: instructeur.email, subject: @subject)
mail(to: instructeur.email, subject: @subject)
end
def notify_brouillon_not_submitted(dossier)
@subject = "Attention : votre dossier nest pas déposé."
@dossier = dossier
I18n.with_locale(dossier.user_locale) do
@subject = default_i18n_subject(dossier_id: dossier.id)
@dossier = dossier
mail(to: dossier.user_email_for(:notification), subject: @subject)
mail(to: dossier.user_email_for(:notification), subject: @subject)
end
end
protected

View file

@ -22,7 +22,9 @@ class NotificationMailer < ApplicationMailer
@logo_url = attach_logo(@dossier.procedure)
@rendered_template = sanitize(@body)
mail(subject: @subject, to: @email, template_name: 'send_notification')
I18n.with_locale(@dossier.user_locale) do
mail(subject: @subject, to: @email, template_name: 'send_notification')
end
end
def self.send_en_construction_notification(dossier)
@ -53,12 +55,14 @@ class NotificationMailer < ApplicationMailer
if @dossier.user_deleted?
mail.perform_deliveries = false
else
mail_template = @dossier.procedure.mail_template_for(params[:state])
I18n.with_locale(@dossier.user_locale) do
mail_template = @dossier.procedure.mail_template_for(params[:state])
@email = @dossier.user_email_for(:notification)
@subject = mail_template.subject_for_dossier(@dossier)
@body = mail_template.body_for_dossier(@dossier)
@actions = mail_template.actions_for_dossier(@dossier)
@email = @dossier.user_email_for(:notification)
@subject = mail_template.subject_for_dossier(@dossier)
@body = mail_template.body_for_dossier(@dossier)
@actions = mail_template.actions_for_dossier(@dossier)
end
end
end

View file

@ -41,7 +41,11 @@ class Champs::LinkedDropDownListChamp < Champ
end
def primary_value=(value)
pack_value(value, secondary_value)
if value.blank?
pack_value("", "")
else
pack_value(value, secondary_value)
end
end
def secondary_value=(value)

View file

@ -18,4 +18,19 @@
# type_de_champ_id :integer
#
class Champs::PaysChamp < Champs::TextChamp
def localized_value
if external_id
I18nData.countries(I18n.locale)[external_id]
else
value.present? ? value.to_s : ''
end
end
def to_s
localized_value
end
def for_tag
localized_value
end
end

View file

@ -47,4 +47,8 @@ class DeletedDossier < ApplicationRecord
def procedure_removed?
reason == self.class.reasons.fetch(:procedure_removed)
end
def user_locale
User.find_by(id: user_id)&.locale || I18n.default_locale
end
end

View file

@ -921,6 +921,10 @@ class Dossier < ApplicationRecord
update_column(:api_entreprise_job_exceptions, exceptions)
end
def user_locale
user&.locale || I18n.default_locale
end
private
def defaut_groupe_instructeur?

View file

@ -13,6 +13,7 @@
# failed_attempts :integer default(0), not null
# last_sign_in_at :datetime
# last_sign_in_ip :string
# locale :string
# locked_at :datetime
# loged_in_with_france_connect :string default(NULL)
# remember_created_at :datetime

View file

@ -1,4 +0,0 @@
.form-group
%p.notice
= 'Email *'
= text_field_tag 'instructeur[email]', nil, class: 'form-control', placeholder: 'ex : laura.azema@exemple.gouv.fr'

View file

@ -1,22 +0,0 @@
- if smart_listing.present?
%table.table#liste-instructeur
%thead
%th#libelle= smart_listing.sortable 'Email', 'email'
%th
- @instructeurs.each do |instructeur|
%tr
%td{ style: 'padding-top: 11px; font-size: 15px;' }= instructeur.email
%td{ style: 'text-align: right;' }
.delete.btn.btn-sm.fa.fa-trash
.confirm
= link_to 'Valider', admin_instructeur_path(id: instructeur.id), { method: :delete, class: 'btn btn-sm btn-success' }
.cancel.btn.btn-sm.btn-danger.fa.fa-minus{ style: 'top: 0;' }
= smart_listing.paginate
= smart_listing.pagination_per_page_links
- else
%h4.center
Aucun instructeur

View file

@ -1,28 +0,0 @@
%h1 Instructeurs disponibles
%p
Cette page vous permet de gérer la liste des instructeurs disponibles pour être affectés à une démarche.
%p{ style: 'font-style: italic' }
N.B. : cette page ne concerne que la liste des personnes disponibles. Si vous souhaitez affecter ou enlever un instructeur dune démarche particulière,
utilisez plutôt la
= link_to "page de la démarche", admin_procedures_path
concernée.
.row
.col-xs-4
= smart_listing_render :instructeurs
.col-xs-1
&nbsp;
.col-xs-6
%h3 Ajouter un instructeur
#procedure_new.section.section-label
= form_with url: { controller: 'admin/instructeurs', action: :create } do
.row
.col-xs-5
= render partial: 'admin/instructeurs/informations'
.col-xs-2
%br
%br
= submit_tag 'Ajouter', class: 'btn btn-info', style: 'float: left;'

View file

@ -1 +0,0 @@
<%= smart_listing_update :instructeurs %>

View file

@ -3,7 +3,7 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
= "Vous suiviez jusqu'à maintenant le dossier n°#{@dossier_id} de la démarche #{@demarche}."
= "Vous suiviez jusqu'à maintenant le dossier n° #{@dossier.id} de la démarche #{@dossier.procedure.libelle}."
Lusager a modifié le groupe de routage. Son dossier appartient maintenant à un groupe instructeur dont vous ne faites pas partie.
%p
Suite à cette modification, vous ne suivez plus ce dossier.

View file

@ -1,31 +0,0 @@
- content_for :procedure_logo do
= render 'layouts/mailers/logo', url: @logo_url
%p= t(:hello, scope: [:views, :shared, :greetings])
- if !@dossier.brouillon?
%p
You received
%strong a new message
from the service in charge of examine your File.
%p
To read the message and answer it, select the following link:
= round_button('Read the message', messagerie_dossier_url(@dossier), :primary)
- else
%p
You received
%strong a new message
from the service in charge of examine the File you started a draft for on the procedure #{@dossier.procedure.libelle}.
%p{ style: "padding: 8px; color: #333333; background-color: #EEEEEE; font-size: 14px;" }
= @body
%p
If you chose to contact the service, please use the email available below in the page.
= round_button('Open the File', dossier_url(@dossier), :primary)
= render 'layouts/mailers/signature', service: @service
- content_for :footer do
= render 'layouts/mailers/service_footer', service: @service, dossier: @dossier

View file

@ -4,25 +4,15 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
- if !@dossier.brouillon?
%p
Vous avez reçu un
%strong nouveau message
de la part du service en charge de votre dossier.
%p
Pour consulter le message et y répondre, cliquez sur le bouton ci-dessous :
= round_button('Lire le message', messagerie_dossier_url(@dossier), :primary)
%p= t('.body', libelle_demarche: @dossier.procedure.libelle)
%p= t('.link')
= round_button(t('.access_message'), messagerie_dossier_url(@dossier), :primary)
- else
%p
Vous avez reçu un
%strong nouveau message
du service pour lequel votre dossier est en brouillon pour la démarche #{@dossier.procedure.libelle}.
%p= t('.body_draft', libelle_demarche: @dossier.procedure.libelle)
%p{ style: "padding: 8px; color: #333333; background-color: #EEEEEE; font-size: 14px;" }
= @body
%p Si vous souhaitez contacter le service, merci de le faire directement à l'aide de l'email en bas de page.
= round_button('Voir le dossier', dossier_url(@dossier), :primary)
%p= t('.contact')
= round_button(t('.access_file'), dossier_url(@dossier), :primary)
= render 'layouts/mailers/signature', service: @service

View file

@ -1,23 +0,0 @@
- content_for :procedure_logo do
= render 'layouts/mailers/logo', url: @logo_url
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
You started filling a File on the procedure:
= succeed '.' do
%strong « #{@dossier.procedure.libelle} »
%p
You can access your File, to review or complete, by clicking on the following button:
= round_button('Access your File', dossier_url(@dossier), :primary)
- if @dossier.procedure.auto_archive_on
%p
Your File needs to be submitted before #{procedure_auto_archive_datetime(@dossier.procedure)}.
= render 'layouts/mailers/signature'
- content_for :footer do
= render 'layouts/mailers/service_footer', service: @service, dossier: @dossier

View file

@ -3,21 +3,12 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
Vous avez commencé à remplir un dossier pour la démarche
= succeed '.' do
%strong « #{@dossier.procedure.libelle} »
%p
Vous pouvez
%strong retrouver et compléter votre dossier
en cliquant sur le bouton ci-dessous:
= round_button('Afficher votre dossier', dossier_url(@dossier), :primary)
%p= t('.body', libelle_demarche: @dossier.procedure.libelle)
%p= t('.link')
= round_button(t('.access_file'), dossier_url(@dossier), :primary)
- if @dossier.procedure.auto_archive_on
%p
Vous pouvez déposer votre dossier jusquau #{procedure_auto_archive_datetime(@dossier.procedure)}.
%p= t('.submit_before', before: procedure_auto_archive_datetime(@dossier.procedure))
= render 'layouts/mailers/signature'

View file

@ -1,21 +0,0 @@
- content_for :procedure_logo do
= render 'layouts/mailers/logo', url: @logo_url
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
Your File will be reexamined. All previous decisions are void.
To see the File created on procedure
%strong= @dossier.procedure.libelle
, select the following link:
= link_to dossier_url(@dossier), dossier_url(@dossier), target: '_blank', rel: 'noopener'
- if @dossier.procedure.service.present?
%p
In order to get more details about the decision to reexamin, please contact the following service:
= mail_to @dossier.procedure.service.email, @dossier.procedure.service.email
= render 'layouts/mailers/signature'
- content_for :footer do
= render 'layouts/mailers/service_footer', service: @service, dossier: @dossier

View file

@ -4,15 +4,11 @@
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
Votre dossier va être réexaminé, la précédente décision sur ce dossier est caduque.
Vous pouvez retrouver le dossier que vous avez créé pour la démarche
%strong= @dossier.procedure.libelle
à l'adresse suivante :
= t('.body', dossier_id: @dossier.id, libelle_demarche: @dossier.procedure.libelle)
= link_to dossier_url(@dossier), dossier_url(@dossier), target: '_blank', rel: 'noopener'
- if @dossier.procedure.service.present?
%p
Pour obtenir le détail de cette modification de la décision, vous pouvez contacter par
email:
= t('.contact')
= mail_to @dossier.procedure.service.email, @dossier.procedure.service.email
= render 'layouts/mailers/signature'

View file

@ -17,7 +17,6 @@
class: 'form',
pattern: '^[a-z0-9_-]{3,200}$',
title: "De 3 à 200 caractères; minuscules, chiffres et tiret seulement",
data: { debounce: true, url: admin_procedure_publish_validate_path(procedure)},
autocomplete: 'off',
style: 'width: 300px; display: inline;')
.text-info.mb-4

View file

@ -1,3 +1,4 @@
- hidden_field_id = SecureRandom.uuid
= form.hidden_field :value, { data: { uuid: hidden_field_id } }
= form.hidden_field :value, { value: champ.localized_value, data: { uuid: hidden_field_id } }
= form.hidden_field :external_id, { data: { reference: true } }
= react_component("ComboPaysSearch", mandatory: champ.mandatory?, hiddenFieldId: hidden_field_id)

View file

@ -1,25 +1,5 @@
{
"ignored_warnings": [
{
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "25d6ed4f7f9120faf69596aa97d9e0558fd86817583b99b9b7879aff43ec2751",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/procedure_presentation.rb",
"line": 114,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "dossiers.with_type_de_champ_private(column).order(\"champs.value #{order}\")",
"render_path": null,
"location": {
"type": "method",
"class": "ProcedurePresentation",
"method": "sorted_ids"
},
"user_input": "order",
"confidence": "Weak",
"note": "`table`, `column` and `order` come from the model, which is validated to prevent injection attacks. Furthermore, `table` and `column` are escaped."
},
{
"warning_type": "Cross-Site Scripting",
"warning_code": 2,
@ -51,26 +31,6 @@
"confidence": "Weak",
"note": ""
},
{
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "7e27a03f04576569601d7ec70bddd05c21c4f2de17448e6e093f76844c59e0a0",
"check_name": "Redirect",
"message": "Possible unprotected redirect",
"file": "app/controllers/instructeurs/procedures_controller.rb",
"line": 175,
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
"code": "redirect_to(Export.find_or_create_export(params[:export_format], (params[:time_span_type] or Export.time_span_types.fetch(:everything)), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url)",
"render_path": null,
"location": {
"type": "method",
"class": "Instructeurs::ProceduresController",
"method": "download_export"
},
"user_input": "Export.find_or_create_export(params[:export_format], (params[:time_span_type] or Export.time_span_types.fetch(:everything)), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url",
"confidence": "High",
"note": ""
},
{
"warning_type": "SQL Injection",
"warning_code": 0,
@ -92,66 +52,26 @@
"note": "The table and column are escaped, which should make this safe"
},
{
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "d6031dd493ff36d62af2d75d0b1e4606c665413a62ef26a847902af4ad97d81f",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/procedure_presentation.rb",
"line": 109,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "dossiers.with_type_de_champ(column).order(\"champs.value #{order}\")",
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "c46b5c9cd6474ffae789f39a2280ba6b5a5a74d3ffa8a38cf8a409f9a027ed0e",
"check_name": "Redirect",
"message": "Possible unprotected redirect",
"file": "app/controllers/instructeurs/procedures_controller.rb",
"line": 180,
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
"code": "redirect_to(Export.find_or_create_export(params[:export_format], (params[:time_span_type] or \"everything\"), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url)",
"render_path": null,
"location": {
"type": "method",
"class": "ProcedurePresentation",
"method": "sorted_ids"
"class": "Instructeurs::ProceduresController",
"method": "download_export"
},
"user_input": "order",
"confidence": "Weak",
"note": "`table`, `column` and `order` come from the model, which is validated to prevent injection attacks. Furthermore, `table` and `column` are escaped."
},
{
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "e6f09095e3d381bcf6280d2f9b06c239946be3e440330136934f34611bc2b2d9",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/procedure_presentation.rb",
"line": 127,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "((\"self\" == \"self\") ? (dossiers) : (dossiers.includes(\"self\"))).order(\"#{self.class.sanitized_column(\"self\", column)} #{order}\")",
"render_path": null,
"location": {
"type": "method",
"class": "ProcedurePresentation",
"method": "sorted_ids"
},
"user_input": "self.class.sanitized_column(\"self\", column)",
"confidence": "Weak",
"note": "`table`, `column` and `order` come from the model, which is validated to prevent injection attacks. Furthermore, `table` and `column` are escaped."
},
{
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "fd9d738975ccb93c8915833fceb3f43ac35410d653b8c64a1c92c1afc36d2177",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/procedure_presentation.rb",
"line": 122,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "dossiers.includes(:followers_instructeurs).joins(\"LEFT OUTER JOIN users instructeurs_users ON instructeurs_users.instructeur_id = instructeurs.id\").order(\"instructeurs_users.email #{order}\")",
"render_path": null,
"location": {
"type": "method",
"class": "ProcedurePresentation",
"method": "sorted_ids"
},
"user_input": "order",
"confidence": "Weak",
"note": "`table`, `column` and `order` come from the model, which is validated to prevent injection attacks. Furthermore, `table` and `column` are escaped."
"user_input": "Export.find_or_create_export(params[:export_format], (params[:time_span_type] or \"everything\"), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url",
"confidence": "High",
"note": ""
}
],
"updated": "2021-06-17 09:26:40 +0200",
"brakeman_version": "5.0.0"
"updated": "2021-09-02 16:12:11 -0500",
"brakeman_version": "5.1.1"
}

View file

@ -202,6 +202,19 @@ en:
connection: Sign in
are_you_new: First time on %{app_name}?
find_procedure: Find your procedure
password:
reset_link_sent:
got_it: Got it!
open_your_mailbox: Now open your mailbox.
email_sent_html: We have sent you an email to the address <strong>%{email}</strong>.
click_link_to_reset_password: Click on the link in the email to change your password.
no_mail: Didn't receive the email?
check_spams: Check your junk or spam email.
check_account: Have you created a %{application_name} account with the adress %{email}? You will not receive any message if no account is linked to your email adress.
check_france_connect_html: Have you once logged in with France Connect? If yes, <a href=\"%{href}\">try again with France Connect</a>.
shared:
email_can_take_a_while_html: <strong>Please note</strong> that this message can take up to 15 minutes to arrive.
contact_us_if_any_trouble_html: You can contact us <a href=\"%{href}\">through this form</a> if a problem still exists.
modal:
publish:
title:

View file

@ -0,0 +1,4 @@
fr:
dossier_mailer:
notify_brouillon_not_submitted:
subject: "Attention : votre dossier nº %{dossier_id} nest pas déposé"

View file

@ -0,0 +1,4 @@
fr:
dossier_mailer:
notify_groupe_instructeur_changed:
subject: Le dossier nº %{dossier_id} a changé de groupe instructeur

View file

@ -0,0 +1,13 @@
fr:
dossier_mailer:
notify_new_answer:
subject: New message on your file nº %{dossier_id} « %{libelle_demarche} »
body_html: |
You received <b>a new message</b> from the service in charge of reviewing your file on « %{libelle_demarche} ».
link: |
To read the message and answer it, select the following link:
body_draft_html: |
You received <b>a new message</b> from the service in charge of reviewing the file you started a draft for on the procedure « %{libelle_demarche} ».
contact: If you chose to contact the service, please use the email available below in the page.
access_message: Read the message
access_file: Open file

View file

@ -0,0 +1,13 @@
fr:
dossier_mailer:
notify_new_answer:
subject: Nouveau message pour votre dossier nº %{dossier_id} « %{libelle_demarche} »
body_html: |
Vous avez reçu un <b>nouveau message</b> de la part du service en charge de votre dossier.
link: |
Pour consulter le message et y répondre, cliquez sur le bouton ci-dessous :
body_draft_html: |
Vous avez reçu un <b>nouveau message</b>du service pour lequel votre dossier est en brouillon pour la démarche « %{libelle_demarche} ».
contact: Si vous souhaitez contacter le service, merci de le faire directement à l'aide de l'email en bas de page.
access_message: Lire le message
access_file: Voir le dossier

View file

@ -1,5 +1,5 @@
fr:
dossier_mailer:
notify_new_dossier_depose_to_instructeur:
subject: Nouveau dossier déposé pour la démarche %{libelle_demarche}
body: Un nouveau dossier a été déposé (n° %{dossier_id}) pour la démarche %{libelle_demarche}
subject: Nouveau dossier déposé pour la démarche « %{libelle_demarche} »
body: Un nouveau dossier a été déposé (n° %{dossier_id}) pour la démarche « %{libelle_demarche} »

View file

@ -0,0 +1,10 @@
en:
dossier_mailer:
notify_new_draft:
subject: Draft file created on procedure « %{libelle_demarche} »
body_html: |
You created a draft file on procedure <b>« %{libelle_demarche} »</b>.
link_html: |
You can access your file, to <b>review or complete</b>, by clicking on the following button:
submit_before: Your file needs to be submitted before %{before}
access_file: Open file

View file

@ -0,0 +1,10 @@
fr:
dossier_mailer:
notify_new_draft:
subject: Retrouvez votre brouillon pour la démarche « %{libelle_demarche} »
body_html: |
Vous avez commencé à remplir un dossier pour la démarche <b>« %{libelle_demarche} »</b>.
link_html: |
Vous pouvez <b>retrouver et compléter votre dossier</b> en cliquant sur le bouton ci-dessous :
submit_before: Vous pouvez déposer votre dossier jusquau %{before}.
access_file: Afficher votre dossier

View file

@ -0,0 +1,9 @@
en:
dossier_mailer:
notify_revert_to_instruction:
subject: Your file nº %{dossier_id} « %{libelle_demarche} » will be reexamined
body_html: |
Your file nº %{dossier_id} will be reexamined. All previous decisions are void.
To see the file created on procedure <b>« %{libelle_demarche} »</b>, select the following link:
contact: |
In order to get more details about the decision to reexamin, please contact the following service:

View file

@ -0,0 +1,9 @@
fr:
dossier_mailer:
notify_revert_to_instruction:
subject: Votre dossier nº %{dossier_id} sur la démarche « %{libelle_demarche} » est en train dêtre réexaminé
body_html: |
Votre dossier va être réexaminé, la précédente décision sur ce dossier est caduque.
Vous pouvez retrouver le dossier que vous avez créé pour la démarche <b>« %{libelle_demarche} »</b> à l'adresse suivante :
contact: |
Pour obtenir le détail de cette modification de la décision, vous pouvez contacter par email :

View file

@ -193,27 +193,9 @@ Rails.application.routes.draw do
get 'procedures/archived', to: redirect('/admin/procedures?statut=archivees')
get 'procedures/draft', to: redirect('/admin/procedures?statut=brouillons')
resources :procedures, only: [:destroy] do
collection do
get 'new_from_existing' => 'procedures#new_from_existing', as: :new_from_existing
end
member do
delete :delete_logo
delete :delete_deliberation
delete :delete_notice
end
put 'archive' => 'procedures#archive', as: :archive
get 'publish_validate' => 'procedures#publish_validate', as: :publish_validate
put 'clone' => 'procedures#clone', as: :clone
end
namespace :assigns do
get 'show' # delete after fixed tests admin/instructeurs/show_spec without this line
end
resources :instructeurs, only: [:index, :create, :destroy]
end
resources :invites, only: [:show, :destroy] do
@ -242,6 +224,8 @@ Rails.application.routes.draw do
get 'dossiers/pdf/:id', format: :pdf, to: "dossiers#pdf", as: :dossier_pdf
get 'dossiers/geojson/:id', to: "dossiers#geojson", as: :dossier_geojson
end
resources :pays, only: :index
end
#
@ -398,7 +382,11 @@ Rails.application.routes.draw do
#
namespace :admin, module: 'new_administrateur' do
resources :procedures, except: [:destroy] do
resources :procedures do
collection do
get 'new_from_existing'
end
member do
get 'apercu'
get 'champs'
@ -411,6 +399,8 @@ Rails.application.routes.draw do
put :experts_require_administrateur_invitation
end
put 'clone'
put 'archive'
get 'publication' => 'procedures#publication', as: :publication
put 'publish' => 'procedures#publish', as: :publish
get 'transfert' => 'procedures#transfert', as: :transfert

View file

@ -0,0 +1,5 @@
class AddLocaleToUsers < ActiveRecord::Migration[6.1]
def change
add_column :users, :locale, :string
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_08_18_083349) do
ActiveRecord::Schema.define(version: 2021_08_26_161956) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -753,6 +753,7 @@ ActiveRecord::Schema.define(version: 2021_08_18_083349) do
t.bigint "instructeur_id"
t.bigint "administrateur_id"
t.bigint "expert_id"
t.string "locale"
t.index ["administrateur_id"], name: "index_users_on_administrateur_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true

View file

@ -1,25 +1,25 @@
{
"dependencies": {
"@babel/preset-react": "^7.12.13",
"@babel/preset-react": "^7.14.5",
"@headlessui/react": "^1.3.0",
"@heroicons/react": "^1.0.1",
"@mapbox/mapbox-gl-draw": "^1.2.2",
"@popperjs/core": "^2.9.2",
"@rails/actiontext": "^6.0.3",
"@rails/activestorage": "^6.0.3",
"@rails/ujs": "^6.0.3",
"@rails/actiontext": "^6.1.4-1",
"@rails/activestorage": "^6.1.4-1",
"@rails/ujs": "^6.1.4-1",
"@rails/webpacker": "5.1.1",
"@reach/combobox": "^0.13.0",
"@reach/slider": "^0.15.0",
"@reach/visually-hidden": "^0.15.2",
"@sentry/browser": "6.9.0",
"@sentry/browser": "6.11.0",
"@tmcw/togeojson": "^4.3.0",
"babel-plugin-macros": "^2.8.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"chartkick": "^3.2.0",
"core-js": "^3.6.5",
"debounce": "^1.2.0",
"dom4": "^2.1.5",
"debounce": "^1.2.1",
"dom4": "^2.1.6",
"email-butler": "^1.0.13",
"highcharts": "^9.0.0",
"intersection-observer": "^0.12.0",
@ -44,13 +44,13 @@
"devDependencies": {
"@2fd/graphdoc": "^2.4.0",
"babel-eslint": "^10.1.0",
"eslint": "^7.0.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.22.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0",
"netlify-cli": "^2.61.2",
"prettier": "^2.0.5",
"prettier": "^2.3.2",
"webpack-bundle-analyzer": "^3.7.0",
"webpack-dev-server": "^3.11.0"
},

View file

@ -1,848 +0,0 @@
[
{
"nom": "FRANCE"
},
{
"nom": "ACORES, MADERE"
},
{
"nom": "AFGHANISTAN"
},
{
"nom": "AFRIQUE DU SUD"
},
{
"nom": "ALASKA"
},
{
"nom": "ALBANIE"
},
{
"nom": "ALGERIE"
},
{
"nom": "ALLEMAGNE"
},
{
"nom": "ANDORRE"
},
{
"nom": "ANGOLA"
},
{
"nom": "ANGUILLA"
},
{
"nom": "ANTIGUA-ET-BARBUDA"
},
{
"nom": "ANTILLES NEERLANDAISES"
},
{
"nom": "ARABIE SAOUDITE"
},
{
"nom": "ARGENTINE"
},
{
"nom": "ARMENIE"
},
{
"nom": "ARUBA"
},
{
"nom": "AUSTRALIE"
},
{
"nom": "AUTRICHE"
},
{
"nom": "AZERBAIDJAN"
},
{
"nom": "BAHAMAS"
},
{
"nom": "BAHREIN"
},
{
"nom": "BANGLADESH"
},
{
"nom": "BARBADE"
},
{
"nom": "BELGIQUE"
},
{
"nom": "BELIZE"
},
{
"nom": "BENIN"
},
{
"nom": "BERMUDES"
},
{
"nom": "BHOUTAN"
},
{
"nom": "BIELORUSSIE"
},
{
"nom": "BIRMANIE"
},
{
"nom": "BOLIVIE"
},
{
"nom": "BONAIRE, SAINT EUSTACHE ET SABA"
},
{
"nom": "BOSNIE-HERZEGOVINE"
},
{
"nom": "BOTSWANA"
},
{
"nom": "BOUVET (ILE)"
},
{
"nom": "BRESIL"
},
{
"nom": "BRUNEI"
},
{
"nom": "BULGARIE"
},
{
"nom": "BURKINA"
},
{
"nom": "BURUNDI"
},
{
"nom": "CAIMANES (ILES)"
},
{
"nom": "CAMBODGE"
},
{
"nom": "CAMEROUN"
},
{
"nom": "CAMEROUN ET TOGO"
},
{
"nom": "CANADA"
},
{
"nom": "CANARIES (ILES)"
},
{
"nom": "CAP-VERT"
},
{
"nom": "CENTRAFRICAINE (REPUBLIQUE)"
},
{
"nom": "CHILI"
},
{
"nom": "CHINE"
},
{
"nom": "CHRISTMAS (ILE)"
},
{
"nom": "CHYPRE"
},
{
"nom": "CLIPPERTON (ILE)"
},
{
"nom": "COCOS ou KEELING (ILES)"
},
{
"nom": "COLOMBIE"
},
{
"nom": "COMORES"
},
{
"nom": "CONGO"
},
{
"nom": "CONGO (REPUBLIQUE DEMOCRATIQUE)"
},
{
"nom": "COOK (ILES)"
},
{
"nom": "COREE"
},
{
"nom": "COREE (REPUBLIQUE DE)"
},
{
"nom": "COREE (REPUBLIQUE POPULAIRE DEMOCRATIQUE DE)"
},
{
"nom": "COSTA RICA"
},
{
"nom": "COTE D'IVOIRE"
},
{
"nom": "CROATIE"
},
{
"nom": "CUBA"
},
{
"nom": "CURAÇAO"
},
{
"nom": "DANEMARK"
},
{
"nom": "DJIBOUTI"
},
{
"nom": "DOMINICAINE (REPUBLIQUE)"
},
{
"nom": "DOMINIQUE"
},
{
"nom": "EGYPTE"
},
{
"nom": "EL SALVADOR"
},
{
"nom": "EMIRATS ARABES UNIS"
},
{
"nom": "EQUATEUR"
},
{
"nom": "ERYTHREE"
},
{
"nom": "ESPAGNE"
},
{
"nom": "ESTONIE"
},
{
"nom": "ETATS MALAIS NON FEDERES"
},
{
"nom": "ETATS-UNIS"
},
{
"nom": "ETHIOPIE"
},
{
"nom": "FEROE (ILES)"
},
{
"nom": "FIDJI"
},
{
"nom": "FINLANDE"
},
{
"nom": "GABON"
},
{
"nom": "GAMBIE"
},
{
"nom": "GEORGIE"
},
{
"nom": "GEORGIE DU SUD ET LES ILES SANDWICH DU SUD"
},
{
"nom": "GHANA"
},
{
"nom": "GIBRALTAR"
},
{
"nom": "GOA"
},
{
"nom": "GRECE"
},
{
"nom": "GRENADE"
},
{
"nom": "GROENLAND"
},
{
"nom": "GUADELOUPE"
},
{
"nom": "GUAM"
},
{
"nom": "GUATEMALA"
},
{
"nom": "GUERNESEY"
},
{
"nom": "GUINEE"
},
{
"nom": "GUINEE EQUATORIALE"
},
{
"nom": "GUINEE-BISSAU"
},
{
"nom": "GUYANA"
},
{
"nom": "GUYANE"
},
{
"nom": "HAITI"
},
{
"nom": "HAWAII (ILES)"
},
{
"nom": "HEARD ET MACDONALD (ILES)"
},
{
"nom": "HONDURAS"
},
{
"nom": "HONG-KONG"
},
{
"nom": "HONGRIE"
},
{
"nom": "ILES PORTUGAISES DE L'OCEAN INDIEN"
},
{
"nom": "INDE"
},
{
"nom": "INDONESIE"
},
{
"nom": "IRAN"
},
{
"nom": "IRAQ"
},
{
"nom": "IRLANDE, ou EIRE"
},
{
"nom": "ISLANDE"
},
{
"nom": "ISRAEL"
},
{
"nom": "ITALIE"
},
{
"nom": "JAMAIQUE"
},
{
"nom": "JAPON"
},
{
"nom": "JERSEY"
},
{
"nom": "JORDANIE"
},
{
"nom": "KAMTCHATKA"
},
{
"nom": "KAZAKHSTAN"
},
{
"nom": "KENYA"
},
{
"nom": "KIRGHIZISTAN"
},
{
"nom": "KIRIBATI"
},
{
"nom": "KOSOVO"
},
{
"nom": "KOWEIT"
},
{
"nom": "LA REUNION"
},
{
"nom": "LABRADOR"
},
{
"nom": "LAOS"
},
{
"nom": "LESOTHO"
},
{
"nom": "LETTONIE"
},
{
"nom": "LIBAN"
},
{
"nom": "LIBERIA"
},
{
"nom": "LIBYE"
},
{
"nom": "LIECHTENSTEIN"
},
{
"nom": "LITUANIE"
},
{
"nom": "LUXEMBOURG"
},
{
"nom": "MACAO"
},
{
"nom": "MACEDOINE DU NORD (REPUBLIQUE DE)"
},
{
"nom": "MADAGASCAR"
},
{
"nom": "MALAISIE"
},
{
"nom": "MALAWI"
},
{
"nom": "MALDIVES"
},
{
"nom": "MALI"
},
{
"nom": "MALOUINES, OU FALKLAND (ILES)"
},
{
"nom": "MALTE"
},
{
"nom": "MAN (ILE)"
},
{
"nom": "MANDCHOURIE"
},
{
"nom": "MARIANNES DU NORD (ILES)"
},
{
"nom": "MAROC"
},
{
"nom": "MARSHALL (ILES)"
},
{
"nom": "MARTINIQUE"
},
{
"nom": "MAURICE"
},
{
"nom": "MAURITANIE"
},
{
"nom": "MAYOTTE"
},
{
"nom": "MEXIQUE"
},
{
"nom": "MICRONESIE (ETATS FEDERES DE)"
},
{
"nom": "MOLDAVIE"
},
{
"nom": "MONACO"
},
{
"nom": "MONGOLIE"
},
{
"nom": "MONTENEGRO"
},
{
"nom": "MONTSERRAT"
},
{
"nom": "MOZAMBIQUE"
},
{
"nom": "NAMIBIE"
},
{
"nom": "NAURU"
},
{
"nom": "NEPAL"
},
{
"nom": "NICARAGUA"
},
{
"nom": "NIGER"
},
{
"nom": "NIGERIA"
},
{
"nom": "NIUE"
},
{
"nom": "NORFOLK (ILE)"
},
{
"nom": "NORVEGE"
},
{
"nom": "NOUVELLE-CALEDONIE"
},
{
"nom": "NOUVELLE-ZELANDE"
},
{
"nom": "OCEAN INDIEN (TERRITOIRE BRITANNIQUE DE L')"
},
{
"nom": "OMAN"
},
{
"nom": "OUGANDA"
},
{
"nom": "OUZBEKISTAN"
},
{
"nom": "PAKISTAN"
},
{
"nom": "PALAOS (ILES)"
},
{
"nom": "PALESTINE (Etat de)"
},
{
"nom": "PANAMA"
},
{
"nom": "PAPOUASIE-NOUVELLE-GUINEE"
},
{
"nom": "PARAGUAY"
},
{
"nom": "PAYS-BAS"
},
{
"nom": "PEROU"
},
{
"nom": "PHILIPPINES"
},
{
"nom": "PITCAIRN (ILE)"
},
{
"nom": "POLOGNE"
},
{
"nom": "POLYNESIE FRANCAISE"
},
{
"nom": "PORTO RICO"
},
{
"nom": "PORTUGAL"
},
{
"nom": "POSSESSIONS BRITANNIQUES AU PROCHE-ORIENT"
},
{
"nom": "PRESIDES"
},
{
"nom": "PROVINCES ESPAGNOLES D'AFRIQUE"
},
{
"nom": "QATAR"
},
{
"nom": "REPUBLIQUE DEMOCRATIQUE ALLEMANDE"
},
{
"nom": "REPUBLIQUE FEDERALE D'ALLEMAGNE"
},
{
"nom": "ROUMANIE"
},
{
"nom": "ROYAUME-UNI"
},
{
"nom": "RUSSIE"
},
{
"nom": "RWANDA"
},
{
"nom": "SAHARA OCCIDENTAL"
},
{
"nom": "SAINT-BARTHELEMY"
},
{
"nom": "SAINT-CHRISTOPHE-ET-NIEVES"
},
{
"nom": "SAINT-MARIN"
},
{
"nom": "SAINT-MARTIN"
},
{
"nom": "SAINT-MARTIN (PARTIE NEERLANDAISE)"
},
{
"nom": "SAINT-PIERRE-ET-MIQUELON"
},
{
"nom": "SAINT-VINCENT-ET-LES GRENADINES"
},
{
"nom": "SAINTE HELENE, ASCENSION ET TRISTAN DA CUNHA"
},
{
"nom": "SAINTE-LUCIE"
},
{
"nom": "SALOMON (ILES)"
},
{
"nom": "SAMOA AMERICAINES"
},
{
"nom": "SAMOA OCCIDENTALES"
},
{
"nom": "SAO TOME-ET-PRINCIPE"
},
{
"nom": "SENEGAL"
},
{
"nom": "SERBIE"
},
{
"nom": "SEYCHELLES"
},
{
"nom": "SIBERIE"
},
{
"nom": "SIERRA LEONE"
},
{
"nom": "SINGAPOUR"
},
{
"nom": "SLOVAQUIE"
},
{
"nom": "SLOVENIE"
},
{
"nom": "SOMALIE"
},
{
"nom": "SOUDAN"
},
{
"nom": "SOUDAN ANGLO-EGYPTIEN, KENYA, OUGANDA"
},
{
"nom": "SOUDAN DU SUD"
},
{
"nom": "SRI LANKA"
},
{
"nom": "SUEDE"
},
{
"nom": "SUISSE"
},
{
"nom": "SURINAME"
},
{
"nom": "SVALBARD et ILE JAN MAYEN"
},
{
"nom": "SWAZILAND"
},
{
"nom": "SYRIE"
},
{
"nom": "TADJIKISTAN"
},
{
"nom": "TAIWAN"
},
{
"nom": "TANGER"
},
{
"nom": "TANZANIE"
},
{
"nom": "TCHAD"
},
{
"nom": "TCHECOSLOVAQUIE"
},
{
"nom": "TCHEQUE (REPUBLIQUE)"
},
{
"nom": "TERR. DES ETATS-UNIS D'AMERIQUE EN AMERIQUE"
},
{
"nom": "TERR. DES ETATS-UNIS D'AMERIQUE EN OCEANIE"
},
{
"nom": "TERR. DU ROYAUME-UNI DANS L'ATLANTIQUE SUD"
},
{
"nom": "TERRE-NEUVE"
},
{
"nom": "TERRES AUSTRALES FRANCAISES"
},
{
"nom": "TERRITOIRES DU ROYAUME-UNI AUX ANTILLES"
},
{
"nom": "THAILANDE"
},
{
"nom": "TIMOR ORIENTAL"
},
{
"nom": "TOGO"
},
{
"nom": "TOKELAU"
},
{
"nom": "TONGA"
},
{
"nom": "TRINITE-ET-TOBAGO"
},
{
"nom": "TUNISIE"
},
{
"nom": "TURKESTAN RUSSE"
},
{
"nom": "TURKMENISTAN"
},
{
"nom": "TURKS ET CAIQUES (ILES)"
},
{
"nom": "TURQUIE"
},
{
"nom": "TURQUIE D'EUROPE"
},
{
"nom": "TUVALU"
},
{
"nom": "UKRAINE"
},
{
"nom": "URUGUAY"
},
{
"nom": "VANUATU"
},
{
"nom": "VATICAN, ou SAINT-SIEGE"
},
{
"nom": "VENEZUELA"
},
{
"nom": "VIERGES BRITANNIQUES (ILES)"
},
{
"nom": "VIERGES DES ETATS-UNIS (ILES)"
},
{
"nom": "VIET NAM"
},
{
"nom": "VIET NAM DU NORD"
},
{
"nom": "VIET NAM DU SUD"
},
{
"nom": "WALLIS-ET-FUTUNA"
},
{
"nom": "YEMEN"
},
{
"nom": "YEMEN (REPUBLIQUE ARABE DU)"
},
{
"nom": "YEMEN DEMOCRATIQUE"
},
{
"nom": "ZAMBIE"
},
{
"nom": "ZANZIBAR"
},
{
"nom": "ZIMBABWE"
}
]

View file

@ -1,177 +0,0 @@
describe Admin::InstructeursController, type: :controller do
let(:admin) { create(:administrateur) }
let(:email_2) { 'plip@octo.com' }
let(:admin_2) { create :administrateur, email: email_2 }
before do
sign_in(admin.user)
end
describe 'GET #index' do
subject { get :index }
it { expect(subject.status).to eq(200) }
end
describe 'GET #index with sorting and pagination' do
subject {
get :index, params: {
'instructeurs_smart_listing[page]': 1,
'instructeurs_smart_listing[per_page]': 10,
'instructeurs_smart_listing[sort][email]': 'asc'
}
}
it { expect(subject.status).to eq(200) }
end
describe 'POST #create' do
let(:email) { 'test@plop.com' }
let(:procedure_id) { nil }
subject { post :create, params: { instructeur: { email: email }, procedure_id: procedure_id } }
context 'When email is valid' do
before do
subject
end
let(:instructeur) { Instructeur.last }
it { expect(response.status).to eq(302) }
it { expect(response).to redirect_to admin_instructeurs_path }
context 'when procedure_id params is not null' do
let(:procedure) { create :procedure }
let(:procedure_id) { procedure.id }
it { expect(response.status).to eq(302) }
it { expect(response).to redirect_to admin_procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur) }
end
describe 'Instructeur attributs in database' do
it { expect(instructeur.email).to eq(email) }
end
describe 'New instructeur is assign to the admin' do
it { expect(instructeur.administrateurs).to include admin }
it { expect(admin.instructeurs).to include instructeur }
end
end
context 'when email is not valid' do
before do
subject
end
let(:email) { 'piou' }
it { expect(response.status).to eq(302) }
it { expect { response }.not_to change(Instructeur, :count) }
it { expect(flash[:alert]).to be_present }
describe 'Email Notification' do
it {
expect(InstructeurMailer).not_to receive(:new_instructeur)
expect(InstructeurMailer).not_to receive(:deliver_later)
subject
}
end
end
context 'when email is empty' do
before do
subject
end
let(:email) { '' }
it { expect(response.status).to eq(302) }
it { expect { response }.not_to change(Instructeur, :count) }
it 'Notification email is not send' do
expect(InstructeurMailer).not_to receive(:new_instructeur)
expect(InstructeurMailer).not_to receive(:deliver_later)
end
end
context 'when email is already assign at the admin' do
before do
create :instructeur, email: email, administrateurs: [admin]
subject
end
it { expect(response.status).to eq(302) }
it { expect { response }.not_to change(Instructeur, :count) }
it { expect(flash[:alert]).to be_present }
describe 'Email notification' do
it 'is not sent when email already exists' do
expect(InstructeurMailer).not_to receive(:new_instructeur)
expect(InstructeurMailer).not_to receive(:deliver_later)
subject
end
end
end
context 'when an other admin will add the same email' do
let(:instructeur) { Instructeur.by_email(email) }
before do
create :instructeur, email: email, administrateurs: [admin]
sign_out(admin.user)
sign_in(admin_2.user)
subject
end
it { expect(response.status).to eq(302) }
it { expect { response }.not_to change(Instructeur, :count) }
it { expect(flash[:notice]).to be_present }
it { expect(admin_2.instructeurs).to include instructeur }
it { expect(instructeur.administrateurs.size).to eq 2 }
end
context 'when an other admin will add the same email with some uppercase in it' do
let(:email) { 'Test@Plop.com' }
let(:instructeur) { Instructeur.by_email(email.downcase) }
before do
create :instructeur, email: email, administrateurs: [admin]
sign_out(admin.user)
sign_in(admin_2.user)
subject
end
it { expect(admin_2.instructeurs).to include instructeur }
end
context 'Email notification' do
it 'Notification email is sent when instructeur is create' do
expect_any_instance_of(User).to receive(:invite!)
subject
end
end
end
describe 'DELETE #destroy' do
let(:email) { 'test@plop.com' }
let!(:admin) { create :administrateur }
let!(:instructeur) { create :instructeur, email: email, administrateurs: [admin] }
subject { delete :destroy, params: { id: instructeur.id } }
context "when gestionaire_id is valid" do
before do
subject
admin.reload
instructeur.reload
end
it { expect(response.status).to eq(302) }
it { expect(response).to redirect_to admin_instructeurs_path }
it { expect(admin.instructeurs).not_to include instructeur }
it { expect(instructeur.administrateurs).not_to include admin }
end
it { expect { subject }.not_to change(Instructeur, :count) }
end
end

View file

@ -1,280 +0,0 @@
require 'uri'
describe Admin::ProceduresController, type: :controller do
let(:admin) { create(:administrateur) }
let(:bad_procedure_id) { 100000 }
let(:path) { 'ma-jolie-demarche' }
let(:libelle) { 'Démarche de test' }
let(:description) { 'Description de test' }
let(:organisation) { 'Organisation de test' }
let(:direction) { 'Direction de test' }
let(:cadre_juridique) { 'cadre juridique' }
let(:duree_conservation_dossiers_dans_ds) { 3 }
let(:duree_conservation_dossiers_hors_ds) { 6 }
let(:monavis_embed) { nil }
let(:lien_site_web) { 'http://mon-site.gouv.fr' }
let(:procedure_params) {
{
path: path,
libelle: libelle,
description: description,
organisation: organisation,
direction: direction,
cadre_juridique: cadre_juridique,
duree_conservation_dossiers_dans_ds: duree_conservation_dossiers_dans_ds,
duree_conservation_dossiers_hors_ds: duree_conservation_dossiers_hors_ds,
monavis_embed: monavis_embed,
lien_site_web: lien_site_web
}
}
before do
sign_in(admin.user)
end
describe 'GET #published' do
subject { get :published }
it { expect(response.status).to eq(200) }
end
describe 'DELETE #destroy' do
let(:procedure_draft) { create(:procedure, administrateurs: [admin]) }
let(:procedure_published) { create(:procedure, :published, administrateurs: [admin]) }
let(:procedure_closed) { create(:procedure, :closed, administrateurs: [admin]) }
let(:procedure) { dossier.procedure }
subject { delete :destroy, params: { id: procedure } }
context 'when the procedure is a brouillon' do
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_draft) }
before { subject }
it 'discard the procedure' do
expect(procedure.reload.discarded?).to be_truthy
end
it 'deletes associated dossiers' do
expect(procedure.dossiers.with_discarded.count).to eq(0)
end
it 'redirects to the procedure drafts page' do
expect(response).to redirect_to admin_procedures_draft_path
expect(flash[:notice]).to be_present
end
end
context 'when procedure is published' do
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_published) }
before { subject }
it { expect(response.status).to eq 403 }
context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.close?).to be_truthy }
it { expect(procedure.reload.discarded?).to be_truthy }
it { expect(dossier.reload.discarded?).to be_truthy }
end
end
context 'when procedure is closed' do
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_closed) }
before { subject }
it { expect(response.status).to eq 403 }
context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.discarded?).to be_truthy }
it { expect(dossier.reload.discarded?).to be_truthy }
end
end
context "when administrateur does not own the procedure" do
let(:dossier) { create(:dossier) }
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
end
end
describe 'PUT #archive' do
let(:procedure) { create(:procedure, :published, administrateur: admin, lien_site_web: lien_site_web) }
context 'when admin is the owner of the procedure' do
before do
put :archive, params: { procedure_id: procedure.id }
procedure.reload
end
context 'when owner want archive procedure' do
it { expect(procedure.close?).to be_truthy }
it { expect(response).to redirect_to :admin_procedures }
it { expect(flash[:notice]).to have_content 'Démarche close' }
end
end
context 'when admin is not the owner of the procedure' do
let(:admin_2) { create(:administrateur) }
before do
sign_out(admin.user)
sign_in(admin_2.user)
put :archive, params: { procedure_id: procedure.id }
procedure.reload
end
it { expect(response).to redirect_to :admin_procedures }
it { expect(flash[:alert]).to have_content 'Démarche inexistante' }
end
end
describe 'PUT #clone' do
let!(:procedure) { create(:procedure, :with_notice, :with_deliberation, administrateur: admin) }
let(:params) { { procedure_id: procedure.id } }
subject { put :clone, params: params }
before do
response = Typhoeus::Response.new(code: 200, body: 'Hello world')
Typhoeus.stub(/active_storage\/disk/).and_return(response)
end
it { expect { subject }.to change(Procedure, :count).by(1) }
context 'when admin is the owner of the procedure' do
before { subject }
it 'creates a new procedure and redirect to it' do
expect(response).to redirect_to edit_admin_procedure_path(id: Procedure.last.id)
expect(Procedure.last.cloned_from_library).to be_falsey
expect(Procedure.last.notice.attached?).to be_truthy
expect(Procedure.last.deliberation.attached?).to be_truthy
expect(flash[:notice]).to have_content 'Démarche clonée'
end
context 'when the procedure is cloned from the library' do
let(:params) { { procedure_id: procedure.id, from_new_from_existing: true } }
it { expect(Procedure.last.cloned_from_library).to be(true) }
end
end
context 'when admin is not the owner of the procedure' do
let(:admin_2) { create(:administrateur) }
before do
sign_out(admin.user)
sign_in(admin_2.user)
subject
end
it 'creates a new procedure and redirect to it' do
expect(response).to redirect_to edit_admin_procedure_path(id: Procedure.last.id)
expect(flash[:notice]).to have_content 'Démarche clonée'
end
end
end
describe 'GET #new_from_existing' do
before do
stub_const("Admin::ProceduresController::SIGNIFICANT_DOSSIERS_THRESHOLD", 2)
end
subject { get :new_from_existing }
let(:grouped_procedures) { subject; assigns(:grouped_procedures) }
let(:response_procedures) { grouped_procedures.map { |_o, procedures| procedures }.flatten }
describe 'selecting' do
let!(:large_draft_procedure) { create(:procedure_with_dossiers, dossiers_count: 2) }
let!(:large_published_procedure) { create(:procedure_with_dossiers, :published, dossiers_count: 2) }
let!(:large_closed_procedure) { create(:procedure_with_dossiers, :closed, dossiers_count: 2) }
let!(:small_closed_procedure) { create(:procedure_with_dossiers, :closed, dossiers_count: 1) }
it 'displays published and closed procedures' do
expect(response_procedures).to include(large_published_procedure)
expect(response_procedures).to include(large_closed_procedure)
end
it 'doesnt display procedures without a significant number of dossiers' do
expect(response_procedures).not_to include(small_closed_procedure)
end
it 'doesnt display draft procedures' do
expect(response_procedures).not_to include(large_draft_procedure)
end
end
describe 'grouping' do
let(:service_1) { create(:service, nom: 'DDT des Vosges') }
let(:service_2) { create(:service, nom: 'DDT du Loiret') }
let!(:procedure_with_service_1) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_1, dossiers_count: 2) }
let!(:procedure_with_service_2) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_2, dossiers_count: 2) }
let!(:procedure_without_service) { create(:procedure_with_dossiers, :published, organisation: 'DDT du Loiret', dossiers_count: 2) }
it 'groups procedures with services as well as procedures with organisations' do
expect(grouped_procedures.length).to eq 2
expect(grouped_procedures.find { |o, _p| o == 'DDT des Vosges' }.last).to contain_exactly(procedure_with_service_1)
expect(grouped_procedures.find { |o, _p| o == 'DDT du Loiret' }.last).to contain_exactly(procedure_with_service_2, procedure_without_service)
end
end
end
describe "DELETE #delete_deliberation" do
context "with a demarche the admin owns" do
let(:procedure) { create(:procedure, :with_deliberation, administrateur: admin) }
before do
delete :delete_deliberation, params: { id: procedure.id }
procedure.reload
end
it { expect(procedure.deliberation.attached?).to eq(false) }
it { expect(response).to redirect_to(edit_admin_procedure_path(procedure)) }
end
context "with a demarche the admin does not own" do
let(:procedure) { create(:procedure, :with_deliberation) }
before do
delete :delete_deliberation, params: { id: procedure.id }
procedure.reload
end
it { expect(response.status).to eq(404) }
end
end
describe "DELETE #delete_notice" do
context "with a demarche the admin owns" do
let(:procedure) { create(:procedure, :with_notice, administrateur: admin) }
before do
delete :delete_notice, params: { id: procedure.id }
procedure.reload
end
it { expect(procedure.notice.attached?).to eq(false) }
it { expect(response).to redirect_to(edit_admin_procedure_path(procedure)) }
end
context "with a demarche the admin does not own" do
let(:procedure) { create(:procedure, :with_notice) }
before do
delete :delete_notice, params: { id: procedure.id }
procedure.reload
end
it { expect(response.status).to eq(404) }
end
end
end

View file

@ -64,6 +64,50 @@ describe NewAdministrateur::ProceduresController, type: :controller do
it { expect(subject.status).to eq(200) }
end
describe 'GET #new_from_existing' do
before do
stub_const("NewAdministrateur::ProceduresController::SIGNIFICANT_DOSSIERS_THRESHOLD", 2)
end
subject { get :new_from_existing }
let(:grouped_procedures) { subject; assigns(:grouped_procedures) }
let(:response_procedures) { grouped_procedures.map { |_o, procedures| procedures }.flatten }
describe 'selecting' do
let!(:large_draft_procedure) { create(:procedure_with_dossiers, dossiers_count: 2) }
let!(:large_published_procedure) { create(:procedure_with_dossiers, :published, dossiers_count: 2) }
let!(:large_closed_procedure) { create(:procedure_with_dossiers, :closed, dossiers_count: 2) }
let!(:small_closed_procedure) { create(:procedure_with_dossiers, :closed, dossiers_count: 1) }
it 'displays published and closed procedures' do
expect(response_procedures).to include(large_published_procedure)
expect(response_procedures).to include(large_closed_procedure)
end
it 'doesnt display procedures without a significant number of dossiers' do
expect(response_procedures).not_to include(small_closed_procedure)
end
it 'doesnt display draft procedures' do
expect(response_procedures).not_to include(large_draft_procedure)
end
end
describe 'grouping' do
let(:service_1) { create(:service, nom: 'DDT des Vosges') }
let(:service_2) { create(:service, nom: 'DDT du Loiret') }
let!(:procedure_with_service_1) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_1, dossiers_count: 2) }
let!(:procedure_with_service_2) { create(:procedure_with_dossiers, :published, organisation: nil, service: service_2, dossiers_count: 2) }
let!(:procedure_without_service) { create(:procedure_with_dossiers, :published, organisation: 'DDT du Loiret', dossiers_count: 2) }
it 'groups procedures with services as well as procedures with organisations' do
expect(grouped_procedures.length).to eq 2
expect(grouped_procedures.find { |o, _p| o == 'DDT des Vosges' }.last).to contain_exactly(procedure_with_service_1)
expect(grouped_procedures.find { |o, _p| o == 'DDT du Loiret' }.last).to contain_exactly(procedure_with_service_2, procedure_without_service)
end
end
end
describe 'GET #edit' do
let(:published_at) { nil }
let(:procedure) { create(:procedure, administrateur: admin, published_at: published_at) }
@ -250,6 +294,154 @@ describe NewAdministrateur::ProceduresController, type: :controller do
end
end
describe 'PUT #clone' do
let(:procedure) { create(:procedure, :with_notice, :with_deliberation, administrateur: admin) }
let(:params) { { procedure_id: procedure.id } }
subject { put :clone, params: params }
before do
procedure
response = Typhoeus::Response.new(code: 200, body: 'Hello world')
Typhoeus.stub(/active_storage\/disk/).and_return(response)
end
it { expect { subject }.to change(Procedure, :count).by(1) }
context 'when admin is the owner of the procedure' do
before { subject }
it 'creates a new procedure and redirect to it' do
expect(response).to redirect_to edit_admin_procedure_path(id: Procedure.last.id)
expect(Procedure.last.cloned_from_library).to be_falsey
expect(Procedure.last.notice.attached?).to be_truthy
expect(Procedure.last.deliberation.attached?).to be_truthy
expect(flash[:notice]).to have_content 'Démarche clonée'
end
context 'when the procedure is cloned from the library' do
let(:params) { { procedure_id: procedure.id, from_new_from_existing: true } }
it { expect(Procedure.last.cloned_from_library).to be(true) }
end
end
context 'when admin is not the owner of the procedure' do
let(:admin_2) { create(:administrateur) }
before do
sign_out(admin.user)
sign_in(admin_2.user)
subject
end
it 'creates a new procedure and redirect to it' do
expect(response).to redirect_to edit_admin_procedure_path(id: Procedure.last.id)
expect(flash[:notice]).to have_content 'Démarche clonée'
end
end
end
describe 'PUT #archive' do
let(:procedure) { create(:procedure, :published, administrateur: admin, lien_site_web: lien_site_web) }
context 'when the admin is an owner of the procedure' do
before do
put :archive, params: { procedure_id: procedure.id }
procedure.reload
end
it 'archives the procedure' do
expect(procedure.close?).to be_truthy
expect(response).to redirect_to :admin_procedures
expect(flash[:notice]).to have_content 'Démarche close'
end
end
context 'when the admin is not an owner of the procedure' do
let(:admin_2) { create(:administrateur) }
before do
sign_out(admin.user)
sign_in(admin_2.user)
put :archive, params: { procedure_id: procedure.id }
procedure.reload
end
it 'displays an error message' do
expect(response).to redirect_to :admin_procedures
expect(flash[:alert]).to have_content 'Démarche inexistante'
end
end
end
describe 'DELETE #destroy' do
let(:procedure_draft) { create(:procedure, administrateurs: [admin]) }
let(:procedure_published) { create(:procedure, :published, administrateurs: [admin]) }
let(:procedure_closed) { create(:procedure, :closed, administrateurs: [admin]) }
let(:procedure) { dossier.procedure }
subject { delete :destroy, params: { id: procedure } }
context 'when the procedure is a brouillon' do
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_draft) }
before { subject }
it 'discard the procedure' do
expect(procedure.reload.discarded?).to be_truthy
end
it 'deletes associated dossiers' do
expect(procedure.dossiers.with_discarded.count).to eq(0)
end
it 'redirects to the procedure drafts page' do
expect(response).to redirect_to admin_procedures_draft_path
expect(flash[:notice]).to be_present
end
end
context 'when procedure is published' do
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_published) }
before { subject }
it { expect(response.status).to eq 403 }
context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.close?).to be_truthy }
it { expect(procedure.reload.discarded?).to be_truthy }
it { expect(dossier.reload.discarded?).to be_truthy }
end
end
context 'when procedure is closed' do
let(:dossier) { create(:dossier, :en_instruction, procedure: procedure_closed) }
before { subject }
it { expect(response.status).to eq 403 }
context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.discarded?).to be_truthy }
it { expect(dossier.reload.discarded?).to be_truthy }
end
end
context "when administrateur does not own the procedure" do
let(:dossier) { create(:dossier) }
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
end
end
describe 'PATCH #monavis' do
let!(:procedure) { create(:procedure, administrateur: admin) }
let(:procedure_params) {

View file

@ -5,13 +5,14 @@ feature 'As an instructeur', js: true do
before do
login_as administrateur.user, scope: :user
visit admin_instructeurs_path
fill_in :instructeur_email, with: instructeur_email
visit admin_procedure_path(procedure)
find('#groupe-instructeurs').click
perform_enqueued_jobs do
click_button 'Ajouter'
end
find("input[aria-label='email instructeur'").send_keys(instructeur_email, :enter)
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Les instructeurs ont bien été affectés à la démarche")
end
scenario 'I can register' do

View file

@ -28,7 +28,7 @@ feature 'The user' do
select_multi_combobox('multiple_choice_drop_down_list_long', 'alp', 'alpha')
select_multi_combobox('multiple_choice_drop_down_list_long', 'cha', 'charly')
select_combobox('pays', 'aust', 'AUSTRALIE')
select_combobox('pays', 'aust', 'Australie')
select_combobox('regions', 'Ma', 'Martinique')
select_combobox('departements', 'Ai', '02 - Aisne')
select_combobox('communes', 'Ambl', 'Ambléon (01300)')
@ -57,7 +57,7 @@ feature 'The user' do
expect(champ_value_for('simple_choice_drop_down_list_long')).to eq('bravo')
expect(JSON.parse(champ_value_for('multiple_choice_drop_down_list_long'))).to match(['alpha', 'charly'])
expect(JSON.parse(champ_value_for('multiple_drop_down_list'))).to match(['val1', 'val3'])
expect(champ_value_for('pays')).to eq('AUSTRALIE')
expect(champ_value_for('pays')).to eq('Australie')
expect(champ_value_for('regions')).to eq('Martinique')
expect(champ_value_for('departements')).to eq('02 - Aisne')
expect(champ_value_for('communes')).to eq('Ambléon (01300)')
@ -82,7 +82,7 @@ feature 'The user' do
expect(page).to have_checked_field('val3')
expect(page).to have_selected_value('simple_choice_drop_down_list_long', selected: 'bravo')
check_selected_values('multiple_choice_drop_down_list_long', ['alpha', 'charly'])
expect(page).to have_hidden_field('pays', with: 'AUSTRALIE')
expect(page).to have_hidden_field('pays', with: 'Australie')
expect(page).to have_hidden_field('regions', with: 'Martinique')
expect(page).to have_hidden_field('departements', with: '02 - Aisne')
expect(page).to have_hidden_field('communes', with: 'Ambléon (01300)')

View file

@ -202,8 +202,8 @@ RSpec.describe DossierMailer, type: :mailer do
subject { described_class.notify_groupe_instructeur_changed(instructeur, dossier) }
it { expect(subject.subject).to eq("Un dossier a changé de groupe instructeur") }
it { expect(subject.body).to include("#{dossier.id}") }
it { expect(subject.subject).to eq("Le dossier nº #{dossier.id} a changé de groupe instructeur") }
it { expect(subject.body).to include(" #{dossier.id}") }
it { expect(subject.body).to include(dossier.procedure.libelle) }
it { expect(subject.body).to include("Suite à cette modification, vous ne suivez plus ce dossier.") }
end

View file

@ -14,6 +14,14 @@ describe Champs::LinkedDropDownListChamp do
it { expect(champ.value).to eq('["tata","tutu"]') }
end
describe '#primary_value=' do
let!(:champ) { described_class.new(primary_value: 'tata', secondary_value: 'tutu') }
before { champ.primary_value = '' }
it { expect(champ.value).to eq('["",""]') }
end
describe '#to_s' do
let(:champ) { described_class.new(primary_value: primary_value, secondary_value: secondary_value) }
let(:primary_value) { nil }

View file

@ -1,31 +0,0 @@
describe 'admin/instructeurs/index.html.haml', type: :view do
let(:admin) { create(:administrateur) }
before do
assign(:instructeurs, (smart_listing_create :instructeurs,
admin.instructeurs,
partial: "admin/instructeurs/list",
array: true))
assign(:instructeur, create(:instructeur))
end
context 'Aucun instructeur' do
before do
render
end
it { expect(rendered).to have_content('Aucun instructeur') }
end
context 'Ajout d\'un instructeur' do
before do
create(:instructeur, administrateurs: [admin])
admin.reload
assign(:instructeurs, (smart_listing_create :instructeurs,
admin.instructeurs,
partial: "admin/instructeurs/list",
array: true))
render
end
it { expect(rendered).to match(/inst\d+@inst.com/) }
end
end

978
yarn.lock

File diff suppressed because it is too large Load diff