Merge pull request #3271 from betagouv/dev

2019-01-10-01
This commit is contained in:
LeSim 2019-01-10 14:03:22 +01:00 committed by GitHub
commit cfa88acf63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 459 additions and 477 deletions

View file

@ -58,6 +58,10 @@ h5 span {
.progress-bar-warning {
background-color: #E4594F;
}
.missing-steps {
color: #FFA500;
}
}
.procedure-list-element:hover {

View file

@ -190,6 +190,12 @@
}
}
.lien-demarche {
p.alert {
margin-bottom: 8px;
}
}
.button-navbar-action:hover {
color: $light-blue;
}

View file

@ -21,7 +21,7 @@ class Users::SessionsController < Sessions::SessionsController
try_to_authenticate(Administrateur, remember_me)
if user_signed_in?
current_user.update(loged_in_with_france_connect: '')
current_user.update(loged_in_with_france_connect: nil)
end
if gestionnaire_signed_in?
@ -30,8 +30,8 @@ class Users::SessionsController < Sessions::SessionsController
redirect_to after_sign_in_path_for(:user)
else
gestionnaire = current_gestionnaire
login_token = gestionnaire.login_token!
GestionnaireMailer.send_login_token(gestionnaire, login_token).deliver_later
send_login_token_or_bufferize(gestionnaire)
[:user, :gestionnaire, :administrateur].each { |role| sign_out(role) }
@ -103,6 +103,13 @@ class Users::SessionsController < Sessions::SessionsController
private
def send_login_token_or_bufferize(gestionnaire)
if !gestionnaire.young_login_token?
login_token = gestionnaire.login_token!
GestionnaireMailer.send_login_token(gestionnaire, login_token).deliver_later
end
end
def error_procedure
session["user_return_to"] = nil
flash.alert = t('errors.messages.procedure_not_found')

View file

@ -9,25 +9,4 @@ module ChampHelper
raw(champ.to_render_data.to_json)
# rubocop:enable Rails/OutputSafety
end
def formatted_value(champ)
value = champ.value
type = champ.type_champ
if type == TypeDeChamp.type_champs.fetch(:date) && value.present?
Date.parse(value).strftime("%d/%m/%Y")
elsif type.in? [TypeDeChamp.type_champs.fetch(:checkbox), TypeDeChamp.type_champs.fetch(:engagement)]
value == 'on' ? 'Oui' : 'Non'
elsif type == TypeDeChamp.type_champs.fetch(:yes_no)
if value == 'true'
'Oui'
elsif value == 'false'
'Non'
end
elsif type == TypeDeChamp.type_champs.fetch(:multiple_drop_down_list) && value.present?
JSON.parse(value).join(', ')
else
value
end
end
end

View file

@ -1,12 +1,16 @@
module DossierLinkHelper
def dossier_linked_path(gestionnaire, dossier)
if dossier.procedure.gestionnaires.include?(gestionnaire)
gestionnaire_dossier_path(dossier.procedure, dossier)
else
avis = dossier.avis.find_by(gestionnaire: gestionnaire)
if avis.present?
gestionnaire_avis_path(avis)
def dossier_linked_path(user, dossier)
if user.is_a?(Gestionnaire)
if dossier.procedure.gestionnaires.include?(user)
gestionnaire_dossier_path(dossier.procedure, dossier)
else
avis = dossier.avis.find_by(gestionnaire: user)
if avis.present?
gestionnaire_avis_path(avis)
end
end
elsif user.owns_or_invite?(dossier)
dossier_path(dossier)
end
end
end

View file

@ -119,4 +119,8 @@ class Administrateur < ApplicationRecord
def owns?(procedure)
id == procedure.administrateur_id
end
def gestionnaire
Gestionnaire.find_by(email: email)
end
end

View file

@ -40,32 +40,18 @@ class Champ < ApplicationRecord
end
def to_s
if value.present?
string_value
else
''
end
value.present? ? value.to_s : ''
end
def for_export
if value.present?
value_for_export
else
nil
end
value.presence
end
def for_api
value
end
def main_value_name
:value
end
private
def string_value
value.to_s
end
def value_for_export
value
end
end

View file

@ -88,4 +88,8 @@ class Champs::CarteChamp < Champ
)
end
end
def for_api
geo_json&.to_json
end
end

View file

@ -6,6 +6,14 @@ class Champs::CheckboxChamp < Champ
end
def to_s
value == 'on' ? 'oui' : 'non'
value == 'on' ? 'Oui' : 'Non'
end
def for_export
value == 'on' ? 'on' : 'off'
end
def for_api
value == 'on' ? 'on' : 'off'
end
end

View file

@ -5,6 +5,10 @@ class Champs::DateChamp < Champ
# Text search is pretty useless for dates so were not including these champs
end
def to_s
value.present? ? Date.parse(value).strftime('%d/%m/%Y') : ""
end
private
def format_before_save
@ -15,8 +19,4 @@ class Champs::DateChamp < Champ
nil
end
end
def string_value
Date.parse(value).strftime('%d/%m/%Y')
end
end

View file

@ -1,7 +1,17 @@
class Champs::DecimalNumberChamp < Champ
validates :value, numericality: { allow_nil: true, allow_blank: true }
def value_for_export
value.to_f
def for_export
processed_value
end
def for_api
processed_value
end
private
def processed_value
value&.to_f
end
end

View file

@ -1,7 +1,2 @@
class Champs::EngagementChamp < Champs::CheckboxChamp
def search_terms
if value == 'on'
[libelle]
end
end
end

View file

@ -1,7 +1,17 @@
class Champs::IntegerNumberChamp < Champ
validates :value, numericality: { only_integer: true, allow_nil: true, allow_blank: true }
def value_for_export
value.to_i
def for_export
processed_value
end
def for_api
processed_value
end
private
def processed_value
value&.to_i
end
end

View file

