Merge pull request #426 from sgmap/attestation

Attestation
This commit is contained in:
gregoirenovel 2017-06-16 12:30:11 +02:00 committed by GitHub
commit 58221c8e8a
38 changed files with 1154 additions and 19 deletions

12
Gemfile
View file

@ -52,6 +52,7 @@ gem 'rest-client'
gem 'clamav-client', require: 'clamav/client'
gem 'carrierwave'
gem 'copy_carrierwave_file'
gem 'fog'
gem 'fog-openstack'
@ -101,6 +102,12 @@ gem 'sinatra', git: 'https://github.com/sinatra/sinatra.git', require: false
gem 'select2-rails'
# PDF Generation
gem 'prawn', '~> 2.0.1'
gem 'prawn_rails', '~> 0.0.11'
gem 'sentry-raven'
group :test do
gem 'capybara'
gem 'launchy'
@ -143,8 +150,3 @@ group :development, :test do
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'dotenv-rails'
end
group :production, :staging do
gem 'sentry-raven'
end

View file

@ -120,6 +120,8 @@ GEM
coffee-script-source (1.12.2)
concurrent-ruby (1.0.5)
connection_pool (2.2.1)
copy_carrierwave_file (1.3.0)
carrierwave (>= 0.9)
crack (0.4.3)
safe_yaml (~> 1.0.0)
database_cleaner (1.5.3)
@ -420,12 +422,19 @@ GEM
parallel (1.11.2)
parser (2.4.0.0)
ast (~> 2.2)
pdf-core (0.6.1)
pg (0.19.0)
poltergeist (1.14.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
websocket-driver (>= 0.2.0)
powerpack (0.1.1)
prawn (2.0.2)
pdf-core (~> 0.6.0)
ttfunk (~> 1.4.0)
prawn_rails (0.0.11)
prawn (>= 0.11.1)
railties (>= 3.0.0)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@ -611,6 +620,7 @@ GEM
tilt (2.0.5)
timecop (0.8.1)
trollop (2.1.2)
ttfunk (1.4.0)
turbolinks (5.0.1)
turbolinks-source (~> 5)
turbolinks-source (5.0.0)
@ -677,6 +687,7 @@ DEPENDENCIES
carrierwave
chartkick
clamav-client
copy_carrierwave_file
database_cleaner
deep_cloneable (~> 2.2.1)
devise
@ -706,6 +717,8 @@ DEPENDENCIES
openstack
pg
poltergeist
prawn (~> 2.0.1)
prawn_rails (~> 0.0.11)
pry-byebug
rack-handlers
rails (= 5.0.0.1)

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="550.801" height="550.801" viewBox="0 0 550.801 550.801"><path d="M160.381 282.225c0-14.832-10.299-23.684-28.474-23.684-7.414 0-12.437.715-15.071 1.432V307.6c3.114.707 6.942.949 12.192.949 19.391 0 31.353-9.809 31.353-26.324zM272.875 259.019c-8.145 0-13.397.717-16.519 1.435v105.523c3.116.729 8.142.729 12.69.729 33.017.231 54.554-17.946 54.554-56.474.242-33.513-19.385-51.213-50.725-51.213z"/><path d="M488.426 197.019H475.2v-63.816c0-.398-.063-.799-.116-1.202-.021-2.534-.827-5.023-2.562-6.995L366.325 3.694c-.032-.031-.063-.042-.085-.076-.633-.707-1.371-1.295-2.151-1.804a9.495 9.495 0 0 0-.706-.419 11.131 11.131 0 0 0-2.131-.896c-.2-.056-.38-.138-.58-.19A10.774 10.774 0 0 0 358.193 0H97.2C85.282 0 75.6 9.693 75.6 21.601v175.413H62.377c-17.049 0-30.873 13.818-30.873 30.873v160.545c0 17.043 13.824 30.87 30.873 30.87h13.224V529.2c0 11.907 9.682 21.601 21.6 21.601h356.4c11.907 0 21.6-9.693 21.6-21.601V419.302h13.226c17.044 0 30.871-13.827 30.871-30.87v-160.54c-.001-17.054-13.828-30.873-30.872-30.873zM97.2 21.605h250.193v110.513c0 5.967 4.841 10.8 10.8 10.8H453.6v54.108H97.2V21.605zm265.159 287.418c0 30.876-11.243 52.165-26.82 65.333-16.971 14.117-42.82 20.814-74.396 20.814-18.9 0-32.297-1.197-41.401-2.389V234.365c13.399-2.149 30.878-3.346 49.304-3.346 30.612 0 50.478 5.508 66.039 17.226 16.743 12.445 27.274 32.302 27.274 60.778zM80.7 393.499V234.365c11.241-1.904 27.042-3.346 49.296-3.346 22.491 0 38.527 4.308 49.291 12.928 10.292 8.131 17.215 21.534 17.215 37.328 0 15.799-5.25 29.198-14.829 38.285-12.442 11.728-30.865 16.996-52.407 16.996-4.778 0-9.1-.243-12.435-.723v57.67H80.7v-.004zm372.901 129.854H97.2V419.302h356.4v104.051zm31.297-261.226h-61.989v36.851h57.913v29.674h-57.913V393.5h-36.593V232.216h98.582v29.911z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -44,6 +44,8 @@
// = require switch_menu
// = require typeahead
// = require users
// = require attestation_template_edit
// = require attestation_recapitulatif
// = require_self
// = require bootstrap-datepicker3

View file

@ -0,0 +1,31 @@
#attestation-recapitulatif {
margin-top: 40px;
.details {
padding: 15px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #FFFFFF;
.left {
position: relative;
padding-left: 30px;
img {
position: absolute;
left: 0px;
top: 15px;
}
.title {
font-weight: bold;
margin: 0;
}
.delivery {
color: #999999;
}
}
}
}

