Merge pull request #3299 from betagouv/dev

2018-01-17-01
This commit is contained in:
Pierre de La Morinerie 2019-01-17 15:07:32 +01:00 committed by GitHub
commit a144da5718
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 1790 additions and 346 deletions

View file

@ -18,7 +18,7 @@ module.exports = {
},
overrides: [
{
files: ['config/webpack/*.js'],
files: ['config/webpack/**/*.js'],
env: {
node: true
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>

After

Width:  |  Height:  |  Size: 132 B

View file

@ -11,6 +11,7 @@
a {
color: $black;
text-decoration: underline;
}
&::after {

View file

@ -110,7 +110,7 @@
width: 18px;
height: 18px;
background-size: 18px 18px;
vertical-align: middle;
vertical-align: text-bottom;
margin-right: $default-spacer;
}

View file

@ -107,6 +107,12 @@
}
}
input[type=checkbox] {
&.small-margin {
margin-bottom: $default-padding / 2;
}
}
input[type=text]:not([data-address='true']),
input[type=email],
input[type=password],

View file

@ -20,3 +20,12 @@
border-color: $light-green;
color: $dark-green;
}
.alert-fixed {
position: fixed;
left: 50%;
margin-left: -100px;
width: 200px;
top: 10px;
border-radius: 10px;
}

View file

@ -14,7 +14,7 @@ footer {
@include vertical-padding(72px);
}
.dossier-footer {
.procedure-footer {
@include vertical-padding(30px);
line-height: 24px;
}

View file

@ -0,0 +1,137 @@
@import "colors";
#champs-editor {
.spinner {
margin-right: auto;
margin-left: auto;
margin-top: 80px;
}
}
.draggable-item {
display: flex;
flex-direction: column;
justify-content: flex-start;
border: 1px solid $border-grey;
border-radius: 5px;
margin-bottom: 10px;
width: 100%;
.handle {
cursor: ns-resize;
margin-right: 10px;
margin-top: 8px;
}
.error-message {
text-align: center;
flex-grow: 1;
font-size: 14px;
color: $light-grey;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-around;
.content {
background-color: $medium-red;
border-radius: 8px;
padding: 4px 10px;
}
}
&.type-header-section {
background-color: $blue;
label {
color: $light-grey;
}
}
&:not(.type-header-section) {
input.error {
border: 1px solid $medium-red;
}
}
.column {
display: flex;
justify-content: flex-start;
flex-direction: column;
&.shift-left {
margin-left: 35px;
}
}
.row {
display: flex;
justify-content: flex-start;
&.section {
padding: 10px 10px 0 10px;
margin-bottom: 8px;
}
&.hr {
border-bottom: 1px solid $border-grey;
&.head {
padding-bottom: 10px;
}
}
&.shift-left {
margin-left: 35px;
}
&.head {
select {
margin-bottom: 0px;
}
}
&.delete {
flex-grow: 1;
display: flex;
justify-content: flex-end;
}
}
.cell {
margin-right: 20px;
&.small {
width: 90px;
}
&.libelle {
width: 300px;
}
label {
margin-bottom: 8px;
text-transform: uppercase;
font-size: 12px;
}
}
.carte-options {
label {
font-weight: initial;
}
}
.inline {
display: inline;
}
}
.header,
.footer {
display: flex;
justify-content: space-between;
margin-top: 30px;
margin-bottom: 30px;
}

View file

@ -79,7 +79,12 @@ class Admin::ProceduresController < AdminController
if gestionnaire
gestionnaire.assign_to_procedure(@procedure)
end
redirect_to admin_procedure_types_de_champ_path(procedure_id: @procedure.id)
if Flipflop.new_champs_editor?
redirect_to champs_procedure_path(@procedure)
else
redirect_to admin_procedure_types_de_champ_path(procedure_id: @procedure.id)
end
end
end

View file

@ -0,0 +1,34 @@
module ProcedureContextConcern
extend ActiveSupport::Concern
include Devise::Controllers::StoreLocation
include Devise::StoreLocationExtension
def restore_procedure_context
if stored_procedure_id.present?
@procedure = Procedure.publiees.find_by(id: stored_procedure_id)
if @procedure.blank?
invalid_procedure_context
end
end
end
private
def stored_procedure_id
stored_location = get_stored_location_for(:user)
if stored_location.present? && stored_location.include?('procedure_id=')
stored_location.split('procedure_id=').second
else
nil
end
end
def invalid_procedure_context
clear_stored_location_for(:user)
flash.alert = t('errors.messages.procedure_not_found')
redirect_to root_path
end
end

View file

@ -0,0 +1,19 @@
module Devise
# Useful helpers additions to Devise::Controllers::StoreLocation
module StoreLocationExtension
# A variant of `stored_location_key_for` which doesn't delete the stored path.
def get_stored_location_for(resource_or_scope)
location = stored_location_for(resource_or_scope)
if location
store_location_for(resource_or_scope, location)
end
location
end
# Delete the url stored in the session for the given scope.
def clear_stored_location_for(resource_or_scope)
session_key = send(:stored_location_key_for, resource_or_scope)
session.delete(session_key)
end
end
end

View file

@ -1,5 +1,5 @@
class InvitesController < ApplicationController
SESSION_USER_RETURN_LOCATION = 'user_return_to'
include Devise::StoreLocationExtension
before_action :authenticate_user!, only: [:create]
before_action :store_user_location!, only: [:show]
@ -62,6 +62,6 @@ class InvitesController < ApplicationController
end
def erase_user_location!
session.delete(SESSION_USER_RETURN_LOCATION)
clear_stored_location_for(:user)
end
end

View file

@ -1,5 +1,28 @@
module NewAdministrateur
class AdministrateurController < ApplicationController
before_action :authenticate_administrateur!
def retrieve_procedure
id = params[:procedure_id] || params[:id]
@procedure = current_administrateur.procedures.find(id)
rescue ActiveRecord::RecordNotFound
flash.alert = 'Démarche inexistante'
redirect_to admin_procedures_path
end
def procedure_locked?
if @procedure.locked?
flash.alert = 'Démarche verrouillée'
redirect_to admin_procedure_path(@procedure)
end
end
def reset_procedure
if @procedure.brouillon_avec_lien?
@procedure.reset!
end
end
end
end

View file

@ -1,10 +1,44 @@
module NewAdministrateur
class ProceduresController < AdministrateurController
before_action :retrieve_procedure, only: [:champs, :annotations, :update]
before_action :procedure_locked?, only: [:champs, :annotations, :update]
TYPE_DE_CHAMP_ATTRIBUTES = [
:_destroy,
:libelle,
:description,
:order_place,
:type_champ,
:id,
:mandatory,
:piece_justificative_template,
:quartiers_prioritaires,
:cadastres,
:parcelles_agricoles,
drop_down_list_attributes: [:value]
]
def apercu
@dossier = procedure_without_control.new_dossier
@tab = apercu_tab
end
def update
if @procedure.update(procedure_params)
flash.now.notice = if params[:procedure][:types_de_champ_attributes].present?
'Champs enregistrés'
elsif params[:procedure][:types_de_champ_private_attributes].present?
'Annotations enregistrés'
else
'Démarche enregistrée.'
end
reset_procedure
else
flash.now.alert = @procedure.errors.full_messages
end
end
private
def apercu_tab
@ -14,5 +48,12 @@ module NewAdministrateur
def procedure_without_control
Procedure.find(params[:id])
end
def procedure_params
params.required(:procedure).permit(
types_de_champ_attributes: TYPE_DE_CHAMP_ATTRIBUTES,
types_de_champ_private_attributes: TYPE_DE_CHAMP_ATTRIBUTES
)
end
end
end

View file

@ -6,7 +6,7 @@ module NewGestionnaire
ITEMS_PER_PAGE = 25
def index
@procedures = current_gestionnaire.visible_procedures.order(archived_at: :desc, published_at: :desc)
@procedures = current_gestionnaire.visible_procedures.order(archived_at: :desc, published_at: :desc, created_at: :desc)
dossiers = current_gestionnaire.dossiers
@dossiers_count_per_procedure = dossiers.all_state.group(:procedure_id).reorder(nil).count

View file

@ -1,10 +1,10 @@
module NewUser
class DossiersController < UserController
include Devise::StoreLocationExtension
include DossierHelper
layout 'procedure_context', only: [:identite, :update_identite, :siret, :update_siret]
SESSION_USER_RETURN_LOCATION = 'user_return_to'
ACTIONS_ALLOWED_TO_ANY_USER = [:index, :recherche, :new]
ACTIONS_ALLOWED_TO_OWNER_OR_INVITE = [:show, :demande, :messagerie, :brouillon, :update_brouillon, :modifier, :update, :create_commentaire, :purge_champ_piece_justificative]
@ -247,7 +247,7 @@ module NewUser
end
def erase_user_location!
session.delete(SESSION_USER_RETURN_LOCATION)
clear_stored_location_for(:user)
end
def show_demarche_en_test_banner

View file

@ -1,6 +1,8 @@
class Sessions::SessionsController < Devise::SessionsController
before_action :before_sign_in, only: [:create]
layout 'new_application'
def before_sign_in
if user_signed_in?
sign_out :user

View file

@ -1,10 +1,11 @@
class Users::RegistrationsController < Devise::RegistrationsController
include ProcedureContextConcern
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
before_action :restore_procedure_context, only: [:new, :create]
# def after_sign_up_path_for(resource_or_scope)
# super
# end
layout 'procedure_context', only: [:new, :create]
# GET /resource/sign_up
def new

View file

@ -1,16 +1,15 @@
class Users::SessionsController < Sessions::SessionsController
include ProcedureContextConcern
include TrustedDeviceConcern
include ActionView::Helpers::DateHelper
layout 'procedure_context', only: [:new, :create]
before_action :restore_procedure_context, only: [:new, :create]
# GET /resource/sign_in
def new
if user_return_to_procedure_id.present? # WTF ?
@dossier = Dossier.new(procedure: Procedure.active(user_return_to_procedure_id))
end
@user = User.new
rescue ActiveRecord::RecordNotFound
error_procedure
end
# POST /resource/sign_in
@ -78,7 +77,7 @@ class Users::SessionsController < Sessions::SessionsController
end
def no_procedure
session['user_return_to'] = nil
clear_stored_location_for(:user)
redirect_to new_user_session_path
end
@ -110,20 +109,6 @@ class Users::SessionsController < Sessions::SessionsController
end
end
def error_procedure
session["user_return_to"] = nil
flash.alert = t('errors.messages.procedure_not_found')
redirect_to url_for root_path
end
def user_return_to_procedure_id
if session["user_return_to"].nil?
return nil
end
NumberService.to_number session["user_return_to"].split("?procedure_id=").second
end
def try_to_authenticate(klass, remember_me = false)
resource = klass.find_for_database_authentication(email: params[:user][:email])

View file

