Merge pull request #3848 from betagouv/dev

2019-05-07-01
This commit is contained in:
Keirua 2019-05-07 16:13:32 +02:00 committed by GitHub
commit 4cedddb6d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 332 additions and 300 deletions

View file

@ -43,4 +43,12 @@
padding-left: $default-spacer * 1.5; padding-left: $default-spacer * 1.5;
} }
} }
.optional-justificatif {
margin-bottom: 16px;
}
input[data-direct-upload-url] {
width: 100%;
}
} }

View file

@ -93,16 +93,17 @@ module Gestionnaires
def terminer def terminer
motivation = params[:dossier] && params[:dossier][:motivation] motivation = params[:dossier] && params[:dossier][:motivation]
justificatif = params[:dossier] && params[:dossier][:justificatif_motivation]
case params[:process_action] case params[:process_action]
when "refuser" when "refuser"
dossier.refuser!(current_gestionnaire, motivation) dossier.refuser!(current_gestionnaire, motivation, justificatif)
flash.notice = "Dossier considéré comme refusé." flash.notice = "Dossier considéré comme refusé."
when "classer_sans_suite" when "classer_sans_suite"
dossier.classer_sans_suite!(current_gestionnaire, motivation) dossier.classer_sans_suite!(current_gestionnaire, motivation, justificatif)
flash.notice = "Dossier considéré comme sans suite." flash.notice = "Dossier considéré comme sans suite."
when "accepter" when "accepter"
dossier.accepter!(current_gestionnaire, motivation) dossier.accepter!(current_gestionnaire, motivation, justificatif)
flash.notice = "Dossier traité avec succès." flash.notice = "Dossier traité avec succès."
end end

View file

@ -0,0 +1,46 @@
module NewAdministrateur
class ProcedureAdministrateursController < AdministrateurController
before_action :retrieve_procedure
def index
end
def create
email = params.require(:administrateur)[:email]&.strip&.downcase
# Find the admin
administrateur = Administrateur.find_by(email: email)
if administrateur.nil?
flash.alert = "Ladministrateur « #{email} » nexiste pas. Invitez-le à demander un compte administrateur à laddresse <a href=#{new_demande_url}>#{new_demande_url}</a>."
return
end
# Prevent duplicates (also enforced in the database in administrateurs_procedures)
if @procedure.administrateurs.include?(administrateur)
flash.alert = "Ladministrateur « #{administrateur.email} » est déjà administrateur de « #{@procedure.libelle} »."
return
end
# Actually add the admin
@procedure.administrateurs << administrateur
@administrateur = administrateur
flash.notice = "Ladministrateur « #{administrateur.email} » a été ajouté à la démarche « #{@procedure.libelle} »."
end
def destroy
administrateur = @procedure.administrateurs.find(params[:id])
# Prevent self-removal (Also enforced in the UI)
if administrateur == current_administrateur
flash.error = "Vous ne pouvez pas vous retirer vous-même dune démarche."
end
# Actually remove the admin
@procedure.administrateurs.delete(administrateur)
@administrateur = administrateur
flash.notice = "Ladministrateur \« #{administrateur.email} » a été retiré de la démarche « #{@procedure.libelle} »."
rescue ActiveRecord::ActiveRecordError => e
flash.alert = e.message
end
end
end

View file

@ -11,3 +11,8 @@ export function motivationCancel() {
document.querySelectorAll('.motivation').forEach(hide); document.querySelectorAll('.motivation').forEach(hide);
show(document.querySelector('.dropdown-items')); show(document.querySelector('.dropdown-items'));
} }
export function showImportJustificatif(name) {
show(document.querySelector('#justificatif_motivation_import_' + name));
hide(document.querySelector('#justificatif_motivation_suggest_' + name));
}

View file

@ -29,7 +29,11 @@ import '../new_design/champs/repetition';
import { toggleCondidentielExplanation } from '../new_design/avis'; import { toggleCondidentielExplanation } from '../new_design/avis';
import { scrollMessagerie } from '../new_design/messagerie'; import { scrollMessagerie } from '../new_design/messagerie';
import { showMotivation, motivationCancel } from '../new_design/state-button'; import {
showMotivation,
motivationCancel,
showImportJustificatif
} from '../new_design/state-button';
import { toggleChart } from '../new_design/toggle-chart'; import { toggleChart } from '../new_design/toggle-chart';
import { replaceSemicolonByComma } from '../new_design/avis'; import { replaceSemicolonByComma } from '../new_design/avis';
@ -40,6 +44,7 @@ const DS = {
scrollMessagerie, scrollMessagerie,
showMotivation, showMotivation,
motivationCancel, motivationCancel,
showImportJustificatif,
toggleChart, toggleChart,
replaceSemicolonByComma replaceSemicolonByComma
}; };

View file