View file

@ -0,0 +1,30 @@
#attestation-template-edit {
.notice {
margin-bottom: 30px;
}
.image-upload {
display: flex;
align-items: center;
input {
margin: 10px 0;
}
}
.thumbnail {
width: 90px;
margin-right: 15px;
}
.balises {
max-height: 180px;
overflow-y: scroll;
margin-bottom: 20px;
}
.table {
border: 1px solid #DDDDDD;
margin-bottom: 0px;
}
}

View file

@ -0,0 +1,74 @@
class Admin::AttestationTemplatesController < AdminController
before_action :retrieve_procedure
def show
attestation_template = @procedure.attestation_template
@logo = attestation_template.logo
@title = attestation_template.title
@body = attestation_template.body
@signature = attestation_template.signature
@footer = attestation_template.footer
@created_at = DateTime.now
end
def edit
@attestation_template = @procedure.attestation_template || AttestationTemplate.new(procedure: @procedure)
end
def update
attestation_template = @procedure.attestation_template
if attestation_template.update_attributes(activated_attestation_params)
flash.notice = "L'attestation a bien été modifiée"
else
flash.alert = attestation_template.errors.full_messages.join('<br>')
end
redirect_to edit_admin_procedure_attestation_template_path(@procedure)
end
def create
attestation_template = AttestationTemplate.new(activated_attestation_params.merge(procedure_id: @procedure.id))
if attestation_template.save
flash.notice = "L'attestation a bien été sauvegardée"
else
flash.alert = attestation_template.errors.full_messages.join('<br>')
end
redirect_to edit_admin_procedure_attestation_template_path(@procedure)
end
def disactivate
attestation_template = @procedure.attestation_template
attestation_template.activated = false
attestation_template.save
flash.notice = "L'attestation a bien été désactivée"
redirect_to edit_admin_procedure_attestation_template_path(@procedure)
end
def preview
@title = activated_attestation_params[:title]
@body = activated_attestation_params[:body]
@footer = activated_attestation_params[:footer]
@created_at = DateTime.now
# In a case of a preview, when the user does not change its images,
# the images are not uploaded and thus should be retrieved from previous
# attestation_template
@logo = activated_attestation_params[:logo] || @procedure.attestation_template&.logo
@signature = activated_attestation_params[:signature] || @procedure.attestation_template&.signature
render 'admin/attestation_templates/show', formats: [:pdf]
end
private
def activated_attestation_params
params.require(:attestation_template)
.permit(:title, :body, :footer, :logo, :signature)
.merge(activated: true)
end
end

View file

@ -1,4 +1,6 @@
class Backoffice::DossiersController < Backoffice::DossiersListController
include ActionView::Helpers::NumberHelper
respond_to :html, :xlsx, :ods, :csv
prepend_before_action :store_current_location, only: :show
@ -112,6 +114,8 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
dossier = @facade.dossier
attestation_pdf = nil
case params[:process_action]
when "refuse"
next_step = "refuse"
@ -125,12 +129,15 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
next_step = "close"
notice = "Dossier traité avec succès."
template = dossier.procedure.closed_mail_template
if check_attestation_emailable(dossier)
attestation_pdf = dossier.attestation.pdf.read
end
end
dossier.next_step! 'gestionnaire', next_step, motivation
flash.notice = notice
NotificationMailer.send_notification(dossier, template).deliver_now!
NotificationMailer.send_notification(dossier, template, attestation_pdf).deliver_now!
redirect_to backoffice_dossier_path(id: dossier.id)
end
@ -185,6 +192,16 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
private
def check_attestation_emailable(dossier)
if dossier&.attestation&.emailable? == false
human_size = number_to_human_size(dossier.attestation.pdf.size)
msg = "the attestation of the dossier #{dossier.id} cannot be mailed because it is too heavy: #{human_size}"
capture_message(msg, level: 'error')
end
dossier&.attestation&.emailable?
end
def store_current_location
if !gestionnaire_signed_in?
store_location_for(:gestionnaire, request.url)

View file