@ -7,11 +7,20 @@ module ApplicationHelper
end
end
def flash_class(level, sticky = false)
case level
when "notice" then "alert-success#{sticky ? ' sticky' : ''}"
when "alert" then "alert-danger#{sticky ? ' sticky' : ''}"
def flash_class(level, sticky: false, fixed: false)
class_names = case level
when 'notice'
['alert-success']
when 'alert'
['alert-danger']
end
if sticky
class_names << 'sticky'
end
if fixed
class_names << 'alert-fixed'
end
class_names.join(' ')
end
def render_to_element(selector, partial:, outer: false, locals: {})
@ -22,9 +31,9 @@ module ApplicationHelper
# rubocop:enable Rails/OutputSafety
end
def render_flash(timeout: false, sticky: false)
def render_flash(timeout: false, sticky: false, fixed: false)
if flash.any?
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky }, outer: true)
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky, fixed: fixed }, outer: true)
flash.clear
if timeout
html += remove_element('#flash_messages', timeout: timeout, inner: true)
@ -56,6 +65,12 @@ module ApplicationHelper
# rubocop:enable Rails/OutputSafety
end
def fire_event(event_name, data)
# rubocop:disable Rails/OutputSafety
raw("DS.fire('#{event_name}', #{raw(data)});")
# rubocop:enable Rails/OutputSafety
end
def current_email
current_user&.email ||
current_gestionnaire&.email ||

View file

@ -10,7 +10,7 @@ module ProcedureHelper
end
def procedure_libelle(procedure)
parts = procedure.brouillon? ? [content_tag(:span, 'démarche non publiée', class: 'badge')] : []
parts = procedure.brouillon? ? [content_tag(:span, 'démarche en test', class: 'badge')] : []
parts << procedure.libelle
safe_join(parts, ' ')
end
@ -33,4 +33,56 @@ module ProcedureHelper
end
end
end
def types_de_champ_data(procedure)
{
type: "champ",
types_de_champ_options: types_de_champ_options.to_json,
types_de_champ: types_de_champ_as_json(procedure.types_de_champ).to_json,
direct_uploads_url: rails_direct_uploads_url,
drag_icon_url: image_url("icons/drag.svg")
}
end
def types_de_champ_private_data(procedure)
{
type: "annotation",
types_de_champ_options: types_de_champ_options.to_json,
types_de_champ: types_de_champ_as_json(procedure.types_de_champ_private).to_json,
direct_uploads_url: rails_direct_uploads_url,
drag_icon_url: image_url("icons/drag.svg")
}
end
def procedure_data(procedure)
{
types_de_champ: types_de_champ_as_json(procedure.types_de_champ),
types_de_champ_private: types_de_champ_as_json(procedure.types_de_champ_private)
}.to_json
end
private
TOGGLES = {
TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?,
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?,
TypeDeChamp.type_champs.fetch(:repetition) => :champ_repetition?
}
def types_de_champ_options
types_de_champ = TypeDeChamp.type_de_champs_list_fr
types_de_champ.select! do |tdc|
toggle = TOGGLES[tdc.last]
toggle.blank? || Flipflop.send(toggle)
end
types_de_champ
end
def types_de_champ_as_json(types_de_champ)
types_de_champ.as_json(except: [:created_at, :updated_at],
methods: [:piece_justificative_template_filename, :piece_justificative_template_url],
include: { drop_down_list: { only: :value } })
end
end

View file

@ -0,0 +1,131 @@
export default {
props: ['state', 'update', 'index', 'item', 'prefix'],
computed: {
isDirty() {
return (
this.state.version &&
this.state.unsavedInvalidItems.size > 0 &&
this.state.unsavedItems.has(this.itemId)
);
},
isInvalid() {
if (this.deleted) {
return false;
}
if (this.libelle) {
return !this.libelle.trim();
}
return true;
},
itemId() {
return this.item.id || this.clientId;
},
changeLog() {
return [this.itemId, !this.isInvalid];
},
itemClassName() {
const classNames = [`draggable-item-${this.index}`];
if (this.isHeaderSection) {
classNames.push('type-header-section');
}
if (this.isDirty) {
if (this.isInvalid) {
classNames.push('invalid');
} else {
classNames.push('dirty');
}
}
return classNames.join(' ');
},
isDropDown() {
return [
'drop_down_list',
'multiple_drop_down_list',
'linked_drop_down_list'
].includes(this.typeChamp);
},
isFile() {
return this.typeChamp === 'piece_justificative';
},
isCarte() {
return this.typeChamp === 'carte';
},
isExplication() {
return this.typeChamp === 'explication';
},
isHeaderSection() {
return this.typeChamp === 'header_section';
},
options() {
const options = this.item.options;
for (let key of Object.keys(options)) {
options[key] = castBoolean(options[key]);
}
return options;
},
attribute() {
if (this.state.isAnnotation) {
return 'types_de_champ_private_attributes';
} else {
return 'types_de_champ_attributes';
}
}
},
data() {
return {
typeChamp: this.item.type_champ,
libelle: this.item.libelle,
mandatory: this.item.mandatory,
description: this.item.description,
dropDownList: this.item.drop_down_list.value,
deleted: false,
clientId: `id-${clientIds++}`
};
},
created() {
for (let path of PATHS_TO_WATCH) {
this.$watch(path, () => this.update(this.changeLog));
}
},
mounted() {
if (this.isInvalid) {
this.update(this.changeLog, false);
}
},
methods: {
removeChamp(item) {
if (item.id) {
this.deleted = true;
} else {
const index = this.state.typesDeChamp.indexOf(item);
this.state.typesDeChamp.splice(index, 1);
this.update([this.itemId, true]);
}
},
nameFor(name) {
return `${this.prefix}[${this.attribute}][${this.index}][${name}]`;
},
elementIdFor(name) {
return `${this.prefix}_${this.attribute}_${this.index}_${name}`;
}
}
};
const PATHS_TO_WATCH = [
'typeChamp',
'libelle',
'mandatory',
'description',
'dropDownList',
'options.quartiers_prioritaires',
'options.cadastres',
'options.parcelles_agricoles',
'index',
'deleted'
];
function castBoolean(value) {
return value && value != 0;
}
let clientIds = 0;

View file

@ -0,0 +1,165 @@
<template>
<div class="deleted" v-if="deleted">
<input type="hidden" :name="nameFor('id')" :value="item.id">
<input type="hidden" :name="nameFor('_destroy')" value="true">
</div>
<div class="draggable-item" v-else :class="itemClassName">
<div class="row section head" :class="{ hr: !isHeaderSection }">
<div class="handle">
<img :src="state.dragIconUrl" alt="">
</div>
<div class="cell">
<select :name="nameFor('type_champ')" v-model="typeChamp" class="small-margin small inline">
<option v-for="option in state.typesDeChampOptions" :key="option[1]" :value="option[1]">
{{ option[0] }}
</option>
</select>
</div>
<div class="row delete">
<div v-if="isDirty" class="error-message">
<span v-if="isInvalid" class="content">
Le libellé doit être rempli.
</span>
<span v-else class="content">
<template v-if="state.isAnnotation">
Modifications non sauvegardées. Le libellé doit être rempli sur tous les annotations.
</template>
<template v-else>
Modifications non sauvegardées. Le libellé doit être rempli sur tous les champs.
</template>
</span>
</div>
<button class="button danger" @click.prevent="removeChamp(item)">
Supprimer
</button>
</div>
</div>
<div class="row section" :class="{ hr: isDropDown || isFile || isCarte }">
<div class="column shift-left">
<div class="cell libelle">
<label :for="elementIdFor('libelle')">
Libellé
</label>
<input
type="text"
:id="elementIdFor('libelle')"
:name="nameFor('libelle')"
v-model="libelle"
class="small-margin small"
:class="{ error: isDirty && isInvalid }">
</div>
<div class="cell" v-show="!isHeaderSection && !isExplication && !state.isAnnotation">
<label :for="elementIdFor('mandatory')">
Obligatoire
</label>
<input :name="nameFor('mandatory')" type="hidden" value="0">
<input
type="checkbox"
:id="elementIdFor('mandatory')"
:name="nameFor('mandatory')"
v-model="mandatory"
class="small-margin small"
value="1">
</div>
</div>
<div class="row">
<div class="cell" v-show="!isHeaderSection">
<label :for="elementIdFor('description')">
Description
</label>
<textarea
:id="elementIdFor('description')"
:name="nameFor('description')"
v-model="description"
rows=3
cols=40
class="small-margin small">
</textarea>
</div>
</div>
</div>
<div class="row section shift-left" v-show="!isHeaderSection">
<div class="cell" v-show="isDropDown">
<label :for="elementIdFor('drop_down_list')">
Liste déroulante
</label>
<textarea
:id="elementIdFor('drop_down_list')"
:name="nameFor('drop_down_list_attributes[value]')"
v-model="dropDownList"
rows=3
cols=40
placeholder="Ecrire une valeur par ligne et --valeur-- pour un séparateur."
class="small-margin small">
</textarea>
</div>
<div class="cell" v-show="isFile">
<label :for="elementIdFor('piece_justificative_template')">
Modèle
</label>
<template v-if="item.piece_justificative_template_url">
<a :href="item.piece_justificative_template_url" target="_blank">
{{item.piece_justificative_template_filename}}
</a>
<br> Modifier :
</template>
<input
type="file"
:id="elementIdFor('piece_justificative_template')"
:name="nameFor('piece_justificative_template')"
:data-direct-upload-url="state.directUploadsUrl"
@change="update(changeLog)"
class="small-margin small">
</div>
<div class="cell" v-show="isCarte">
<label>
Utilisation de la cartographie
</label>
<div class="carte-options">
<label :for="elementIdFor('quartiers_prioritaires')">
<input :name="nameFor('quartiers_prioritaires')" type="hidden" value="0">
<input
type="checkbox"
:id="elementIdFor('quartiers_prioritaires')"
:name="nameFor('quartiers_prioritaires')"
v-model="options.quartiers_prioritaires"
class="small-margin small"
value="1">
Quartiers prioritaires
</label>
<label :for="elementIdFor('cadastres')">
<input :name="nameFor('cadastres')" type="hidden" value="0">
<input
type="checkbox"
:id="elementIdFor('cadastres')"
:name="nameFor('cadastres')"
v-model="options.cadastres"
class="small-margin small"
value="1">
Cadastres
</label>
<label :for="elementIdFor('parcelles_agricoles')">
<input :name="nameFor('parcelles_agricoles')" type="hidden" value="0">
<input
type="checkbox"
:id="elementIdFor('parcelles_agricoles')"
:name="nameFor('parcelles_agricoles')"
v-model="options.parcelles_agricoles"
class="small-margin small"
value="1">
Parcelles Agricoles
</label>
</div>
</div>
</div>
<div class="meta">
<input type="hidden" :name="nameFor('order_place')" :value="index">
<input type="hidden" :name="nameFor('id')" :value="item.id">
</div>
</div>
</template>
<script src="./DraggableItem.js"></script>

View file

@ -0,0 +1,12 @@
export default {
props: ['state', 'version', 'update', 'updateAll'],
methods: {
addChamp() {
this.state.typesDeChamp.push({
type_champ: 'text',
drop_down_list: {},
options: {}
});
}
}
};