@ -29,8 +29,16 @@ class Champs::LinkedDropDownListChamp < Champ
:primary_value
end
def for_display
string_value
def to_s
value.present? ? [primary_value, secondary_value].compact.join(' / ') : ""
end
def for_export
value.present? ? "#{primary_value || ''};#{secondary_value || ''}" : nil
end
def for_api
value.present? ? { primary: primary_value, secondary: secondary_value } : nil
end
def mandatory_and_blank?
@ -43,14 +51,6 @@ class Champs::LinkedDropDownListChamp < Champ
private
def string_value
[primary_value, secondary_value].compact.join(' / ')
end
def value_for_export
"#{primary_value || ''};#{secondary_value || ''}"
end
def pack_value(primary, secondary)
self.value = JSON.generate([primary, secondary])
end

View file

@ -2,7 +2,19 @@ class Champs::MultipleDropDownListChamp < Champ
before_save :format_before_save
def search_terms
drop_down_list.selected_options_without_decorator(self)
selected_options
end
def selected_options
value.blank? ? [] : JSON.parse(value)
end
def to_s
selected_options.join(', ')
end
def for_export
value.present? ? selected_options.join(', ') : nil
end
private
@ -18,12 +30,4 @@ class Champs::MultipleDropDownListChamp < Champ
end
end
end
def string_value
drop_down_list.selected_options_without_decorator(self).join(', ')
end
def value_for_export
drop_down_list.selected_options_without_decorator(self).join(', ')
end
end

View file

@ -48,6 +48,10 @@ class Champs::PieceJustificativeChamp < Champ
errors
end
def for_api
Rails.application.routes.url_helpers.url_for(piece_justificative_file)
end
private
def create_virus_scan

View file

@ -1,4 +1,6 @@
class Champs::RepetitionChamp < Champ
before_save :setup_dossier
has_many :champs, -> { ordered }, foreign_key: :parent_id, dependent: :destroy
accepts_nested_attributes_for :champs, allow_destroy: true
@ -10,4 +12,10 @@ class Champs::RepetitionChamp < Champ
def search_terms
# The user cannot enter any information here so it doesnt make much sense to search
end
def setup_dossier
champs.each do |champ|
champ.dossier = dossier
end
end
end

View file

@ -1,7 +1,5 @@
class Champs::TextareaChamp < Champs::TextChamp
private
def value_for_export
ActionView::Base.full_sanitizer.sanitize(value)
def for_export
value.present? ? ActionView::Base.full_sanitizer.sanitize(value) : nil
end
end

View file

@ -6,12 +6,20 @@ class Champs::YesNoChamp < Champs::CheckboxChamp
end
def to_s
value_for_export
processed_value
end
def for_export
processed_value
end
def for_api
processed_value
end
private
def value_for_export
value == 'true' ? 'oui' : 'non'
def processed_value
value == 'true' ? 'Oui' : 'Non'
end
end

View file

@ -10,10 +10,6 @@ class DropDownList < ApplicationRecord
options.select { |v| (v =~ /^--.*--$/).present? }
end
def selected_options_without_decorator(champ)
champ.value.blank? ? [] : multiple ? JSON.parse(champ.value) : [champ.value]
end
def multiple
type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:multiple_drop_down_list)
end

View file

@ -3,7 +3,8 @@ class Gestionnaire < ApplicationRecord
include EmailSanitizableConcern
include ActiveRecord::SecureToken
LOGIN_TOKEN_VALIDITY = 30.minutes
LOGIN_TOKEN_VALIDITY = 45.minutes
LOGIN_TOKEN_YOUTH = 15.minutes
devise :database_authenticatable, :registerable, :async,
:recoverable, :rememberable, :trackable, :validatable
@ -211,6 +212,11 @@ class Gestionnaire < ApplicationRecord
save
end
def young_login_token?
login_token_created_at.present? &&
LOGIN_TOKEN_YOUTH.ago < login_token_created_at
end
private
def annotations_hash(demande, annotations_privees, avis, messagerie)

View file

@ -4,8 +4,8 @@ class Procedure < ApplicationRecord
MAX_DUREE_CONSERVATION = 36
has_many :types_de_piece_justificative, -> { ordered }, dependent: :destroy
has_many :types_de_champ, -> { public_only.ordered }, dependent: :destroy
has_many :types_de_champ_private, -> { private_only.ordered }, class_name: 'TypeDeChamp', dependent: :destroy
has_many :types_de_champ, -> { root.public_only.ordered }, dependent: :destroy
has_many :types_de_champ_private, -> { root.private_only.ordered }, class_name: 'TypeDeChamp', dependent: :destroy
has_many :dossiers
has_many :deleted_dossiers, dependent: :destroy
@ -218,6 +218,8 @@ class Procedure < ApplicationRecord
procedure.service = self.service.clone_and_assign_to_administrateur(admin)
end
admin.gestionnaire.assign_to_procedure(procedure)
procedure
end
@ -360,6 +362,20 @@ class Procedure < ApplicationRecord
end
end
def missing_steps
result = []
if service.nil?
result << :service
end
if gestionnaires.empty?
result << :instructeurs
end
result
end
private
def claim_path_ownership!(path)

View file

@ -61,12 +61,14 @@ class TypeDeChamp < ApplicationRecord
after_initialize :set_dynamic_type
after_create :populate_stable_id
before_save :setup_procedure
attr_reader :dynamic_type
scope :public_only, -> { where(private: false) }
scope :private_only, -> { where(private: true) }
scope :ordered, -> { order(order_place: :asc) }
scope :root, -> { where(parent_id: nil) }
has_many :champ, inverse_of: :type_de_champ, dependent: :destroy do
def build(params = {})
@ -82,6 +84,7 @@ class TypeDeChamp < ApplicationRecord
has_one_attached :piece_justificative_template
accepts_nested_attributes_for :drop_down_list
accepts_nested_attributes_for :types_de_champ, allow_destroy: true
validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :type_champ, presence: true, allow_blank: false, allow_nil: false
@ -160,6 +163,12 @@ class TypeDeChamp < ApplicationRecord
private
def setup_procedure
types_de_champ.each do |type_de_champ|
type_de_champ.procedure = procedure
end
end
def populate_stable_id
if !stable_id
update_column(:stable_id, id)

View file