@ -7,6 +7,10 @@ class Users::RecapitulatifController < UsersController
create_dossier_facade
end
def attestation
send_data(current_user_dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf')
end
def initiate
create_dossier_facade

View file

@ -3,12 +3,16 @@ class NotificationMailer < ApplicationMailer
after_action :create_commentaire_for_notification, only: :send_notification
def send_notification(dossier, mail_template)
def send_notification(dossier, mail_template, attestation = nil)
vars_mailer(dossier)
@object = mail_template.object_for_dossier dossier
@body = mail_template.body_for_dossier dossier
if attestation.present?
attachments['attestation.pdf'] = attestation
end
mail(subject: @object) { |format| format.html { @body } }
end

11
app/models/attestation.rb Normal file
View file

@ -0,0 +1,11 @@
class Attestation < ApplicationRecord
belongs_to :dossier
mount_uploader :pdf, AttestationUploader
MAX_SIZE_EMAILABLE = 2.megabytes
def emailable?
pdf.size <= MAX_SIZE_EMAILABLE
end
end

View file

@ -0,0 +1,143 @@
class AttestationTemplate < ApplicationRecord
include ActionView::Helpers::NumberHelper
belongs_to :procedure
mount_uploader :logo, AttestationTemplateImageUploader
mount_uploader :signature, AttestationTemplateImageUploader
validate :logo_signature_file_size
FILE_MAX_SIZE_IN_MB = 0.5
def tags
if procedure.for_individual?
identity_tags = individual_tags
else
identity_tags = entreprise_tags + etablissement_tags
end
identity_tags + dossier_tags + procedure_type_de_champ_public_private_tags
end
def attestation_for(dossier)
Attestation.new(title: replace_tags(title, dossier), pdf: build_pdf(dossier))
end
def dup
result = AttestationTemplate.new(title: title, body: body, footer: footer, activated: activated)
if logo.present?
CopyCarrierwaveFile::CopyFileService.new(self, result, :logo).set_file
end
if signature.present?
CopyCarrierwaveFile::CopyFileService.new(self, result, :signature).set_file
end
result
end
private
def logo_signature_file_size
%i[logo signature]
.select { |file_name| send(file_name).present? }
.each { |file_name| file_size_check(file_name) }
end
def file_size_check(file_name)
if send(file_name).file.size.to_f > FILE_MAX_SIZE_IN_MB.megabyte.to_f
errors.add(file_name, " : vous ne pouvez pas charger une image de plus de #{number_with_delimiter(FILE_MAX_SIZE_IN_MB, locale: :fr)} Mo")
end
end
def procedure_type_de_champ_public_private_tags
(procedure.types_de_champ + procedure.types_de_champ_private)
.map { |tdc| { libelle: tdc.libelle, description: tdc.description } }
end
def dossier_tags
[{ libelle: 'motivation', description: '', target: 'motivation' }]
end
def individual_tags
[{ libelle: 'civilité', description: 'M., Mme', target: 'gender' },
{ libelle: 'nom', description: "nom de l'usager", target: 'nom' },
{ libelle: 'prénom', description: "prénom de l'usager", target: 'prenom' }]
end
def entreprise_tags
[{ libelle: 'SIREN', description: '', target: 'siren' },
{ libelle: 'numéro de TVA intracommunautaire', description: '', target: 'numero_tva_intracommunautaire' },
{ libelle: 'SIRET du siège social', description: '', target: 'siret_siege_social' },
{ libelle: 'raison sociale', description: '', target: 'raison_sociale' }]
end
def etablissement_tags
[{ libelle: 'adresse', description: '', target: 'adresse' }]
end
def build_pdf(dossier)
action_view = ActionView::Base.new(ActionController::Base.view_paths,
logo: logo,
title: replace_tags(title, dossier),
body: replace_tags(body, dossier),
signature: signature,
footer: footer,
created_at: Time.now)
attestation_view = action_view.render(file: 'admin/attestation_templates/show',
formats: [:pdf])
view_to_memory_file(attestation_view)
end
def view_to_memory_file(view)
pdf = StringIO.new(view)
def pdf.original_filename
'attestation'
end
pdf
end
def replace_tags(text, dossier)
if text.nil?
return ''
end
text = replace_type_de_champ_tags(text, procedure.types_de_champ, dossier.champs)
text = replace_type_de_champ_tags(text, procedure.types_de_champ_private, dossier.champs_private)
tags_and_datas = [
[dossier_tags, dossier],
[individual_tags, dossier.individual],
[entreprise_tags, dossier.entreprise],
[etablissement_tags, dossier.entreprise&.etablissement]]
tags_and_datas.inject(text) { |acc, (tags, data)| replace_tags_with_values_from_data(acc, tags, data) }
end
def replace_type_de_champ_tags(text, types_de_champ, dossier_champs)
types_de_champ.inject(text) do |acc, tag|
value = dossier_champs
.select { |champ| champ.libelle == tag[:libelle] }
.first
.value
acc.gsub("--#{tag[:libelle]}--", value.to_s)
end
end
def replace_tags_with_values_from_data(text, tags, data)
if data.present?
tags.inject(text) do |acc, tag|
acc.gsub("--#{tag[:libelle]}--", data.send(tag[:target].to_sym).to_s)
end
else
text
end
end
end

View file

@ -23,6 +23,7 @@ class Dossier < ActiveRecord::Base
has_one :etablissement, dependent: :destroy
has_one :entreprise, dependent: :destroy
has_one :individual, dependent: :destroy
has_one :attestation
has_many :cerfa, dependent: :destroy
has_many :pieces_justificatives, dependent: :destroy
@ -169,6 +170,9 @@ class Dossier < ActiveRecord::Base
end
when 'close'
if received?
self.attestation = build_attestation
save
closed!
if motivation
@ -305,6 +309,12 @@ class Dossier < ActiveRecord::Base
private
def build_attestation
if procedure.attestation_template.present? && procedure.attestation_template.activated?
procedure.attestation_template.attestation_for(self)
end
end
def update_state_dates
if initiated? && !self.initiated_at
self.initiated_at = DateTime.now

View file

@ -7,6 +7,7 @@ class Procedure < ActiveRecord::Base
has_one :procedure_path, dependent: :destroy
has_one :module_api_carto, dependent: :destroy
has_one :attestation_template, dependent: :destroy
belongs_to :administrateur
@ -93,6 +94,7 @@ class Procedure < ActiveRecord::Base
:types_de_champ,
:types_de_champ_private,
:module_api_carto,
:attestation_template,
types_de_champ: [:drop_down_list]
])
procedure.archived = false

View file

@ -0,0 +1,26 @@
class AttestationTemplateImageUploader < BaseUploader
def root
File.join(Rails.root, 'public')
end
# Choose what kind of storage to use for this uploader:
if Features.remote_storage
storage :fog
else
storage :file
end
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
unless Features.remote_storage
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
%w(jpg jpeg png)
end
end

