Merge pull request #3311 from betagouv/dev

2019-01-21-01
This commit is contained in:
Pierre de La Morinerie 2019-01-21 15:40:52 +01:00 committed by GitHub
commit f00736d7b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 733 additions and 103 deletions

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="M6 18.857C6 20.037 6.749 21 7.667 21h8.666C17.25 21 18 20.037 18 18.857V6H6v12.857zM4 6h16M9 11v5m3-5v5m3-5v5M8.5 4.889c0-.858.696-1.89 1.556-1.89h3.888c.86 0 1.556 1.032 1.556 1.89" stroke="#4393F3" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="M0 0h24v24H0z"/></g></svg>

After

Width:  |  Height:  |  Size: 427 B

View file

@ -60,6 +60,10 @@
color: #FFFFFF;
border-color: $medium-red;
background-color: $medium-red;
> .icon {
filter: brightness(100);
}
}
}

View file

@ -0,0 +1,11 @@
@import "constants";
.commencer {
.button:first-of-type {
margin-top: 4 * $default-spacer;
}
.button {
margin-bottom: 2 * $default-spacer;
}
}

View file

@ -70,10 +70,17 @@
width: 110px;
}
.follow-col {
width: 200px;
.action-col {
text-align: right;
padding-left: $default-spacer;
padding-right: $default-spacer;
}
.follow-col {
width: 200px;
}
.delete-col {
width: 150px;
}
}

View file

@ -5,6 +5,10 @@
h1 {
text-align: center;
margin-bottom: 20px;
@media (max-width: $two-columns-breakpoint) {
font-size: 28px;
}
}
hr {

View file

@ -105,4 +105,8 @@
&.meh {
background-image: image-url("icons/meh-regular.svg");
}
&.delete {
background-image: image-url("icons/trash.svg");
}
}

View file

@ -5,8 +5,8 @@ module ProcedureContextConcern
include Devise::StoreLocationExtension
def restore_procedure_context
if stored_procedure_id.present?
@procedure = Procedure.publiees.find_by(id: stored_procedure_id)
if has_stored_procedure_path?
@procedure = find_procedure_in_context
if @procedure.blank?
invalid_procedure_context
@ -16,11 +16,18 @@ module ProcedureContextConcern
private
def stored_procedure_id
stored_location = get_stored_location_for(:user)
def has_stored_procedure_path?
get_stored_location_for(:user)&.start_with?('/commencer/')
end
if stored_location.present? && stored_location.include?('procedure_id=')
stored_location.split('procedure_id=').second
def find_procedure_in_context
uri = URI(get_stored_location_for(:user))
path_components = uri.path.split('/')
if uri.path.start_with?('/commencer/test/')
Procedure.brouillon.find_by(path: path_components[3])
elsif uri.path.start_with?('/commencer/')
Procedure.publiee.find_by(path: path_components[2])
else
nil
end

View file

@ -1,25 +1,44 @@
module NewUser
class CommencerController < ApplicationController
def commencer_test
procedure = Procedure.brouillons.find_by(path: params[:path])
if procedure.present?
redirect_to new_dossier_path(procedure_id: procedure.id, brouillon: true)
else
flash.alert = "La démarche est inconnue."
redirect_to root_path
end
end
layout 'procedure_context'
def commencer
procedure = Procedure.publiees.find_by(path: params[:path])
@procedure = Procedure.publiees.find_by(path: params[:path])
if procedure.present?
redirect_to new_dossier_path(procedure_id: procedure.id)
else
if @procedure.blank?
flash.alert = "La démarche est inconnue, ou la création de nouveaux dossiers pour cette démarche est terminée."
redirect_to root_path
return redirect_to root_path
end
render 'commencer/show'
end
def commencer_test
@procedure = Procedure.brouillons.find_by(path: params[:path])
if @procedure.blank?
flash.alert = "La démarche est inconnue, ou cette démarche nest maintenant plus en test."
return redirect_to root_path
end
render 'commencer/show'
end
def sign_in
store_user_location!
redirect_to new_user_session_path
end
def sign_up
store_user_location!
redirect_to new_user_registration_path
end
private
def store_user_location!
procedure = Procedure.find_by(path: params[:path])
store_location_for(:user, commencer_path(path: procedure.path))
end
end
end

View file

@ -85,13 +85,8 @@ module ApplicationHelper
tags, type, dossier_id = options.values_at(:tags, :type, :dossier_id)
options.except!(:tags, :type, :dossier_id)
if Flipflop.support_form?
params = { tags: tags, type: type, dossier_id: dossier_id }.compact
link_to title, contact_url(params), options
else
mail_to CONTACT_EMAIL, title,
options.merge(subject: "Question à propos de demarches-simplifiees.fr")
end
params = { tags: tags, type: type, dossier_id: dossier_id }.compact
link_to title, contact_url(params), options
end
def root_path_for_profile(nav_bar_profile)

View file

@ -23,6 +23,14 @@ module DossierHelper
end
end
def url_for_new_dossier(procedure)
if procedure.brouillon?
new_dossier_url(procedure_id: procedure.id, brouillon: true)
else
new_dossier_url(procedure_id: procedure.id)
end
end
def dossier_submission_is_closed?(dossier)
dossier.brouillon? && dossier.procedure.archivee?
end

View file

@ -64,7 +64,6 @@ module ProcedureHelper
private
TOGGLES = {
TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?,
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?,
TypeDeChamp.type_champs.fetch(:repetition) => :champ_repetition?
}

View file