@ -10,8 +10,12 @@ addEventListener(INITIALIZE_EVENT, ({ target, detail: { id, file } }) => {
ProgressBar.init(target, id, file); ProgressBar.init(target, id, file);
}); });
addEventListener(START_EVENT, ({ detail: { id } }) => { addEventListener(START_EVENT, ({ target, detail: { id } }) => {
ProgressBar.start(id); ProgressBar.start(id);
const button = target.form.querySelector('button.primary');
if (button) {
delete button.dataset.confirm;
}
}); });
addEventListener(PROGRESS_EVENT, ({ detail: { id, progress } }) => { addEventListener(PROGRESS_EVENT, ({ detail: { id, progress } }) => {

View file

@ -2,4 +2,8 @@ class Champs::PaysChamp < Champs::TextChamp
def self.pays def self.pays
ApiGeo::API.pays.pluck(:nom) ApiGeo::API.pays.pluck(:nom)
end end
def self.disabled_options
pays.select { |v| (v =~ /^--.*--$/).present? }
end
end end

View file

@ -59,7 +59,13 @@ module TagsSubstitutionConcern
{ {
libelle: 'lien attestation', libelle: 'lien attestation',
description: '', description: '',
lambda: -> (d) { external_link(attestation_dossier_url(d)) }, lambda: -> (d) {
links = [external_link(attestation_dossier_url(d))]
if d.justificatif_motivation.attached?
links.push external_link("Télécharger le justificatif", url_for_justificatif_motivation(d))
end
links.join "<br />\n"
},
available_for_states: [Dossier.states.fetch(:accepte)] available_for_states: [Dossier.states.fetch(:accepte)]
} }
] ]
@ -138,8 +144,14 @@ module TagsSubstitutionConcern
end end
end end
def external_link(url) def external_link(url, title = nil)
link_to(url, url, target: '_blank', rel: 'noopener') link_to(title || url, url, target: '_blank', rel: 'noopener')
end
def url_for_justificatif_motivation(dossier)
if dossier.justificatif_motivation.attached?
Rails.application.routes.url_helpers.url_for(dossier.justificatif_motivation)
end
end end
def dossier_tags def dossier_tags

View file

@ -20,6 +20,8 @@ class Dossier < ApplicationRecord
has_one :attestation, dependent: :destroy has_one :attestation, dependent: :destroy
has_many :pieces_justificatives, dependent: :destroy has_many :pieces_justificatives, dependent: :destroy
has_one_attached :justificatif_motivation
has_many :champs, -> { root.public_only.ordered }, dependent: :destroy has_many :champs, -> { root.public_only.ordered }, dependent: :destroy
has_many :champs_private, -> { root.private_only.ordered }, class_name: 'Champ', dependent: :destroy has_many :champs_private, -> { root.private_only.ordered }, class_name: 'Champ', dependent: :destroy
has_many :commentaires, dependent: :destroy has_many :commentaires, dependent: :destroy
@ -296,10 +298,12 @@ class Dossier < ApplicationRecord
log_dossier_operation(gestionnaire, :repasser_en_construction) log_dossier_operation(gestionnaire, :repasser_en_construction)
end end
def accepter!(gestionnaire, motivation) def accepter!(gestionnaire, motivation, justificatif = nil)
self.motivation = motivation self.motivation = motivation
self.en_instruction_at ||= Time.zone.now self.en_instruction_at ||= Time.zone.now
if justificatif
self.justificatif_motivation.attach(justificatif)
end
accepte! accepte!
if attestation.nil? if attestation.nil?
@ -330,20 +334,24 @@ class Dossier < ApplicationRecord
DeletedDossier.create_from_dossier(self) DeletedDossier.create_from_dossier(self)
end end
def refuser!(gestionnaire, motivation) def refuser!(gestionnaire, motivation, justificatif = nil)
self.motivation = motivation self.motivation = motivation
self.en_instruction_at ||= Time.zone.now self.en_instruction_at ||= Time.zone.now
if justificatif
self.justificatif_motivation.attach(justificatif)
end
refuse! refuse!
NotificationMailer.send_refused_notification(self).deliver_later NotificationMailer.send_refused_notification(self).deliver_later
log_dossier_operation(gestionnaire, :refuser) log_dossier_operation(gestionnaire, :refuser)
end end
def classer_sans_suite!(gestionnaire, motivation) def classer_sans_suite!(gestionnaire, motivation, justificatif = nil)
self.motivation = motivation self.motivation = motivation
self.en_instruction_at ||= Time.zone.now self.en_instruction_at ||= Time.zone.now
if justificatif
self.justificatif_motivation.attach(justificatif)
end
sans_suite! sans_suite!
NotificationMailer.send_without_continuation_notification(self).deliver_later NotificationMailer.send_without_continuation_notification(self).deliver_later

View file

@ -1,6 +1,8 @@
require Rails.root.join('lib', 'percentile') require Rails.root.join('lib', 'percentile')
class Procedure < ApplicationRecord class Procedure < ApplicationRecord
self.ignored_columns = [:administrateur_id]
MAX_DUREE_CONSERVATION = 36 MAX_DUREE_CONSERVATION = 36
has_many :types_de_piece_justificative, -> { ordered }, dependent: :destroy has_many :types_de_piece_justificative, -> { ordered }, dependent: :destroy
@ -17,7 +19,7 @@ class Procedure < ApplicationRecord
has_many :assign_to, dependent: :destroy has_many :assign_to, dependent: :destroy
has_many :administrateurs_procedures has_many :administrateurs_procedures
has_many :administrateurs, through: :administrateurs_procedures has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! }
has_many :gestionnaires, through: :assign_to has_many :gestionnaires, through: :assign_to
has_one :initiated_mail, class_name: "Mails::InitiatedMail", dependent: :destroy has_one :initiated_mail, class_name: "Mails::InitiatedMail", dependent: :destroy
@ -57,6 +59,7 @@ class Procedure < ApplicationRecord
validates :libelle, presence: true, allow_blank: false, allow_nil: false validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :description, presence: true, allow_blank: false, allow_nil: false validates :description, presence: true, allow_blank: false, allow_nil: false
validates :administrateurs, presence: true
validate :check_juridique validate :check_juridique
validates :path, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: :aasm_state, case_sensitive: false }, presence: true, allow_blank: false, allow_nil: true validates :path, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: :aasm_state, case_sensitive: false }, presence: true, allow_blank: false, allow_nil: true
# FIXME: remove duree_conservation_required flag once all procedures are converted to the new style # FIXME: remove duree_conservation_required flag once all procedures are converted to the new style