View file

@ -0,0 +1,42 @@
<template>
<div class="champs-editor">
<div v-if="state.typesDeChamp.length > 3" class="header">
<button class="button" @click.prevent="addChamp">
<template v-if="state.isAnnotation">
Ajouter une annotation
</template>
<template v-else>
Ajouter un champ
</template>
</button>
<button class="button primary" @click.prevent="updateAll">Enregistrer</button>
</div>
<Draggable :list="state.typesDeChamp" :options="{handle:'.handle'}">
<DraggableItem
v-for="(item, index) in state.typesDeChamp"
prefix="procedure"
:state="state"
:update="update"
:index="index"
:item="item"
:key="item.id" />
</Draggable>
<div class="footer">
<button class="button" @click.prevent="addChamp">
<template v-if="state.isAnnotation">
Ajouter une annotation
</template>
<template v-else>
Ajouter un champ
</template>
</button>
<button class="button primary" @click.prevent="updateAll">Enregistrer</button>
</div>
</div>
</template>
<script src="./DraggableList.js"></script>

View file

@ -0,0 +1,109 @@
import Vue from 'vue';
import Draggable from 'vuedraggable';
import { fire, debounce } from '@utils';
import DraggableItem from './DraggableItem';
import DraggableList from './DraggableList';
Vue.component('Draggable', Draggable);
Vue.component('DraggableItem', DraggableItem);
addEventListener('DOMContentLoaded', () => {
const el = document.querySelector('#champs-editor');
const { directUploadsUrl, dragIconUrl } = el.dataset;
const state = {
typesDeChamp: JSON.parse(el.dataset.typesDeChamp),
typesDeChampOptions: JSON.parse(el.dataset.typesDeChampOptions),
directUploadsUrl,
dragIconUrl,
isAnnotation: el.dataset.type === 'annotation',
unsavedItems: new Set(),
unsavedInvalidItems: new Set(),
version: 1
};
new Vue({
el,
data: {
state,
update: null
},
render(h) {
return h(DraggableList, {
props: {
state: this.state,
update: this.update,
updateAll: this.updateAll
}
});
},
mounted() {
const [update, updateAll] = createUpdateFunctions(
this,
state.isAnnotation
);
this.update = update;
this.updateAll = updateAll;
}
});
});
function createUpdateFunctions(app, isAnnotation) {
let isSaving = false;
const form = app.$el.closest('form');
const update = ([id, isValid], refresh = true) => {
app.state.unsavedItems.add(id);
if (isValid) {
app.state.unsavedInvalidItems.delete(id);
} else {
app.state.unsavedInvalidItems.add(id);
}
if (refresh) {
app.state.version += 1;
}
updateAll();
};
const updateAll = debounce(() => {
if (isSaving) {
updateAll();
} else if (
app.state.typesDeChamp.length > 0 &&
app.state.unsavedInvalidItems.size === 0
) {
isSaving = true;
app.state.unsavedItems.clear();
app.state.version += 1;
fire(form, 'submit');
}
}, 500);
addEventListener('ProcedureUpdated', event => {
const { types_de_champ, types_de_champ_private } = event.detail;
app.state.typesDeChamp = isAnnotation
? types_de_champ_private
: types_de_champ;
isSaving = false;
updateFileInputs();
});
return [update, updateAll];
}
// This is needed du to the way ActiveStorage javascript integration works.
// It is built to be used with traditional forms. Another way would be to not use
// high level ActiveStorage abstractions (and maybe this is what we should do in the future).
function updateFileInputs() {
for (let element of document.querySelectorAll('.direct-upload')) {
let hiddenInput = element.nextElementSibling;
let fileInput = hiddenInput.nextElementSibling;
element.remove();
hiddenInput.remove();
fileInput.value = '';
fileInput.removeAttribute('disabled');
}
}

View file

@ -22,6 +22,8 @@ import '../new_design/select2';
import '../new_design/champs/carte';
import '../new_design/champs/linked-drop-down-list';
import '../new_design/administrateur/champs-editor';
import { toggleCondidentielExplanation } from '../new_design/avis';
import { scrollMessagerie } from '../new_design/messagerie';
import { showMotivation, motivationCancel } from '../new_design/state-button';
@ -30,6 +32,7 @@ import { replaceSemicolonByComma } from '../new_design/avis';
// This is the global application namespace where we expose helpers used from rails views
const DS = {
fire: (eventName, data) => Rails.fire(document, eventName, data),
toggleCondidentielExplanation,
scrollMessagerie,
showMotivation,

View file

@ -3,11 +3,10 @@ class AutoArchiveProcedureJob < ApplicationJob
def perform(*args)
Procedure.publiees.where("auto_archive_on <= ?", Date.today).each do |procedure|
gestionnaire = procedure.gestionnaire_for_cron_job
procedure.dossiers.state_en_construction.find_each do |dossier|
dossier.passer_en_instruction!(gestionnaire)
end
procedure
.dossiers
.state_en_construction
.find_each(&:passer_automatiquement_en_instruction!)
procedure.archive!
end

View file

@ -3,17 +3,18 @@ class AutoReceiveDossiersForProcedureJob < ApplicationJob
def perform(procedure_id, state)
procedure = Procedure.find(procedure_id)
gestionnaire = procedure.gestionnaire_for_cron_job
case state
when Dossier.states.fetch(:en_instruction)
procedure.dossiers.state_en_construction.find_each do |dossier|
dossier.passer_en_instruction!(gestionnaire)
end
procedure
.dossiers
.state_en_construction
.find_each(&:passer_automatiquement_en_instruction!)
when Dossier.states.fetch(:accepte)
procedure.dossiers.state_en_construction.find_each do |dossier|
dossier.accepter!(gestionnaire, '')
end
procedure
.dossiers
.state_en_construction
.find_each(&:accepter_automatiquement!)
else
raise "Receiving Procedure##{procedure_id} in invalid state \"#{state}\""
end

View file

@ -274,6 +274,12 @@ class Dossier < ApplicationRecord
log_dossier_operation(gestionnaire, :passer_en_instruction)
end
def passer_automatiquement_en_instruction!
en_instruction!
log_dossier_operation(nil, :passer_en_instruction, automatic_operation: true)
end
def repasser_en_construction!(gestionnaire)
self.en_instruction_at = nil
en_construction!
@ -295,6 +301,19 @@ class Dossier < ApplicationRecord
log_dossier_operation(gestionnaire, :accepter)
end
def accepter_automatiquement!
self.en_instruction_at ||= Time.zone.now
accepte!
if attestation.nil?
update(attestation: build_attestation)
end
NotificationMailer.send_closed_notification(self).deliver_later
log_dossier_operation(nil, :accepter, automatic_operation: true)
end
def refuser!(gestionnaire, motivation)
self.motivation = motivation
self.en_instruction_at ||= Time.zone.now
@ -317,10 +336,11 @@ class Dossier < ApplicationRecord
private
def log_dossier_operation(gestionnaire, operation)
def log_dossier_operation(gestionnaire, operation, automatic_operation: false)
dossier_operation_logs.create(
gestionnaire: gestionnaire,
operation: DossierOperationLog.operations.fetch(operation)
operation: DossierOperationLog.operations.fetch(operation),
automatic_operation: automatic_operation
)
end

View file

@ -1,6 +1,8 @@
class DropDownList < ApplicationRecord
belongs_to :type_de_champ
before_validation :clean_value
def options
result = value.split(/[\r\n]|[\r]|[\n]|[\n\r]/).reject(&:empty?)
result.blank? ? [] : [''] + result
@ -13,4 +15,12 @@ class DropDownList < ApplicationRecord
def multiple
type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:multiple_drop_down_list)
end
private
def clean_value
value = read_attribute(:value)
value = value ? value.split("\r\n").map(&:strip).join("\r\n") : ''
write_attribute(:value, value)
end
end

View file

@ -30,9 +30,9 @@ class Procedure < ApplicationRecord
has_one_attached :notice
has_one_attached :deliberation
accepts_nested_attributes_for :types_de_champ, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true
accepts_nested_attributes_for :types_de_piece_justificative, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true
accepts_nested_attributes_for :types_de_champ_private
accepts_nested_attributes_for :types_de_champ, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
accepts_nested_attributes_for :types_de_champ_private, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
accepts_nested_attributes_for :types_de_piece_justificative, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
mount_uploader :logo, ProcedureLogoUploader
@ -68,6 +68,7 @@ class Procedure < ApplicationRecord
before_save :update_juridique_required
before_save :update_durees_conservation_required
before_create :ensure_path_exists
include AASM
@ -350,12 +351,6 @@ class Procedure < ApplicationRecord
where.not(aasm_state: :archivee).where("path LIKE ?", "%#{path}%")
end
def gestionnaire_for_cron_job
administrateur_email = administrateur.email
gestionnaire = Gestionnaire.find_by(email: administrateur_email)
gestionnaire || gestionnaires.first
end
def populate_champ_stable_ids
TypeDeChamp.where(procedure: self, stable_id: nil).find_each do |type_de_champ|
type_de_champ.update_column(:stable_id, type_de_champ.id)
@ -458,4 +453,12 @@ class Procedure < ApplicationRecord
times.percentile(p).ceil
end
end
def ensure_path_exists
if Flipflop.publish_draft?
if self.path.nil?
self.path = SecureRandom.uuid
end
end
end
end

View file

@ -83,7 +83,7 @@ class TypeDeChamp < ApplicationRecord
has_one_attached :piece_justificative_template
accepts_nested_attributes_for :drop_down_list
accepts_nested_attributes_for :drop_down_list, update_only: true
accepts_nested_attributes_for :types_de_champ, allow_destroy: true
validates :libelle, presence: true, allow_blank: false, allow_nil: false
@ -161,6 +161,18 @@ class TypeDeChamp < ApplicationRecord
"TypesDeChamp::#{type_champ.classify}TypeDeChamp"
end
def piece_justificative_template_url
if piece_justificative_template.attached?
Rails.application.routes.url_helpers.url_for(piece_justificative_template)
end
end
def piece_justificative_template_filename
if piece_justificative_template.attached?
piece_justificative_template.filename
end
end
private
def setup_procedure

View file

@ -1,5 +0,0 @@
class NumberService
def self.to_number(string)
string.to_s
end
end

View file

@ -1,9 +1,10 @@
- champs_editor_path = Flipflop.new_champs_editor? ? champs_procedure_path(@procedure) : admin_procedure_types_de_champ_path(@procedure)
.row.white-back
.alert.alert-info
.form-group
%p
Pour vos nouveaux besoins de pièces jointes, nous vous invitons à
= link_to(admin_procedure_types_de_champ_path(@procedure)) do
= link_to(champs_editor_path) do
rajouter des champs
\ <em>pièce justificative</em> à votre formulaire.
@ -17,7 +18,7 @@
%li Support des pièces de grande taille (jusquà 200 Mo par pièce)
%li Pas de limite de soumission simultanée de plusieurs pièces, pour une expérience usager plus confortable
= link_to(admin_procedure_types_de_champ_path(@procedure), class: 'btn btn-success') do
= link_to(champs_editor_path, class: 'btn btn-success') do
Ajouter un champ PJ
- if @procedure.has_old_pjs?