@ -13,28 +13,8 @@ class ChampSerializer < ActiveModel::Serializer
case object
when GeoArea
object.geometry
when Champs::CarteChamp
if object.geo_json.present?
object.geo_json.to_json
end
when Champs::DecimalNumberChamp
if object.value.present?
object.value.to_f
end
when Champs::IntegerNumberChamp
if object.value.present?
object.value.to_i
end
when Champs::LinkedDropDownListChamp
if object.value.present?
{ primary: object.primary_value, secondary: object.secondary_value }
end
when Champs::PieceJustificativeChamp
if object.piece_justificative_file.attached?
url_for(object.piece_justificative_file)
end
else
object.value
object.for_api
end
end

View file

@ -120,11 +120,9 @@ class ProcedureExportService
def etablissements_table_data
@etablissements = @dossiers.flat_map do |dossier|
dossier.champs.select do |champ|
champ.is_a?(Champs::SiretChamp)
end + dossier.champs_private.select do |champ|
champ.is_a?(Champs::SiretChamp)
end
[dossier.champs, dossier.champs_private]
.flatten
.select { |champ| champ.is_a?(Champs::SiretChamp) }
end.map(&:etablissement).compact
if @etablissements.any?
@ -139,22 +137,13 @@ class ProcedureExportService
end
def dossiers_headers
headers = ATTRIBUTES.map do |key|
label_for_export(key.to_s)
end
headers += @procedure.types_de_champ.reject(&:exclude_from_export?).map do |champ|
label_for_export(champ.libelle)
end
headers += @procedure.types_de_champ_private.reject(&:exclude_from_export?).map do |champ|
label_for_export(champ.libelle)
end
headers += ETABLISSEMENT_ATTRIBUTES.map do |key|
label_for_export("etablissement.#{key}")
end
headers += ENTREPRISE_ATTRIBUTES.map do |key|
label_for_export("entreprise.#{key}")
end
headers
headers = ATTRIBUTES.map(&:to_s) +
@procedure.types_de_champ.reject(&:exclude_from_export?).map(&:libelle) +
@procedure.types_de_champ_private.reject(&:exclude_from_export?).map(&:libelle) +
ETABLISSEMENT_ATTRIBUTES.map { |key| "etablissement.#{key}" } +
ENTREPRISE_ATTRIBUTES.map { |key| "entreprise.#{key}" }
headers.map { |header| label_for_export(header) }
end
def dossiers_data
@ -183,37 +172,28 @@ class ProcedureExportService
dossier.read_attribute(key)
end
end
values = normalize_values(values)
values += dossier.champs.reject(&:exclude_from_export?).map do |champ|
value_for_export(champ)
end
values += dossier.champs_private.reject(&:exclude_from_export?).map do |champ|
value_for_export(champ)
end
values += etablissement_data(dossier.etablissement)
values
normalize_values(values) +
dossier.champs.reject(&:exclude_from_export?).map(&:for_export) +
dossier.champs_private.reject(&:exclude_from_export?).map(&:for_export) +
etablissement_data(dossier.etablissement)
end
end
def etablissements_headers
headers = [:dossier_id, :libelle]
headers += ETABLISSEMENT_ATTRIBUTES.map do |key|
label_for_export("etablissement.#{key}")
end
headers += ENTREPRISE_ATTRIBUTES.map do |key|
label_for_export("entreprise.#{key}")
end
headers
headers = ["dossier_id", "libelle"] +
ETABLISSEMENT_ATTRIBUTES.map { |key| "etablissement.#{key}" } +
ENTREPRISE_ATTRIBUTES.map { |key| "entreprise.#{key}" }
headers.map { |header| label_for_export(header) }
end
def etablissements_data
@etablissements.map do |etablissement|
data = [
[
etablissement.champ.dossier_id,
label_for_export(etablissement.champ.libelle).to_s
]
data + etablissement_data(etablissement)
] + etablissement_data(etablissement)
end
end
@ -245,10 +225,6 @@ class ProcedureExportService
label.parameterize.underscore.to_sym
end
def value_for_export(champ)
champ.for_export
end
def normalize_values(values)
values.map do |value|
case value

View file

@ -47,7 +47,7 @@
.alert.alert-info
Cette démarche a été publiée, certains éléments ne peuvent plus être modifiés.
%div
.lien-demarche
%h3 Lien démarche
%div{ style: 'margin-top: 30px;' }
- if @procedure.archivee?
@ -72,5 +72,15 @@
= link_to("un service", services_path(procedure_id: @procedure))
\.
- else
.alert.alert-info
Cette démarche na pas encore de lien, et nest donc pas accessible par le public.
- if @procedure.missing_steps.include?(:service)
%p.alert.alert-danger
Vous devez renseigner les coordonnées de votre Service administratif avant de pouvoir publier votre démarche.
= link_to 'Cliquez ici.', (current_administrateur.services.present? ? url_for(services_path(procedure_id: @procedure.id)) : url_for(new_service_path(procedure_id: @procedure.id)))
- if @procedure.missing_steps.include?(:instructeurs)
%p.alert.alert-danger
Vous devez affecter des instructeurs avant de pouvoir publier votre démarche.
= link_to 'Cliquez ici.', admin_procedure_instructeurs_path(@procedure)
%p.alert.alert-info
Cette démarche na pas encore de lien, et nest pas accessible par le public.

View file

@ -20,11 +20,15 @@
%a#onglet-services{ href: current_administrateur.services.present? ? url_for(services_path(procedure_id: @procedure.id)) : url_for(new_service_path(procedure_id: @procedure.id)) }
.procedure-list-element
Services
Service
- if @procedure.missing_steps.include?(:service)
%p.missing-steps (à compléter)
%a#onglet-instructeurs{ href: url_for(admin_procedure_instructeurs_path(@procedure)) }
.procedure-list-element{ class: ('active' if active == 'Instructeurs') }
Instructeurs
- if @procedure.missing_steps.include?(:instructeurs)
%p.missing-steps (à compléter)
- if !@procedure.locked?
%a#onglet-champs{ href: url_for(admin_procedure_types_de_champ_path(@procedure)) }

View file

@ -33,14 +33,9 @@
%h2 Annotations privées
- if @dossier.champs_private.present?
%table
- @dossier.champs_private.each do |champ|
%tr
%th
= champ.libelle
%td
= champ.value
- champs_annotations_privees = @dossier.champs_private
- if champs_annotations_privees.any?
= render partial: "shared/dossiers/champs", locals: { champs: champs_annotations_privees, dossier: @dossier, demande_seen_at: nil, profile: 'instructeur' }
- else
Aucune annotation privée