View file

@ -0,0 +1,20 @@
class AttestationUploader < BaseUploader
def root
File.join(Rails.root, 'public')
end
# Choose what kind of storage to use for this uploader:
if Features.remote_storage
storage :fog
else
storage :file
end
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
unless Features.remote_storage
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
end

View file

@ -0,0 +1,73 @@
#attestation-template-edit.row.white-back
= form_for @attestation_template, url: admin_procedure_attestation_template_path do |f|
.row
.col-md-10
%h1
Attestation
- if @attestation_template.activated?
%small.text-success Activée
- else
%small Désactivée
%p.notice Les attestation, si elles sont activées, sont délivrées par email aux usagers lorsque leurs dossiers sont acceptés, et sont également disponibles au téléchargement sur leur espace personnel.
.image-upload
- if @attestation_template.logo.present?
= image_tag @attestation_template.logo.url, class: 'thumbnail'
.form-group
= f.label :logo, "Logo de l'attestation"
= f.file_field :logo, accept: 'image/png, image/jpg, image/jpeg'
%p.help-block
Fichier accepté : JPG / JPEG / PNG
%br
Dimensions conseillées : au minimum 500 px de largeur ou de hauteur, poids maximum : 0,5 Mo.
.form-group
= f.label :title, 'Titre'
= f.text_field :title, class: 'form-control'
.form-group
= f.label :body, 'Corps du document'
~ f.text_area :body, class: 'form-control', rows: 10
.row
.col-md-12.balises
%table.table.table-striped
%tr
%th.col-md-3
Balise
%th
Description
- @attestation_template.tags.each do |tag|
%tr
%td
= "--#{tag[:libelle]}--"
%td
= tag[:description]
.image-upload
- if @attestation_template.signature.present?
= image_tag @attestation_template.signature.url, class: 'thumbnail'
.form-group
= f.label :signature, "Tampon de l'attestation"
= f.file_field :signature, accept: 'image/png, image/jpg, image/jpeg'
%p.help-block
Fichier accepté : JPG / JPEG / PNG
%br
Dimensions conseillées : au minimum 500 px de largeur ou de hauteur, poids maximum : 0,5 Mo.
.form-group
= f.label :footer, 'Adresse en bas de page'
~ f.text_area :footer, class: 'form-control', rows: 2,
placeholder: "Direction interministérielle du numérique et du système d'information et de communication de l'Etat (DINSIC)"
%button.btn.btn-primary{ formaction: admin_procedure_attestation_template_preview_path, formtarget: '_blank' } Prévisualiser
.pull-right
- if @attestation_template.activated
%button.btn.btn-warning{ formaction: admin_procedure_attestation_template_disactivate_path } désactiver l'attestation
- if @attestation_template.id.nil? || !@attestation_template.activated
%button.btn.btn-success Activer l'attestation
- else
%button.btn.btn-success Enregistrer

View file

@ -0,0 +1,38 @@
require 'prawn/measurement_extensions'
prawn_document(margin: [50, 100, 20, 100]) do |pdf|
pdf.font_families.update( 'open sans' => { normal: './lib/prawn/fonts/OpenSans-Regular.ttf' })
pdf.font 'open sans'
grey = '555555'
black = '333333'
max_logo_size = 40.mm
max_signature_size = 40.mm
pdf.bounding_box([0, pdf.cursor], width: 400, height: 650) do
if @logo.present?
pdf.image StringIO.new(@logo.read), fit: [max_logo_size , max_logo_size], position: :center
end
pdf.fill_color grey
pdf.pad_top(40) { pdf.text "le #{l(@created_at, format: '%e %B %Y')}", size: 10, align: :right, character_spacing: -0.5 }
pdf.fill_color black
pdf.pad_top(40) { pdf.text @title, size: 18, character_spacing: -0.2 }
pdf.fill_color grey
pdf.pad_top(30) { pdf.text @body, size: 10, character_spacing: -0.2 }
if @signature.present?
pdf.pad_top(40) do
pdf.image StringIO.new(@signature.read), fit: [max_signature_size , max_signature_size], position: :right
end
end
end
pdf.repeat(:all) do
pdf.move_cursor_to 20
pdf.fill_color grey
pdf.text @footer, align: :center, size: 8
end
end

View file

@ -0,0 +1,14 @@
- if dossier.attestation.present?
#attestation-recapitulatif.default-data-block
.row.show-block.clearfix
.header
.title
.carret-right
.carret-down
ATTESTATION
.details
.left
= image_tag('pdf.svg', width: '20px')
%p.title= dossier.attestation.title
%p.delivery Délivrée le #{l(dossier.attestation.created_at, format: '%d %B %Y')}
%a.btn.btn-primary{ href: users_dossier_recapitulatif_attestation_path(dossier), target: '_blank' } Télécharger

View file

@ -1,5 +1,7 @@
= render partial: 'dossiers/edit_avis', locals: { dossier_facade: @facade }
= render partial: 'dossiers/attestation', locals: { dossier: @facade.dossier }
= render partial: 'dossiers/messagerie', locals: { dossier_facade: @facade }
= render partial: 'dossiers/motivation', locals: { dossier_facade: @facade }

View file

@ -0,0 +1 @@
= render partial: 'layouts/left_panels/left_panel_admin_procedurescontroller_navbar', locals: { active: 'Attestation' }

View file