@ -57,7 +57,7 @@ export default {
return this.typeChamp === 'header_section';
},
options() {
const options = this.item.options;
const options = this.item.options || {};
for (let key of Object.keys(options)) {
options[key] = castBoolean(options[key]);
}
@ -77,7 +77,7 @@ export default {
libelle: this.item.libelle,
mandatory: this.item.mandatory,
description: this.item.description,
dropDownList: this.item.drop_down_list.value,
dropDownList: this.item.drop_down_list && this.item.drop_down_list.value,
deleted: false,
clientId: `id-${clientIds++}`
};

View file

@ -10,6 +10,12 @@ Vue.component('DraggableItem', DraggableItem);
addEventListener('DOMContentLoaded', () => {
const el = document.querySelector('#champs-editor');
if (el) {
initEditor(el);
}
});
function initEditor(el) {
const { directUploadsUrl, dragIconUrl } = el.dataset;
const state = {
@ -48,7 +54,7 @@ addEventListener('DOMContentLoaded', () => {
this.updateAll = updateAll;
}
});
});
}
function createUpdateFunctions(app, isAnnotation) {
let isSaving = false;

View file

@ -184,10 +184,11 @@ class Procedure < ApplicationRecord
end
def clone(admin, from_library)
is_different_admin = self.administrateur_id != admin.id
populate_champ_stable_ids
procedure = self.deep_clone(include:
{
types_de_piece_justificative: nil,
attestation_template: nil,
types_de_champ: :drop_down_list,
types_de_champ_private: :drop_down_list
@ -203,6 +204,11 @@ class Procedure < ApplicationRecord
[:notice, :deliberation].each { |attachment| clone_attachment(procedure, attachment) }
procedure.types_de_champ += PiecesJustificativesService.types_pj_as_types_de_champ(self)
if is_different_admin || from_library
procedure.types_de_champ.each { |tdc| tdc.options&.delete(:old_pj) }
end
procedure.administrateur = admin
procedure.initiated_mail = initiated_mail&.dup
procedure.received_mail = received_mail&.dup
@ -215,7 +221,7 @@ class Procedure < ApplicationRecord
if from_library
procedure.service = nil
elsif self.service.present? && (self.administrateur_id != admin.id)
elsif self.service.present? && is_different_admin
procedure.service = self.service.clone_and_assign_to_administrateur(admin)
end

View file

@ -37,7 +37,7 @@ class TypeDeChamp < ApplicationRecord
belongs_to :parent, class_name: 'TypeDeChamp'
has_many :types_de_champ, foreign_key: :parent_id, class_name: 'TypeDeChamp', dependent: :destroy
store_accessor :options, :cadastres, :quartiers_prioritaires, :parcelles_agricoles
store_accessor :options, :cadastres, :quartiers_prioritaires, :parcelles_agricoles, :old_pj
# TODO simplify after migrating `options` column to (non YAML encoded) JSON
class MaybeYaml

View file

@ -26,7 +26,7 @@ class DossierSerializer < ActiveModel::Serializer
has_many :champs, serializer: ChampSerializer
def champs
champs = object.champs.to_a
champs = object.champs.reject { |c| c.type_de_champ.old_pj.present? }
if object.expose_legacy_carto_api?
champ_carte = champs.find do |champ|
@ -47,6 +47,16 @@ class DossierSerializer < ActiveModel::Serializer
[]
end
def pieces_justificatives
ActiveModelSerializers::SerializableResource.new(object.pieces_justificatives).serializable_hash +
PiecesJustificativesService.serialize_champs_as_pjs(object)
end
def types_de_piece_justificative
ActiveModelSerializers::SerializableResource.new(object.types_de_piece_justificative).serializable_hash +
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
end
def email
object.user&.email
end

View file

@ -16,7 +16,7 @@ class ProcedureSerializer < ActiveModel::Serializer
has_one :geographic_information, serializer: ModuleApiCartoSerializer
has_many :types_de_champ, serializer: TypeDeChampSerializer
has_many :types_de_champ_private, serializer: TypeDeChampSerializer
has_many :types_de_piece_justificative, serializer: TypeDePieceJustificativeSerializer
has_many :types_de_piece_justificative
def archived_at
object.archived_at&.in_time_zone('UTC')
@ -43,4 +43,13 @@ class ProcedureSerializer < ActiveModel::Serializer
ModuleAPICarto.new(procedure: object)
end
end
def types_de_champ
object.types_de_champ.reject { |c| c.old_pj.present? }
end
def types_de_piece_justificative
ActiveModelSerializers::SerializableResource.new(object.types_de_piece_justificative).serializable_hash +
PiecesJustificativesService.serialize_types_de_champ_as_type_pj(object)
end
end

View file

@ -31,4 +31,68 @@ class PiecesJustificativesService
missing_pjs.map { |pj| "La pièce jointe #{pj.libelle.truncate(200)} doit être fournie." }
end
def self.types_pj_as_types_de_champ(procedure)
order_place = procedure.types_de_champ.last&.order_place || 0
types_de_champ = [
TypeDeChamp.new(
libelle: "Pièces jointes",
type_champ: TypeDeChamp.type_champs.fetch(:header_section),
order_place: order_place
)
]
types_de_champ += procedure.types_de_piece_justificative.map do |tpj|
order_place += 1
description = tpj.description
if tpj.lien_demarche.present?
if description.present?
description += "\n"
end
description += "Récupérer le formulaire vierge pour mon dossier : #{tpj.lien_demarche}"
end
TypeDeChamp.new(
libelle: tpj.libelle,
type_champ: TypeDeChamp.type_champs.fetch(:piece_justificative),
description: description,
order_place: order_place,
mandatory: tpj.mandatory,
old_pj: {
stable_id: tpj.id
}
)
end
if types_de_champ.count > 1
types_de_champ
else
[]
end
end
def self.serialize_types_de_champ_as_type_pj(procedure)
tdcs = procedure.types_de_champ.select { |type_champ| type_champ.old_pj.present? }
tdcs.map.with_index do |type_champ, order_place|
description = type_champ.description
if /^(?<original_description>.*?)(?:[\r\n]+)Récupérer le formulaire vierge pour mon dossier : (?<lien_demarche>http.*)$/m =~ description
description = original_description
end
{
id: type_champ.old_pj[:stable_id],
libelle: type_champ.libelle,
description: description,
order_place: order_place,
lien_demarche: lien_demarche
}
end
end
def self.serialize_champs_as_pjs(dossier)
dossier.champs.select { |champ| champ.type_de_champ.old_pj }.map do |champ|
{
created_at: champ.created_at&.in_time_zone('UTC'),
type_de_piece_justificative_id: champ.type_de_champ.old_pj[:stable_id],
content_url: champ.for_api,
user: champ.dossier.user
}
end
end
end

View file

@ -2,7 +2,6 @@ class TypesDeChampService
include Rails.application.routes.url_helpers
TOGGLES = {
TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?,
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?,
TypeDeChamp.type_champs.fetch(:repetition) => :champ_repetition?
}

View file

@ -66,6 +66,12 @@
= f.label :footer, 'Pied de page'
= f.text_field :footer, class: 'form-control', maxlength: 190
- if @attestation_template.activated && @procedure.locked?
.row
.col-md-12
.pull-right
%p.help-block Lattestation ne peut plus être désactivée car la démarche a déjà été publiée.
%button.btn.btn-primary{ formaction: admin_procedure_attestation_template_preview_path, formtarget: '_blank' } Prévisualiser
.pull-right

View file

@ -0,0 +1,30 @@
- content_for(:title, @procedure.libelle)
.commencer.form
- if !user_signed_in?
%h1 Commencer la démarche
= link_to 'Créer un compte demarches-simplifiees.fr', commencer_sign_up_path(path: @procedure.path), class: ['button large expand primary']
= link_to 'Jai déjà un compte', commencer_sign_in_path(path: @procedure.path), class: ['button large expand']
- else
- dossiers = current_user.dossiers.where(procedure: @procedure)
- drafts = dossiers.merge(Dossier.state_brouillon)
- not_drafts = dossiers.merge(Dossier.state_not_brouillon)
- if dossiers.count == 0
= link_to 'Commencer la démarche', url_for_new_dossier(@procedure), class: ['button large expand primary']
- elsif drafts.count == 1 && not_drafts.count == 0
%h1 Vous avez déjà commencé à remplir un dossier
= link_to 'Continuer à remplir mon dossier', brouillon_dossier_path(drafts.first), class: ['button large expand primary']
= link_to 'Commencer un nouveau dossier', url_for_new_dossier(@procedure), class: ['button large expand']
- elsif not_drafts.count == 1
%h1 Vous avez déjà déposé un dossier
= link_to 'Voir mon dossier', dossier_path(not_drafts.first), class: ['button large expand primary']
= link_to 'Commencer un nouveau dossier', url_for_new_dossier(@procedure), class: ['button large expand']
- else
%h1 Vous avez déjà des dossiers pour cette démarche
= link_to 'Voir mes dossiers en cours', dossiers_path, class: ['button large expand primary']
= link_to 'Commencer un nouveau dossier', url_for_new_dossier(@procedure), class: ['button large expand']

View file

@ -44,7 +44,7 @@
= text_field_tag :phone, nil, required: true
= label_tag :source do
Comment avez-vous entendu parlé de demarches-simplifiees.fr ?
Comment avez-vous entendu parler de demarches-simplifiees.fr ?
%span.mandatory *
= text_field_tag :source, nil, required: true

View file

@ -77,7 +77,7 @@
= render partial: "header_field", locals: { field: { "label" => "Statut", "table" => "self", "column" => "state" }, classname: "status-col" }
%th.follow-col
%th.action-col.follow-col
%span.dropdown
%button.button.dropdown-button
Personnaliser
@ -112,7 +112,7 @@
%td.status-col
= link_to(gestionnaire_dossier_path(@procedure, dossier), class: 'cell-link') do
= render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier }
%td.follow-col= render partial: 'dossier_actions', locals: { procedure: @procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
%td.action-col.follow-col= render partial: 'dossier_actions', locals: { procedure: @procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
= paginate @dossiers
- else
%h2.empty-text Aucun dossier

View file

@ -14,7 +14,7 @@
%th Démarche
%th Demandeur
%th.status-col Statut
%th.follow-col
%th.action-col.follow-col
%tbody
- @dossiers.each do |dossier|
/ # FIXME: here we have a n+1, we fire a request
@ -31,6 +31,6 @@
%td.status-col
= link_to(dossier_linked_path(current_gestionnaire, dossier), class: 'cell-link') do
= render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier }
%td.follow-col= render partial: 'new_gestionnaire/procedures/dossier_actions', locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
%td.action-col.follow-col= render partial: 'new_gestionnaire/procedures/dossier_actions', locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
- else
%h2 Aucun dossier correspondant à votre recherche n'a été trouvé

View file

@ -31,6 +31,7 @@
%th Démarche
%th.status-col Statut
%th.updated-at-col Mis à jour
%th
%tbody
- @dossiers.each do |dossier|
%tr
@ -49,6 +50,11 @@
%td.updated-at-col
= link_to(url_for_dossier(dossier), class: 'cell-link') do
= dossier.updated_at.strftime("%d/%m/%Y")
%td.action-col.delete-col
- if dossier.brouillon?
= link_to(ask_deletion_dossier_path(dossier), method: :post, class: 'button danger', data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations quil contient. Toute suppression entraine lannulation de la démarche en cours.\n\nConfirmer la suppression ?" }) do
%span.icon.delete
Supprimer
= paginate(@dossiers)
- if current_user.feedbacks.empty? || current_user.feedbacks.last.created_at < 1.month.ago

View file

@ -7,8 +7,6 @@ Flipflop.configure do
strategy :default
group :champs do
feature :champ_siret,
title: "Champ SIRET"
feature :champ_integer_number,
title: "Champ nombre entier"
feature :champ_repetition,
@ -17,7 +15,6 @@ Flipflop.configure do
feature :web_hook
feature :publish_draft
feature :support_form
feature :enable_email_login_token
feature :new_champs_editor

View file

@ -265,6 +265,8 @@ Rails.application.routes.draw do
namespace :commencer do
get '/test/:path', action: 'commencer_test', as: :test
get '/:path', action: 'commencer'
get '/:path/sign_in', action: 'sign_in', as: :sign_in
get '/:path/sign_up', action: 'sign_up', as: :sign_up
end
resources :dossiers, only: [:index, :show, :new] do

View file

@ -0,0 +1,61 @@
class FixAutomaticDossierLogs_2019_01_16
def find_handlers
# rubocop:disable Security/YAMLLoad
Delayed::Job.where(queue: 'cron')
.map { |job| YAML.load(job.handler) }
.select { |handler| handler.job_data['job_class'] == 'AutoReceiveDossiersForProcedureJob' }
# rubocop:enable Security/YAMLLoad
end
def run
handlers = find_handlers
handlers
.map { |handler| handler.job_data['arguments'] }
.each do |(procedure_id, state)|
procedure = Procedure
.includes(:administrateur, dossiers: [:dossier_operation_logs, :follows])
.find(procedure_id)
rake_puts "working on procedure #{procedure_id}, #{procedure.libelle} whose admin is #{procedure.administrateur.email}"
case state
when Dossier.states.fetch(:en_instruction)
dossiers = procedure.dossiers.state_en_instruction
operation = 'passer_en_instruction'
when Dossier.states.fetch(:accepte)
dossiers = procedure.dossiers.accepte
operation = 'accepter'
end
dossier_operation_logs = DossierOperationLog
.where(dossier: dossiers, operation: operation)
rake_puts "affecting #{dossier_operation_logs.count} dossier_operation_logs"
dossier_operation_logs
.update_all(gestionnaire_id: nil, automatic_operation: true)
# if the dossier is only followed by the procedure administrateur
# unfollow
if state == Dossier.states.fetch(:en_instruction)
dossier_to_unfollows = dossiers
.select { |d| d.follows.count == 1 && d.follows.first.gestionnaire.email == procedure.administrateur.email }
rake_puts "affecting #{dossier_to_unfollows.count} dossiers"
dossier_to_unfollows
.each { |d| d.follows.destroy_all }
end
rake_puts ""
end
end
end
namespace :'2019_01_16_fix_automatic_dossier_logs' do
task run: :environment do
FixAutomaticDossierLogs_2019_01_16.new.run
end
end

View file

@ -24,7 +24,7 @@ RSpec.describe ProcedureContextConcern, type: :controller do
end
end
context 'when no procedure_id is present in the stored return location' do
context 'when the stored return location is not a procedure URL' do
before do
controller.store_location_for(:user, dossiers_path)
end
@ -36,9 +36,9 @@ RSpec.describe ProcedureContextConcern, type: :controller do
end
context 'when a procedure location has been stored' do
context 'when the stored procedure does not exist' do
context 'when the procedure path does not exist' do
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: '0'))
controller.store_location_for(:user, commencer_path(path: 'non-existent-path'))
end
it 'redirects with an error' do
@ -47,11 +47,11 @@ RSpec.describe ProcedureContextConcern, type: :controller do
end
end
context 'when the stored procedure is not published' do
let(:procedure) { create :procedure }
context 'when the procedure path exists, but not with the same publication status' do
let(:published_procedure) { create :procedure, :published }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
controller.store_location_for(:user, commencer_test_path(path: published_procedure.path))
end
it 'redirects with an error' do
@ -60,16 +60,29 @@ RSpec.describe ProcedureContextConcern, type: :controller do
end
end
context 'when the stored procedure exists' do
let(:procedure) { create :procedure, :published }
context 'when the stored procedure is in test' do
let(:test_procedure) { create :procedure, :with_path }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
controller.store_location_for(:user, commencer_test_path(path: test_procedure.path))
end
it 'succeeds, and assigns the procedure on the controller' do
expect(subject.status).to eq 200
expect(assigns(:procedure)).to eq procedure
expect(assigns(:procedure)).to eq test_procedure
end
end
context 'when the stored procedure is published' do
let(:published_procedure) { create :procedure, :published }
before do
controller.store_location_for(:user, commencer_path(path: published_procedure.path))
end
it 'succeeds, and assigns the procedure on the controller' do
expect(subject.status).to eq 200
expect(assigns(:procedure)).to eq published_procedure
end
end
end

View file

@ -2,39 +2,88 @@ require 'spec_helper'
describe NewUser::CommencerController, type: :controller do
let(:user) { create(:user) }
let(:procedure) { create(:procedure, :published) }
let(:procedure_id) { procedure.id }
let(:published_procedure) { create(:procedure, :published) }
let(:draft_procedure) { create(:procedure, :with_path) }
describe 'GET #commencer' do
describe '#commencer' do
subject { get :commencer, params: { path: path } }
let(:path) { procedure.path }
it { expect(subject.status).to eq 302 }
it { expect(subject).to redirect_to new_dossier_path(procedure_id: procedure.id) }
context 'when the path is for a published procedure' do
let(:path) { published_procedure.path }
context 'when procedure path does not exist' do
it 'renders the view' do
expect(subject.status).to eq(200)
expect(subject).to render_template('show')
expect(assigns(:procedure)).to eq published_procedure
end
end
context 'when the path is for a non-published procedure' do
let(:path) { draft_procedure.path }
it 'redirects with an error message' do
expect(subject).to redirect_to(root_path)
end
end
context 'when the path does not exist' do
let(:path) { 'hello' }
it { expect(subject).to redirect_to(root_path) }
it 'redirects with an error message' do
expect(subject).to redirect_to(root_path)
end
end
end
describe 'GET #commencer_test' do
before do
Flipflop::FeatureSet.current.test!.switch!(:publish_draft, true)
describe '#commencer_test' do
subject { get :commencer_test, params: { path: path } }
context 'when the path is for a draft procedure' do
let(:path) { draft_procedure.path }
it 'renders the view' do
expect(subject.status).to eq(200)
expect(subject).to render_template('show')
expect(assigns(:procedure)).to eq draft_procedure
end
end
subject { get :commencer_test, params: { path: path } }
let(:procedure) { create(:procedure, :with_path) }
let(:path) { procedure.path }
context 'when the path is for a published procedure' do
let(:path) { published_procedure.path }
it { expect(subject.status).to eq 302 }
it { expect(subject).to redirect_to new_dossier_path(procedure_id: procedure.id, brouillon: true) }
it 'redirects with an error message' do
expect(subject).to redirect_to(root_path)
end
end
context 'when procedure path does not exist' do
context 'when the path does not exist' do
let(:path) { 'hello' }
it { expect(subject).to redirect_to(root_path) }
it 'redirects with an error message' do
expect(subject).to redirect_to(root_path)
end
end
end
describe '#sign_in' do
subject { get :sign_in, params: { path: published_procedure.path } }
it 'set the path to return after sign-in to the dossier creation path' do
subject
expect(controller.stored_location_for(:user)).to eq(commencer_path(path: published_procedure.path))
end
it { expect(subject).to redirect_to(new_user_session_path) }
end
describe '#sign_up' do
subject { get :sign_up, params: { path: published_procedure.path } }
it 'set the path to return after sign-up to the dossier creation path' do
subject
expect(controller.stored_location_for(:user)).to eq(commencer_path(path: published_procedure.path))
end
it { expect(subject).to redirect_to(new_user_registration_path) }
end
end

View file

@ -27,7 +27,7 @@ describe Users::RegistrationsController, type: :controller do
let(:procedure) { create :procedure, :published }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
controller.store_location_for(:user, commencer_path(path: procedure.path))
end
it 'makes the saved procedure available' do

View file

@ -179,7 +179,7 @@ describe Users::SessionsController, type: :controller do
let(:procedure) { create :procedure, :published }
before do
controller.store_location_for(:user, new_dossier_path(procedure_id: procedure.id))
controller.store_location_for(:user, commencer_path(path: procedure.path))
end
it 'makes the saved procedure available' do

View file

@ -14,10 +14,16 @@ feature 'The gestionnaire part' do
scenario 'a gestionnaire can fill a dossier' do
visit commencer_path(path: procedure.path)
click_on 'Jai déjà un compte'
expect(page).to have_current_path new_user_session_path
sign_in_with(gestionnaire.email, password, true)
expect(page).to have_current_path(commencer_path(path: procedure.path))
click_on 'Commencer la démarche'
expect(page).to have_content('Identifier votre établissement')
expect(page).to have_current_path(siret_dossier_path(procedure.reload.dossiers.last))
expect(page).to have_content(procedure.libelle)
end
end

View file

@ -130,11 +130,15 @@ feature 'The user' do
def log_in(email, password, procedure)
visit "/commencer/#{procedure.path}"
expect(page).to have_current_path(new_user_session_path)
click_on 'Jai déjà un compte'
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on 'Se connecter'
expect(page).to have_current_path(new_user_session_path)
sign_in_with(email, password)
expect(page).to have_current_path("/commencer/#{procedure.path}")
click_on 'Commencer la démarche'
expect(page).to have_content("Données d'identité")
expect(page).to have_current_path(identite_dossier_path(user_dossier))
end

View file

@ -17,10 +17,10 @@ feature 'Creating a new dossier:' do
before do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
expect(page).to have_content(procedure.libelle)
expect(page).to have_content(procedure.description)
expect(page).to have_content(procedure.service.email)
expect(page).to have_current_path identite_dossier_path(user.reload.dossiers.last)
expect_page_to_have_procedure_description(procedure)
fill_in 'individual_nom', with: 'Nom'
fill_in 'individual_prenom', with: 'Prenom'
@ -77,13 +77,12 @@ feature 'Creating a new dossier:' do
.to_return(status: 404, body: '')
end
scenario 'the user can enter the SIRET of its etablissement and create a new draft', vcr: { cassette_name: 'api_adresse_search_paris_3' }, js: true do
scenario 'the user can enter the SIRET of its etablissement and create a new draft', vcr: { cassette_name: 'api_adresse_search_paris_3' } do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
expect(page).to have_current_path(siret_dossier_path(dossier))
expect(page).to have_content(procedure.libelle)
expect(page).to have_content(procedure.description)
expect(page).to have_content(procedure.service.email)
expect(page).to have_current_path siret_dossier_path(dossier)
expect_page_to_have_procedure_description(procedure)
fill_in 'Numéro SIRET', with: siret
click_on 'Valider'
@ -97,7 +96,10 @@ feature 'Creating a new dossier:' do
scenario 'the user is notified when its SIRET is invalid' do
visit commencer_path(path: procedure.path)
click_on 'Commencer la démarche'
expect(page).to have_current_path(siret_dossier_path(dossier))
expect_page_to_have_procedure_description(procedure)
fill_in 'Numéro SIRET', with: '0000'
click_on 'Valider'

View file

@ -48,11 +48,16 @@ feature 'linked dropdown lists' do
def log_in(email, password, procedure)
visit "/commencer/#{procedure.path}"
click_on 'Jai déjà un compte'
expect(page).to have_current_path(new_user_session_path)
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on 'Se connecter'
sign_in_with(email, password)
expect(page).to have_current_path(commencer_path(path: procedure.path))
click_on 'Commencer la démarche'
expect(page).to have_content("Données d'identité")
expect(page).to have_current_path(identite_dossier_path(user_dossier))
end

View file

@ -5,6 +5,7 @@ describe 'user access to the list of his dossier' do
let!(:last_updated_dossier) { create(:dossier, :with_entreprise, user: user, state: Dossier.states.fetch(:en_construction)) }
let!(:dossier1) { create(:dossier, :with_entreprise, user: user, state: Dossier.states.fetch(:en_construction)) }
let!(:dossier2) { create(:dossier, :with_entreprise) }
let!(:dossier_brouillon) { create(:dossier, :with_entreprise, user: user) }
let!(:dossier_archived) { create(:dossier, :with_entreprise, user: user, state: Dossier.states.fetch(:en_construction)) }
let(:dossiers_per_page) { 25 }
@ -43,6 +44,21 @@ describe 'user access to the list of his dossier' do
expect(page).to have_content(dossier_archived.procedure.libelle)
end
it 'should have link to only delete brouillon' do
expect(page).to have_link(nil, href: ask_deletion_dossier_path(dossier_brouillon))
expect(page).not_to have_link(nil, href: ask_deletion_dossier_path(dossier1))
end
context 'when user clicks on delete brouillon list', js: true do
before do
find(:xpath, "//a[@href='#{ask_deletion_dossier_path(dossier_brouillon)}']").click
page.driver.browser.switch_to.alert.accept
end
scenario 'dossier is deleted' do
expect(page).not_to have_link("Supprimer", href: dossier_brouillon.procedure.libelle)
end
end
context 'when user clicks on a projet in list', js: true do
before do
page.click_on(dossier1.procedure.libelle)

View file

@ -41,15 +41,20 @@ feature 'Signing up:' do
end
scenario 'a new user can sign-up and fill the procedure' do
expect(page).to have_current_path new_user_session_path
click_on 'Créer un compte'
expect(page).to have_current_path new_user_registration_path
expect_page_to_have_procedure_description(procedure)
sign_up_with user_email, user_password
expect(page).to have_content "nous avons besoin de vérifier votre adresse #{user_email}"
click_confirmation_link_for user_email
expect(page).to have_current_path(commencer_path(path: procedure.path))
expect(page).to have_content 'Votre compte a été activé'
click_on 'Commencer la démarche'
expect(page).to have_current_path identite_dossier_path(procedure.reload.dossiers.last)
expect_page_to_have_procedure_description(procedure)
end
end

View file

@ -21,11 +21,15 @@ feature 'Signin in:' do
end
scenario 'an existing user can sign-in and fill the procedure' do
click_on 'Jai déjà un compte'
expect(page).to have_current_path new_user_session_path
expect_page_to_have_procedure_description(procedure)
sign_in_with user.email, password
expect(page).to have_current_path(commencer_path(path: procedure.path))
click_on 'Commencer la démarche'
expect(page).to have_current_path identite_dossier_path(user.reload.dossiers.last)
expect_page_to_have_procedure_description(procedure)
expect(page).to have_content "Données d'identité"

View file

@ -0,0 +1,82 @@
require 'spec_helper'
load Rails.root.join('lib', 'tasks', '2019_01_16_fix_automatic_dossier_logs.rake')
describe '2019_01_16_fix_automatic_dossier_logs' do
let!(:rake_task) { Rake::Task['2019_01_16_fix_automatic_dossier_logs:run'] }
let!(:administrateur) { create(:administrateur) }
let!(:another_gestionnaire) { create(:gestionnaire) }
let!(:procedure) { create(:procedure, administrateur: administrateur) }
let!(:dossier) { create(:dossier, procedure: procedure) }
let!(:fix_automatic_dossier_logs) { FixAutomaticDossierLogs_2019_01_16.new }
before do
allow(fix_automatic_dossier_logs).to receive(:find_handlers)
.and_return([double(job_data: { 'arguments' => [procedure.id, final_state] })])
end
subject do
fix_automatic_dossier_logs.run
dossier.reload
end
context 'when the dossiers are automatically moved to en_instruction' do
let(:final_state) { 'en_instruction' }
context 'and a dossier has been accidentally affected to an administrateur' do
before do
dossier.passer_en_instruction!(administrateur.gestionnaire)
control = DossierOperationLog.create(
gestionnaire: another_gestionnaire,
operation: 'refuser',
automatic_operation: false
)
dossier.dossier_operation_logs << control
subject
end
it { expect(dossier.follows.count).to eq(0) }
it do
expect(dossier_logs).to match_array([
[nil, 'passer_en_instruction', true],
[another_gestionnaire.id, "refuser", false]
])
end
end
context ', followed anyway by another person and accidentally ...' do
before do
another_gestionnaire.follow(dossier)
dossier.passer_en_instruction!(administrateur.gestionnaire)
subject
end
it { expect(dossier.follows.count).to eq(2) }
it { expect(dossier_logs).to match([[nil, 'passer_en_instruction', true]]) }
end
end
context 'when the dossiers are automatically moved to accepte' do
let(:final_state) { 'accepte' }
context 'and a dossier has been accidentally affected to an administrateur' do
before do
dossier.accepter!(administrateur.gestionnaire, '')
subject
end
it { expect(dossier_logs).to match([[nil, 'accepter', true]]) }
end
end
private
def dossier_logs
dossier.dossier_operation_logs.pluck(:gestionnaire_id, :operation, :automatic_operation)
end
end

View file

@ -336,6 +336,7 @@ describe Procedure do
let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 0) }
let!(:type_de_champ_1) { create(:type_de_champ, procedure: procedure, order_place: 1) }
let!(:type_de_champ_2) { create(:type_de_champ_drop_down_list, procedure: procedure, order_place: 2) }
let!(:type_de_champ_pj) { create(:type_de_champ_piece_justificative, procedure: procedure, order_place: 3, old_pj: { stable_id: 2713 }) }
let!(:type_de_champ_private_0) { create(:type_de_champ, :private, procedure: procedure, order_place: 0) }
let!(:type_de_champ_private_1) { create(:type_de_champ, :private, procedure: procedure, order_place: 1) }
let!(:type_de_champ_private_2) { create(:type_de_champ_drop_down_list, :private, procedure: procedure, order_place: 2) }
@ -366,13 +367,12 @@ describe Procedure do
it 'should duplicate specific objects with different id' do
expect(subject.id).not_to eq(procedure.id)
expect(subject.types_de_piece_justificative.size).to eq procedure.types_de_piece_justificative.size
expect(subject.types_de_champ.size).to eq procedure.types_de_champ.size
expect(subject.types_de_champ.size).to eq(procedure.types_de_champ.size + 1 + procedure.types_de_piece_justificative.size)
expect(subject.types_de_champ_private.size).to eq procedure.types_de_champ_private.size
expect(subject.types_de_champ.map(&:drop_down_list).compact.size).to eq procedure.types_de_champ.map(&:drop_down_list).compact.size
expect(subject.types_de_champ_private.map(&:drop_down_list).compact.size).to eq procedure.types_de_champ_private.map(&:drop_down_list).compact.size
subject.types_de_champ.zip(procedure.types_de_champ).each do |stc, ptc|
procedure.types_de_champ.zip(subject.types_de_champ).each do |ptc, stc|
expect(stc).to have_same_attributes_as(ptc)
end
@ -380,10 +380,6 @@ describe Procedure do
expect(stc).to have_same_attributes_as(ptc)
end
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)
expect(subject.cloned_from_library).to be(false)
@ -393,7 +389,19 @@ describe Procedure do
expect(cloned_procedure).to have_same_attributes_as(procedure)
end
context 'when the procedure is clone from the library' do
it 'should not clone piece justificatives but create corresponding champs' do
expect(subject.types_de_piece_justificative.size).to eq(0)
champs_pj = subject.types_de_champ[procedure.types_de_champ.size + 1, procedure.types_de_piece_justificative.size]
champs_pj.zip(procedure.types_de_piece_justificative).each do |stc, ptpj|
expect(stc.libelle).to eq(ptpj.libelle)
expect(stc.description).to eq(ptpj.description)
expect(stc.mandatory).to eq(ptpj.mandatory)
expect(stc.old_pj[:stable_id]).to eq(ptpj.id)
end
end
context 'when the procedure is cloned from the library' do
let(:from_library) { true }
it { expect(subject.cloned_from_library).to be(true) }
@ -401,6 +409,12 @@ describe Procedure do
it 'should set service_id to nil' do
expect(subject.service).to eq(nil)
end
it 'should discard old pj information' do
subject.types_de_champ.each do |stc|
expect(stc.old_pj).to be_nil
end
end
end
it 'should keep service_id' do
@ -415,6 +429,12 @@ describe Procedure do
expect(subject.service.administrateur_id).not_to eq(service.administrateur_id)
expect(subject.service.attributes.except("id", "administrateur_id", "created_at", "updated_at")).to eq(service.attributes.except("id", "administrateur_id", "created_at", "updated_at"))
end
it 'should discard old pj information' do
subject.types_de_champ.each do |stc|
expect(stc.old_pj).to be_nil
end
end
end
it 'should duplicate existing mail_templates' do