View file

@ -1,3 +1,4 @@
.carte{ data: { geo: geo_data(champ) } }
.geo-areas
= render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false }
- if champ.to_s.present?
.carte{ data: { geo: geo_data(champ) } }
.geo-areas
= render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false }

View file

@ -0,0 +1,11 @@
- dossier = Dossier.includes(:procedure).find_by(id: champ.to_s)
- if dossier
- path = dossier_linked_path(current_gestionnaire || current_user, dossier)
- if path.present?
= link_to("Dossier nº #{dossier.id}", path, target: '_blank')
- else
Dossier nº #{dossier.id}
%br
= sanitize(dossier.text_summary)
- else
Pas de dossier associé

View file

@ -0,0 +1,5 @@
- if champ.to_s.present?
%ul
- champ.to_s.split(", ").each do |item|
%li
= item

View file

@ -0,0 +1,5 @@
- pj = champ.piece_justificative_file
- if pj.attached?
= render partial: "shared/champs/piece_justificative/pj_link", locals: { champ: champ, user_can_upload: false }
- else
Pièce justificative non fournie

View file

@ -0,0 +1,2 @@
- if champ.etablissement.present?
= render partial: "shared/dossiers/identite_entreprise", locals: { etablissement: champ.etablissement, profile: profile }

View file

@ -0,0 +1 @@
= simple_format(champ.to_s)

View file

@ -2,74 +2,30 @@
%tbody
- champs.reject(&:exclude_from_view?).each do |c|
%tr
- value = formatted_value(c)
- case c.type_champ
- when TypeDeChamp.type_champs.fetch(:header_section)
- if c.type_champ == TypeDeChamp.type_champs.fetch(:header_section)
%th.header-section{ colspan: 3 }
= c.libelle
- when TypeDeChamp.type_champs.fetch(:multiple_drop_down_list)
%th.libelle
= "#{c.libelle} :"
%td.rich-text
- if value.present?
%ul
- value.split(", ").each do |item|
%li
= item
- when TypeDeChamp.type_champs.fetch(:linked_drop_down_list)
%th.libelle
= "#{c.libelle} :"
%td= c.for_display
- when TypeDeChamp.type_champs.fetch(:dossier_link)
%th.libelle
= "#{c.libelle} :"
%td.rich-text
- dossier = Dossier.includes(:procedure).find_by(id: value)
- if dossier
- path = dossier_linked_path(current_gestionnaire, dossier)
- if path.present?
= link_to("Dossier nº #{dossier.id}", path, target: '_blank')
- else
Dossier nº #{dossier.id}
%br
= sanitize(dossier.text_summary)
- else
Pas de dossier associé
- when TypeDeChamp.type_champs.fetch(:piece_justificative)
%th.libelle
= "#{c.libelle} :"
%td.rich-text
- pj = c.piece_justificative_file
- if pj.attached?
= render partial: "shared/champs/piece_justificative/pj_link", locals: { champ: c, user_can_upload: false }
- else
Pièce justificative non fournie
- when TypeDeChamp.type_champs.fetch(:textarea)
%th.libelle
= "#{c.libelle} :"
%td.rich-text
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
= simple_format(value)
- when TypeDeChamp.type_champs.fetch(:siret)
%th.libelle
= "#{c.libelle} :"
%td.rich-text
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
- if c.etablissement.present?
= render partial: "shared/dossiers/identite_entreprise", locals: { etablissement: c.etablissement, profile: profile }
- when TypeDeChamp.type_champs.fetch(:carte)
%th.libelle
= "#{c.libelle} :"
%td.rich-text
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
- if value.present?
= render partial: "shared/champs/carte/show", locals: { champ: c }
- else
%th.libelle
= "#{c.libelle} :"
%td.rich-text
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
= sanitize(value)
- case c.type_champ
- when TypeDeChamp.type_champs.fetch(:carte)
= render partial: "shared/champs/carte/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:dossier_link)
= render partial: "shared/champs/dossier_link/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:multiple_drop_down_list)
= render partial: "shared/champs/multiple_drop_down_list/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:piece_justificative)
= render partial: "shared/champs/piece_justificative/show", locals: { champ: c }
- when TypeDeChamp.type_champs.fetch(:siret)
= render partial: "shared/champs/siret/show", locals: { champ: c, profile: profile }
- when TypeDeChamp.type_champs.fetch(:textarea)
= render partial: "shared/champs/textarea/show", locals: { champ: c }
- else
= sanitize(c.to_s)
- if c.type_champ != TypeDeChamp.type_champs.fetch(:header_section)
%td.updated-at
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }

View file