View file

@ -22,6 +22,7 @@ class DossierSerializer < ActiveModel::Serializer
has_many :champs_private has_many :champs_private
has_many :pieces_justificatives has_many :pieces_justificatives
has_many :types_de_piece_justificative has_many :types_de_piece_justificative
has_one :justificatif_motivation
has_many :champs, serializer: ChampSerializer has_many :champs, serializer: ChampSerializer
@ -52,6 +53,12 @@ class DossierSerializer < ActiveModel::Serializer
PiecesJustificativesService.serialize_champs_as_pjs(object) PiecesJustificativesService.serialize_champs_as_pjs(object)
end end
def justificatif_motivation
if object.justificatif_motivation.attached?
Rails.application.routes.url_helpers.url_for(object.justificatif_motivation)
end
end
def types_de_piece_justificative def types_de_piece_justificative
ActiveModelSerializers::SerializableResource.new(object.types_de_piece_justificative).serializable_hash + ActiveModelSerializers::SerializableResource.new(object.types_de_piece_justificative).serializable_hash +
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object) PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)

View file

@ -60,6 +60,7 @@
- if dossier.motivation.present? - if dossier.motivation.present?
%h4 Motivation %h4 Motivation
%p.dossier-motivation= dossier.motivation %p.dossier-motivation= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present? - if dossier.attestation.present?
%h4 Attestation %h4 Attestation

View file

@ -28,6 +28,10 @@
%li= unspecified_annotations_privee.libelle %li= unspecified_annotations_privee.libelle
- else - else
= text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: placeholder, required: true = text_area :dossier, :motivation, class: 'motivation-text-area', placeholder: placeholder, required: true
.optional-justificatif{ id: "justificatif_motivation_suggest_#{popup_class}", onclick: "DS.showImportJustificatif('#{popup_class}');" }
.button Ajouter un justificatif (optionnel)
.hidden{ id: "justificatif_motivation_import_#{popup_class}" }
= file_field :dossier, :justificatif_motivation, direct_upload: true
.text-right .text-right
- if title == 'Accepter' && dossier.procedure.attestation_template&.activated? - if title == 'Accepter' && dossier.procedure.attestation_template&.activated?
= link_to "Voir l'attestation", apercu_attestation_gestionnaire_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener', class: 'button', title: "Voir l'attestation qui sera envoyée au demandeur" = link_to "Voir l'attestation", apercu_attestation_gestionnaire_dossier_path(dossier.procedure, dossier), target: '_blank', rel: 'noopener', class: 'button', title: "Voir l'attestation qui sera envoyée au demandeur"

View file

@ -19,7 +19,7 @@
= stylesheet_link_tag 'new_design/new_application', media: 'all', 'data-turbolinks-track': 'reload' = stylesheet_link_tag 'new_design/new_application', media: 'all', 'data-turbolinks-track': 'reload'
= stylesheet_link_tag 'new_design/print', media: 'print', 'data-turbolinks-track': 'reload' = stylesheet_link_tag 'new_design/print', media: 'print', 'data-turbolinks-track': 'reload'
= Gon::Base.render_data(camel_case: true, init: true) = Gon::Base.render_data(camel_case: true, init: true, nonce: request.content_security_policy_nonce)
- if Rails.env.development? - if Rails.env.development?
= stylesheet_link_tag :xray = stylesheet_link_tag :xray

View file

@ -27,6 +27,10 @@
- if @procedure.missing_steps.include?(:service) - if @procedure.missing_steps.include?(:service)
%p.missing-steps (à compléter) %p.missing-steps (à compléter)
%a#onglet-administrateurs{ href: url_for(procedure_administrateurs_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Administrateurs') }
Administrateurs
%a#onglet-instructeurs{ href: url_for(admin_procedure_instructeurs_path(@procedure)) } %a#onglet-instructeurs{ href: url_for(admin_procedure_instructeurs_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Instructeurs') } .procedure-list-element{ class: ('active' if active == 'Instructeurs') }
Instructeurs Instructeurs