View file

@ -11,20 +11,6 @@
= f.text_area :description, rows: '6', placeholder: 'Description du projet', class: 'form-control'
- if !@procedure.locked?
- if Flipflop.publish_draft?
.form-group
%h4 Lien public*
.procedure-lien
%span.prefix
= commencer_test_url(path: '')
= f.text_field :path, value: @path, class: 'form-control', data: { remote: true, debounce: true, url: admin_procedures_available_path, params: { id: @procedure.id }.to_query(:procedure) }
%p.unavailable-path-message.text-warning
- if @availability != Procedure::PATH_AVAILABLE
= render partial: 'unavailable', locals: { availability: @availability }
%p.help-block
%i.fa.fa-info-circle
Afin de faciliter laccès à la démarche, vous êtes invité à personnaliser ladresse d'accès public. C'est ce lien qu'il va falloir communiquer aux usagers souhaitant faire la démarche.
.form-group
%h4 Conservation des données
= f.label :duree_conservation_dossiers_dans_ds, "Sur demarches-simplifiees.fr* (durée en mois après le début de linstruction)"

View file

@ -3,7 +3,10 @@
%li{ class: @draft_class }
%a{ :href => "#{url_for :admin_procedures_draft}" }
%h5.text-primary
Brouillons
- if Flipflop.publish_draft?
En test
- else
Brouillons
%li{ class: @active_class }
%a{ :href => "#{url_for :admin_procedures}" }

View file

@ -4,7 +4,7 @@
%br
Si vous voulez lutiliser, lancienne démarche sera archivée lors de la publication de la démarche (plus accessible du public).
- when Procedure::PATH_NOT_AVAILABLE_BROUILLON
Un brouillon de démarche existe déjà avec ce lien.
Une démarche en test existe déjà avec ce lien.
- when Procedure::PATH_NOT_AVAILABLE
Ce lien est déjà utilisé par une démarche.
%br

View file

@ -42,25 +42,45 @@
%i.fa.fa-remove
Annuler
- if @procedure.locked?
#procedure_locked
.alert.alert-info
Cette démarche a été publiée, certains éléments ne peuvent plus être modifiés.
.lien-demarche
%h3 Lien démarche
%h3
- if @procedure.brouillon_avec_lien?
Test et publication
- else
Publication
%div{ style: 'margin-top: 30px;' }
- if @procedure.archivee?
.alert.alert-info
Cette démarche est archivée et nest donc pas accessible par le public.
Cette démarche est <strong>archivée</strong> et nest donc plus accessible par le public.
- elsif @procedure.publiee?
Cette démarche est <strong>publiée</strong>, certains éléments ne peuvent plus être modifiés.
Pour y accéder vous pouvez utiliser le lien :
= link_to procedure_lien(@procedure), sanitize_url(procedure_lien(@procedure)), target: :blank
%br
%br
Attention, diffusez toujours le <strong>lien complet</strong> affiché ci-dessus, et non pas un lien générique vers demarches-simplifiees.fr. Ne dites pas non plus aux usagers de se rendre sur le site générique demarches-simplifiees.fr, donnez-leur toujours le lien complet.
- elsif @procedure.brouillon_avec_lien?
- if @procedure.gestionnaires.present? && @procedure.service.present?
= link_to procedure_lien(@procedure), sanitize_url(procedure_lien(@procedure)), target: :blank
%p
Cette démarche est actuellement <strong>en test</strong>,
pour y accéder vous pouvez utiliser le lien :
= link_to procedure_lien(@procedure), sanitize_url(procedure_lien(@procedure)), target: :blank
%p
Tout personne ayant la connaissance de ce lien pourra ainsi remplir des dossiers sur votre démarche.
%br
%h4 Ce que vous pouvez faire lorsque vous êtes en test
%p
Profitez de la phase de test pour tester la saisie de dossiers, ainsi que toutes les fonctionnalités associées (instruction, emails automatiques, attestations, etc.).
%p
Vous pouvez effectuer toutes les modifications que vous souhaitez sur votre démarche pendant cette phase de test.
%p
Les dossiers qui seront remplis pendant la phase de test seront automatiquement supprimés lors de la modification ou la publication de votre démarche.
%br
%h4 Ce qui se passe lorsque vous passez en publication
%p
Une fois que vous êtes prêt à publier définitivement votre démarche, cliquez sur le bouton "Publier" pour choisir le lien définitif de votre démarche, les modifications sur la démarches ne seront alors plus possibles.
- else
.alert.alert-info
Pour pouvoir tester cette démarche, vous devez dabord lui affecter

View file

@ -3,11 +3,12 @@
#flash_message.center
- flash.each do |key, value|
- sticky = defined?(sticky) ? sticky : false
- fixed = defined?(fixed) ? fixed : false
- if value.class == Array
.alert{ class: flash_class(key, sticky) }
.alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
- value.each do |message|
= sanitize(message)
%br
- else
.alert{ class: flash_class(key, sticky) }
.alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
= sanitize(value)

View file

@ -7,12 +7,13 @@
})();
sendinblue.identify('#{current_administrateur.email}', {
'nb_demarches_brouillons': '#{current_administrateur.procedures.brouillons.count}',
'nb_demarches_actives': '#{current_administrateur.procedures.publiees.count}',
'nb_demarches_archivees': '#{current_administrateur.procedures.archivees.count}',
'sign_in_count' : '#{current_administrateur.sign_in_count}',
'created_at' : '#{current_administrateur.created_at}',
'active' : '#{current_administrateur.active}'
'DS_NB_DEMARCHES_BROUILLONS': '#{current_administrateur.procedures.brouillons.count}',
'DS_NB_DEMARCHES_ACTIVES': '#{current_administrateur.procedures.publiees.count}',
'DS_NB_DEMARCHES_ARCHIVES': '#{current_administrateur.procedures.archivees.count}',
'DS_SIGN_IN_COUNT' : '#{current_administrateur.sign_in_count}',
'DS_CREATED_AT' : '#{current_administrateur.created_at}',
'DS_ACTIVE' : '#{current_administrateur.active}',
'DS_ID' : '#{current_administrateur.id}'
// Dans l'ideal :
// 'nom' : //pour personnaliser les emails
// 'prenom' : //pour personnaliser les emails

View file

@ -0,0 +1,8 @@
.no-procedure
= image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo"
.baseline.center
%h3 Un outil simple
%p
pour gérer les formulaires
%br
administratifs dématérialisés.

View file

@ -0,0 +1,8 @@
.procedure-logos
= image_tag logo_img(procedure)
- if procedure.euro_flag
= image_tag "flag_of_europe.svg"
%h2.procedure-title
= procedure.libelle
.procedure-description
= h string_to_html(procedure.description)

View file

@ -11,13 +11,16 @@
#procedure-list
%a#draft-procedures{ :href => "#{url_for :admin_procedures_draft}" }
.procedure-list-element{ class: @draft_class }
Brouillons
- if Flipflop.publish_draft?
En test
- else
Brouillons
.badge.progress-bar-default
= current_administrateur.procedures.brouillons.count
%a#active-procedures{ :href => "#{url_for :admin_procedures}" }
.procedure-list-element{ class: @active_class }
Actives
Publiées
.badge.progress-bar-success
= current_administrateur.procedures.publiees.count

View file

@ -12,7 +12,10 @@
#procedure-list
%a#onglet-infos{ href: url_for(admin_procedure_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Informations') }
Publication
- if @procedure.brouillon_avec_lien?
Test et publication
- else
Publication
%a#onglet-description{ href: url_for(edit_admin_procedure_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Description') }
@ -31,7 +34,8 @@
%p.missing-steps (à compléter)
- if !@procedure.locked?
%a#onglet-champs{ href: url_for(admin_procedure_types_de_champ_path(@procedure)) }
- champs_editor_path = Flipflop.new_champs_editor? ? champs_procedure_path(@procedure) : admin_procedure_types_de_champ_path(@procedure)
%a#onglet-champs{ href: champs_editor_path }
.procedure-list-element{ class: ('active' if active == 'Champs') }
Champs
@ -41,7 +45,8 @@
Pièces jointes
- if !@procedure.locked?
%a#onglet-private-champs{ href: url_for(admin_procedure_types_de_champ_private_path(@procedure)) }
- annotations_editor_path = Flipflop.new_champs_editor? ? annotations_procedure_path(@procedure) : admin_procedure_types_de_champ_private_path(@procedure)
%a#onglet-private-champs{ href: annotations_editor_path }
.procedure-list-element{ class: ('active' if active == 'Annotations privées') }
Annotations privées

View file

@ -1,22 +1,22 @@
- procedure = @procedure || @dossier&.procedure || nil
- content_for :content do
.two-columns.procedure-context
.columns-container
.column.procedure-preview
- procedure = @dossier.procedure
.procedure-logos
= image_tag logo_img(procedure)
- if procedure.euro_flag
= image_tag "flag_of_europe.svg"
%h2.procedure-title
= procedure.libelle
.procedure-description
= h string_to_html(procedure.description)
- if procedure
= render partial: 'layouts/commencer/procedure_description', locals: { procedure: procedure }
- else
= render partial: 'layouts/commencer/no_procedure'
.column.procedure-context-content
= yield
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
- if procedure
= render partial: 'new_user/procedure_footer', locals: { procedure: procedure, dossier: @dossier }
- else
= render partial: 'new_user/dossiers/index_footer'
= render template: 'layouts/application'

View file

@ -38,7 +38,7 @@ as well as a link to its edit page.
<% end %>
<% if procedure.publiee? && procedure.dossiers.empty? %>
<%= link_to 'passer en brouillon', draft_manager_procedure_path(procedure), method: :post, class: 'button' %>
<%= link_to 'repasser en test', draft_manager_procedure_path(procedure), method: :post, class: 'button' %>
<% end %>
<%= link_to 'supprimer la démarche', hide_manager_procedure_path(procedure), method: :post, class: 'button', data: { confirm: "Confirmez-vous la suppression de la démarche ?" } %>

View file

@ -1,5 +1,7 @@
.sub-header
.container
.container.flex.justify-between.align-baseline
%ul.breadcrumbs
- steps.each do |step|
%li= step
- if defined?(preview) && preview
= link_to "Prévisualiser le formulaire", apercu_procedure_path(@procedure), target: "_blank", class: 'button'

View file

@ -0,0 +1,12 @@
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to('Démarches', admin_procedures_path),
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
'Configuration des annotations privées'], preview: true }
.container
%h1 Configuration des annotations privées
%br
= form_for @procedure, remote: true, html: { class: 'form' } do |form|
#champs-editor{ data: types_de_champ_private_data(@procedure) }
.spinner

View file