@ -1,7 +1,7 @@
- if champ.drop_down_list && champ.drop_down_list.options.any?
= form.select :value,
champ.drop_down_list.options,
{ selected: champ.drop_down_list.selected_options_without_decorator(champ),
{ selected: champ.selected_options,
disabled: champ.drop_down_list.disabled_options },
multiple: true,
class: 'select2'

View file

@ -9,6 +9,9 @@
%p.mail
Ouvrez votre boite email <b>#{@email}</b> puis cliquez sur le lien d'activation du message <b>Connexion sécurisée à demarches-simplifiees.fr</b>.
%br
%br
<b>Attention</b>, ce message peut mettre jusqu'à <b>15 minutes</b> pour arriver.
%p.help
En cas de difficultés, nous restons joignables sur #{link_to 'contact@demarches-simplifiees.fr', 'mailto:contact@demarches-simplifiees.fr'}.

View file

@ -1,7 +1,9 @@
require Rails.root.join("lib", "tasks", "task_helper")
namespace :after_party do
desc 'Deployment task: clone_service_for_transferred_procedures'
task clone_service_for_transferred_procedures: :environment do
puts "Running deploy task 'clone_service_for_transferred_procedures'"
rake_puts "Running deploy task 'clone_service_for_transferred_procedures'"
procedures = Procedure.includes(:service).where.not(service_id: nil)
procedures_to_fix_in_array = procedures.select do |p|
@ -16,12 +18,12 @@ namespace :after_party do
cloned_service = Service.find(service_id).clone_and_assign_to_administrateur(Administrateur.find(administrateur_id))
if cloned_service.save
puts "Fixing Service #{service_id} for Administrateur #{administrateur_id}"
rake_puts "Fixing Service #{service_id} for Administrateur #{administrateur_id}"
procedures_to_fix
.where(service_id: service_id, administrateur_id: administrateur_id)
.update_all(service_id: cloned_service.id)
else
puts "Cannot fix Service #{service_id} for Administrateur #{administrateur_id}, it should be fixed manually. Errors : #{cloned_service.errors.full_messages}"
rake_puts "Cannot fix Service #{service_id} for Administrateur #{administrateur_id}, it should be fixed manually. Errors : #{cloned_service.errors.full_messages}"
end
progress.inc
end

View file

@ -203,7 +203,7 @@ describe Admin::ProceduresController, type: :controller do
end
context 'when procedure is correctly saved' do
let!(:gestionnaire) { create(:gestionnaire, email: admin.email) }
let(:gestionnaire) { admin.gestionnaire }
before do
post :create, params: { procedure: procedure_params }

View file

@ -7,9 +7,9 @@ describe Gestionnaires::PasswordsController, type: :controller do
describe "update" do
context "unified login" do
let(:gestionnaire) { create(:gestionnaire, email: 'unique@plop.com', password: 'un super mot de passe') }
let(:user) { create(:user, email: 'unique@plop.com', password: 'un super mot de passe') }
let(:administrateur) { create(:administrateur, email: 'unique@plop.com', password: 'un super mot de passe') }
let(:gestionnaire) { administrateur.gestionnaire }
before do
@token = gestionnaire.send(:set_reset_password_token)

View file

@ -8,12 +8,10 @@ describe Users::PasswordsController, type: :controller do
describe "update" do
context "unified login" do
let(:user) { create(:user, email: 'unique@plop.com', password: 'mot de passe complexe') }
let(:gestionnaire) { create(:gestionnaire, email: 'unique@plop.com', password: 'mot de passe complexe') }
let(:administrateur) { create(:administrateur, email: 'unique@plop.com', password: 'mot de passe complexe') }
before do
@token = user.send(:set_reset_password_token)
gestionnaire # make sure it's created
administrateur # make sure it's created
end
@ -26,7 +24,7 @@ describe Users::PasswordsController, type: :controller do
}
}
expect(subject.current_user).to eq(user)
expect(subject.current_gestionnaire).to eq(gestionnaire)
expect(subject.current_gestionnaire.email).to eq(administrateur.email)
end
it "also signs administrateur in" do

View file