View file

@ -45,4 +45,60 @@ describe DossierSerializer do
}
end
end
context 'when a type PJ was cloned to a type champ PJ' do
let(:original_procedure) do
p = create(:procedure, :published)
p.types_de_piece_justificative.create(
libelle: "Vidéo de votre demande de subvention",
description: "Pour optimiser vos chances, soignez la chorégraphie et privilégiez le chant polyphonique",
lien_demarche: "https://www.dance-academy.gouv.fr",
order_place: 0
)
p
end
let(:procedure) do
p = original_procedure.clone(original_procedure.administrateur, false)
p.save
p
end
let(:type_pj) { original_procedure.types_de_piece_justificative.first }
let(:migrated_type_champ) { procedure.types_de_champ.find_by(libelle: type_pj.libelle) }
let(:dossier) { create(:dossier, procedure: procedure) }
let(:champ_pj) { dossier.champs.last }
before do
champ_pj.piece_justificative_file.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain")
end
subject { DossierSerializer.new(dossier).serializable_hash }
it "exposes the PJ in the legacy format" do
is_expected.to include(
types_de_piece_justificative: [
{
"id" => type_pj.id,
"libelle" => type_pj.libelle,
"description" => type_pj.description,
"lien_demarche" => type_pj.lien_demarche,
"order_place" => type_pj.order_place
}
],
pieces_justificatives: [
{
"content_url" => champ_pj.for_api,
"created_at" => champ_pj.created_at.in_time_zone('UTC').iso8601(3),
"type_de_piece_justificative_id" => type_pj.id,
"user" => a_hash_including("id" => dossier.user.id)
}
]
)
end
it "does not expose the PJ as a champ" do
expect(subject[:champs]).not_to include(a_hash_including(type_de_champ: a_hash_including(id: migrated_type_champ.id)))
end
end
end