@ -0,0 +1,12 @@
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to('Démarches', admin_procedures_path),
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
'Configuration des champs'], preview: true }
.container
%h1 Configuration des champs
%br
= form_for @procedure, remote: true, html: { class: 'form' } do |form|
#champs-editor{ data: types_de_champ_data(@procedure) }
.spinner

View file

@ -0,0 +1,3 @@
<%= render_flash timeout: 6000, fixed: true %>
<%= fire_event(:ProcedureUpdated, procedure_data(@procedure)) %>

View file

@ -3,7 +3,10 @@
.flex.justify-between
%ul.breadcrumbs
%li
= link_to dossier.procedure.libelle.truncate_words(10), gestionnaire_procedure_path(dossier.procedure), title: dossier.procedure.libelle
= link_to gestionnaire_procedure_path(dossier.procedure), title: dossier.procedure.libelle do
- if dossier.procedure.brouillon?
%span.badge démarche en test
= dossier.procedure.libelle.truncate_words(10)
%li
= "Dossier nº #{dossier.id}"
.mixed-buttons-bar

View file

@ -1,6 +1,6 @@
%footer.dossier-footer
%footer.procedure-footer
.container
- service = dossier.procedure.service
- service = procedure.service
- if service.present?
%ul.footer-row.footer-columns
%li.footer-column
@ -15,12 +15,12 @@
%li.footer-column
%h3.footer-header Poser une question sur votre dossier :
%p
- if dossier.brouillon?
Par email :
= link_to service.email, "mailto:#{service.email}"
- else
- if dossier.present? && !dossier.brouillon?
Directement
= link_to "par la messagerie", messagerie_dossier_path(dossier)
- else
Par email :
= link_to service.email, "mailto:#{service.email}"
%p
Par téléphone :
@ -29,7 +29,7 @@
%p
Horaires : #{ service.horaires.sub(/\S/, &:downcase) }
- politiques = politiques_conservation_de_donnees(dossier.procedure)
- politiques = politiques_conservation_de_donnees(procedure)
- if politiques.present?
%li.footer-column
%h3.footer-header Conservation des données :
@ -37,4 +37,4 @@
%p= politique
.footer-row.footer-bottom-line
= render partial: "new_user/dossiers/general_footer_row", locals: { dossier: @dossier }
= render partial: 'new_user/general_footer_row', locals: { dossier: dossier }

View file

@ -1,4 +1,4 @@
%footer.dossier-footer
%footer.procedure-footer
.container
.footer-row.footer-bottom-line
= render partial: "new_user/dossiers/general_footer_row", locals: { dossier: nil }
= render partial: "new_user/general_footer_row", locals: { dossier: nil }

View file

@ -1,7 +1,7 @@
- content_for(:title, "Modification du brouillon nº #{@dossier.id} (#{@dossier.procedure.libelle})")
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
= render partial: "new_user/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
.dossier-header.sub-header
.container

View file

@ -1,7 +1,7 @@
- content_for(:title, "Demande · Dossier nº #{@dossier.id} (#{@dossier.procedure.libelle})")
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
= render partial: "new_user/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
#dossier-show
= render partial: 'new_user/dossiers/show/header', locals: { dossier: @dossier }

View file

@ -1,7 +1,7 @@
- content_for(:title, "Informations sur létablissement")
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
= render partial: "new_user/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
.etablissement
.container

View file

@ -1,7 +1,7 @@
- content_for(:title, "Dossier bien envoyé (#{@dossier.procedure.libelle})")
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
= render partial: "new_user/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
.merci
.container

View file

@ -1,7 +1,7 @@
- content_for(:title, "Messagerie · Dossier nº #{@dossier.id} (#{@dossier.procedure.libelle})")
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
= render partial: "new_user/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
#dossier-show
= render partial: 'new_user/dossiers/show/header', locals: { dossier: @dossier }

View file

@ -1,7 +1,7 @@
- content_for(:title, "Modifier · Dossier nº #{@dossier.id} (#{@dossier.procedure.libelle})")
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
= render partial: "new_user/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
#dossier-show
= render partial: 'new_user/dossiers/show/header', locals: { dossier: @dossier }

View file

@ -1,7 +1,7 @@
- content_for(:title, "Résumé · Dossier nº #{@dossier.id} (#{@dossier.procedure.libelle})")
- content_for :footer do
= render partial: "new_user/dossiers/dossier_footer", locals: { dossier: @dossier }
= render partial: "new_user/procedure_footer", locals: { procedure: @dossier.procedure, dossier: @dossier }
#dossier-show
= render partial: 'new_user/dossiers/show/header', locals: { dossier: @dossier }

View file

@ -1,33 +1,22 @@
.two-columns.auth
.columns-container
.column.procedure-preview
= image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo"
.baseline.center
%h3 Un outil simple
%p
pour gérer les formulaires
%br
administratifs dématérialisés.
.auth-form
= devise_error_messages!
= form_for resource, url: user_registration_path, html: { class: "form" } do |f|
%h1 Créez-vous un compte demarches-simplifiees.fr
.column.procedure-context-content.auth-form
= devise_error_messages!
= form_for resource, url: user_registration_path, html: { class: "form" } do |f|
%h1 Créez-vous un compte
= f.label :email, "Email"
= f.text_field :email, autofocus: true, placeholder: "Votre adresse email"
= f.label :email, "Email"
= f.text_field :email, autofocus: true
= f.label :password, "Mot de passe"
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum"
= f.label :password, "Mot de passe"
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum"
= f.submit "Créer un compte", class: "button large primary expand"
= f.submit "Créer un compte", class: "button large primary expand"
.separation.center
ou
.separation.center
ou
.center
= image_tag "login-with-fc-hover.svg", style: "display: none"
= link_to "", france_connect_particulier_path, class: "login-with-fc"
.center
= image_tag "login-with-fc-hover.svg", style: "display: none"
= link_to "", france_connect_particulier_path, class: "login-with-fc"
.center
= link_to "Quest-ce que FranceConnect ?", "https://franceconnect.gouv.fr/", target: "_blank", class: "link"
.center
= link_to "Quest-ce que FranceConnect ?", "https://franceconnect.gouv.fr/", target: "_blank", class: "link"

View file

@ -1,63 +1,39 @@
.two-columns.auth
.columns-container
.column.procedure-preview
- if !@dossier
= image_tag "landing/hero/dematerialiser.svg", class: "paperless-logo"
.baseline.center
%h3 Un outil simple
%p
pour gérer les formulaires
%br
administratifs dématérialisés.
- else
.auth-form.sign-in-form
- if resource_name == :user
%p.register
%span
Nouveau sur demarches-simplifiees.fr ?
= link_to "Créer un compte", new_registration_path(resource_name), class: "button primary auth-signup-button"
%hr
= form_for @user, url: user_session_path, html: { class: "form" } do |f|
%h1 Connectez-vous
= f.label :email, "Email"
= f.text_field :email, autofocus: true
= f.label :password, "Mot de passe"
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum"
.auth-options
- if devise_mapping.rememberable?
%div
= f.check_box :remember_me, as: :boolean
= f.label :remember_me, "Se souvenir de moi", class: 'remember-me'
- if [:user, :gestionnaire].include?(resource_name)
.text-right
= link_to "Fermer", users_no_procedure_url, class: "link close-procedure"
.procedure-logos
= image_tag logo_img(@dossier.procedure)
- if @dossier.procedure.euro_flag
= image_tag "flag_of_europe.svg"
= link_to "Mot de passe oublié ?", new_password_path(resource_name), class: "link"
%h2.procedure-title
= @dossier.procedure.libelle
.procedure-description
= h simple_format(@dossier.procedure.description)
= f.submit "Se connecter", class: "button large primary expand"
.column.procedure-context-content.auth-form.sign-in-form
- if resource_name == :user
%p.register
%span
Nouveau sur demarches-simplifiees.fr ?
= link_to "Créer un compte", new_registration_path(resource_name), class: "button primary auth-signup-button"
.separation.center
ou
%hr
.center
= image_tag "login-with-fc-hover.svg", style: "display: none"
= link_to "", france_connect_particulier_path, class: "login-with-fc"
= form_for @user, url: user_session_path, html: { class: "form" } do |f|
%h1 Connectez-vous
= f.label :email, "Email"
= f.text_field :email, autofocus: true
= f.label :password, "Mot de passe"
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum"
.auth-options
- if devise_mapping.rememberable?
%div
= f.check_box :remember_me, as: :boolean
= f.label :remember_me, "Se souvenir de moi", class: 'remember-me'
- if [:user, :gestionnaire].include?(resource_name)
.text-right
= link_to "Mot de passe oublié ?", new_password_path(resource_name), class: "link"
= f.submit "Se connecter", class: "button large primary expand"
.separation.center
ou
.center
= image_tag "login-with-fc-hover.svg", style: "display: none"
= link_to "", france_connect_particulier_path, class: "login-with-fc"
.center
= link_to "Quest-ce que FranceConnect ?", "https://franceconnect.gouv.fr/", target: "_blank", class: "link"
.center
= link_to "Quest-ce que FranceConnect ?", "https://franceconnect.gouv.fr/", target: "_blank", class: "link"

View file

@ -19,6 +19,7 @@ Flipflop.configure do
feature :publish_draft
feature :support_form
feature :enable_email_login_token
feature :new_champs_editor
group :production do
feature :remote_storage,

View file

@ -357,9 +357,11 @@ Rails.application.routes.draw do
#
scope module: 'new_administrateur' do
resources :procedures, only: [] do
resources :procedures, only: [:update] do
member do
get 'apercu'
get 'champs'
get 'annotations'
end
end

View file