View file

@ -0,0 +1,13 @@
%tr{ id: "procedure-#{@procedure.id}-administrateur-#{administrateur.id}" }
%td= administrateur.email
%td= administrateur.created_at.strftime('%d/%m/%Y %H:%M')
%td= administrateur.registration_state
%td
- if administrateur == current_administrateur
Cest vous !
- else
= link_to 'Retirer',
[@procedure, administrateur],
method: :delete,
'data-confirm': "Retirer « #{administrateur.email} » des administrateurs de « #{@procedure.libelle} » ?",
remote: true

View file

@ -0,0 +1,6 @@
= render_flash(sticky: true)
- if @administrateur
= append_to_element("#procedure-#{@procedure.id}-administrateurs",
partial: 'administrateur',
locals: { administrateur: @administrateur })
document.getElementById('procedure-#{@procedure.id}-new_administrateur').reset()

View file

@ -0,0 +1,4 @@
= render_flash(sticky: true)
- if @administrateur
= remove_element("#procedure-#{@procedure.id}-administrateur-#{@administrateur.id}")

View file

@ -0,0 +1,26 @@
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to('Démarches', admin_procedures_path),
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
'Administrateurs'], preview: false }
.container
%h1 Administrateurs de « #{@procedure.libelle} »
%table.table
%thead
%th= 'Adresse email'
%th= 'Enregistré le'
%th= 'État'
%tbody{ id: "procedure-#{@procedure.id}-administrateurs" }
= render partial: 'administrateur', collection: @procedure.administrateurs.order(:email)
%tfoot
%tr
%th{ colspan: 4 }
= form_for @procedure.administrateurs.new,
url: { controller: 'procedure_administrateurs' },
html: { class: 'form', id: "procedure-#{@procedure.id}-new_administrateur" } ,
remote: true do |f|
= f.label :email do
Ajouter un administrateur
%span.notice= "Renseignez lemail dun administrateur déjà enregistré sur demarches-simplifiees.fr pour lui permettre de modifier « #{@procedure.libelle} »."
= f.email_field :email, placeholder: 'marie.dupont@exemple.fr', required: true
= f.submit 'Ajouter comme administrateur', class: 'button primary send'

View file

@ -0,0 +1,7 @@
- if dossier.present?
- justificatif = dossier.justificatif_motivation
- if dossier.justificatif_motivation.attached? and dossier.justificatif_motivation.virus_scanner.safe?
.action
= link_to (justificatif), target: '_blank', class: 'button primary' do
%span.icon.download
Télécharger le justificatif

View file

@ -1,7 +1,7 @@
.container .container
- if dossier.en_construction_at.present? - if dossier.en_construction_at.present?
.card .card
= render partial: "shared/dossiers/horodatage", locals: { dossier: dossier } = render partial: "shared/dossiers/infos_generales", locals: { dossier: dossier }
.tab-title Identité du demandeur .tab-title Identité du demandeur
.card .card

View file

@ -1,5 +0,0 @@
%table.table.vertical.dossier-champs
%tbody
%tr
%th.libelle Déposé le :
%td= l(dossier.en_construction_at, format: '%d %B %Y')

View file

@ -0,0 +1,11 @@
%table.table.vertical.dossier-champs
%tbody
%tr
%th.libelle Déposé le :
%td= l(dossier.en_construction_at, format: '%d %B %Y')
- if dossier.justificatif_motivation.attached?
%tr
%th.libelle Justificatif :
%td
.action
= render partial: 'shared/piece_jointe/pj_link', locals: { object: dossier, pj: dossier.justificatif_motivation }

View file

@ -1,4 +1,5 @@
= form.select :value, = form.select :value,
Champs::PaysChamp.pays, Champs::PaysChamp.pays,
disabled: Champs::PaysChamp.disabled_options,
include_blank: true, include_blank: true,
required: champ.mandatory? required: champ.mandatory?

View file

@ -50,12 +50,15 @@
%h3 Motif de lacceptation %h3 Motif de lacceptation
%blockquote= dossier.motivation %blockquote= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: dossier }
- if dossier.attestation.present? - if dossier.attestation.present?
.action .action
= link_to attestation_dossier_path(dossier), target: '_blank', rel: 'noopener', class: 'button primary' do = link_to attestation_dossier_path(dossier), target: '_blank', rel: 'noopener', class: 'button primary' do
%span.icon.download %span.icon.download
Télécharger lattestation Télécharger lattestation
- elsif dossier.refuse? - elsif dossier.refuse?
.refuse .refuse
%p.decision %p.decision
@ -68,6 +71,7 @@
%h3 Motif du refus %h3 Motif du refus
%blockquote= dossier.motivation %blockquote= dossier.motivation
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
.action .action
= link_to 'Envoyer un message à ladministration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button' = link_to 'Envoyer un message à ladministration', messagerie_dossier_url(dossier, anchor: 'new_commentaire'), class: 'button'
@ -79,6 +83,8 @@
= succeed '.' do = succeed '.' do
%strong sans suite %strong sans suite
= render partial: 'new_user/dossiers/show/download_justificatif', locals: { dossier: @dossier }
- if dossier.motivation.present? - if dossier.motivation.present?
%h3 Motif du classement sans suite %h3 Motif du classement sans suite
%blockquote= dossier.motivation %blockquote= dossier.motivation