View file

@ -8,4 +8,42 @@ describe ProcedureSerializer do
is_expected.to include(state: "publiee")
}
end
context 'when a type PJ was cloned to a type champ PJ' do
let(:original_procedure) do
p = create(:procedure, :published)
p.types_de_piece_justificative.create(
libelle: "Vidéo de votre demande de subvention",
description: "Pour optimiser vos chances, soignez la chorégraphie et privilégiez le chant polyphonique",
lien_demarche: "https://www.dance-academy.gouv.fr",
order_place: 0
)
p
end
let(:procedure) { original_procedure.clone(original_procedure.administrateur, false) }
let(:type_pj) { original_procedure.types_de_piece_justificative.first }
let(:migrated_type_champ) { procedure.types_de_champ.find_by(libelle: type_pj.libelle) }
subject { ProcedureSerializer.new(procedure).serializable_hash }
it "is exposed as a legacy type PJ" do
is_expected.to include(
types_de_piece_justificative: [
{
"id" => type_pj.id,
"libelle" => type_pj.libelle,
"description" => type_pj.description,
"lien_demarche" => type_pj.lien_demarche,
"order_place" => type_pj.order_place
}
]
)
end
it "is not exposed as a type de champ" do
expect(subject[:types_de_champ]).not_to include(a_hash_including(libelle: type_pj.libelle))
end
end
end