@ -1,129 +1,88 @@
require 'spec_helper'
describe Users::SessionsController, type: :controller do
let(:email) { 'unique@plop.com' }
let(:password) { 'un super mot de passe' }
let(:loged_in_with_france_connect) { User.loged_in_with_france_connects.fetch(:particulier) }
let(:user) { create(:user, loged_in_with_france_connect: loged_in_with_france_connect) }
let!(:user) { create(:user, email: email, password: password, loged_in_with_france_connect: loged_in_with_france_connect) }
before do
@request.env["devise.mapping"] = Devise.mappings[:user]
end
describe '#create' do
it { expect(described_class).to be < Sessions::SessionsController }
context "when the user is also a gestionnaire and an administrateur" do
let!(:administrateur) { create(:administrateur, :with_admin_trusted_device, email: email, password: password) }
let(:gestionnaire) { administrateur.gestionnaire }
let(:trusted_device) { true }
let(:send_password) { password }
describe 'France Connect attribut' do
before do
post :create, params: { user: { email: user.email, password: user.password } }
allow(controller).to receive(:trusted_device?).and_return(trusted_device)
allow(GestionnaireMailer).to receive(:send_login_token).and_return(double(deliver_later: true))
end
subject do
post :create, params: { user: { email: email, password: send_password } }
user.reload
end
subject { user.loged_in_with_france_connect.present? }
context 'when the device is not trusted' do
let(:trusted_device) { false }
it { is_expected.to be_falsey }
end
it 'redirects to the confirmation link path' do
subject
context "unified login" do
let(:email) { 'unique@plop.com' }
let(:password) { 'un super mot de passe' }
expect(controller).to redirect_to link_sent_path(email: email)
let(:user) { create(:user, email: email, password: password) }
let(:gestionnaire) { create(:gestionnaire, :with_trusted_device, email: email, password: password) }
let(:administrateur) { create(:administrateur, email: email, password: password) }
# do not know why, should be test related
expect(controller.current_user).to eq(user)
it 'signs user in' do
post :create, params: { user: { email: user.email, password: user.password } }
expect(@response.redirect?).to be(true)
expect(subject.current_user).to eq(user)
expect(subject.current_gestionnaire).to be(nil)
expect(subject.current_administrateur).to be(nil)
expect(user.reload.loged_in_with_france_connect).to be(nil)
end
expect(controller.current_gestionnaire).to be(nil)
expect(controller.current_administrateur).to be(nil)
expect(user.loged_in_with_france_connect).to be(nil)
expect(GestionnaireMailer).to have_received(:send_login_token)
end
it 'signs gestionnaire in' do
post :create, params: { user: { email: gestionnaire.email, password: gestionnaire.password } }
context 'and the user try to connect multiple times in a short period' do
before do
allow_any_instance_of(Gestionnaire).to receive(:young_login_token?).and_return(true)
allow(GestionnaireMailer).to receive(:send_login_token)
end
expect(subject).to redirect_to link_sent_path(email: gestionnaire.email)
expect(subject.current_user).to be(nil)
expect(subject.current_gestionnaire).to be(nil)
expect(subject.current_administrateur).to be(nil)
it 'does not renew nor send a new login token' do
subject
expect(GestionnaireMailer).not_to have_received(:send_login_token)
end
end
end
context 'when the device is trusted' do
before do
allow(controller).to receive(:trusted_device?).and_return(true)
post :create, params: { user: { email: gestionnaire.email, password: gestionnaire.password } }
end
it 'signs in as user, gestionnaire and adminstrateur' do
subject
it 'directly log the gestionnaire' do
expect(@response.redirect?).to be(true)
expect(subject).not_to redirect_to link_sent_path(email: gestionnaire.email)
expect(response.redirect?).to be(true)
expect(controller).not_to redirect_to link_sent_path(email: email)
# TODO when signing in as non-administrateur, and not starting a demarche, log in to gestionnaire path
# expect(subject).to redirect_to gestionnaire_procedures_path
expect(subject.current_user).to be(nil)
expect(subject.current_gestionnaire).to eq(gestionnaire)
expect(subject.current_administrateur).to be(nil)
# expect(controller).to redirect_to gestionnaire_procedures_path
expect(controller.current_user).to eq(user)
expect(controller.current_gestionnaire).to eq(gestionnaire)
expect(controller.current_administrateur).to eq(administrateur)
expect(user.loged_in_with_france_connect).to be(nil)
expect(GestionnaireMailer).not_to have_received(:send_login_token)
end
end
context 'signs administrateur in' do
# an admin has always an gestionnaire role
before { gestionnaire }
context 'when the credentials are wrong' do
let(:send_password) { 'wrong_password' }
it 'signs administrateur in' do
post :create, params: { user: { email: administrateur.email, password: administrateur.password } }
it 'fails to sign in with bad credentials' do
subject
expect(subject).to redirect_to link_sent_path(email: gestionnaire.email)
expect(subject.current_user).to be(nil)
expect(subject.current_gestionnaire).to be(nil)
expect(subject.current_administrateur).to eq(nil)
end
end
context {
before do
user
gestionnaire
end
it 'signs user + gestionnaire + administrateur in' do
post :create, params: { user: { email: administrateur.email, password: administrateur.password } }
expect(subject).to redirect_to link_sent_path(email: gestionnaire.email)
# TODO: fix me
# Strange behaviour: sign_out(:user) does not work in spec
# but seems to work in live
# expect(controller.current_user).to be(nil)
expect(subject.current_gestionnaire).to be(nil)
expect(subject.current_administrateur).to be(nil)
expect(user.reload.loged_in_with_france_connect).to be(nil)
end
}
it 'fails to sign in with bad credentials' do
post :create, params: { user: { email: user.email, password: 'wrong_password' } }
expect(@response.unauthorized?).to be(true)
expect(subject.current_user).to be(nil)
expect(subject.current_gestionnaire).to be(nil)
expect(subject.current_administrateur).to be(nil)
end
context 'with different passwords' do
let!(:gestionnaire) { create(:gestionnaire, email: email, password: 'mot de passe complexe') }
let!(:administrateur) { create(:administrateur, email: email, password: 'mot de passe complexe') }
before do
user
end
it 'should sync passwords on login' do
post :create, params: { user: { email: email, password: password } }
gestionnaire.reload
administrateur.reload
expect(user.valid_password?(password)).to be(true)
expect(gestionnaire.valid_password?(password)).to be(true)
expect(administrateur.valid_password?(password)).to be(true)
expect(response.unauthorized?).to be(true)
expect(controller.current_user).to be(nil)
expect(controller.current_gestionnaire).to be(nil)
expect(controller.current_administrateur).to be(nil)
end
end
end
@ -193,20 +152,20 @@ describe Users::SessionsController, type: :controller do
delete :destroy
expect(@response.headers["Location"]).to eq(FRANCE_CONNECT[:particulier][:logout_endpoint])
end
end
context "when associated administrateur" do
let(:administrateur) { create(:administrateur, email: 'unique@plop.com') }
context "when associated administrateur" do
let(:administrateur) { create(:administrateur, email: 'unique@plop.com') }
it 'signs user + gestionnaire + administrateur out' do
sign_in user
sign_in gestionnaire
sign_in administrateur
delete :destroy
expect(@response.redirect?).to be(true)
expect(subject.current_user).to be(nil)
expect(subject.current_gestionnaire).to be(nil)
expect(subject.current_administrateur).to be(nil)
end
it 'signs user + gestionnaire + administrateur out' do
sign_in user
sign_in administrateur.gestionnaire
sign_in administrateur
delete :destroy
expect(@response.redirect?).to be(true)
expect(subject.current_user).to be(nil)
expect(subject.current_gestionnaire).to be(nil)
expect(subject.current_administrateur).to be(nil)
end
end
end
@ -284,8 +243,8 @@ describe Users::SessionsController, type: :controller do
let(:password) { 'un super mot de passe' }
let!(:user) { create(:user, email: email, password: password) }
let!(:gestionnaire) { create(:gestionnaire, email: email, password: password) }
let!(:administrateur) { create(:administrateur, email: email, password: password) }
let(:gestionnaire) { administrateur.gestionnaire }
before do
post :sign_in_by_link, params: { id: gestionnaire.id, jeton: jeton }

View file

@ -3,6 +3,16 @@ FactoryBot.define do
factory :administrateur do
email { generate(:administrateur_email) }
password { 'mon chien aime les bananes' }
after(:create) do |admin|
create(:gestionnaire, email: admin.email, password: admin.password)
end
end
trait :with_admin_trusted_device do
after(:create) do |admin|
admin.gestionnaire.update(features: { "enable_email_login_token" => true })
end
end
trait :with_api_token do

View file

@ -5,8 +5,7 @@ feature 'Administrator connection' do
let(:email) { 'admin1@admin.com' }
let(:password) { 'mon chien aime les bananes' }
let!(:admin) { create(:administrateur, :with_procedure, email: email, password: password) }
let!(:gestionnaire) { create(:gestionnaire, :with_trusted_device, email: email, password: password) }
let!(:admin) { create(:administrateur, :with_admin_trusted_device, :with_procedure, email: email, password: password) }
before do
visit new_administrateur_session_path

View file