View file

@ -59,6 +59,9 @@ Rails.application.configure do
port: 3000 port: 3000
} }
# Use Content-Security-Policy-Report-Only instead of Content-Security-Policy
config.content_security_policy_report_only = true
# Raises error for missing translations # Raises error for missing translations
# config.action_view.raise_on_missing_translations = true # config.action_view.raise_on_missing_translations = true

View file

@ -109,5 +109,7 @@ Rails.application.configure do
host: ENV['APP_HOST'] host: ENV['APP_HOST']
} }
config.content_security_policy_report_only = true
config.lograge.enabled = ENV['LOGRAGE_ENABLED'] == 'enabled' config.lograge.enabled = ENV['LOGRAGE_ENABLED'] == 'enabled'
end end

View file

@ -45,6 +45,9 @@ Rails.application.configure do
protocol: :http protocol: :http
} }
# Use Content-Security-Policy-Report-Only instead of Content-Security-Policy
config.content_security_policy_report_only = true
config.active_job.queue_adapter = :test config.active_job.queue_adapter = :test
config.active_storage.service = :test config.active_storage.service = :test

View file

@ -0,0 +1,23 @@
Rails.application.config.content_security_policy do |policy|
# En cas de non respect d'une des règles, faire un POST sur cette URL
policy.report_uri "https://e30e0ed9c14194254481124271b34a72.report-uri.com/r/d/csp/reportOnly"
# Whitelist image
policy.img_src :self, "https://*.openstreetmap.org"
# Whitelist JS: nous, sendinblue et matomo, et… miniprofiler :(
if Rails.env.development?
# https://github.com/MiniProfiler/rack-mini-profiler/issues/327
policy.script_src :self, "https://sibautomation.com", "//stats.data.gouv.fr", :unsafe_eval, :unsafe_inline
else
policy.script_src :self, "https://sibautomation.com", "//stats.data.gouv.fr"
end
# Génération d'un nonce pour les balises script inline qu'on maitrise (Gon)
Rails.application.config.content_security_policy_nonce_generator = -> _request { SecureRandom.base64(16) }
# Pour les CSS, on a beaucoup de style inline et quelques balises <style>
# c'est trop compliqué pour être rectifié immédiatement (et sans valeur ajoutée:
# c'est hardcodé dans les vues, donc pas injectable).
policy.style_src :self, :unsafe_inline
# Pour tout le reste, par défaut on accepte uniquement ce qui vient de chez nous
# et dans la notification on inclue la source de l'erreur
policy.default_src :self, :data, :report_sample
end

View file

@ -374,6 +374,8 @@ Rails.application.routes.draw do
get 'annotations' get 'annotations'
end end
resources :administrateurs, controller: 'procedure_administrateurs', only: [:index, :create, :destroy]
resources :types_de_champ, only: [:create, :update, :destroy] do resources :types_de_champ, only: [:create, :update, :destroy] do
member do member do
patch :move patch :move

View file

@ -1,32 +0,0 @@
namespace :after_party do
desc 'Deployment task: add_path_to_procedures'
task add_path_to_procedures: :environment do
puts "Running deploy task 'add_path_to_procedures'"
def print_procedure(procedure)
puts "#{procedure.id}##{procedure.path} - #{procedure.errors.full_messages}"
end
puts "Démarches publiées :"
Procedure.publiees.where(path: nil).find_each do |procedure|
procedure.path = procedure.path
if !procedure.save
print_procedure(procedure)
end
end
puts "Démarches archivées :"
Procedure.archivees.where(path: nil).find_each do |procedure|
if procedure.procedure_path.present?
procedure.path = procedure.path
if !procedure.save
print_procedure(procedure)
end
end
end
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord.create version: '20180913161001'
end
end

View file