View file

@ -0,0 +1,76 @@
require 'rails_helper'
RSpec.describe 'commencer/show.html.haml', type: :view do
include Rails.application.routes.url_helpers
let(:procedure) { create(:procedure, :with_service, :published) }
before do
assign(:procedure, procedure)
if user
sign_in user
end
end
subject { render }
context 'when no user is signed in' do
let(:user) { nil }
it 'renders sign-in and sign-up links' do
subject
expect(rendered).to have_link('Créer un compte')
expect(rendered).to have_link('Jai déjà un compte')
end
end
context 'when the user is already signed in' do
let(:user) { create :user }
shared_examples_for 'it renders a link to create a new dossier' do |button_label|
it 'renders a link to create a new dossier' do
subject
expect(rendered).to have_link(button_label, href: new_dossier_url(procedure_id: procedure.id))
end
end
context 'and they dont have any dossier on this procedure' do
it_behaves_like 'it renders a link to create a new dossier', 'Commencer la démarche'
end
context 'and they have a pending draft' do
let!(:brouillon) { create(:dossier, user: user, procedure: procedure) }
it_behaves_like 'it renders a link to create a new dossier', 'Commencer un nouveau dossier'
it 'renders a link to resume the pending draft' do
subject
expect(rendered).to have_link('Continuer à remplir mon dossier', href: brouillon_dossier_path(brouillon))
end
end
context 'and they have a submitted dossier' do
let!(:brouillon) { create(:dossier, user: user, procedure: procedure) }
let!(:dossier) { create(:dossier, :en_construction, :for_individual, user: user, procedure: procedure) }
it_behaves_like 'it renders a link to create a new dossier', 'Commencer un nouveau dossier'
it 'renders a link to the submitted dossier' do
subject
expect(rendered).to have_link('Voir mon dossier', href: dossier_path(dossier))
end
end
context 'and they have several submitted dossiers' do
let!(:brouillon) { create(:dossier, user: user, procedure: procedure) }
let!(:dossiers) { create_list(:dossier, 2, :en_construction, :for_individual, user: user, procedure: procedure) }
it_behaves_like 'it renders a link to create a new dossier', 'Commencer un nouveau dossier'
it 'renders a link to the dossiers list' do
subject
expect(rendered).to have_link('Voir mes dossiers en cours', href: dossiers_path)
end
end
end
end

View file

@ -3,7 +3,6 @@ require 'spec_helper'
describe 'layouts/_new_header.html.haml', type: :view do
describe 'logo link' do
before do
Flipflop::FeatureSet.current.test!.switch!(:support_form, true)
sign_in user
allow(controller).to receive(:nav_bar_profile).and_return(profile)
render