@ -45,6 +45,13 @@
.procedure-list-element{ class: ('active' if active == 'Prévisualisation') }
Prévisualisation
- if @procedure.locked?
%a#onglet-attestation{ href: url_for(admin_procedure_attestation_template_path(@procedure, format: :pdf)), target: '_blank' }
.procedure-list-element{ class: ('active' if active == 'Attestation') } Attestation
- else
%a#onglet-attestation{ href: url_for(edit_admin_procedure_attestation_template_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Attestation') } Attestation
.split-hr-left

View file

@ -82,6 +82,8 @@ Rails.application.routes.draw do
get '/recapitulatif' => 'recapitulatif#show'
post '/recapitulatif/initiate' => 'recapitulatif#initiate'
get '/recapitulatif/attestation' => 'recapitulatif#attestation'
post '/commentaire' => 'commentaires#create'
resources :commentaires, only: [:index]
@ -143,6 +145,14 @@ Rails.application.routes.draw do
resource :accompagnateurs, only: [:show, :update]
resource :previsualisation, only: [:show]
resource :attestation_template, only: [:show, :edit, :update, :create]
post 'attestation_template/disactivate' => 'attestation_templates#disactivate'
patch 'attestation_template/disactivate' => 'attestation_templates#disactivate'
post 'attestation_template/preview' => 'attestation_templates#preview'
patch 'attestation_template/preview' => 'attestation_templates#preview'
end
namespace :accompagnateurs do

View file

@ -0,0 +1,16 @@
class CreateAttestationTemplates < ActiveRecord::Migration[5.0]
def change
create_table :attestation_templates do |t|
t.text :title
t.text :body
t.text :footer
t.string :logo
t.string :signature
t.boolean :activated
t.timestamps
t.references :procedure, index: { unique: true }, foreign_key: true
end
end
end

View file

@ -0,0 +1,11 @@
class CreateAttestations < ActiveRecord::Migration[5.0]
def change
create_table :attestations do |t|
t.string :pdf
t.string :title
t.references :dossier, foreign_key: true, null: false
t.timestamps
end
end
end

View file

@ -10,7 +10,8 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170530141608) do
ActiveRecord::Schema.define(version: 20170601123221) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -70,6 +71,28 @@ ActiveRecord::Schema.define(version: 20170530141608) do
t.index ["procedure_id"], name: "index_assign_tos_on_procedure_id", using: :btree
end
create_table "attestation_templates", force: :cascade do |t|
t.text "title"
t.text "body"
t.text "footer"
t.string "logo"
t.string "signature"
t.boolean "activated"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "procedure_id"
t.index ["procedure_id"], name: "index_attestation_templates_on_procedure_id", unique: true, using: :btree
end
create_table "attestations", force: :cascade do |t|
t.string "pdf"
t.string "title"
t.integer "dossier_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["dossier_id"], name: "index_attestations_on_dossier_id", using: :btree
end
create_table "avis", force: :cascade do |t|
t.string "email"
t.text "introduction"
@ -448,6 +471,8 @@ ActiveRecord::Schema.define(version: 20170530141608) do
t.index ["procedure_id"], name: "index_without_continuation_mails_on_procedure_id", using: :btree
end
add_foreign_key "attestation_templates", "procedures"
add_foreign_key "attestations", "dossiers"
add_foreign_key "avis", "gestionnaires", column: "claimant_id"
add_foreign_key "cerfas", "dossiers"
add_foreign_key "closed_mails", "procedures"

Binary file not shown.

View file