@ -1,5 +1,7 @@
const path = require('path');
const { environment } = require('@rails/webpacker');
const { VueLoaderPlugin } = require('vue-loader');
const vue = require('./loaders/vue');
const resolve = {
alias: {
@ -9,4 +11,6 @@ const resolve = {
environment.config.merge({ resolve });
environment.plugins.append('VueLoaderPlugin', new VueLoaderPlugin());
environment.loaders.append('vue', vue);
module.exports = environment;

View file

@ -0,0 +1,8 @@
module.exports = {
test: /\.vue(\.erb)?$/,
use: [
{
loader: 'vue-loader'
}
]
};

View file

@ -14,6 +14,7 @@ default: &default
cache_manifest: false
extensions:
- .vue
- .js
- .sass
- .scss

View file

@ -0,0 +1,5 @@
class AddAutomaticOperationColumnToDossierOperationLog < ActiveRecord::Migration[5.2]
def change
add_column :dossier_operation_logs, :automatic_operation, :bool, default: false, null: false
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: 2018_12_21_103901) do
ActiveRecord::Schema.define(version: 2019_01_10_163655) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -218,6 +218,7 @@ ActiveRecord::Schema.define(version: 2018_12_21_103901) do
t.bigint "gestionnaire_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "automatic_operation", default: false, null: false
t.index ["dossier_id"], name: "index_dossier_operation_logs_on_dossier_id"
t.index ["gestionnaire_id"], name: "index_dossier_operation_logs_on_gestionnaire_id"
end

View file

@ -15,7 +15,11 @@
"rails-ujs": "^5.2.1",
"ramda": "^0.25.0",
"select2": "^4.0.6-rc.1",
"turbolinks": "^5.2.0"
"turbolinks": "^5.2.0",
"vue": "^2.5.21",
"vue-loader": "^15.5.1",
"vue-template-compiler": "^2.5.21",
"vuedraggable": "^2.16.0"
},
"devDependencies": {
"eclint": "^2.8.0",

View file

@ -182,6 +182,7 @@ describe Admin::ProceduresController, type: :controller do
context 'when procedure is correctly save' do
before do
Flipflop::FeatureSet.current.test!.switch!(:new_champs_editor, true)
post :create, params: { procedure: procedure_params }
end
@ -197,8 +198,7 @@ describe Admin::ProceduresController, type: :controller do
it { expect(subject.duree_conservation_dossiers_hors_ds).to eq(duree_conservation_dossiers_hors_ds) }
end
it { is_expected.to redirect_to(admin_procedure_types_de_champ_path(procedure_id: Procedure.last.id)) }
it { is_expected.to redirect_to(champs_procedure_path(Procedure.last)) }
it { expect(flash[:notice]).to be_present }
end
@ -723,7 +723,7 @@ describe Admin::ProceduresController, type: :controller do
let(:path) { procedure_owned.path }
it {
expect(response.body).to include('Un brouillon de démarche existe déjà avec ce lien.')
expect(response.body).to include('Une démarche en test existe déjà avec ce lien.')
}
end

View file

@ -0,0 +1,77 @@
require 'rails_helper'
RSpec.describe ProcedureContextConcern, type: :controller do
class TestController < ActionController::Base
include ProcedureContextConcern
before_action :restore_procedure_context
def index
head :ok
end
end
controller TestController do
end
describe '#restore_procedure_context' do
subject { get :index }
context 'when no return location has been stored' do
it 'succeeds, without defining a procedure on the controller' do
expect(subject.status).to eq 200
expect(assigns(:procedure)).to be nil
end
end
context 'when no procedure_id is present in the stored return location' do
before do
controller.store_location_for(:user, dossiers_path)
end
it 'succeeds, without assigns a procedure on the controller' do
expect(subject.status).to eq 200
expect(assigns(:procedure)).to be nil
end
end
context 'when a procedure location has been stored' do
context 'when the stored procedure does not exist' do
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: '0'))
end
it 'redirects with an error' do
expect(subject.status).to eq 302
expect(subject).to redirect_to root_path
end
end
context 'when the stored procedure is not published' do
let(:procedure) { create :procedure }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
end
it 'redirects with an error' do
expect(subject.status).to eq 302
expect(subject).to redirect_to root_path
end
end
context 'when the stored procedure exists' do
let(:procedure) { create :procedure, :published }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
end
it 'succeeds, and assigns the procedure on the controller' do
expect(subject.status).to eq 200
expect(assigns(:procedure)).to eq procedure
end
end
end
end
end

View file

@ -0,0 +1,41 @@
require 'rails_helper'
RSpec.describe Devise::StoreLocationExtension, type: :controller do
class TestController < ActionController::Base
include Devise::Controllers::StoreLocation
include Devise::StoreLocationExtension
end
controller TestController do
end
describe '#get_stored_location_for' do
context 'when a location has been previously stored' do
before { subject.store_location_for(:user, dossiers_path) }
it 'returns the stored location without clearing it' do
expect(subject.get_stored_location_for(:user)).to eq dossiers_path
expect(subject.stored_location_for(:user)).to eq dossiers_path
end
end
context 'when no location has been stored' do
it { expect(subject.get_stored_location_for(:user)).to be nil }
end
end
describe "#clear_stored_location_for" do
context 'when a location has been previously stored' do
before { subject.store_location_for(:user, dossiers_path) }
it 'delete the stored location' do
subject.clear_stored_location_for(:user)
expect(subject.stored_location_for(:user)).to be nil
end
end
context 'when no location has been stored' do
it { expect(subject.clear_stored_location_for(:user)).to be nil }
end
end
end

View file

@ -171,7 +171,10 @@ describe InvitesController, type: :controller do
let(:email) { nil }
context 'and user is not connected' do
it { is_expected.to redirect_to new_user_session_path }
it 'redirects to the sign-in page' do
expect(subject).to redirect_to new_user_session_path
expect(controller.stored_location_for(:user)).to be_present
end
end
context 'and user is connected' do
@ -186,20 +189,29 @@ describe InvitesController, type: :controller do
context 'when email is blank' do
let(:email) { '' }
it { is_expected.to redirect_to new_user_session_path }
it 'redirects to the sign-in page' do
expect(subject).to redirect_to new_user_session_path
expect(controller.stored_location_for(:user)).to be_present
end
end
context 'when email is not blank' do
context 'when email is affected at an user' do
let(:email) { user.email }
it { is_expected.to redirect_to new_user_session_path }
it 'redirects to the sign-in page' do
expect(subject).to redirect_to new_user_session_path
expect(controller.stored_location_for(:user)).to be_present
end
end
context 'when email is not affected at an user' do
let(:email) { 'new_user@octo.com' }
it { is_expected.to redirect_to new_user_registration_path(user: { email: email }) }
it 'redirects to the sign-up page' do
expect(subject).to redirect_to new_user_registration_path(user: { email: email })
expect(controller.stored_location_for(:user)).to be_present
end
end
end
end
@ -213,6 +225,10 @@ describe InvitesController, type: :controller do
subject! { get :show, params: { id: invite.id } }
it 'clears the stored return location' do
expect(controller.stored_location_for(:user)).to be nil
end
context 'when invitation ID is attached at the user email account' do
let(:email) { user.email }

View file

@ -854,6 +854,11 @@ describe NewUser::DossiersController, type: :controller do
subject { get :new, params: { procedure_id: procedure_id } }
it 'clears the stored procedure context' do
subject
expect(controller.stored_location_for(:user)).to be nil
end
context 'when params procedure_id is present' do
context 'when procedure_id is valid' do
context 'when user is logged in' do

View file

@ -9,17 +9,30 @@ describe Users::RegistrationsController, type: :controller do
end
describe '#new' do
subject! { get :new }
subject { get :new }
it { expect(response).to have_http_status(:ok) }
it { expect(response).to render_template(:new) }
it { expect(subject).to have_http_status(:ok) }
it { expect(subject).to render_template(:new) }
context 'when an email address is provided' do
render_views true
subject! { get :new, params: { user: { email: 'test@exemple.fr' } } }
subject { get :new, params: { user: { email: 'test@exemple.fr' } } }
it 'prefills the form with the email address' do
expect(response.body).to include('test@exemple.fr')
expect(subject.body).to include('test@exemple.fr')
end
end
context 'when a procedure location has been stored' do
let(:procedure) { create :procedure, :published }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
end
it 'makes the saved procedure available' do
expect(subject.status).to eq 200
expect(assigns(:procedure)).to eq procedure
end
end
end

View file

@ -173,38 +173,18 @@ describe Users::SessionsController, type: :controller do
describe '#new' do
subject { get :new }
context 'when procedure_id is not present in user_return_to session params' do
it { expect(subject.status).to eq 200 }
end
it { expect(subject.status).to eq 200 }
context 'when procedure_id is present in user_return_to session params' do
context 'when procedure_id does not exist' do
before do
session["user_return_to"] = '?procedure_id=0'
end
context 'when a procedure location has been stored' do
let(:procedure) { create :procedure, :published }
it { expect(subject.status).to eq 302 }
it { expect(subject).to redirect_to root_path }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
end
context 'when procedure is not published' do
let(:procedure) { create :procedure }
before do
session["user_return_to"] = "?procedure_id=#{procedure.id}"
end
it { expect(subject.status).to eq 302 }
it { expect(subject).to redirect_to root_path }
end
context 'when procedure_id exist' do
let(:procedure) { create :procedure, :published }
before do
session["user_return_to"] = "?procedure_id=#{procedure.id}"
end
it { expect(subject.status).to eq 200 }
it 'makes the saved procedure available' do
expect(subject.status).to eq 200
expect(assigns(:procedure)).to eq procedure
end
end
end

View file

@ -5,9 +5,11 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
include ProcedureSpecHelper
let(:administrateur) { create(:administrateur, :with_procedure) }
let(:test_strategy) { Flipflop::FeatureSet.current.test! }
before do
Flipflop::FeatureSet.current.test!.switch!(:publish_draft, true)
test_strategy.switch!(:publish_draft, true)
test_strategy.switch!(:new_champs_editor, true)
login_as administrateur, scope: :administrateur
visit root_path
end
@ -45,13 +47,13 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
fill_in_dummy_procedure_details
click_on 'save-procedure'
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
end
end
context "when publish_draft disabled" do
before do
Flipflop::FeatureSet.current.test!.switch!(:publish_draft, false)
test_strategy.switch!(:publish_draft, false)
end
scenario 'Finding save button for new procedure, libelle, description and cadre_juridique required' do
@ -68,7 +70,7 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
fill_in_dummy_procedure_details(fill_path: false)
click_on 'save-procedure'
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
end
end
end
@ -95,12 +97,22 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
end
scenario 'Add champ, add file, visualize them in procedure preview' do
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
click_on 'add_type_de_champ'
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
expect(Procedure.last.types_de_champ.first.libelle).to eq('libelle de champ')
page.refresh
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
expect(page).to have_content('Champs enregistrés')
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
click_on Procedure.last.libelle
click_on 'onglet-pieces'
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
fill_in 'procedure_types_de_piece_justificative_attributes_0_libelle', with: 'libelle de piece'
@ -117,8 +129,15 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
end
scenario 'After adding champ and file, make publication' do
page.refresh
within '.footer' do
click_on 'Ajouter un champ'
end
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
click_on 'add_type_de_champ'
expect(page).to have_content('Champs enregistrés')
click_on Procedure.last.libelle
click_on 'onglet-pieces'
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
@ -131,7 +150,7 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
find('#publish-procedure').click
within '#publish-modal' do
expect(page).to have_field('procedure_path', with: 'lien-de-la-procedure')
expect(page).to have_field('procedure_path')
click_on 'publish'
end

View file

@ -2,9 +2,6 @@ module ProcedureSpecHelper
def fill_in_dummy_procedure_details(fill_path: true)
fill_in 'procedure_libelle', with: 'libelle de la procedure'
fill_in 'procedure_description', with: 'description de la procedure'
if fill_path
fill_in 'procedure_path', with: 'lien-de-la-procedure'
end
fill_in 'procedure_cadre_juridique', with: 'cadre juridique'
fill_in 'procedure_duree_conservation_dossiers_dans_ds', with: '3'
fill_in 'procedure_duree_conservation_dossiers_hors_ds', with: '6'

View file

