Merge branch 'dev'

This commit is contained in:
Frederic Merizen 2018-05-31 17:07:02 +02:00
commit d28d467aea
34 changed files with 402 additions and 124 deletions

View file

@ -217,6 +217,15 @@ class Admin::ProceduresController < AdminController
render json: json_path_list
end
def delete_deliberation
procedure = Procedure.find(params[:id])
procedure.deliberation.purge_later
flash.notice = 'la délibération a bien été supprimée'
redirect_to edit_admin_procedure_path(procedure)
end
private
def cloned_from_library?
@ -224,7 +233,7 @@ class Admin::ProceduresController < AdminController
end
def procedure_params
editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :notice, :web_hook_url, :euro_flag, :logo, :auto_archive_on]
editable_params = [:libelle, :description, :organisation, :direction, :lien_site_web, :cadre_juridique, :deliberation, :notice, :web_hook_url, :euro_flag, :logo, :auto_archive_on]
if @procedure&.locked?
params.require(:procedure).permit(*editable_params)
else

View file

@ -0,0 +1,4 @@
module Manager
class DossiersController < Manager::ApplicationController
end
end

View file

@ -0,0 +1,4 @@
module Manager
class GestionnairesController < Manager::ApplicationController
end
end

View file

@ -0,0 +1,4 @@
module Manager
class UsersController < Manager::ApplicationController
end
end

View file

@ -73,7 +73,7 @@ module NewUser
@dossier.en_construction!
NotificationMailer.send_initiated_notification(@dossier).deliver_later
redirect_to merci_dossier_path(@dossier)
elsif owns_dossier?
elsif current_user.owns?(dossier)
redirect_to users_dossier_recapitulatif_path(@dossier)
else
redirect_to users_dossiers_invite_path(@dossier.invite_for_user(current_user))
@ -142,19 +142,19 @@ module NewUser
end
def ensure_ownership!
if !owns_dossier?
if !current_user.owns?(dossier)
forbidden!
end
end
def ensure_ownership_or_invitation!
if !dossier.owner_or_invite?(current_user)
if !current_user.owns_or_invite?(dossier)
forbidden!
end
end
def forbid_invite_submission!
if passage_en_construction? && !owns_dossier?
if passage_en_construction? && !current_user.owns?(dossier)
forbidden!
end
end
@ -172,10 +172,6 @@ module NewUser
params.require(:dossier).permit(:autorisation_donnees)
end
def owns_dossier?
dossier.user_id == current_user.id
end
def passage_en_construction?
dossier.brouillon? && !draft?
end

View file

@ -10,7 +10,7 @@ class UsersController < ApplicationController
dossier = Dossier.find(dossier_id)
if !dossier.owner_or_invite?(current_user)
if !current_user.owns_or_invite?(dossier)
raise ActiveRecord::RecordNotFound
end

View file

@ -0,0 +1,54 @@
require "administrate/base_dashboard"
class DossierDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
procedure: Field::HasOne,
state: Field::String,
text_summary: Field::String,
created_at: Field::DateTime,
updated_at: Field::DateTime,
types_de_champ: TypesDeChampCollectionField,
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:id,
:procedure,
:created_at,
:state
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:text_summary,
:state,
:procedure,
:types_de_champ,
:created_at,
:updated_at,
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [].freeze
# Overwrite this method to customize how users are displayed
# across all pages of the admin dashboard.
#
# def display_resource(user)
# "User ##{user.id}"
# end
end

View file

@ -0,0 +1,50 @@
require "administrate/base_dashboard"
class GestionnaireDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
email: Field::String,
created_at: Field::DateTime,
updated_at: Field::DateTime,
current_sign_in_at: Field::DateTime,
dossiers: Field::HasMany,
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:email,
:created_at,
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:dossiers,
:id,
:email,
:current_sign_in_at,
:created_at,
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [].freeze
# Overwrite this method to customize how users are displayed
# across all pages of the admin dashboard.
#
def display_resource(gestionnaire)
gestionnaire.email
end
end

View file

@ -74,7 +74,7 @@ class ProcedureDashboard < Administrate::BaseDashboard
# Overwrite this method to customize how procedures are displayed
# across all pages of the admin dashboard.
#
# def display_resource(procedure)
# "Procedure ##{procedure.id}"
# end
def display_resource(procedure)
"#{procedure.libelle} ##{procedure.id}"
end
end

View file