@ -0,0 +1,163 @@
describe Admin::AttestationTemplatesController, type: :controller do
let!(:attestation_template) { create(:attestation_template) }
let(:admin) { create(:administrateur) }
let!(:procedure) { create :procedure, administrateur: admin, attestation_template: attestation_template }
let(:logo) { fixture_file_upload('spec/fixtures/white.png', 'image/png') }
let(:signature) { fixture_file_upload('spec/fixtures/black.png', 'image/png') }
before do
sign_in admin
Timecop.freeze(Time.now)
end
describe 'GET #show' do
before { get :show, params: { procedure_id: procedure.id, format: :pdf } }
it { expect(subject.status).to eq(200) }
it { expect(assigns[:title]).to eq(attestation_template.title) }
it { expect(assigns[:logo].read).to eq(attestation_template.logo.read) }
it { expect(assigns[:body]).to eq(attestation_template.body) }
it { expect(assigns[:signature].read).to eq(attestation_template.signature.read) }
it { expect(assigns[:footer]).to eq(attestation_template.footer) }
it { expect(assigns[:created_at]).to eq(DateTime.now) }
end
describe 'POST #preview' do
let(:upload_params) { { title: 't', body: 'b', footer: 'f' } }
before do
post :preview,
params: { procedure_id: procedure.id,
attestation_template: upload_params }
end
context 'if an attestation template does not exist on the procedure' do
let(:attestation_template) { nil }
it { expect(subject.status).to eq(200) }
it { expect(assigns).to include(upload_params.stringify_keys) }
end
context 'if an attestation template exists on the procedure' do
context 'with logos' do
let!(:attestation_template) do
create(:attestation_template, logo: logo, signature: signature)
end
it { expect(subject.status).to eq(200) }
it { expect(assigns).to include(upload_params.stringify_keys) }
it { expect(assigns[:created_at]).to eq(DateTime.now) }
it { expect(assigns(:logo).read).to eq(logo.read) }
it { expect(assigns(:signature).read).to eq(signature.read) }
after { procedure.attestation_template.destroy }
end
end
end
describe 'GET #edit' do
before { get :edit, params: { procedure_id: procedure.id } }
context 'if an attestation template exists on the procedure' do
it { expect(subject.status).to eq(200) }
it { expect(assigns(:attestation_template)).to eq(attestation_template) }
end
context 'if an attestation template does not exist on the procedure' do
let(:attestation_template) { nil }
it { expect(subject.status).to eq(200) }
it { expect(assigns(:attestation_template).id).to be_nil }
it { expect(assigns(:attestation_template)).to be_an_instance_of(AttestationTemplate) }
end
end
describe 'POST #create' do
let(:attestation_template) { nil }
let(:attestation_params) { { title: 't', body: 'b', footer: 'f' } }
context 'nominal' do
before do
post :create,
params: { procedure_id: procedure.id,
attestation_template: attestation_params.merge(logo: logo, signature: signature) }
procedure.reload
end
it { expect(procedure.attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.activated).to be true }
it { expect(procedure.attestation_template.logo.read).to eq(logo.read) }
it { expect(procedure.attestation_template.signature.read).to eq(signature.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été sauvegardée") }
after { procedure.attestation_template.destroy }
end
context 'when something wrong happens in the attestation template creation' do
before do
expect_any_instance_of(AttestationTemplate).to receive(:save)
.and_return(false)
expect_any_instance_of(AttestationTemplate).to receive(:errors)
.and_return(double(full_messages: ['nop']))
post :create,
params: { procedure_id: procedure.id,
attestation_template: attestation_params }
procedure.reload
end
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.alert).to eq('nop') }
end
end
describe 'PATCH #update' do
let(:attestation_params) { { title: 't', body: 'b', footer: 'f' } }
let(:attestation_params_with_images) { attestation_params.merge(logo: logo, signature: signature) }
context 'nominal' do
before do
patch :update,
params: { procedure_id: procedure.id,
attestation_template: attestation_params_with_images }
procedure.reload
end
it { expect(procedure.attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.logo.read).to eq(logo.read) }
it { expect(procedure.attestation_template.signature.read).to eq(signature.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été modifiée") }
after { procedure.attestation_template.destroy }
end
context 'when something wrong happens in the attestation template creation' do
before do
expect_any_instance_of(AttestationTemplate).to receive(:update_attributes).and_return(false)
expect_any_instance_of(AttestationTemplate).to receive(:errors)
.and_return(double(full_messages: ['nop']))
patch :update,
params: { procedure_id: procedure.id,
attestation_template: attestation_params_with_images }
procedure.reload
end
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.alert).to eq('nop') }
end
end
describe 'post #disactivate' do
context 'when the attestation_template is activated' do
let(:attestation_template) { create(:attestation_template, activated: true) }
before do
post :disactivate, params: { procedure_id: procedure.id }
attestation_template.reload
end
it { expect(attestation_template.activated).to be false }
it { expect(flash.notice).to eq("L'attestation a bien été désactivée") }
end
end
end

View file

@ -268,7 +268,7 @@ describe Backoffice::DossiersController, type: :controller do
it 'Notification email is sent' do
expect(NotificationMailer).to receive(:send_notification)
.with(dossier, kind_of(Mails::RefusedMail)).and_return(NotificationMailer)
.with(dossier, kind_of(Mails::RefusedMail), nil).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_now!)
subject
@ -294,7 +294,7 @@ describe Backoffice::DossiersController, type: :controller do
it 'Notification email is sent' do
expect(NotificationMailer).to receive(:send_notification)
.with(dossier, kind_of(Mails::WithoutContinuationMail)).and_return(NotificationMailer)
.with(dossier, kind_of(Mails::WithoutContinuationMail), nil).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_now!)
subject
@ -304,9 +304,17 @@ describe Backoffice::DossiersController, type: :controller do
end
context "with close" do
let(:expected_attestation) { nil }
before do
dossier.received!
sign_in gestionnaire
expect(NotificationMailer).to receive(:send_notification)
.with(dossier, kind_of(Mails::ClosedMail), expected_attestation)
.and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_now!)
end
subject { post :process_dossier, params: { process_action: "close", dossier_id: dossier_id} }
@ -318,15 +326,42 @@ describe Backoffice::DossiersController, type: :controller do
expect(dossier.state).to eq('closed')
end
it 'Notification email is sent' do
expect(NotificationMailer).to receive(:send_notification)
.with(dossier, kind_of(Mails::ClosedMail)).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_now!)
subject
context 'when the dossier does not have any attestation' do
it 'Notification email is sent' do
subject
end
end
it { is_expected.to redirect_to backoffice_dossier_path(id: dossier.id) }
context 'when the dossier has an attestation' do
let(:emailable) { false }
before do
fake_attestation = double(pdf: double(read: 'pdf', size: 2.megabytes), emailable?: emailable)
allow_any_instance_of(Dossier).to receive(:attestation).and_return(fake_attestation)
end
context 'emailable' do
let(:emailable) { true }
let(:expected_attestation) { 'pdf' }
it 'Notification email is sent with the attestation' do
subject
end
end
context 'when the dossier has an attestation not emailable' do
let(:emailable) { false }
let(:expected_attestation) { nil }
it 'Notification email is sent without the attestation' do
expect(controller).to receive(:capture_message)
subject
end
end
it { is_expected.to redirect_to backoffice_dossier_path(id: dossier.id) }
end
end
end

View file