@ -26,21 +26,18 @@ feature 'Administrateurs can edit procedures', js: true do
administrateur: administrateur)
end
scenario 'the administrator can edit the libelle and the path' do
scenario 'the administrator can edit the libelle' do
visit admin_procedures_draft_path
click_on procedure.libelle
click_on 'Description'
expect(page).to have_field('procedure_libelle', with: procedure.libelle)
expect(page).to have_field('procedure_path', with: procedure.path)
fill_in('procedure_libelle', with: 'Ma petite démarche')
fill_in('procedure_path', with: 'nouveau-lien-demarche')
click_on 'Enregistrer'
expect(page).to have_field('procedure_libelle', with: 'Ma petite démarche')
expect(page).to have_field('procedure_path', with: 'nouveau-lien-demarche')
end
end

View file

@ -0,0 +1,95 @@
require 'spec_helper'
feature 'As an administrateur I edit procedure', js: true do
let(:administrateur) { procedure.administrateur }
let(:procedure) { create(:procedure) }
before do
login_as administrateur, scope: :administrateur
visit champs_procedure_path(procedure)
end
it "Add a new champ" do
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
expect(page).to have_content('Champs enregistrés')
page.refresh
within '.footer' do
click_on 'Enregistrer'
end
expect(page).to have_content('Champs enregistrés')
end
it "Add multiple champs" do
within '.footer' do
click_on 'Ajouter un champ'
click_on 'Ajouter un champ'
click_on 'Ajouter un champ'
click_on 'Ajouter un champ'
end
expect(page).not_to have_content('Le libellé doit être rempli.')
expect(page).not_to have_content('Modifications non sauvegardées.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ 0'
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
expect(page).to have_selector('#procedure_types_de_champ_attributes_2_libelle')
expect(page).to have_selector('#procedure_types_de_champ_attributes_3_libelle')
expect(page).to have_content('Le libellé doit être rempli.')
expect(page).to have_content('Modifications non sauvegardées.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_2_libelle', with: 'libellé de champ 2'
within '.draggable-item-3' do
click_on 'Supprimer'
end
expect(page).to have_content('Le libellé doit être rempli.')
expect(page).to have_content('Modifications non sauvegardées.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_1_libelle', with: 'libellé de champ 1'
expect(page).not_to have_content('Le libellé doit être rempli.')
expect(page).not_to have_content('Modifications non sauvegardées.')
expect(page).to have_content('Champs enregistrés')
page.refresh
expect(page).to have_content('Supprimer', count: 3)
end
it "Remove champs" do
within '.footer' do
click_on 'Ajouter un champ'
end
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
expect(page).to have_content('Champs enregistrés')
page.refresh
click_on 'Supprimer'
expect(page).to have_content('Champs enregistrés')
expect(page).not_to have_content('Supprimer')
page.refresh
expect(page).not_to have_content('Supprimer')
end
it "Only add valid champs" do
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_description')
fill_in 'procedure_types_de_champ_attributes_0_description', with: 'déscription du champ'
expect(page).to have_content('Le libellé doit être rempli.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
expect(page).to have_content('Champs enregistrés')
end
end

View file

@ -34,7 +34,7 @@ feature 'Signing up:' do
end
context 'when visiting a procedure' do
let(:procedure) { create :simple_procedure }
let(:procedure) { create :simple_procedure, :with_service }
before do
visit commencer_path(path: procedure.path)
@ -43,13 +43,14 @@ feature 'Signing up:' do
scenario 'a new user can sign-up and fill the procedure' do
expect(page).to have_current_path new_user_session_path
click_on 'Créer un compte'
expect_page_to_have_procedure_description(procedure)
sign_up_with user_email, user_password
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
click_confirmation_link_for user_email
expect(page).to have_content 'Votre compte a été activé'
expect(page).to have_content procedure.libelle
expect_page_to_have_procedure_description(procedure)
end
end

View file

@ -0,0 +1,53 @@
require 'spec_helper'
feature 'Signin in:' do
let!(:user) { create(:user, password: password) }
let(:password) { 'testpassword' }
scenario 'an existing user can sign-in' do
visit root_path
click_on 'Connexion'
sign_in_with user.email, password
expect(page).to have_current_path dossiers_path
end
context 'when visiting a procedure' do
let(:procedure) { create :simple_procedure, :with_service }
before do
visit commencer_path(path: procedure.path)
end
scenario 'an existing user can sign-in and fill the procedure' do
expect(page).to have_current_path new_user_session_path
expect_page_to_have_procedure_description(procedure)
sign_in_with user.email, password
expect(page).to have_current_path identite_dossier_path(user.reload.dossiers.last)
expect_page_to_have_procedure_description(procedure)
expect(page).to have_content "Données d'identité"
end
end
context 'when a user is not confirmed yet' do
let!(:user) { create(:user, password: password, confirmed_at: nil) }
# Ideally, when signing-in with an unconfirmed account,
# the user would be redirected to the "resend email confirmation" page.
#
# However the check for unconfirmed accounts is made by Warden every time a page is loaded 
# and much earlier than SessionsController#create.
#
# For now only test the default behavior (an error message is displayed).
scenario 'they get an error message' do
visit root_path
click_on 'Connexion'
sign_in_with user.email, password
expect(page).to have_content 'Vous devez confirmer votre adresse email pour continuer'
end
end
end

View file

@ -40,6 +40,7 @@ RSpec.describe AutoArchiveProcedureJob, type: :job do
it {
expect(dossier1.state).to eq Dossier.states.fetch(:brouillon)
expect(dossier2.state).to eq Dossier.states.fetch(:en_instruction)
expect(dossier2.dossier_operation_logs.pluck(:gestionnaire_id, :operation, :automatic_operation)).to match([[nil, 'passer_en_instruction', true]])
expect(dossier3.state).to eq Dossier.states.fetch(:en_instruction)
expect(dossier4.state).to eq Dossier.states.fetch(:en_instruction)
expect(dossier5.state).to eq Dossier.states.fetch(:en_instruction)

View file

@ -13,13 +13,17 @@ RSpec.describe AutoReceiveDossiersForProcedureJob, type: :job do
before do
Timecop.freeze(date)
nouveau_dossier1
nouveau_dossier2
dossier_recu
dossier_brouillon
dossiers = [
nouveau_dossier1,
nouveau_dossier2,
dossier_recu,
dossier_brouillon
]
create(:attestation_template, procedure: procedure)
AutoReceiveDossiersForProcedureJob.new.perform(procedure.id, state)
dossiers.each(&:reload)
end
after { Timecop.return }
@ -29,17 +33,18 @@ RSpec.describe AutoReceiveDossiersForProcedureJob, type: :job do
let(:state) { Dossier.states.fetch(:en_instruction) }
it {
expect(nouveau_dossier1.reload.en_instruction?).to be true
expect(nouveau_dossier1.reload.en_instruction_at).to eq(date)
expect(nouveau_dossier1.en_instruction?).to be true
expect(nouveau_dossier1.en_instruction_at).to eq(date)
expect(nouveau_dossier1.dossier_operation_logs.pluck(:gestionnaire_id, :operation, :automatic_operation)).to match([[nil, 'passer_en_instruction', true]])
expect(nouveau_dossier2.reload.en_instruction?).to be true
expect(nouveau_dossier2.reload.en_instruction_at).to eq(date)
expect(nouveau_dossier2.en_instruction?).to be true
expect(nouveau_dossier2.en_instruction_at).to eq(date)
expect(dossier_recu.reload.en_instruction?).to be true
expect(dossier_recu.reload.en_instruction_at).to eq(instruction_date)
expect(dossier_recu.en_instruction?).to be true
expect(dossier_recu.en_instruction_at).to eq(instruction_date)
expect(dossier_brouillon.reload.brouillon?).to be true
expect(dossier_brouillon.reload.en_instruction_at).to eq(nil)
expect(dossier_brouillon.brouillon?).to be true
expect(dossier_brouillon.en_instruction_at).to eq(nil)
}
end
@ -47,23 +52,24 @@ RSpec.describe AutoReceiveDossiersForProcedureJob, type: :job do
let(:state) { Dossier.states.fetch(:accepte) }
it {
expect(nouveau_dossier1.reload.accepte?).to be true
expect(nouveau_dossier1.reload.en_instruction_at).to eq(date)
expect(nouveau_dossier1.reload.processed_at).to eq(date)
expect(nouveau_dossier1.reload.attestation).to be_present
expect(nouveau_dossier1.accepte?).to be true
expect(nouveau_dossier1.en_instruction_at).to eq(date)
expect(nouveau_dossier1.processed_at).to eq(date)
expect(nouveau_dossier1.attestation).to be_present
expect(nouveau_dossier1.dossier_operation_logs.pluck(:gestionnaire_id, :operation, :automatic_operation)).to match([[nil, 'accepter', true]])
expect(nouveau_dossier2.reload.accepte?).to be true
expect(nouveau_dossier2.reload.en_instruction_at).to eq(date)
expect(nouveau_dossier2.reload.processed_at).to eq(date)
expect(nouveau_dossier2.reload.attestation).to be_present
expect(nouveau_dossier2.accepte?).to be true
expect(nouveau_dossier2.en_instruction_at).to eq(date)
expect(nouveau_dossier2.processed_at).to eq(date)
expect(nouveau_dossier2.attestation).to be_present
expect(dossier_recu.reload.en_instruction?).to be true
expect(dossier_recu.reload.en_instruction_at).to eq(instruction_date)
expect(dossier_recu.reload.processed_at).to eq(nil)
expect(dossier_recu.en_instruction?).to be true
expect(dossier_recu.en_instruction_at).to eq(instruction_date)
expect(dossier_recu.processed_at).to eq(nil)
expect(dossier_brouillon.reload.brouillon?).to be true
expect(dossier_brouillon.reload.en_instruction_at).to eq(nil)
expect(dossier_brouillon.reload.processed_at).to eq(nil)
expect(dossier_brouillon.brouillon?).to be true
expect(dossier_brouillon.en_instruction_at).to eq(nil)
expect(dossier_brouillon.processed_at).to eq(nil)
}
end
end

View file

@ -760,4 +760,76 @@ describe Dossier do
it { expect(long_expired_dossier).to be_retention_expired }
end
end
describe '#accepter!' do
let(:dossier) { create(:dossier) }
let!(:gestionnaire) { create(:gestionnaire) }
let!(:now) { Time.zone.parse('01/01/2100') }
let(:attestation) { Attestation.new }
before do
allow(NotificationMailer).to receive(:send_closed_notification).and_return(double(deliver_later: true))
allow(dossier).to receive(:build_attestation).and_return(attestation)
Timecop.freeze(now)
dossier.accepter!(gestionnaire, 'motivation')
dossier.reload
end
after { Timecop.return }
it { expect(dossier.motivation).to eq('motivation') }
it { expect(dossier.en_instruction_at).to eq(now) }
it { expect(dossier.processed_at).to eq(now) }
it { expect(dossier.state).to eq('accepte') }
it { expect(dossier.dossier_operation_logs.pluck(:gestionnaire_id, :operation, :automatic_operation)).to match([[gestionnaire.id, 'accepter', false]]) }
it { expect(NotificationMailer).to have_received(:send_closed_notification).with(dossier) }
it { expect(dossier.attestation).to eq(attestation) }
end
describe '#accepter_automatiquement!' do
let(:dossier) { create(:dossier) }
let!(:now) { Time.zone.parse('01/01/2100') }
let(:attestation) { Attestation.new }
before do
allow(NotificationMailer).to receive(:send_closed_notification).and_return(double(deliver_later: true))
allow(dossier).to receive(:build_attestation).and_return(attestation)
Timecop.freeze(now)
dossier.accepter_automatiquement!
dossier.reload
end
after { Timecop.return }
it { expect(dossier.motivation).to eq(nil) }
it { expect(dossier.en_instruction_at).to eq(now) }
it { expect(dossier.processed_at).to eq(now) }
it { expect(dossier.state).to eq('accepte') }
it { expect(dossier.dossier_operation_logs.pluck(:gestionnaire_id, :operation, :automatic_operation)).to match([[nil, 'accepter', true]]) }
it { expect(NotificationMailer).to have_received(:send_closed_notification).with(dossier) }
it { expect(dossier.attestation).to eq(attestation) }
end
describe '#passer_en_instruction!' do
let(:dossier) { create(:dossier) }
let(:gestionnaire) { create(:gestionnaire) }
before { dossier.passer_en_instruction!(gestionnaire) }
it { expect(dossier.state).to eq('en_instruction') }
it { expect(dossier.followers_gestionnaires).to include(gestionnaire) }
it { expect(dossier.dossier_operation_logs.pluck(:gestionnaire_id, :operation)).to match([[gestionnaire.id, 'passer_en_instruction']]) }
end
describe '#passer_automatiquement_en_instruction!' do
let(:dossier) { create(:dossier) }
let(:gestionnaire) { create(:gestionnaire) }
before { dossier.passer_automatiquement_en_instruction! }
it { expect(dossier.followers_gestionnaires).not_to include(gestionnaire) }
it { expect(dossier.dossier_operation_logs.pluck(:gestionnaire_id, :operation, :automatic_operation)).to match([[nil, 'passer_en_instruction', true]]) }
end
end

View file

@ -50,6 +50,14 @@ module FeatureHelpers
visit "/users/confirmation?#{token_params}"
end
def expect_page_to_have_procedure_description(procedure)
# Procedure context on the page
expect(page).to have_content(procedure.libelle)
expect(page).to have_content(procedure.description)
# Procedure contact infos in the footer
expect(page).to have_content(procedure.service.email)
end
end
RSpec.configure do |config|

View file

@ -72,7 +72,7 @@ describe 'admin/procedures/show.html.haml', type: :view do
end
describe 'procedure link is present' do
it { expect(rendered).to have_content('Cette démarche est archivée et nest donc pas accessible par le public.') }
it { expect(rendered).to have_content('Cette démarche est archivée et nest donc plus accessible par le public.') }
end
end
end

View file

@ -4,25 +4,61 @@ describe 'layouts/procedure_context.html.haml', type: :view do
let(:procedure) { create(:simple_procedure, :with_service) }
let(:dossier) { create(:dossier, procedure: procedure) }
before do
assign(:dossier, dossier)
end
subject do
render html: 'Column content', layout: 'layouts/procedure_context.html.haml'
end
it 'renders a description of the procedure' do
expect(subject).to have_text(dossier.procedure.libelle)
expect(subject).to have_text(dossier.procedure.description)
context 'when a procedure is assigned' do
before do
assign(:procedure, procedure)
end
it 'renders a description of the procedure' do
expect(subject).to have_text(procedure.libelle)
expect(subject).to have_text(procedure.description)
end
it 'renders the inner content' do
expect(subject).to have_text('Column content')
end
it 'renders the procedure footer' do
expect(subject).to have_text(procedure.service.nom)
expect(subject).to have_text(procedure.service.email)
end
end
it 'renders the inner content' do
expect(subject).to have_text('Column content')
context 'when a dossier is assigned' do
before do
assign(:dossier, dossier)
end
it 'renders a description of the procedure' do
expect(subject).to have_text(dossier.procedure.libelle)
expect(subject).to have_text(dossier.procedure.description)
end
it 'renders the inner content' do
expect(subject).to have_text('Column content')
end
it 'renders the procedure footer' do
expect(subject).to have_text(dossier.procedure.service.nom)
expect(subject).to have_text(dossier.procedure.service.email)
end
end
it 'renders the dossier footer' do
expect(subject).to have_text(dossier.procedure.service.nom)
expect(subject).to have_text(dossier.procedure.service.email)
context 'when neither procedure or dossier are assigned' do
it 'renders a placeholder for the procedure' do
expect(subject).to have_selector('.no-procedure')
end
it 'renders the inner content' do
expect(subject).to have_text('Column content')
end
it 'renders a generic footer' do
expect(subject).to have_text('Mentions légales')
end
end
end

View file

@ -1,4 +1,4 @@
describe 'new_user/dossiers/_dossier_footer.html.haml', type: :view do
describe 'new_user/procedure_footer.html.haml', type: :view do
let(:service) { create(:service) }
let(:dossier) {
dossier = create(:dossier)
@ -6,7 +6,7 @@ describe 'new_user/dossiers/_dossier_footer.html.haml', type: :view do
return dossier
}
subject { render 'new_user/dossiers/dossier_footer.html.haml', dossier: dossier }
subject { render 'new_user/procedure_footer.html.haml', procedure: dossier.procedure, dossier: dossier }
it "affiche les informations de contact" do
expect(subject).to have_text(service.nom)

View file

@ -10,24 +10,12 @@ describe 'users/sessions/new.html.haml', type: :view do
before do
assign(:user, User.new)
render
end
context 'when user_return_to session params contains a procedure_id' do
before do
assign(:dossier, dossier)
render
end
it { expect(rendered).to have_selector('.procedure-logos') }
it { expect(rendered).to have_content(dossier.procedure.libelle) }
it { expect(rendered).to have_content(dossier.procedure.description) }
end
context 'when user_return_to session params not contains a procedure_id' do
before do
render
end
it { expect(rendered).to have_content('Un outil simple') }
it 'renders' do
expect(rendered).to have_field('Email')
expect(rendered).to have_field('Mot de passe')
expect(rendered).to have_button('Se connecter')
end
end

149
yarn.lock
View file

@ -767,6 +767,21 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
"@vue/component-compiler-utils@^2.4.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.5.0.tgz#411846d582d393f701f747517ddd29275ce64ca4"
integrity sha512-mSB8jWmE/ZeYZHPDEx9hNiiRh5P2V1Q0tObxEQWtxxfXtkIAvPnj7oucGm5SO8Y/QwIlDJgAGqHfj5MCjoKoOg==
dependencies:
consolidate "^0.15.1"
hash-sum "^1.0.2"
lru-cache "^4.1.2"
merge-source-map "^1.1.0"
postcss "^7.0.7"
postcss-selector-parser "^5.0.0"
prettier "1.13.7"
source-map "^0.7.3"
vue-template-es2015-compiler "^1.6.0"
"@webassemblyjs/ast@1.7.11":
version "1.7.11"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace"
@ -1370,6 +1385,11 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
bluebird@^3.1.1:
version "3.5.3"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
@ -1757,6 +1777,15 @@ chalk@^2.0, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@ -2089,6 +2118,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
consolidate@^0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
dependencies:
bluebird "^3.1.1"
constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@ -2467,6 +2503,11 @@ date-now@^0.1.4:
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
debounce@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
@ -3827,6 +3868,11 @@ hash-base@^3.0.0:
inherits "^2.0.1"
safe-buffer "^5.0.1"
hash-sum@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.5"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812"
@ -3835,6 +3881,11 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
he@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hex-color-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
@ -4932,7 +4983,7 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^4.1.3:
lru-cache@^4.1.2, lru-cache@^4.1.3:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@ -5051,6 +5102,13 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
merge-source-map@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
dependencies:
source-map "^0.6.1"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -6568,6 +6626,15 @@ postcss-selector-parser@^4.0.0:
indexes-of "^1.0.1"
uniq "^1.0.1"
postcss-selector-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
dependencies:
cssesc "^2.0.0"
indexes-of "^1.0.1"
uniq "^1.0.1"
postcss-selector-parser@^5.0.0-rc.4:
version "5.0.0-rc.4"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0-rc.4.tgz#ca5e77238bf152966378c13e91ad6d611568ea87"
@ -6633,6 +6700,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.5:
source-map "^0.6.1"
supports-color "^5.5.0"
postcss@^7.0.7:
version "7.0.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696"
integrity sha512-WudsIzuTKRw9IInRTPBgVXJ7DKR26HT09Rxp0g3w0Fqh3TUtYICcUmvC0xURj04o3vdcDtnjCAUCECg/p341iQ==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.0.0"
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@ -6650,6 +6726,11 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
prettier@1.13.7:
version "1.13.7"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
prettier@^1.15.3:
version "1.15.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
@ -7505,6 +7586,11 @@ sort-keys@^2.0.0:
dependencies:
is-plain-obj "^1.0.0"
sortablejs@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.7.0.tgz#80a2b2370abd568e1cec8c271131ef30a904fa28"
integrity sha1-gKKyNwq9Vo4c7IwnETHvMKkE+ig=
source-list-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
@ -7551,6 +7637,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
spark-md5@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.0.tgz#3722227c54e2faf24b1dc6d933cc144e6f71bfef"
@ -7821,6 +7912,13 @@ supports-color@^5.5.0:
dependencies:
has-flag "^3.0.0"
supports-color@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a"
integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==
dependencies:
has-flag "^3.0.0"
svgo@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"
@ -8410,6 +8508,55 @@ vm-browserify@0.0.4:
dependencies:
indexof "0.0.1"
vue-hot-reload-api@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
integrity sha512-AA86yKZ5uOKz87/q1UpngEXhbRkaYg1b7HMMVRobNV1IVKqZe8oLIzo6iMocVwZXnYitlGwf2k4ZRLOZlS8oPQ==
vue-loader@^15.5.1:
version "15.5.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.5.1.tgz#e005d5fc2481a55a19910f0dcf434206d68acc2a"
integrity sha512-gsTA9xRzu9jGBzkcrAB8my14RkHMzdr5rY/mCFmxgY2tOVsd2Z1MaYCDXHu5nX6PyHAsVK2/hXmarPln/2MiIw==
dependencies:
"@vue/component-compiler-utils" "^2.4.0"
hash-sum "^1.0.2"
loader-utils "^1.1.0"
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-style-loader@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.5.21:
version "2.5.21"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
vue-template-es2015-compiler@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==
vue@^2.5.21:
version "2.5.21"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
vuedraggable@^2.16.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.17.0.tgz#489c16c9bcb9eb06f441944d3c52e5f2bb7060f9"
integrity sha512-TAC5tJTSbHSINQCSB59qHnuzT0Ad+E3IgvSWuA1e9UaebD8DxKaY1tCdvL3XvuLoaM3wc1dhpP/NbjpdxYsrng==
dependencies:
sortablejs "^1.7.0"
watchpack@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"