@ -1,209 +0,0 @@
require Rails.root.join("lib", "tasks", "task_helper")
namespace :after_party do
desc 'Deployment task: restore_deleted_dossiers'
task restore_deleted_dossiers: :environment do
Class.new do
def run
rake_puts "Running deploy task 'restore_deleted_dossiers'"
restore_candidats_libres_deleted_dossiers
restore_neph_deleted_dossiers
AfterParty::TaskRecord.create version: '20181009130216'
end
def restore_candidats_libres_deleted_dossiers
mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
def setup_mapping
champ_opts = {
16 => {
source_overrides: { 'libelle' => 'Adresse postale du candidat' },
destination_overrides: { 'libelle' => 'Adresse postale complète du candidat' }
}
}
(0..23).each do |i|
map_source_to_destination_champ(i, i, **(champ_opts[i] || {}))
end
end
end
private_mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
def setup_mapping
compute_destination_champ(
TypeDeChamp.new(
type_champ: 'datetime',
order_place: 0,
libelle: 'Date et heure de convocation',
mandatory: false
)
) do |d, target_tdc|
target_tdc.champ.create(dossier: d)
end
compute_destination_champ(
TypeDeChamp.new(
type_champ: 'text',
order_place: 1,
libelle: 'Lieu de convocation',
mandatory: false
)
) do |d, target_tdc|
target_tdc.champ.create(dossier: d)
end
compute_destination_champ(
TypeDeChamp.new(
type_champ: 'address',
order_place: 2,
libelle: 'Adresse centre examen',
mandatory: false
)
) do |d, target_tdc|
target_tdc.champ.create(dossier: d)
end
end
end
pj_mapping = Class.new(Tasks::DossierProcedureMigrator::PieceJustificativeMapping) do
def setup_mapping
(0..3).each do |i|
map_source_to_destination_pj(i, i + 2)
end
leave_destination_pj_blank(
TypeDePieceJustificative.new(
order_place: 0,
libelle: "Télécharger la Charte de l'accompagnateur"
)
)
leave_destination_pj_blank(
TypeDePieceJustificative.new(
order_place: 1,
libelle: "Télécharger l'attestation d'assurance"
)
)
end
end
restore_deleted_dossiers(4860, 8603, mapping, private_mapping, pj_mapping)
end
def restore_neph_deleted_dossiers
mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
def can_migrate?(dossier)
!(dossier.termine? ||
dossier.champs.joins(:type_de_champ).find_by(types_de_champ: { order_place: 3 }).value&.include?('"Demande de duplicata de dossier d\'inscription (suite perte)"'))
end
def setup_mapping
champ_opts = {
3 => {
source_overrides: { 'drop_down' => ["", "Demande de réactualisation du numéro NEPH", "Demande de communication du numéro NEPH", "Demande de duplicata de dossier d'inscription (suite perte)", "Demande de correction sur le Fichier National des Permis de conduire"] },
destination_overrides: { 'drop_down' => ["", "Demande de réactualisation du numéro NEPH", "Demande de communication du numéro NEPH", "Demande de correction sur le Fichier National des Permis de conduire"] }
}
}
(0..14).each do |i|
map_source_to_destination_champ(i, i, **(champ_opts[i] || {}))
end
(16..22).each do |i|
map_source_to_destination_champ(i, i + 2, **(champ_opts[i] || {}))
end
discard_source_champ(
TypeDeChamp.new(
type_champ: 'address',
order_place: 15,
libelle: 'Adresse du candidat'
)
)
compute_destination_champ(
TypeDeChamp.new(
type_champ: 'address',
order_place: 15,
libelle: 'Adresse du candidat',
mandatory: true
)
) do |d, target_tdc|
value = d.champs.joins(:type_de_champ).find_by(types_de_champ: { order_place: 3 }).value
if !d.brouillon?
value ||= 'non renseigné'
end
target_tdc.champ.create(dossier: d, value: value)
end
compute_destination_champ(
TypeDeChamp.new(
type_champ: 'address',
order_place: 16,
libelle: 'Code postal',
mandatory: true
)
) do |d, target_tdc|
target_tdc.champ.create(dossier: d, value: d.brouillon? ? nil : 'non renseigné')
end
compute_destination_champ(
TypeDeChamp.new(
type_champ: 'address',
order_place: 17,
libelle: 'Ville',
mandatory: true
)
) do |d, target_tdc|
target_tdc.champ.create(dossier: d, value: d.brouillon? ? nil : 'non renseigné')
end
end
end
private_mapping = Class.new(Tasks::DossierProcedureMigrator::ChampMapping) do
def setup_mapping
(0..2).each do |i|
map_source_to_destination_champ(i, i)
end
end
end
pj_mapping = Class.new(Tasks::DossierProcedureMigrator::PieceJustificativeMapping) do
def setup_mapping
(0..3).each do |i|
map_source_to_destination_pj(i, i)
end
end
end
restore_deleted_dossiers(6388, 8770, mapping, private_mapping, pj_mapping)
end
def restore_deleted_dossiers(deleted_procedure_id, new_procedure_id, champ_mapping, champ_private_mapping, pj_mapping)
source_procedure = Procedure.unscoped.find(deleted_procedure_id)
destination_procedure = Procedure.find(new_procedure_id)
deleted_dossiers = Dossier.unscoped
.where(procedure_id: deleted_procedure_id)
.where('dossiers.hidden_at >= ?', source_procedure.hidden_at)
deleted_dossier_ids = deleted_dossiers.pluck(:id).to_a
deleted_dossiers.update_all(hidden_at: nil)
source_procedure
.update_columns(
hidden_at: nil,
archived_at: source_procedure.hidden_at,
aasm_state: :archivee
)
migrator = Tasks::DossierProcedureMigrator.new(source_procedure, destination_procedure, champ_mapping, champ_private_mapping, pj_mapping) do |dossier|
DossierMailer.notify_undelete_to_user(dossier).deliver_later
end
migrator.check_consistency
migrator.migrate_dossiers
source_procedure.dossiers.where(id: deleted_dossier_ids).find_each do |dossier|
if dossier.termine?
DossierMailer.notify_undelete_to_user(dossier).deliver_later
else
rake_puts "Dossier #{dossier.id} non migré\n"
DossierMailer.notify_unmigrated_to_user(dossier, destination_procedure).deliver_later
end
end
end
end.new.run
end
end