@ -51,4 +51,22 @@ describe Users::RecapitulatifController, type: :controller do
end
end
end
describe 'Get #attestation' do
context 'when a dossier has an attestation' do
let(:fake_pdf) { double(read: 'pdf content') }
let!(:dossier) { create(:dossier, attestation: Attestation.new) }
it 'returns the attestation pdf' do
allow_any_instance_of(Attestation).to receive(:pdf).and_return(fake_pdf)
expect(controller).to receive(:send_data)
.with('pdf content', filename: 'attestation.pdf', type: 'application/pdf') do
controller.render nothing: true
end
get :attestation, params: { dossier_id: dossier.id }
end
end
end
end

View file

@ -0,0 +1,8 @@
FactoryGirl.define do
factory :attestation_template do
title 'title'
body 'body'
footer 'footer'
activated true
end
end

BIN
spec/fixtures/black.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

BIN
spec/fixtures/white.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

View file

@ -5,11 +5,25 @@ RSpec.describe NotificationMailer, type: :mailer do
let(:user) { create(:user) }
let(:dossier) { create(:dossier, user: user) }
let(:email) { instance_double('email', object_for_dossier: 'object', body_for_dossier: 'body') }
let (:notifications_count_before) { Notification.count }
subject { described_class.send_notification(dossier, email) }
let(:attestation) { nil }
let(:notifications_count_before) { Notification.count }
subject { described_class.send_notification(dossier, email, attestation) }
it { expect(subject.subject).to eq(email.object_for_dossier) }
it { expect(subject.body).to eq(email.body_for_dossier) }
it { expect(subject.attachments['attestation.pdf']).to eq(nil) }
context 'when an attestation is provided' do
let(:attestation) { 'attestation' }
it do
expect(subject.attachments['attestation.pdf'].content_type)
.to eq('application/pdf; filename=attestation.pdf')
expect(subject.attachments['attestation.pdf'].body).to eq('attestation')
end
end
it "creates a commentaire, which is not notified" do
described_class.send_notification(dossier, email).deliver_now

View file

@ -0,0 +1,23 @@
RSpec.describe Attestation, type: :model do
describe 'emailable' do
let(:attestation) do
attestation = Attestation.new
expect(attestation).to receive(:pdf).and_return(double(size: size))
attestation
end
subject { attestation.emailable? }
context 'when the pdf size is acceptable' do
let(:size) { Attestation::MAX_SIZE_EMAILABLE }
it { is_expected.to be true }
end
context 'when the pdf size is unacceptable' do
let(:size) { Attestation::MAX_SIZE_EMAILABLE + 1 }
it { is_expected.to be false }
end
end
end

View file