@ -116,7 +116,7 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
end
end
scenario 'After adding champ and file, check impossibility to publish procedure, add instructeur and make publication' do
scenario 'After adding champ and file, make publication' do
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
click_on 'add_type_de_champ'
click_on 'onglet-pieces'
@ -125,17 +125,6 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
fill_in 'procedure_types_de_piece_justificative_attributes_0_libelle', with: 'libelle de piece'
click_on 'add_piece_justificative'
click_on 'onglet-infos'
expect(page).to have_current_path(admin_procedure_path(Procedure.last))
expect(page).to have_selector('#disabled-publish-procedure')
expect(page.find_by_id('disabled-publish-procedure')[:disabled]).to eq('true')
click_on 'onglet-instructeurs'
expect(page).to have_current_path(admin_procedure_instructeurs_path(Procedure.last))
fill_in 'gestionnaire_email', with: 'gestionnaire@apientreprise.fr'
click_on 'add-gestionnaire-email'
page.first('.gestionnaire-affectation').click
click_on 'onglet-infos'
expect(page).to have_current_path(admin_procedure_path(Procedure.last))
expect(page).to have_selector('#publish-procedure', visible: true)

View file

@ -1,64 +0,0 @@
require 'rails_helper'
RSpec.describe ChampHelper, type: :helper do
let(:type_de_champ) { create(:type_de_champ) }
let(:champ) { type_de_champ.champ.create }
describe '.formatted_value' do
subject { formatted_value(champ) }
describe 'for a checkbox' do
let(:type_de_champ) { create(:type_de_champ_checkbox) }
context 'when value is on' do
before { champ.update value: 'on' }
it { is_expected.to eq 'Oui' }
end
context 'when value is other' do
it { is_expected.to eq 'Non' }
end
end
describe 'for a engagement' do
let(:type_de_champ) { create(:type_de_champ_engagement) }
context 'when value is on' do
before { champ.update value: 'on' }
it { is_expected.to eq 'Oui' }
end
context 'when value is other' do
it { is_expected.to eq 'Non' }
end
end
describe 'for a multiple_drop_down_list' do
let(:type_de_champ) { create(:type_de_champ_multiple_drop_down_list) }
context 'when value is an array' do
before { champ.update value: '["1", "2"]' }
it { is_expected.to eq '1, 2' }
end
context 'when value is empty' do
before { champ.update value: '' }
it { is_expected.to eq '' }
end
end
describe "for a date" do
let(:type_de_champ) { create(:type_de_champ_date) }
context "when value is an ISO date" do
before { champ.update value: "2017-12-31" }
it { is_expected.to eq "31/12/2017" }
end
context "when value is empty" do
before { champ.update value: nil }
it { is_expected.to eq nil }
end
end
end
end

View file

@ -1,12 +1,19 @@
describe DossierLinkHelper do
describe "#dossier_linked_path" do
context "when no access" do
context "when no access as a gestionnaire" do
let(:gestionnaire) { create(:gestionnaire) }
let(:dossier) { create(:dossier) }
it { expect(helper.dossier_linked_path(gestionnaire, dossier)).to be_nil }
end
context "when no access as a user" do
let(:user) { create(:user) }
let(:dossier) { create(:dossier) }
it { expect(helper.dossier_linked_path(user, dossier)).to be_nil }
end
context "when access as gestionnaire" do
let(:dossier) { create(:dossier) }
let(:gestionnaire) { create(:gestionnaire) }
@ -23,5 +30,14 @@ describe DossierLinkHelper do
it { expect(helper.dossier_linked_path(gestionnaire, dossier)).to eq(gestionnaire_avis_path(avis)) }
end
context "when access as user" do
let(:dossier) { create(:dossier) }
let(:user) { create(:user) }
before { dossier.user = user }
it { expect(helper.dossier_linked_path(user, dossier)).to eq(dossier_path(dossier)) }
end
end
end

View file

@ -22,7 +22,7 @@ describe Administrateur, type: :model do
it 'syncs credentials to associated administrateur' do
administrateur = create(:administrateur)
gestionnaire = create(:gestionnaire, email: administrateur.email)
gestionnaire = administrateur.gestionnaire
administrateur.update(email: 'whoami@plop.com', password: 'et encore un autre mdp')

View file

@ -104,19 +104,19 @@ describe Champ do
context 'if yes' do
let(:value) { 'true' }
it { expect(champ.for_export).to eq('oui') }
it { expect(champ.for_export).to eq('Oui') }
end
context 'if no' do
let(:value) { 'false' }
it { expect(champ.for_export).to eq('non') }
it { expect(champ.for_export).to eq('Non') }
end
context 'if nil' do
let(:value) { nil }
it { expect(champ.for_export).to eq(nil) }
it { expect(champ.for_export).to eq('Non') }
end
end
@ -401,17 +401,31 @@ describe Champ do
end
describe "repetition" do
let(:champ) { create(:champ_repetition) }
let(:dossier) { create(:dossier) }
let(:champ) { create(:champ_repetition, dossier: dossier) }
let(:champ_text) { create(:champ_text, row: 0) }
let(:champ_integer_number) { create(:champ_integer_number, row: 0) }
let(:champ_text2) { create(:champ_text, row: 1) }
let(:champ_text_attrs) { attributes_for(:champ_text, row: 1) }
it {
it "associates nested champs to the parent dossier" do
expect(champ.rows.size).to eq(0)
dossier.reload
expect(dossier.champs.size).to eq(2)
champ.champs << champ_text2
dossier.update(champs_attributes: [
{
id: champ.id,
champs_attributes: [champ_text_attrs]
}
])
champ.reload
dossier.reload
expect(dossier.champs.size).to eq(2)
expect(champ.rows.size).to eq(1)
expect(champ.champs.first.dossier).to eq(dossier)
champ.champs << champ_integer_number
row = champ.reload.rows.first
expect(row.size).to eq(1)
@ -423,6 +437,6 @@ describe Champ do
expect(row.second).to eq(champ_text)
expect(champ.rows.size).to eq(2)
}
end
end
end

View file

@ -9,13 +9,13 @@ describe Champs::CheckboxChamp do
context 'when the value is on' do
let(:value) { 'on' }
it { is_expected.to eq('oui') }
it { is_expected.to eq('Oui') }
end
context 'when the value is off' do
let(:value) { 'off' }
it { is_expected.to eq('non') }
it { is_expected.to eq('Non') }
end
end
end

View file