View file

@ -1,10 +0,0 @@
namespace :after_party do
desc 'Deployment task: remove_invite_gestionnaires'
task remove_invite_gestionnaires: :environment do
InviteGestionnaire.destroy_all
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord.create version: '20181010102500'
end
end

View file

@ -139,7 +139,7 @@ describe API::V1::DossiersController do
let!(:dossier) { Timecop.freeze(date_creation) { create(:dossier, :with_entreprise, :en_construction, procedure: procedure, motivation: "Motivation") } } let!(:dossier) { Timecop.freeze(date_creation) { create(:dossier, :with_entreprise, :en_construction, procedure: procedure, motivation: "Motivation") } }
let(:dossier_id) { dossier.id } let(:dossier_id) { dossier.id }
let(:body) { JSON.parse(retour.body, symbolize_names: true) } let(:body) { JSON.parse(retour.body, symbolize_names: true) }
let(:field_list) { [:id, :created_at, :updated_at, :archived, :individual, :entreprise, :etablissement, :cerfa, :types_de_piece_justificative, :pieces_justificatives, :champs, :champs_private, :commentaires, :state, :simplified_state, :initiated_at, :processed_at, :received_at, :motivation, :email, :instructeurs] } let(:field_list) { [:id, :created_at, :updated_at, :archived, :individual, :entreprise, :etablissement, :cerfa, :types_de_piece_justificative, :pieces_justificatives, :champs, :champs_private, :commentaires, :state, :simplified_state, :initiated_at, :processed_at, :received_at, :motivation, :email, :instructeurs, :justificatif_motivation] }
subject { body[:dossier] } subject { body[:dossier] }
it 'return REST code 200', :show_in_doc do it 'return REST code 200', :show_in_doc do

View file

@ -7,6 +7,7 @@ describe Gestionnaires::DossiersController, type: :controller do
let(:gestionnaires) { [gestionnaire] } let(:gestionnaires) { [gestionnaire] }
let(:procedure) { create(:procedure, :published, gestionnaires: gestionnaires) } let(:procedure) { create(:procedure, :published, gestionnaires: gestionnaires) }
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
let(:fake_justificatif) { Rack::Test::UploadedFile.new("./spec/fixtures/files/piece_justificative_0.pdf", 'application/pdf') }
before { sign_in(gestionnaire) } before { sign_in(gestionnaire) }
@ -141,24 +142,39 @@ describe Gestionnaires::DossiersController, type: :controller do
sign_in gestionnaire sign_in gestionnaire
end end
subject { post :terminer, params: { process_action: "refuser", procedure_id: procedure.id, dossier_id: dossier.id }, format: 'js' } context 'simple refusal' do
subject { post :terminer, params: { process_action: "refuser", procedure_id: procedure.id, dossier_id: dossier.id }, format: 'js' }
it 'change state to refuse' do it 'change state to refuse' do
subject subject
dossier.reload dossier.reload
expect(dossier.state).to eq(Dossier.states.fetch(:refuse)) expect(dossier.state).to eq(Dossier.states.fetch(:refuse))
expect(dossier.justificatif_motivation).to_not be_attached
end
it 'Notification email is sent' do
expect(NotificationMailer).to receive(:send_refused_notification)
.with(dossier).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_later)
subject
end
end end
it 'Notification email is sent' do context 'refusal with a justificatif' do
expect(NotificationMailer).to receive(:send_refused_notification) subject { post :terminer, params: { process_action: "refuser", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { justificatif_motivation: fake_justificatif } }, format: 'js' }
.with(dossier).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_later)
subject it 'attachs a justificatif' do
subject
dossier.reload
expect(dossier.state).to eq(Dossier.states.fetch(:refuse))
expect(dossier.justificatif_motivation).to be_attached
end
it { expect(subject.body).to include('.state-button') }
end end
it { expect(subject.body).to include('.state-button') }
end end
context "with classer_sans_suite" do context "with classer_sans_suite" do
@ -166,25 +182,41 @@ describe Gestionnaires::DossiersController, type: :controller do
dossier.en_instruction! dossier.en_instruction!
sign_in gestionnaire sign_in gestionnaire
end end
context 'without attachment' do
subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure.id, dossier_id: dossier.id }, format: 'js' }
subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure.id, dossier_id: dossier.id }, format: 'js' } it 'change state to sans_suite' do
subject
it 'change state to sans_suite' do dossier.reload
subject expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite))
expect(dossier.justificatif_motivation).to_not be_attached
end
dossier.reload it 'Notification email is sent' do
expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite)) expect(NotificationMailer).to receive(:send_without_continuation_notification)
.with(dossier).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_later)
subject
end
it { expect(subject.body).to include('.state-button') }
end end
it 'Notification email is sent' do context 'with attachment' do
expect(NotificationMailer).to receive(:send_without_continuation_notification) subject { post :terminer, params: { process_action: "classer_sans_suite", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { justificatif_motivation: fake_justificatif } }, format: 'js' }
.with(dossier).and_return(NotificationMailer)
expect(NotificationMailer).to receive(:deliver_later)
subject it 'change state to sans_suite' do
subject
dossier.reload
expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite))
expect(dossier.justificatif_motivation).to be_attached
end
it { expect(subject.body).to include('.state-button') }
end end
it { expect(subject.body).to include('.state-button') }
end end
context "with accepter" do context "with accepter" do
@ -206,6 +238,7 @@ describe Gestionnaires::DossiersController, type: :controller do
dossier.reload dossier.reload
expect(dossier.state).to eq(Dossier.states.fetch(:accepte)) expect(dossier.state).to eq(Dossier.states.fetch(:accepte))
expect(dossier.justificatif_motivation).to_not be_attached
end end
context 'when the dossier does not have any attestation' do context 'when the dossier does not have any attestation' do
@ -261,6 +294,20 @@ describe Gestionnaires::DossiersController, type: :controller do
it { subject } it { subject }
end end
context 'with an attachment' do
subject { post :terminer, params: { process_action: "accepter", procedure_id: procedure.id, dossier_id: dossier.id, dossier: { justificatif_motivation: fake_justificatif } }, format: 'js' }
it 'change state to accepte' do
subject
dossier.reload
expect(dossier.state).to eq(Dossier.states.fetch(:accepte))
expect(dossier.justificatif_motivation).to be_attached
end
it { expect(subject.body).to include('.state-button') }
end
end end
end end