@ -0,0 +1,50 @@
require "administrate/base_dashboard"
class UserDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
email: Field::String,
created_at: Field::DateTime,
updated_at: Field::DateTime,
current_sign_in_at: Field::DateTime,
dossiers: Field::HasMany,
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = [
:email,
:created_at,
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = [
:dossiers,
:id,
:email,
:current_sign_in_at,
:created_at,
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = [].freeze
# Overwrite this method to customize how users are displayed
# across all pages of the admin dashboard.
#
def display_resource(user)
user.email
end
end

View file

@ -16,10 +16,12 @@ module DossierHelper
end
def delete_dossier_confirm(dossier)
message = "Vous vous apprêtez à supprimer votre dossier ainsi que les informations quil contient. "
message = ["Vous vous apprêtez à supprimer votre dossier ainsi que les informations quil contient."]
if dossier.en_construction_ou_instruction?
message += "Nous vous rappelons que toute suppression entraine lannulation de la démarche en cours. "
message << "Nous vous rappelons que toute suppression entraine lannulation de la démarche en cours."
end
message += "Confirmer la suppression ?"
message << "Confirmer la suppression ?"
message.join(" ")
end
end

View file

@ -163,10 +163,6 @@ class Dossier < ApplicationRecord
en_instruction? || accepte? || refuse? || sans_suite?
end
def owner_or_invite?(user)
self.user == user || invite_for_user(user).present?
end
def invite_for_user(user)
invites_user.find_by(user_id: user.id)
end

View file

@ -24,6 +24,7 @@ class Procedure < ApplicationRecord
has_one :without_continuation_mail, class_name: "Mails::WithoutContinuationMail", dependent: :destroy
has_one_attached :notice
has_one_attached :deliberation
delegate :use_api_carto, to: :module_api_carto
@ -45,6 +46,7 @@ class Procedure < ApplicationRecord
validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :description, presence: true, allow_blank: false, allow_nil: false
validate :check_juridique
include AASM
@ -198,15 +200,7 @@ class Procedure < ApplicationRecord
procedure.logo_secure_token = nil
procedure.remote_logo_url = self.logo_url
if notice.attached?
response = Typhoeus.get(notice.service_url, timeout: 5)
if response.success?
procedure.notice.attach(
io: StringIO.new(response.body),
filename: notice.filename
)
end
end
%i(notice deliberation).each { |attachment| clone_attachment(procedure, attachment) }
procedure.administrateur = admin
procedure.initiated_mail = initiated_mail&.dup
@ -349,6 +343,25 @@ class Procedure < ApplicationRecord
private
def clone_attachment(cloned_procedure, attachment_symbol)
attachment = send(attachment_symbol)
if attachment.attached?
response = Typhoeus.get(attachment.service_url, timeout: 5)
if response.success?
cloned_procedure.send(attachment_symbol).attach(
io: StringIO.new(response.body),
filename: attachment.filename
)
end
end
end
def check_juridique
if cadre_juridique.blank? && !deliberation.attached?
errors.add(:cadre_juridique, " : veuillez remplir le texte de loi ou la délibération")
end
end
def field_hash(label, table, column)
{
'label' => label,

View file

@ -38,7 +38,15 @@ class User < ApplicationRecord
loged_in_with_france_connect.present?
end
def owns?(dossier)
dossier.user_id == id
end
def invite?(dossier_id)
invites.pluck(:dossier_id).include?(dossier_id.to_i)
end
def owns_or_invite?(dossier)
owns?(dossier) || invite?(dossier.id)
end
end

View file

@ -21,6 +21,27 @@
Un lien de rappel HTTP (aussi appelé webhook) est utilisé pour notifier un service tiers du changement de l'état dun dossier sur demarches-simplifiees.fr. À chaque changement détat d'un dossier, notre site va effectuer une requête sur le lien renseigné avec en paramètres : le nouvel état du dossier, lidentifiant de la procédure, l'identifiant dossier et la date du changement. Vous pourrez alors utiliser notre API pour récupérer les nouvelles informations du dossier concerné.
= f.text_field :web_hook_url, class: 'form-control', placeholder: 'https://callback.exemple.fr/'
.form-group
%h4 Cadre juridique *
%p Indiquez la référence ou l'URL du texte juridique ou chargez la délibération qui justifie cette procédure
= f.label :cadre_juridique, 'Référence ou texte de loi'
= f.text_field :cadre_juridique, class: 'form-control', placeholder: 'https://www.legifrance.gouv.fr/'
= f.label :deliberation, 'Délibération'
- deliberation = @procedure.deliberation
- if !deliberation.attached?
= f.file_field :deliberation,
direct_upload: true
- else
%a{ href: url_for(deliberation), target: '_blank' }
= deliberation.filename.to_s
= link_to 'supprimer', delete_deliberation_admin_procedure_path(@procedure),
method: :delete
%br
Modifier :
= f.file_field :deliberation,
direct_upload: true
.form-group
%h4 Notice explicative de la procédure
- notice = @procedure.notice

View file

@ -1,5 +1,5 @@
- if !@facade.dossier.read_only?
- if user_signed_in? && (@facade.dossier.owner_or_invite?(current_user))
- if user_signed_in? && (current_user.owns_or_invite?(@facade.dossier))
%a#maj_carte.action{ href: "/users/dossiers/#{@facade.dossier.id}/carte" }
.col-lg-2.col-md-2.col-sm-2.col-xs-2.action
= 'ÉDITER'

View file

@ -1,5 +1,5 @@
- if !@facade.dossier.read_only?
- if user_signed_in? && (@facade.dossier.owner_or_invite?(current_user))
- if user_signed_in? && (current_user.owns_or_invite?(@facade.dossier))
= link_to modifier_dossier_path(@facade.dossier), class: 'action', id: 'maj_infos' do
#edit-dossier.col-lg-2.col-md-2.col-sm-2.col-xs-2.action
= "ÉDITER"

View file

@ -1,5 +1,5 @@
- if !@facade.dossier.read_only?
- if user_signed_in? && (@facade.dossier.owner_or_invite?(current_user))
- if user_signed_in? && (current_user.owns_or_invite?(@facade.dossier))
- if @facade.procedure.cerfa_flag? || @facade.dossier.types_de_piece_justificative.size > 0
.col-lg-4.col-md-4.col-sm-4.col-xs-4.action
%a#maj_pj.action{ "data-target" => "#upload-pj-modal",

View file

@ -101,7 +101,7 @@
Pièce non fournie
- if !@facade.dossier.read_only?
- if user_signed_in? && (@facade.dossier.owner_or_invite?(current_user))
- if user_signed_in? && (current_user.owns_or_invite?(@facade.dossier))
- if @facade.procedure.cerfa_flag? || @facade.dossier.types_de_piece_justificative.size > 0
.row
.col-xs-4

View file

@ -14,7 +14,7 @@
.dossier-state= @facade.dossier.display_state
.split-hr-left
- if @facade.dossier.user == current_user
- if current_user.owns?(@facade.dossier)
.text-center.mt-1
= link_to ask_deletion_dossier_path(@facade.dossier), method: :post, class: "btn btn-danger", data: { confirm: delete_dossier_confirm(@facade.dossier) } do
Supprimer définitivement

View file

@ -12,7 +12,8 @@ as defined by the routes in the `admin/` namespace
<hr />
<% Administrate::Namespace.new(namespace).resources.each do |resource| %>
<% Administrate::Namespace.new(namespace).resources.reject { |resource| resource.resource == 'dossiers'}.each do |resource| %>
<%= link_to(
display_resource_name(resource),
[namespace, resource.path],

View file

@ -58,7 +58,7 @@
class: 'button send',
data: { action: 'draft', disable_with: 'Envoi...' }
- if dossier.user == current_user
- if current_user.owns?(dossier)
= f.button 'Soumettre le dossier',
class: 'button send primary',
data: { action: 'submit', disable_with: 'Envoi...' }

View file

@ -3,3 +3,17 @@ fr:
attributes:
procedure:
organisation: Organisme
errors:
models:
procedure:
attributes:
libelle:
blank: Attribut manquant
description:
blank: Attribut manquant
lien_demarche:
blank: Attribut manquant
organisation:
blank: Attribut manquant
'cadre_juridique':
blank: Attribut manquant

View file

@ -11,6 +11,10 @@ Rails.application.routes.draw do
put 'enable_feature', on: :member
end
resources :users, only: [:index, :show]
resources :gestionnaires, only: [:index, :show]
resources :dossiers, only: [:show]
resources :demandes, only: [:index]
post 'demandes/create_administrateur'
post 'demandes/refuse_administrateur'
@ -143,6 +147,7 @@ Rails.application.routes.draw do
member do
post :hide
delete :delete_deliberation
end
resources :types_de_champ, only: [:destroy]

View file

@ -0,0 +1,5 @@
class AddCadreJuridiqueToProcedure < ActiveRecord::Migration[5.2]
def change
add_column :procedures, :cadre_juridique, :string
end
end

View file

@ -481,6 +481,7 @@ ActiveRecord::Schema.define(version: 2018_05_30_095508) do
t.bigint "service_id"
t.integer "duree_conservation_dossiers_dans_ds"
t.integer "duree_conservation_dossiers_hors_ds"
t.string "cadre_juridique"
t.index ["hidden_at"], name: "index_procedures_on_hidden_at"
t.index ["parent_procedure_id"], name: "index_procedures_on_parent_procedure_id"
t.index ["service_id"], name: "index_procedures_on_service_id"

View file

@ -15,6 +15,7 @@ describe Admin::ProceduresController, type: :controller do
let(:quartiers_prioritaires) { '0' }
let(:cadastre) { '0' }
let(:cerfa_flag) { true }
let(:cadre_juridique) { 'cadre juridique' }
let(:procedure_params) {
{
@ -24,6 +25,7 @@ describe Admin::ProceduresController, type: :controller do
direction: direction,
lien_demarche: lien_demarche,
cerfa_flag: cerfa_flag,
cadre_juridique: cadre_juridique,
module_api_carto_attributes: {
use_api_carto: use_api_carto,
quartiers_prioritaires: quartiers_prioritaires,
@ -471,7 +473,7 @@ describe Admin::ProceduresController, type: :controller do
end
describe 'PUT #clone' do
let!(:procedure) { create(:procedure, :with_notice, administrateur: admin) }
let!(:procedure) { create(:procedure, :with_notice, :with_deliberation, administrateur: admin) }
let(:params) { { procedure_id: procedure.id } }
subject { put :clone, params: params }
@ -489,6 +491,7 @@ describe Admin::ProceduresController, type: :controller do
expect(response).to redirect_to edit_admin_procedure_path(id: Procedure.last.id)
expect(Procedure.last.cloned_from_library).to be_falsey
expect(Procedure.last.notice.attached?).to be_truthy
expect(Procedure.last.deliberation.attached?).to be_truthy
expect(flash[:notice]).to have_content 'Procédure clonée'
end
@ -634,4 +637,16 @@ describe Admin::ProceduresController, type: :controller do
end
end
end
describe "DELETE #delete_deliberation" do
let(:procedure) { create(:procedure, :with_deliberation) }
before do
delete :delete_deliberation, params: { id: procedure.id }
procedure.reload
end
it { expect(procedure.deliberation.attached?).to eq(false) }
it { expect(response).to redirect_to(edit_admin_procedure_path(procedure)) }
end
end

View file

@ -6,6 +6,7 @@ FactoryBot.define do
description "Demande de subvention à l'intention des associations"
organisation "Orga DINSIC"
direction "direction DINSIC"
cadre_juridique "un cadre juridique important"
published_at nil
cerfa_flag false
administrateur { create(:administrateur) }
@ -134,6 +135,15 @@ FactoryBot.define do
end
end
trait :with_deliberation do
after(:create) do |procedure, _evaluator|
procedure.deliberation.attach(
io: StringIO.new('Hello World'),
filename: 'hello.txt'
)
end
end
trait :with_all_champs_mandatory do
after(:build) do |procedure, _evaluator|
tdcs = []

View file

@ -14,6 +14,7 @@ feature 'As an administrateur I wanna clone a procedure', js: true do
page.find_by_id('from-scratch').click
fill_in 'procedure_libelle', with: 'libelle de la procedure'
page.execute_script("$('#procedure_description').val('description de la procedure')")
fill_in 'procedure_cadre_juridique', with: 'cadre juridique'
page.find_by_id('save-procedure').click
end

View file

@ -32,13 +32,14 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
expect(page).to have_current_path(new_admin_procedure_path)
end
scenario 'Finding save button for new procedure, libelle and description required' do
scenario 'Finding save button for new procedure, libelle, description and cadre_juridique required' do
page.find_by_id('new-procedure').click
page.find_by_id('from-scratch').click
page.find_by_id('save-procedure').click
page.find_by_id('flash_message').visible?
fill_in 'procedure_libelle', with: 'libelle de la procedure'
page.execute_script("$('#procedure_description').val('description de la procedure')")
fill_in 'procedure_cadre_juridique', with: 'cadre juridique'
page.find_by_id('save-procedure').click
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.first.id.to_s))
end
@ -50,6 +51,7 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
page.find_by_id('from-scratch').click
fill_in 'procedure_libelle', with: 'libelle de la procedure'
page.execute_script("$('#procedure_description').val('description de la procedure')")
fill_in 'procedure_cadre_juridique', with: 'cadre juridique'
page.find_by_id('save-procedure').click
procedure = Procedure.last

View file

@ -397,44 +397,6 @@ describe Dossier do
end
end
describe '#owner_or_invite?' do
let(:owner) { create(:user) }
let(:dossier) { create(:dossier, user: owner) }
let(:invite_user) { create(:user) }
let(:invite_gestionnaire) { create(:user) }
before do
create(:invite, dossier: dossier, user: invite_user, type: 'InviteUser')
create(:invite, dossier: dossier, user: invite_gestionnaire, type: 'InviteGestionnaire')
end
subject { dossier.owner_or_invite?(user) }
context 'when user is owner' do
let(:user) { owner }
it { is_expected.to be_truthy }
end
context 'when user was invited by user' do
let(:user) { invite_user }
it { is_expected.to be_truthy }
end
context 'when user was invited by gestionnaire (legacy, no new invitations happen)' do
let(:user) { invite_gestionnaire }
it { is_expected.to be_falsey }
end
context 'when user is quidam' do
let(:user) { create(:user) }
it { is_expected.to be_falsey }
end
end
describe "#text_summary" do
let(:procedure) { create(:procedure, libelle: "Procédure", organisation: "Organisme") }

View file

@ -173,6 +173,26 @@ describe Procedure do
context 'organisation' do
it { is_expected.to allow_value('URRSAF').for(:organisation) }
end
context 'juridique' do
it { is_expected.not_to allow_value(nil).for(:cadre_juridique) }
it { is_expected.to allow_value('text').for(:cadre_juridique) }
context 'with deliberation' do
let(:procedure) { build(:procedure, cadre_juridique: nil) }
it { expect(procedure.valid?).to eq(false) }
context 'when the deliberation is uploaded ' do
before do
allow(procedure).to receive(:deliberation)
.and_return(double('attached?': true))
end
it { expect(procedure.valid?).to eq(true) }
end
end
end
end
describe '#types_de_champ_ordered' do

View file

@ -31,6 +31,37 @@ describe User, type: :model do
end
end
describe '#owns?' do
let(:owner) { create(:user) }
let(:dossier) { create(:dossier, user: owner) }
let(:invite_user) { create(:user) }
let(:invite_gestionnaire) { create(:user) }
subject { user.owns?(dossier) }
context 'when user is owner' do
let(:user) { owner }
it { is_expected.to be_truthy }
end
context 'when user was invited by user' do
before do
create(:invite, dossier: dossier, user: invite_user, type: 'InviteUser')
end
let(:user) { invite_user }
it { is_expected.to be_falsy }
end
context 'when user is quidam' do
let(:user) { create(:user) }
it { is_expected.to be_falsey }
end
end
describe '#invite?' do
let(:dossier) { create :dossier }
let(:user) { dossier.user }
@ -50,6 +81,37 @@ describe User, type: :model do
end
end
describe '#owns_or_invite?' do
let(:owner) { create(:user) }
let(:dossier) { create(:dossier, user: owner) }
let(:invite_user) { create(:user) }
let(:invite_gestionnaire) { create(:user) }
subject { user.owns_or_invite?(dossier) }
context 'when user is owner' do
let(:user) { owner }
it { is_expected.to be_truthy }
end
context 'when user was invited by user' do
before do
create(:invite, dossier: dossier, user: invite_user, type: 'InviteUser')
end
let(:user) { invite_user }
it { is_expected.to be_truthy }
end
context 'when user is quidam' do
let(:user) { create(:user) }
it { is_expected.to be_falsey }
end
end
context 'unified login' do
it 'syncs credentials to associated gestionnaire' do
user = create(:user)

View file

@ -72,36 +72,6 @@ describe 'users/recapitulatif/show.html.haml', type: :view do
end
context 'when invite is logged' do
context 'when invite is by Gestionnaire' do
let!(:invite_user) { create(:user, email: 'invite@octo.com') }
before do
create(:invite) { create(:invite, email: invite_user.email, user: invite_user, dossier: dossier) }
sign_out dossier.user
sign_in invite_user
render
end
describe 'les liens de modifications' do
it 'describe link is not present' do
expect(rendered).not_to have_css('#maj_infos')
end
it 'map link is not present' do
expect(rendered).not_to have_css('#maj_carte')
end
it 'PJ link is not present' do
expect(rendered).not_to have_css('#maj_pj')
end
it 'archive link is not present' do
expect(rendered).not_to have_content('Archiver')
end
end
end
context 'invite is by User' do
let!(:invite_user) { create(:user, email: 'invite@octo.com') }
before do
@ -131,4 +101,3 @@ describe 'users/recapitulatif/show.html.haml', type: :view do
end
end
end
end