@ -16,12 +16,12 @@ describe Champs::LinkedDropDownListChamp do
it { expect(champ.value).to eq('["tata","tutu"]') }
end
describe '#for_display' do
describe '#to_s' do
let(:champ) { described_class.new(primary_value: primary_value, secondary_value: secondary_value) }
let(:primary_value) { nil }
let(:secondary_value) { nil }
subject { champ.for_display }
subject { champ.to_s }
context 'with no value' do
it { is_expected.to eq('') }

View file

@ -5,19 +5,19 @@ describe Champs::YesNoChamp do
context 'when the value is false' do
let(:value) { "false" }
it { is_expected.to eq("non") }
it { is_expected.to eq("Non") }
end
context 'when the value is true' do
let(:value) { "true" }
it { is_expected.to eq("oui") }
it { is_expected.to eq("Oui") }
end
context 'when the value is nil' do
let(:value) { nil }
it { is_expected.to eq("non") }
it { is_expected.to eq("Non") }
end
end
end

View file

@ -149,13 +149,12 @@ describe Gestionnaire, type: :model do
end
it 'syncs credentials to associated administrateur' do
gestionnaire = create(:gestionnaire)
admin = create(:administrateur, email: gestionnaire.email)
admin = create(:administrateur)
gestionnaire = admin.gestionnaire
gestionnaire.update(email: 'whoami@plop.com', password: 'super secret')
gestionnaire.update(password: 'super secret')
admin.reload
expect(admin.email).to eq('whoami@plop.com')
expect(admin.valid_password?('super secret')).to be(true)
end
end
@ -413,6 +412,27 @@ describe Gestionnaire, type: :model do
end
end
describe '#young_login_token?' do
let!(:gestionnaire) { create(:gestionnaire) }
context 'when there is a token' do
let!(:good_token) { gestionnaire.login_token! }
context 'when the token has just been created' do
it { expect(gestionnaire.young_login_token?).to be true }
end
context 'when the token is a bit old' do
before { gestionnaire.update(login_token_created_at: (Gestionnaire::LOGIN_TOKEN_YOUTH + 1.minute).ago) }
it { expect(gestionnaire.young_login_token?).to be false }
end
end
context 'when there are no token' do
it { expect(gestionnaire.young_login_token?).to be false }
end
end
private
def assign(procedure_to_assign)

View file

@ -361,6 +361,7 @@ describe Procedure do
subject { @procedure }
it { expect(subject.parent_procedure).to eq(procedure) }
it { expect(subject.gestionnaires.pluck(:email)).to eq([administrateur.email]) }
it 'should duplicate specific objects with different id' do
expect(subject.id).not_to eq(procedure.id)
@ -430,8 +431,6 @@ describe Procedure do
it 'should not duplicate specific related objects' do
expect(subject.dossiers).to eq([])
expect(subject.gestionnaires).to eq([])
expect(subject.assign_to).to eq([])
end
describe 'should not duplicate lien_notice' do

View file

@ -112,18 +112,35 @@ shared_examples 'type_de_champ_spec' do
end
describe "repetition" do
let(:type_de_champ) { create(:type_de_champ_repetition) }
let(:procedure) { create(:procedure) }
let(:type_de_champ) { create(:type_de_champ_repetition, procedure: procedure) }
let(:type_de_champ_text) { create(:type_de_champ_text) }
let(:type_de_champ_integer_number) { create(:type_de_champ_integer_number) }
let(:type_de_champ_integer_number_attrs) { attributes_for(:type_de_champ_integer_number) }
it {
it "associates nested types_de_champ to the parent procedure" do
expect(type_de_champ.types_de_champ.size).to eq(0)
type_de_champ.types_de_champ << type_de_champ_integer_number
expect(procedure.types_de_champ.size).to eq(1)
procedure.update(types_de_champ_attributes: [
{
id: type_de_champ.id,
libelle: type_de_champ.libelle,
types_de_champ_attributes: [type_de_champ_integer_number_attrs]
}
])
procedure.reload
type_de_champ.reload
expect(procedure.types_de_champ.size).to eq(1)
expect(type_de_champ.types_de_champ.size).to eq(1)
expect(type_de_champ.types_de_champ.first.parent).to eq(type_de_champ)
expect(type_de_champ.types_de_champ.first.procedure).to eq(procedure)
expect(type_de_champ.types_de_champ.first.private?).to eq(false)
type_de_champ.types_de_champ << type_de_champ_text
expect(type_de_champ.types_de_champ.size).to eq(2)
expect(type_de_champ_integer_number.parent).to eq(type_de_champ)
expect(type_de_champ_text.parent).to eq(type_de_champ)
}
end
end
end

View file

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

View file

@ -17,21 +17,29 @@ describe 'shared/dossiers/champs.html.haml', type: :view do
let(:champ2) { create(:champ, :header_section, value: "Section") }
let(:champ3) { create(:champ, :explication, value: "mazette") }
let(:champ4) { create(:champ, :dossier_link, value: dossier.id) }
let(:champs) { [champ1, champ2, champ3, champ4] }
let(:champ5) { create(:champ_textarea, value: "Some long text in a textarea.") }
let(:champs) { [champ1, champ2, champ3, champ4, champ5] }
before { dossier.avis << avis }
it { is_expected.to include(champ1.libelle) }
it { is_expected.to include(champ1.value) }
it "renders titles and values of champs" do
expect(subject).to include(champ1.libelle)
expect(subject).to include(champ1.value)
it { is_expected.to have_css(".header-section") }
it { is_expected.to include(champ2.libelle) }
expect(subject).to have_css(".header-section")
expect(subject).to include(champ2.libelle)
it { is_expected.not_to include(champ3.libelle) }
it { is_expected.not_to include(champ3.value) }
expect(subject).to have_link("Dossier nº #{dossier.id}")
expect(subject).to include(dossier.text_summary)
it { is_expected.to have_link("Dossier nº #{dossier.id}") }
it { is_expected.to include(dossier.text_summary) }
expect(subject).to include(champ5.libelle)
expect(subject).to include(champ5.libelle)
end
it "doesn't render explication champs" do
expect(subject).not_to include(champ3.libelle)
expect(subject).not_to include(champ3.value)
end
end
context "with a dossier champ, but we are not authorized to acces the dossier" do