View file

@ -5,6 +5,7 @@ describe MailTemplateConcern do
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure: procedure) }
let(:dossier2) { create(:dossier, procedure: procedure) } let(:dossier2) { create(:dossier, procedure: procedure) }
let(:initiated_mail) { create(:initiated_mail, procedure: procedure) } let(:initiated_mail) { create(:initiated_mail, procedure: procedure) }
let(:justificatif) { Rack::Test::UploadedFile.new("./spec/fixtures/files/piece_justificative_0.pdf", 'application/pdf') }
shared_examples "can replace tokens in template" do shared_examples "can replace tokens in template" do
describe 'with no token to replace' do describe 'with no token to replace' do
@ -74,10 +75,21 @@ describe MailTemplateConcern do
mail.body = "--lien attestation--" mail.body = "--lien attestation--"
end end
describe "in closed mail" do describe "in closed mail without justificatif" do
let(:mail) { create(:closed_mail, procedure: procedure) }
it { is_expected.to eq("<a target=\"_blank\" rel=\"noopener\" href=\"http://localhost:3000/dossiers/#{dossier.id}/attestation\">http://localhost:3000/dossiers/#{dossier.id}/attestation</a>") }
it { is_expected.to_not include("Télécharger le justificatif") }
end
describe "in closed mail with justificatif" do
before do
dossier.justificatif_motivation.attach(justificatif)
end
let(:mail) { create(:closed_mail, procedure: procedure) } let(:mail) { create(:closed_mail, procedure: procedure) }
it { is_expected.to eq("<a target=\"_blank\" rel=\"noopener\" href=\"http://localhost:3000/dossiers/#{dossier.id}/attestation\">http://localhost:3000/dossiers/#{dossier.id}/attestation</a>") } it { expect(dossier.justificatif_motivation).to be_attached }
it { is_expected.to include("<a target=\"_blank\" rel=\"noopener\" href=\"http://localhost:3000/dossiers/#{dossier.id}/attestation\">http://localhost:3000/dossiers/#{dossier.id}/attestation</a>") }
it { is_expected.to include("Télécharger le justificatif") }
end end
describe "in refuse mail" do describe "in refuse mail" do
@ -85,6 +97,12 @@ describe MailTemplateConcern do
it { is_expected.to eq("--lien attestation--") } it { is_expected.to eq("--lien attestation--") }
end end
describe "in without continuation mail" do
let(:mail) { create(:without_continuation_mail, procedure: procedure) }
it { is_expected.to eq("--lien attestation--") }
end
end end
end end

View file

@ -168,6 +168,10 @@ describe Procedure do
it { is_expected.to allow_value('URRSAF').for(:organisation) } it { is_expected.to allow_value('URRSAF').for(:organisation) }
end end
context 'administrateurs' do
it { is_expected.not_to allow_value([]).for(:administrateurs) }
end
context 'juridique' do context 'juridique' do
it { is_expected.not_to allow_value(nil).for(:cadre_juridique) } it { is_expected.not_to allow_value(nil).for(:cadre_juridique) }
it { is_expected.to allow_value('text').for(:cadre_juridique) } it { is_expected.to allow_value('text').for(:cadre_juridique) }