@ -0,0 +1,244 @@
describe AttestationTemplate, type: :model do
describe 'validate' do
let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte }
let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte }
let(:fake_logo) { double(AttestationTemplateImageUploader, file: double(size: logo_size)) }
let(:fake_signature) { double(AttestationTemplateImageUploader, file: double(size: signature_size)) }
let(:attestation_template) { AttestationTemplate.new }
before do
allow(attestation_template).to receive(:logo).and_return(fake_logo)
allow(attestation_template).to receive(:signature).and_return(fake_signature)
attestation_template.validate
end
subject { attestation_template.errors.details }
context 'when no files are present' do
let(:fake_logo) { nil }
let(:fake_signature) { nil }
it { is_expected.to match({}) }
end
context 'when the logo and the signature have the right size' do
it { is_expected.to match({}) }
end
context 'when the logo and the signature are too heavy' do
let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
it do
expected = {
signature: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }],
logo: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }]
}
is_expected.to match(expected)
end
end
end
describe 'dup' do
before do
@logo = File.open('spec/fixtures/white.png')
@signature = File.open('spec/fixtures/black.png')
end
after do
@logo.close
@signature.close
subject.destroy
end
let(:attestation_template) { AttestationTemplate.create(attributes) }
subject { attestation_template.dup }
context 'with an attestation without images' do
let(:attributes) { { title: 't', body: 'b', footer: 'f', activated: true } }
it { is_expected.to have_attributes(attributes) }
it { is_expected.to have_attributes(id: nil) }
it { expect(subject.logo.file).to be_nil }
end
context 'with an attestation with images' do
let(:attributes) { { logo: @logo, signature: @signature } }
it { expect(subject.logo.file.file).not_to eq(attestation_template.logo.file.file) }
it { expect(subject.logo.file.read).to eq(attestation_template.logo.file.read) }
it { expect(subject.signature.file.file).not_to eq(attestation_template.signature.file.file) }
it { expect(subject.signature.file.read).to eq(attestation_template.signature.file.read) }
end
end
describe 'attestation_for' do
let(:procedure) do
create(:procedure,
types_de_champ: types_de_champ,
types_de_champ_private: types_de_champ_private,
for_individual: for_individual)
end
let(:for_individual) { false }
let(:individual) { nil }
let(:etablissement) { nil }
let(:entreprise) { create(:entreprise, etablissement: etablissement) }
let(:types_de_champ) { [] }
let(:types_de_champ_private) { [] }
let(:dossier) { create(:dossier, procedure: procedure, individual: individual, entreprise: entreprise) }
let(:template_title) { 'title' }
let(:template_body) { 'body' }
let(:attestation_template) do
AttestationTemplate.new(procedure: procedure,
title: template_title,
body: template_body,
logo: @logo,
signature: @signature)
end
before do
@logo = File.open('spec/fixtures/white.png')
@signature = File.open('spec/fixtures/black.png')
Timecop.freeze(Time.now)
end
after do
@logo.close
@signature.close
end
let(:view_args) do
original_new = ActionView::Base.method(:new)
arguments = nil
allow(ActionView::Base).to receive(:new) do |paths, args|
arguments = args
original_new.call(paths, args)
end
attestation_template.attestation_for(dossier)
arguments
end
let(:attestation) { attestation_template.attestation_for(dossier) }
it 'provides a pseudo file' do
expect(attestation.pdf.file).to exist
expect(attestation.pdf.filename).to eq('attestation')
end
context 'when the dossier and the procedure has an individual' do
let(:for_individual) { true }
let(:individual) { Individual.create(nom: 'nom', prenom: 'prenom', gender: 'Mme') }
context 'and the template title use the individual tags' do
let(:template_title) { '--civilité-- --nom-- --prénom--' }
it { expect(view_args[:title]).to eq('Mme nom prenom') }
end
end
context 'when the dossier and the procedure has an entreprise' do
let(:for_individual) { false }
context 'and the template title use the entreprise tags' do
let(:template_title) do
'--SIREN-- --numéro de TVA intracommunautaire-- --SIRET du siège social-- --raison sociale-- --adresse--'
end
let(:expected_title) do
"#{entreprise.siren} #{entreprise.numero_tva_intracommunautaire} #{entreprise.siret_siege_social} #{entreprise.raison_sociale} --adresse--"
end
it { expect(view_args[:title]).to eq(expected_title) }
context 'and the entreprise has a etablissement with an adresse' do
let(:etablissement) { create(:etablissement, adresse: 'adresse') }
let(:template_title) { '--adresse--' }
it { expect(view_args[:title]).to eq('adresse') }
end
end
end
context 'when the procedure has a type de champ named libelleA et libelleB' do
let(:types_de_champ) do
[create(:type_de_champ_public, libelle: 'libelleA'),
create(:type_de_champ_public, libelle: 'libelleB')]
end
context 'and the template title is nil' do
let(:template_title) { nil }
it { expect(view_args[:title]).to eq('') }
end
context 'and it is not used in the template title nor body' do
it { expect(view_args[:title]).to eq('title') }
it { expect(view_args[:body]).to eq('body') }
it { expect(view_args[:created_at]).to eq(Time.now) }
it { expect(view_args[:logo]).to eq(attestation_template.logo) }
it { expect(view_args[:signature]).to eq(attestation_template.signature) }
end
context 'and the are used in the template title and body' do
let(:template_title) { 'title --libelleA--' }
let(:template_body) { 'body --libelleB--' }
context 'and their value in the dossier are nil' do
it { expect(view_args[:title]).to eq('title ') }
end
context 'and their value in the dossier are not nil' do
before :each do
dossier.champs
.select { |champ| champ.libelle == 'libelleA' }
.first
.value = 'libelle1'
dossier.champs
.select { |champ| champ.libelle == 'libelleB' }
.first
.value = 'libelle2'
end
it { expect(view_args[:title]).to eq('title libelle1') }
it { expect(view_args[:body]).to eq('body libelle2') }
it { expect(attestation.title).to eq('title libelle1') }
end
end
end
context 'when the dossier has a motivation' do
let(:dossier) { create(:dossier, motivation: 'motivation') }
context 'and the title has the motivation tag' do
let(:template_title) { 'title --motivation--' }
it { expect(view_args[:title]).to eq('title motivation') }
end
end
context 'when the procedure has a type de champ prive named libelleA' do
let(:types_de_champ_private) { [create(:type_de_champ_private, libelle: 'libelleA')] }
context 'and the are used in the template title' do
let(:template_title) { 'title --libelleA--' }
context 'and its value in the dossier are not nil' do
before :each do
dossier.champs_private
.select { |champ| champ.libelle == 'libelleA' }
.first
.value = 'libelle1'
end
it { expect(view_args[:title]).to eq('title libelle1') }
end
end
end
end
end

View file

@ -886,4 +886,34 @@ describe Dossier do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
describe '.build_attestation' do
let(:attestation_template) { nil }
let(:procedure) { create(:procedure, attestation_template: attestation_template) }
before :each do
dossier.next_step!('gestionnaire', 'close')
dossier.reload
end
context 'when the dossier is in received state ' do
let!(:dossier) { create(:dossier, procedure: procedure, state: :received) }
context 'when the procedure has no attestation' do
it { expect(dossier.attestation).to be_nil }
end
context 'when the procedure has an unactivated attestation' do
let(:attestation_template) { AttestationTemplate.new(activated: false) }
it { expect(dossier.attestation).to be_nil }
end
context 'when the procedure attached has an activated attestation' do
let(:attestation_template) { AttestationTemplate.new(activated: true) }
it { expect(dossier.attestation).not_to be_nil }
end
end
end
end

View file

@ -153,6 +153,17 @@ describe Procedure do
let!(:piece_justificative_1) { create(:type_de_piece_justificative, procedure: procedure, order_place: 1) }
let(:received_mail){ create(:received_mail) }
before do
@logo = File.open('spec/fixtures/white.png')
@signature = File.open('spec/fixtures/black.png')
@attestation_template = create(:attestation_template, procedure: procedure, logo: @logo, signature: @signature)
end
after do
@logo.close
@signature.close
end
subject { procedure.clone }
it 'should duplicate specific objects with different id' do
@ -175,6 +186,8 @@ describe Procedure do
subject.types_de_piece_justificative.zip(procedure.types_de_piece_justificative).each do |stc, ptc|
expect(stc).to have_same_attributes_as(ptc)
end
expect(subject.attestation_template.title).to eq(procedure.attestation_template.title)
end
it 'should duplicate existing mail_templates' do