commit
396299cd8f
37 changed files with 458 additions and 366 deletions
2
Gemfile
2
Gemfile
|
@ -49,7 +49,7 @@ gem 'kaminari', '1.2.1' # Pagination
|
||||||
gem 'listen' # Required by ActiveSupport::EventedFileUpdateChecker
|
gem 'listen' # Required by ActiveSupport::EventedFileUpdateChecker
|
||||||
gem 'lograge'
|
gem 'lograge'
|
||||||
gem 'logstash-event'
|
gem 'logstash-event'
|
||||||
gem 'mailjet'
|
gem 'mailjet', require: false
|
||||||
gem 'openid_connect'
|
gem 'openid_connect'
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
gem 'phonelib'
|
gem 'phonelib'
|
||||||
|
|
|
@ -4,3 +4,9 @@
|
||||||
font-family: "Muli", system-ui, -apple-system, sans-serif;
|
font-family: "Muli", system-ui, -apple-system, sans-serif;
|
||||||
color: $black;
|
color: $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
line-height: 28px;
|
||||||
|
list-style-type: decimal;
|
||||||
|
list-style-position: inside;
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
// The procedure description can still be read from the /commencer
|
// The procedure description can still be read from the /commencer
|
||||||
// pages.
|
// pages.
|
||||||
@media (max-width: $two-columns-breakpoint) {
|
@media (max-width: $two-columns-breakpoint) {
|
||||||
.procedure-description {
|
.procedure-preview {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,3 +45,7 @@
|
||||||
.flex-grow {
|
.flex-grow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-no-shrink {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -5,26 +5,38 @@
|
||||||
padding-top: 2 * $default-padding;
|
padding-top: 2 * $default-padding;
|
||||||
padding-bottom: 2 * $default-padding;
|
padding-bottom: 2 * $default-padding;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 600px;
|
max-width: 700px;
|
||||||
|
|
||||||
b {
|
section {
|
||||||
|
text-align: left;
|
||||||
|
margin: 3 * $default-padding auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
ol {
|
||||||
|
margin-top: $default-padding;
|
||||||
|
margin-bottom: $default-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-sent-info {
|
||||||
|
color: #000000;
|
||||||
|
background-color: $yellow;
|
||||||
|
padding: 0 $default-padding;
|
||||||
|
border: 1px solid transparent; // prevent margin collapse of first paragraph
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-sent-help {
|
||||||
|
border-top: 1px solid $grey;
|
||||||
|
padding-top: $default-padding;
|
||||||
|
margin-bottom: $default-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-sent-help-title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
.link-sent-help-list {
|
||||||
text-align: left;
|
list-style-position: outside;
|
||||||
margin: 6 * $default-spacer auto;
|
padding-left: $default-padding;
|
||||||
}
|
|
||||||
|
|
||||||
p.mail {
|
|
||||||
color: #000000;
|
|
||||||
background-color: $yellow;
|
|
||||||
padding: $default-padding;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.help {
|
|
||||||
border-top: 1px solid $grey;
|
|
||||||
padding-top: 6 * $default-spacer;
|
|
||||||
margin-bottom: 2 * $default-spacer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,3 +36,13 @@
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-metadata {
|
||||||
|
margin-top: -8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -108,10 +108,10 @@ module Instructeurs
|
||||||
|
|
||||||
@dossiers = @dossiers.where(id: filtered_sorted_paginated_ids)
|
@dossiers = @dossiers.where(id: filtered_sorted_paginated_ids)
|
||||||
|
|
||||||
@dossiers = procedure_presentation.eager_load_displayed_fields(@dossiers)
|
|
||||||
|
|
||||||
@dossiers = @dossiers.sort_by { |d| filtered_sorted_paginated_ids.index(d.id) }
|
@dossiers = @dossiers.sort_by { |d| filtered_sorted_paginated_ids.index(d.id) }
|
||||||
|
|
||||||
|
@projected_dossiers = DossierProjectionService.project(filtered_sorted_paginated_ids, procedure_presentation.displayed_fields)
|
||||||
|
|
||||||
kaminarize(page, filtered_sorted_ids.count)
|
kaminarize(page, filtered_sorted_ids.count)
|
||||||
|
|
||||||
assign_exports
|
assign_exports
|
||||||
|
|
|
@ -33,6 +33,10 @@ class Users::PasswordsController < Devise::PasswordsController
|
||||||
# super
|
# super
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
def reset_link_sent
|
||||||
|
@email = params[:email]
|
||||||
|
end
|
||||||
|
|
||||||
# protected
|
# protected
|
||||||
|
|
||||||
# def after_resetting_password_path_for(resource)
|
# def after_resetting_password_path_for(resource)
|
||||||
|
@ -74,4 +78,9 @@ class Users::PasswordsController < Devise::PasswordsController
|
||||||
def password_params
|
def password_params
|
||||||
params.require(:user).permit(:reset_password_token, :password)
|
params.require(:user).permit(:reset_password_token, :password)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def after_sending_reset_password_instructions_path_for(resource_name)
|
||||||
|
flash.discard(:notice)
|
||||||
|
users_password_reset_link_sent_path(email: resource.email)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,13 @@ function buildURL(scope, term) {
|
||||||
return `${api_adresse_url}/search?q=${term}&limit=5`;
|
return `${api_adresse_url}/search?q=${term}&limit=5`;
|
||||||
} else if (scope === 'annuaire-education') {
|
} else if (scope === 'annuaire-education') {
|
||||||
return `${api_education_url}/search?dataset=fr-en-annuaire-education&q=${term}&rows=5`;
|
return `${api_education_url}/search?dataset=fr-en-annuaire-education&q=${term}&rows=5`;
|
||||||
|
} else if (scope === 'communes') {
|
||||||
|
if (isNumeric(term)) {
|
||||||
|
return `${api_geo_url}/communes?codePostal=${term}&limit=5`;
|
||||||
|
}
|
||||||
|
// Avoid hiding similar matches for precise queries (like "Sainte Marie")
|
||||||
|
const limit = term.length > 5 ? 10 : 5;
|
||||||
|
return `${api_geo_url}/communes?nom=${term}&boost=population&limit=${limit}`;
|
||||||
} else if (isNumeric(term)) {
|
} else if (isNumeric(term)) {
|
||||||
const code = term.padStart(2, '0');
|
const code = term.padStart(2, '0');
|
||||||
return `${api_geo_url}/${scope}?code=${code}&limit=5`;
|
return `${api_geo_url}/${scope}?code=${code}&limit=5`;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Preview all emails at http://localhost:3000/rails/mailers/devise_user_mailer
|
# Preview all emails at http://localhost:3000/rails/mailers/devise_user_mailer
|
||||||
class DeviseUserMailer < Devise::Mailer
|
class DeviseUserMailer < Devise::Mailer
|
||||||
helper :application # gives access to all helpers defined within `application_helper`.
|
helper :application # gives access to all helpers defined within `application_helper`.
|
||||||
|
helper MailerHelper
|
||||||
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
|
include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
|
||||||
layout 'mailers/layout'
|
layout 'mailers/layout'
|
||||||
|
|
||||||
|
|
|
@ -90,10 +90,6 @@ class ProcedurePresentation < ApplicationRecord
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def displayed_fields_values(dossier)
|
|
||||||
displayed_fields.map { |field| get_value(dossier, field[TABLE], field[COLUMN]) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def sorted_ids(dossiers, instructeur)
|
def sorted_ids(dossiers, instructeur)
|
||||||
table, column, order = sort.values_at(TABLE, COLUMN, 'order')
|
table, column, order = sort.values_at(TABLE, COLUMN, 'order')
|
||||||
|
|
||||||
|
@ -178,25 +174,6 @@ class ProcedurePresentation < ApplicationRecord
|
||||||
end.reduce(:&)
|
end.reduce(:&)
|
||||||
end
|
end
|
||||||
|
|
||||||
def eager_load_displayed_fields(dossiers)
|
|
||||||
relations_to_include = displayed_fields
|
|
||||||
.pluck(TABLE)
|
|
||||||
.reject { |table| table == 'self' }
|
|
||||||
.map do |table|
|
|
||||||
case table
|
|
||||||
when TYPE_DE_CHAMP
|
|
||||||
{ champs: :type_de_champ }
|
|
||||||
when TYPE_DE_CHAMP_PRIVATE
|
|
||||||
{ champs_private: :type_de_champ }
|
|
||||||
else
|
|
||||||
table
|
|
||||||
end
|
|
||||||
end
|
|
||||||
.uniq
|
|
||||||
|
|
||||||
dossiers.includes(relations_to_include)
|
|
||||||
end
|
|
||||||
|
|
||||||
def human_value_for_filter(filter)
|
def human_value_for_filter(filter)
|
||||||
case filter[TABLE]
|
case filter[TABLE]
|
||||||
when TYPE_DE_CHAMP, TYPE_DE_CHAMP_PRIVATE
|
when TYPE_DE_CHAMP, TYPE_DE_CHAMP_PRIVATE
|
||||||
|
@ -314,23 +291,6 @@ class ProcedurePresentation < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_value(dossier, table, column)
|
|
||||||
case table
|
|
||||||
when 'self'
|
|
||||||
dossier.send(column)&.strftime('%d/%m/%Y')
|
|
||||||
when 'user', 'individual', 'etablissement'
|
|
||||||
dossier.send(table)&.send(column)
|
|
||||||
when 'followers_instructeurs'
|
|
||||||
dossier.send(table)&.map { |g| g.send(column) }&.join(', ')
|
|
||||||
when TYPE_DE_CHAMP
|
|
||||||
dossier.champs.find { |c| c.stable_id == column.to_i }.to_s
|
|
||||||
when TYPE_DE_CHAMP_PRIVATE
|
|
||||||
dossier.champs_private.find { |c| c.stable_id == column.to_i }.to_s
|
|
||||||
when 'groupe_instructeur'
|
|
||||||
dossier.groupe_instructeur.label
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def field_hash(label, table, column)
|
def field_hash(label, table, column)
|
||||||
{
|
{
|
||||||
'label' => label,
|
'label' => label,
|
||||||
|
|
88
app/services/dossier_projection_service.rb
Normal file
88
app/services/dossier_projection_service.rb
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
class DossierProjectionService
|
||||||
|
class DossierProjection < Struct.new(:dossier, :columns)
|
||||||
|
end
|
||||||
|
|
||||||
|
TABLE = 'table'
|
||||||
|
COLUMN = 'column'
|
||||||
|
|
||||||
|
# Returns [DossierProjection(dossier, columns)] ordered by dossiers_ids
|
||||||
|
# and the columns orderd by fields.
|
||||||
|
#
|
||||||
|
# It tries to be fast by using `pluck` (or at least `select`)
|
||||||
|
# to avoid deserializing entire records.
|
||||||
|
#
|
||||||
|
# It stores its intermediary queries results in an hash in the corresponding field.
|
||||||
|
# ex: field_email[:id_value_h] = { dossier_id_1: email_1, dossier_id_3: email_3 }
|
||||||
|
#
|
||||||
|
# Those hashes are needed because:
|
||||||
|
# - the order of the intermediary query results are unknown
|
||||||
|
# - some values can be missing (if a revision added or removed them)
|
||||||
|
def self.project(dossiers_ids, fields)
|
||||||
|
champ_fields, other_fields = fields
|
||||||
|
.partition { |f| ['type_de_champ', 'type_de_champ_private'].include?(f[TABLE]) }
|
||||||
|
|
||||||
|
if champ_fields.present?
|
||||||
|
Champ
|
||||||
|
.includes(:type_de_champ)
|
||||||
|
.where(
|
||||||
|
# as querying the champs table is costly
|
||||||
|
# we fetch all the requested champs at once
|
||||||
|
types_de_champ: { stable_id: champ_fields.map { |f| f[COLUMN] } },
|
||||||
|
dossier_id: dossiers_ids
|
||||||
|
)
|
||||||
|
.select(:dossier_id, :value, :type_de_champ_id, :stable_id) # we cannot pluck :value, as we need the champ.to_s method
|
||||||
|
.group_by(&:stable_id) # the champs are redispatched to their respective fields
|
||||||
|
.map do |stable_id, champs|
|
||||||
|
field = champ_fields.find { |f| f[COLUMN] == stable_id.to_s }
|
||||||
|
field[:id_value_h] = champs.to_h { |c| [c.dossier_id, c.to_s] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
other_fields.each do |field|
|
||||||
|
field[:id_value_h] = case field[TABLE]
|
||||||
|
when 'self'
|
||||||
|
Dossier
|
||||||
|
.where(id: dossiers_ids)
|
||||||
|
.pluck(:id, field[COLUMN].to_sym)
|
||||||
|
.to_h { |id, col| [id, col&.strftime('%d/%m/%Y')] }
|
||||||
|
when 'user'
|
||||||
|
Dossier
|
||||||
|
.joins(:user)
|
||||||
|
.where(id: dossiers_ids)
|
||||||
|
.pluck('dossiers.id, users.email')
|
||||||
|
.to_h
|
||||||
|
when 'individual'
|
||||||
|
Individual
|
||||||
|
.where(dossier_id: dossiers_ids)
|
||||||
|
.pluck(:dossier_id, field[COLUMN].to_sym)
|
||||||
|
.to_h
|
||||||
|
when 'etablissement'
|
||||||
|
Etablissement
|
||||||
|
.where(dossier_id: dossiers_ids)
|
||||||
|
.pluck(:dossier_id, field[COLUMN].to_sym)
|
||||||
|
.to_h
|
||||||
|
when 'groupe_instructeur'
|
||||||
|
Dossier
|
||||||
|
.joins(:groupe_instructeur)
|
||||||
|
.where(id: dossiers_ids)
|
||||||
|
.pluck('dossiers.id, groupe_instructeurs.label')
|
||||||
|
.to_h
|
||||||
|
when 'followers_instructeurs'
|
||||||
|
Follow
|
||||||
|
.active
|
||||||
|
.joins(instructeur: :user)
|
||||||
|
.where(dossier_id: dossiers_ids)
|
||||||
|
.pluck('dossier_id, users.email')
|
||||||
|
.group_by { |dossier_id, _| dossier_id }
|
||||||
|
.to_h { |dossier_id, dossier_id_emails| [dossier_id, dossier_id_emails.map { |_, email| email }&.join(', ')] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Dossier
|
||||||
|
.select(:id, :state, :archived) # the dossier object is needed in the view
|
||||||
|
.find(dossiers_ids) # keeps dossiers_ids order and raise exception if one is missing
|
||||||
|
.map do |dossier|
|
||||||
|
DossierProjection.new(dossier, fields.map { |f| f[:id_value_h][dossier.id] })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,9 +2,9 @@
|
||||||
Bonjour,
|
Bonjour,
|
||||||
|
|
||||||
%p
|
%p
|
||||||
Vous avez demandé à regénérer votre mot de passe sur #{APPLICATION_BASE_URL}. Pour ceci, merci de suivre le lien suivant :
|
Vous avez demandé à changer votre mot de passe sur #{APPLICATION_NAME}. Pour ceci, merci de cliquer sur le lien suivant :
|
||||||
%br
|
|
||||||
= link_to edit_password_url(@resource, reset_password_token: @token), edit_password_url(@resource, reset_password_token: @token)
|
= round_button 'Changer mon mot de passe', edit_password_url(@resource, reset_password_token: @token), :primary
|
||||||
|
|
||||||
%p
|
%p
|
||||||
Si vous n'avez pas effectué une telle demande, merci d'ignorer cet email. Votre mot de passe ne sera pas changé.
|
Si vous n'avez pas effectué une telle demande, merci d'ignorer cet email. Votre mot de passe ne sera pas changé.
|
||||||
|
|
|
@ -17,5 +17,5 @@
|
||||||
%br
|
%br
|
||||||
%br
|
%br
|
||||||
.actions
|
.actions
|
||||||
= f.submit 'Réinitialiser', class: 'btn btn-primary'
|
= f.submit 'Demander un nouveau mot de passe', class: 'button large expand primary'
|
||||||
%br
|
%br
|
||||||
|
|
|
@ -129,25 +129,27 @@
|
||||||
= submit_tag "Enregistrer", class: 'button'
|
= submit_tag "Enregistrer", class: 'button'
|
||||||
|
|
||||||
%tbody
|
%tbody
|
||||||
- @dossiers.each do |dossier|
|
- @projected_dossiers.each do |p|
|
||||||
|
- dossier = p.dossier
|
||||||
|
- path = instructeur_dossier_path(@procedure, dossier.id)
|
||||||
|
|
||||||
%tr
|
%tr
|
||||||
%td.folder-col
|
%td.folder-col
|
||||||
= link_to(instructeur_dossier_path(@procedure, dossier), class: 'cell-link') do
|
%a.cell-link{ href: path }
|
||||||
%span.icon.folder
|
%span.icon.folder
|
||||||
- if @not_archived_notifications_dossier_ids.include?(dossier.id)
|
- if @not_archived_notifications_dossier_ids.include?(dossier.id)
|
||||||
%span.notifications{ 'aria-label': 'notifications' }
|
%span.notifications{ 'aria-label': 'notifications' }
|
||||||
|
|
||||||
%td.number-col
|
%td.number-col
|
||||||
= link_to(instructeur_dossier_path(@procedure, dossier), class: 'cell-link') do
|
%a.cell-link{ href: path }= dossier.id
|
||||||
= dossier.id
|
|
||||||
|
|
||||||
- @procedure_presentation.displayed_fields_values(dossier).each do |value|
|
- p.columns.each do |column|
|
||||||
%td
|
%td
|
||||||
= link_to(value, instructeur_dossier_path(@procedure, dossier), class: 'cell-link')
|
%a.cell-link{ href: path }= column
|
||||||
|
|
||||||
%td.status-col
|
%td.status-col
|
||||||
= link_to(instructeur_dossier_path(@procedure, dossier), class: 'cell-link') do
|
%a.cell-link{ href: path }= status_badge(dossier.state)
|
||||||
= status_badge(dossier.state)
|
|
||||||
%td.action-col.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
|
= paginate @dossiers
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
.sub-header
|
.sub-header
|
||||||
.container.flex.justify-between.align-baseline
|
.container.flex.justify-between.align-baseline.column
|
||||||
%ul.breadcrumbs
|
%ul.breadcrumbs
|
||||||
- steps.each do |step|
|
- steps.each do |step|
|
||||||
%li= step
|
%li= step
|
||||||
- if defined?(preview) && preview
|
- if defined?(preview) && preview
|
||||||
= link_to "Prévisualiser le formulaire", apercu_admin_procedure_path(@procedure), target: "_blank", rel: "noopener", class: 'button'
|
= link_to "Prévisualiser le formulaire", apercu_admin_procedure_path(@procedure), target: "_blank", rel: "noopener", class: 'button'
|
||||||
= link_to "Continuer >", admin_procedure_path(@procedure), title: 'Vous pourrez revenir ici par la suite', class: 'button accepted'
|
= link_to "Continuer >", admin_procedure_path(@procedure), title: 'Vous pourrez revenir ici par la suite', class: 'button accepted'
|
||||||
|
- if defined?(metadatas)
|
||||||
|
%ul.admin-metadata
|
||||||
|
- metadatas.each do |metadata|
|
||||||
|
%li= metadata
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
.flex.column.ml-1
|
.flex.column.ml-1
|
||||||
.card-title
|
.card-title
|
||||||
= link_to procedure.libelle, admin_procedure_path(procedure), style: 'color: black;'
|
= link_to procedure.libelle, admin_procedure_path(procedure), style: 'color: black;'
|
||||||
= link_to(procedure_lien(procedure), procedure_lien(procedure), class: 'procedure-lien mb-1')
|
= link_to(procedure_lien(procedure), procedure_lien(procedure), class: 'mb-1')
|
||||||
|
|
||||||
.admin-procedures-list-timestamps
|
.admin-procedures-list-timestamps
|
||||||
%p.notice N° #{procedure.id}
|
%p.notice N° #{procedure.id}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
= render partial: 'new_administrateur/breadcrumbs',
|
= render partial: 'new_administrateur/breadcrumbs',
|
||||||
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
||||||
"#{@procedure.libelle} (crée le #{@procedure.created_at.strftime('%d/%m/%Y')})", "#{@procedure.close? ? "Close" : @procedure.locked? ? "Publiée" : "Brouillon"}"] }
|
"#{@procedure.libelle}", ],
|
||||||
|
metadatas: ["Créée le #{@procedure.created_at.strftime('%d/%m/%Y')} - n° #{@procedure.id}", "#{@procedure.close? ? "Close le #{@procedure.closed_at.strftime('%d/%m/%Y')}" : @procedure.locked? ? "Publiée - #{procedure_lien(@procedure)}" : "Brouillon"}"] }
|
||||||
|
|
||||||
.container.procedure-admin-container
|
.container.procedure-admin-container
|
||||||
= link_to apercu_admin_procedure_path(@procedure), class: 'button', id: "preview-procedure" do
|
= link_to apercu_admin_procedure_path(@procedure), class: 'button', id: "preview-procedure" do
|
||||||
|
|
|
@ -14,4 +14,4 @@
|
||||||
= f.label :email, 'Email'
|
= f.label :email, 'Email'
|
||||||
= f.email_field :email, autofocus: true
|
= f.email_field :email, autofocus: true
|
||||||
|
|
||||||
= f.submit 'Réinitialiser', class: 'button primary'
|
= f.submit 'Demander un nouveau mot de passe', class: 'button large expand primary'
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
|
|
||||||
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: 'form' }) do |f|
|
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { class: 'form' }) do |f|
|
||||||
|
|
||||||
%h1 Mot de passe oublié
|
%h1= t('devise.passwords.new.forgot_your_password')
|
||||||
|
|
||||||
|
%p.notice= t('views.passwords.new.send_me_reset_password_instructions')
|
||||||
|
|
||||||
= f.label :email, 'Email'
|
= f.label :email, 'Email'
|
||||||
= f.email_field :email, autofocus: true
|
= f.email_field :email, autofocus: true
|
||||||
|
|
||||||
= f.submit 'Réinitialiser', class: 'button primary'
|
= f.submit 'Demander un nouveau mot de passe', class: 'button expand primary'
|
||||||
|
|
32
app/views/users/passwords/reset_link_sent.html.haml
Normal file
32
app/views/users/passwords/reset_link_sent.html.haml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
- content_for(:title, t('views.users.passwords.reset_link_sent.title'))
|
||||||
|
|
||||||
|
- content_for :footer do
|
||||||
|
= render partial: 'root/footer'
|
||||||
|
|
||||||
|
#link-sent.container
|
||||||
|
= image_tag('user/confirmation-email.svg')
|
||||||
|
%h1
|
||||||
|
= t('views.users.passwords.reset_link_sent.got_it')
|
||||||
|
%br
|
||||||
|
= t('views.users.passwords.reset_link_sent.open_your_mailbox')
|
||||||
|
|
||||||
|
%section.link-sent-info
|
||||||
|
%p
|
||||||
|
= t('views.users.passwords.reset_link_sent.email_sent_html', email: @email)
|
||||||
|
%p
|
||||||
|
= t('views.users.passwords.reset_link_sent.click_link_to_reset_password')
|
||||||
|
%p
|
||||||
|
= t('views.users.shared.email_can_take_a_while_html')
|
||||||
|
|
||||||
|
%section.link-sent-help
|
||||||
|
%h2.link-sent-help-title= t('views.users.passwords.reset_link_sent.no_mail')
|
||||||
|
%ol.link-sent-help-list
|
||||||
|
%li
|
||||||
|
= t('views.users.passwords.reset_link_sent.check_spams')
|
||||||
|
%li
|
||||||
|
= t('views.users.passwords.reset_link_sent.check_account', email: @email, application_name: APPLICATION_NAME)
|
||||||
|
- if FranceConnectService.enabled?
|
||||||
|
%li
|
||||||
|
= t('views.users.passwords.reset_link_sent.check_france_connect_html', href: france_connect_particulier_path)
|
||||||
|
%p
|
||||||
|
= t('views.users.shared.contact_us_if_any_trouble_html', href: contact_url)
|
|
@ -7,16 +7,14 @@
|
||||||
= image_tag('user/confirmation-email.svg')
|
= image_tag('user/confirmation-email.svg')
|
||||||
%h1 Encore une petite étape :)
|
%h1 Encore une petite étape :)
|
||||||
|
|
||||||
%p.mail
|
%section.link-sent-info
|
||||||
Ouvrez votre boite email <b>#{@email}</b> puis cliquez sur le lien d'activation du message <b>Connexion sécurisée à #{APPLICATION_NAME}</b>.
|
%p
|
||||||
%br
|
Ouvrez votre boite email <strong>#{@email}</strong> puis cliquez sur le lien d’activation du message <strong>Connexion sécurisée à #{APPLICATION_NAME}</strong>.
|
||||||
%br
|
%p
|
||||||
<b>Attention</b>, ce message peut mettre jusqu'à <b>15 minutes</b> pour arriver.
|
= t('views.users.shared.email_can_take_a_while')
|
||||||
|
|
||||||
%p.help
|
%section.link-sent-help
|
||||||
Si vous voyez cette page trop souvent, consultez notre aide : #{link_to FAQ_CONFIRMER_COMPTE_CHAQUE_CONNEXION_URL, FAQ_CONFIRMER_COMPTE_CHAQUE_CONNEXION_URL, target: '_blank', rel: 'noopener' }
|
%p
|
||||||
%br
|
Si vous voyez cette page trop souvent, consultez notre aide : #{link_to FAQ_CONFIRMER_COMPTE_CHAQUE_CONNEXION_URL, FAQ_CONFIRMER_COMPTE_CHAQUE_CONNEXION_URL, target: '_blank', rel: 'noopener' }
|
||||||
%br
|
%p
|
||||||
En cas de difficultés, nous restons joignables
|
= t('views.users.shared.contact_us_if_any_trouble_html', href: contact_admin_url)
|
||||||
= succeed '.' do
|
|
||||||
= link_to("via ce formulaire", contact_admin_url)
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
.auth-form.sign-in-form
|
.auth-form.sign-in-form
|
||||||
|
|
||||||
= form_for User.new, url: user_session_path, html: { class: "form" } do |f|
|
= form_for resource, url: user_session_path, html: { class: "form" } do |f|
|
||||||
%h1.huge-title= t('views.sessions.new.title')
|
%h1.huge-title= t('views.sessions.new.title')
|
||||||
|
|
||||||
= render partial: 'shared/france_connect_login', locals: { url: france_connect_particulier_path }
|
= render partial: 'shared/france_connect_login', locals: { url: france_connect_particulier_path }
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
= f.password_field :password, autocomplete: 'current-password'
|
= f.password_field :password, autocomplete: 'current-password'
|
||||||
|
|
||||||
.auth-options
|
.auth-options
|
||||||
%div
|
.flex-no-shrink
|
||||||
= f.check_box :remember_me
|
= f.check_box :remember_me
|
||||||
= f.label :remember_me, t('views.sessions.new.remember_me'), class: 'remember-me'
|
= f.label :remember_me, t('views.sessions.new.remember_me'), class: 'remember-me'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
Mailjet.configure do |config|
|
ActiveSupport.on_load(:action_mailer) do
|
||||||
config.api_key = Rails.application.secrets.mailjet[:api_key]
|
require 'mailjet'
|
||||||
config.secret_key = Rails.application.secrets.mailjet[:secret_key]
|
|
||||||
config.default_from = CONTACT_EMAIL
|
Mailjet.configure do |config|
|
||||||
|
config.api_key = Rails.application.secrets.mailjet[:api_key]
|
||||||
|
config.secret_key = Rails.application.secrets.mailjet[:secret_key]
|
||||||
|
config.default_from = CONTACT_EMAIL
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,7 +49,7 @@ en:
|
||||||
new:
|
new:
|
||||||
title: Sign in
|
title: Sign in
|
||||||
email: Email address (name@site.com)
|
email: Email address (name@site.com)
|
||||||
password: Password (minimum length %{min_length} characters)
|
password: Password
|
||||||
remember_me: Remember me
|
remember_me: Remember me
|
||||||
reset_password: Forgot password?
|
reset_password: Forgot password?
|
||||||
connection: Sign in
|
connection: Sign in
|
||||||
|
@ -58,8 +58,11 @@ en:
|
||||||
commencer:
|
commencer:
|
||||||
no_procedure:
|
no_procedure:
|
||||||
ligne1: A simple tool
|
ligne1: A simple tool
|
||||||
ligne2: to manage dematerialized
|
ligne2: to manage dematerialized
|
||||||
ligne3: administrative forms.
|
ligne3: administrative forms.
|
||||||
|
passwords:
|
||||||
|
new:
|
||||||
|
send_me_reset_password_instructions: "Fill-in your account's email, and we’ll send you instructions to reset your password."
|
||||||
modal:
|
modal:
|
||||||
publish:
|
publish:
|
||||||
title:
|
title:
|
||||||
|
@ -172,4 +175,4 @@ en:
|
||||||
draft:
|
draft:
|
||||||
zero: Draft
|
zero: Draft
|
||||||
one: Draft
|
one: Draft
|
||||||
other: Drafts
|
other: Drafts
|
||||||
|
|
|
@ -39,9 +39,9 @@ fr:
|
||||||
new:
|
new:
|
||||||
title: Connectez-vous
|
title: Connectez-vous
|
||||||
email: Email (nom@site.com)
|
email: Email (nom@site.com)
|
||||||
password: Mot de passe (%{min_length} caractères minimum)
|
password: Mot de passe
|
||||||
remember_me: Se souvenir de moi
|
remember_me: Se souvenir de moi
|
||||||
reset_password: Mot de passe oublié ?
|
reset_password: Mot de passe oublié ?
|
||||||
connection: Se connecter
|
connection: Se connecter
|
||||||
are_you_new: Vous êtes nouveau sur %{app_name} ?
|
are_you_new: Vous êtes nouveau sur %{app_name} ?
|
||||||
find_procedure: Trouvez votre démarche
|
find_procedure: Trouvez votre démarche
|
||||||
|
@ -50,6 +50,24 @@ fr:
|
||||||
ligne1: Un outil simple
|
ligne1: Un outil simple
|
||||||
ligne2: pour gérer les formulaires
|
ligne2: pour gérer les formulaires
|
||||||
ligne3: administratifs dématérialisés.
|
ligne3: administratifs dématérialisés.
|
||||||
|
passwords:
|
||||||
|
new:
|
||||||
|
send_me_reset_password_instructions: "Indiquez l’email de votre compte, et nous vous enverrons un lien pour créer un nouveau mot de passe."
|
||||||
|
users:
|
||||||
|
passwords:
|
||||||
|
reset_link_sent:
|
||||||
|
email_sent_html: "Nous vous avons envoyé un email à l’adresse <strong>%{email}</strong>."
|
||||||
|
click_link_to_reset_password: "Cliquez sur le lien contenu dans l’email pour changer votre mot de passe."
|
||||||
|
no_mail: "Vous n’avez pas reçu l’email ?"
|
||||||
|
check_spams: "Vérifiez la boite Indésirables ou Spam de votre boite email."
|
||||||
|
check_account: "Avez-vous bien créé un compte %{application_name} avec l’adresse %{email} ? Si aucun compte n’existe avec cette adresse, vous ne recevrez pas de message."
|
||||||
|
check_france_connect_html: "Vous êtes-vous connecté avec France Connect par le passé ? Dans ce cas <a href=\"%{href}\">essayez à nouveau avec France Connect</a>."
|
||||||
|
got_it: "Bien reçu !"
|
||||||
|
open_your_mailbox: "Maintenant ouvrez votre boite email."
|
||||||
|
title: "Lien de réinitialisation du mot de passe envoyé"
|
||||||
|
shared:
|
||||||
|
email_can_take_a_while_html: "<strong>Attention</strong>, ce message peut mettre jusqu’à <strong>15 minutes</strong> pour arriver."
|
||||||
|
contact_us_if_any_trouble_html: "En cas de difficultés, nous restons joignables <a href=\"%{href}\">via ce formulaire</a>."
|
||||||
modal:
|
modal:
|
||||||
publish:
|
publish:
|
||||||
title:
|
title:
|
||||||
|
@ -185,4 +203,4 @@ fr:
|
||||||
draft:
|
draft:
|
||||||
zero: Brouillon
|
zero: Brouillon
|
||||||
one: Brouillon
|
one: Brouillon
|
||||||
other: Brouillons
|
other: Brouillons
|
||||||
|
|
|
@ -106,6 +106,7 @@ Rails.application.routes.draw do
|
||||||
get '/users/no_procedure' => 'users/sessions#no_procedure'
|
get '/users/no_procedure' => 'users/sessions#no_procedure'
|
||||||
get 'connexion-par-jeton/:id' => 'users/sessions#sign_in_by_link', as: 'sign_in_by_link'
|
get 'connexion-par-jeton/:id' => 'users/sessions#sign_in_by_link', as: 'sign_in_by_link'
|
||||||
get 'lien-envoye/:email' => 'users/sessions#link_sent', constraints: { email: /.*/ }, as: 'link_sent'
|
get 'lien-envoye/:email' => 'users/sessions#link_sent', constraints: { email: /.*/ }, as: 'link_sent'
|
||||||
|
get '/users/password/reset-link-sent' => 'users/passwords#reset_link_sent'
|
||||||
end
|
end
|
||||||
|
|
||||||
devise_scope :administrateur do
|
devise_scope :administrateur do
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddIndexToExerciceEtablissementId < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_index :exercices, :etablissement_id
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2021_04_16_074049) do
|
ActiveRecord::Schema.define(version: 2021_04_16_160721) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -325,6 +325,7 @@ ActiveRecord::Schema.define(version: 2021_04_16_074049) do
|
||||||
t.datetime "date_fin_exercice"
|
t.datetime "date_fin_exercice"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
|
t.index ["etablissement_id"], name: "index_exercices_on_etablissement_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "experts", force: :cascade do |t|
|
create_table "experts", force: :cascade do |t|
|
||||||
|
|
|
@ -38,4 +38,16 @@ describe Users::PasswordsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#reset_link_sent' do
|
||||||
|
let(:email) { 'test@example.com' }
|
||||||
|
|
||||||
|
it 'displays the page' do
|
||||||
|
get 'reset_link_sent', params: { email: email }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response).to render_template('reset_link_sent')
|
||||||
|
expect(assigns(:email)).to eq email
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,7 +42,7 @@ feature 'Inviting an expert:', js: true do
|
||||||
expect(emails_sent_to(expert2.email.to_s).size).to eq(1)
|
expect(emails_sent_to(expert2.email.to_s).size).to eq(1)
|
||||||
|
|
||||||
invitation_email = open_email(expert.email.to_s)
|
invitation_email = open_email(expert.email.to_s)
|
||||||
avis = expert.avis.reload.last
|
avis = expert.avis.find_by(dossier: dossier)
|
||||||
sign_up_link = sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email)
|
sign_up_link = sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email)
|
||||||
expect(invitation_email.body).to include(sign_up_link)
|
expect(invitation_email.body).to include(sign_up_link)
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,11 @@ feature 'Signin in:' do
|
||||||
visit root_path
|
visit root_path
|
||||||
click_on 'Connexion'
|
click_on 'Connexion'
|
||||||
|
|
||||||
sign_in_with user.email, password
|
sign_in_with user.email, 'invalid-password'
|
||||||
|
expect(page).to have_content 'Courriel ou mot de passe incorrect.'
|
||||||
|
expect(page).to have_field('Email', with: user.email)
|
||||||
|
|
||||||
|
sign_in_with user.email, password
|
||||||
expect(page).to have_current_path dossiers_path
|
expect(page).to have_current_path dossiers_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,15 @@ feature 'Managing password:' do
|
||||||
scenario 'a simple user can reset their password' do
|
scenario 'a simple user can reset their password' do
|
||||||
visit root_path
|
visit root_path
|
||||||
click_on 'Connexion'
|
click_on 'Connexion'
|
||||||
click_on 'Mot de passe oublié ?'
|
click_on 'Mot de passe oublié ?'
|
||||||
expect(page).to have_current_path(new_user_password_path)
|
expect(page).to have_current_path(new_user_password_path)
|
||||||
|
|
||||||
fill_in 'Email', with: user.email
|
fill_in 'Email', with: user.email
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
click_on 'Réinitialiser'
|
click_on 'Demander un nouveau mot de passe'
|
||||||
end
|
end
|
||||||
expect(page).to have_content('Si votre courriel existe dans notre base de données, vous recevrez un lien vous permettant de récupérer votre mot de passe.')
|
expect(page).to have_text 'Nous vous avons envoyé un email'
|
||||||
|
expect(page).to have_text user.email
|
||||||
|
|
||||||
click_reset_password_link_for user.email
|
click_reset_password_link_for user.email
|
||||||
expect(page).to have_content 'Changement de mot de passe'
|
expect(page).to have_content 'Changement de mot de passe'
|
||||||
|
@ -33,14 +34,15 @@ feature 'Managing password:' do
|
||||||
scenario 'an admin can reset their password' do
|
scenario 'an admin can reset their password' do
|
||||||
visit root_path
|
visit root_path
|
||||||
click_on 'Connexion'
|
click_on 'Connexion'
|
||||||
click_on 'Mot de passe oublié ?'
|
click_on 'Mot de passe oublié ?'
|
||||||
expect(page).to have_current_path(new_user_password_path)
|
expect(page).to have_current_path(new_user_password_path)
|
||||||
|
|
||||||
fill_in 'Email', with: user.email
|
fill_in 'Email', with: user.email
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
click_on 'Réinitialiser'
|
click_on 'Demander un nouveau mot de passe'
|
||||||
end
|
end
|
||||||
expect(page).to have_content('Si votre courriel existe dans notre base de données, vous recevrez un lien vous permettant de récupérer votre mot de passe.')
|
expect(page).to have_text 'Nous vous avons envoyé un email'
|
||||||
|
expect(page).to have_text user.email
|
||||||
|
|
||||||
click_reset_password_link_for user.email
|
click_reset_password_link_for user.email
|
||||||
|
|
||||||
|
|
|
@ -125,126 +125,6 @@ describe ProcedurePresentation do
|
||||||
it { expect(subject.displayed_fields_for_select).to eq([[["label1", "table1/column1"], ["label2", "table2/column2"]], ["user/email"]]) }
|
it { expect(subject.displayed_fields_for_select).to eq([[["label1", "table1/column1"], ["label2", "table2/column2"]], ["user/email"]]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#get_value' do
|
|
||||||
let(:procedure_presentation) { create(:procedure_presentation, procedure: procedure, assign_to: assign_to, displayed_fields: [{ 'table' => table, 'column' => column }]) }
|
|
||||||
|
|
||||||
subject { procedure_presentation.displayed_fields_values(dossier).first }
|
|
||||||
|
|
||||||
context 'for self table' do
|
|
||||||
let(:table) { 'self' }
|
|
||||||
|
|
||||||
context 'for created_at column' do
|
|
||||||
let(:column) { 'created_at' }
|
|
||||||
let(:dossier) { Timecop.freeze(Time.zone.local(1992, 3, 22)) { create(:dossier, procedure: procedure) } }
|
|
||||||
|
|
||||||
it { is_expected.to eq('22/03/1992') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for en_construction_at column' do
|
|
||||||
let(:column) { 'en_construction_at' }
|
|
||||||
let(:dossier) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: Time.zone.local(2018, 10, 17)) }
|
|
||||||
|
|
||||||
it { is_expected.to eq('17/10/2018') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for updated_at column' do
|
|
||||||
let(:column) { 'updated_at' }
|
|
||||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
|
||||||
|
|
||||||
before { dossier.touch(time: Time.zone.local(2018, 9, 25)) }
|
|
||||||
|
|
||||||
it { is_expected.to eq('25/09/2018') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for user table' do
|
|
||||||
let(:table) { 'user' }
|
|
||||||
let(:column) { 'email' }
|
|
||||||
|
|
||||||
let(:dossier) { create(:dossier, procedure: procedure, user: create(:user, email: 'bla@yopmail.com')) }
|
|
||||||
|
|
||||||
it { is_expected.to eq('bla@yopmail.com') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for individual table' do
|
|
||||||
let(:table) { 'individual' }
|
|
||||||
let(:procedure) { create(:procedure, :for_individual, :with_type_de_champ, :with_type_de_champ_private) }
|
|
||||||
let(:dossier) { create(:dossier, procedure: procedure, individual: create(:individual, nom: 'Martin', prenom: 'Jacques', gender: 'M.')) }
|
|
||||||
|
|
||||||
context 'for prenom column' do
|
|
||||||
let(:column) { 'prenom' }
|
|
||||||
|
|
||||||
it { is_expected.to eq('Jacques') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for nom column' do
|
|
||||||
let(:column) { 'nom' }
|
|
||||||
|
|
||||||
it { is_expected.to eq('Martin') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for gender column' do
|
|
||||||
let(:column) { 'gender' }
|
|
||||||
|
|
||||||
it { is_expected.to eq('M.') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for etablissement table' do
|
|
||||||
let(:table) { 'etablissement' }
|
|
||||||
let(:column) { 'code_postal' } # All other columns work the same, no extra test required
|
|
||||||
|
|
||||||
let!(:dossier) { create(:dossier, procedure: procedure, etablissement: create(:etablissement, code_postal: '75008')) }
|
|
||||||
|
|
||||||
it { is_expected.to eq('75008') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for groupe_instructeur table' do
|
|
||||||
let(:table) { 'groupe_instructeur' }
|
|
||||||
let(:column) { 'label' }
|
|
||||||
|
|
||||||
let!(:dossier) { create(:dossier, procedure: procedure) }
|
|
||||||
|
|
||||||
it { is_expected.to eq('défaut') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for followers_instructeurs table' do
|
|
||||||
let(:table) { 'followers_instructeurs' }
|
|
||||||
let(:column) { 'email' }
|
|
||||||
|
|
||||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
|
||||||
let!(:follow1) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'user1@host')) }
|
|
||||||
let!(:follow2) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'user2@host')) }
|
|
||||||
|
|
||||||
it "return emails of followers instructeurs" do
|
|
||||||
emails_to_display = procedure_presentation.displayed_fields_values(dossier).first.split(', ').sort
|
|
||||||
expect(emails_to_display).to eq ["user1@host", "user2@host"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for type_de_champ table' do
|
|
||||||
let(:table) { 'type_de_champ' }
|
|
||||||
let(:column) { procedure.types_de_champ.first.stable_id.to_s }
|
|
||||||
|
|
||||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
|
||||||
|
|
||||||
before { dossier.champs.first.update(value: 'kale') }
|
|
||||||
|
|
||||||
it { is_expected.to eq('kale') }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for type_de_champ_private table' do
|
|
||||||
let(:table) { 'type_de_champ_private' }
|
|
||||||
let(:column) { procedure.types_de_champ_private.first.stable_id.to_s }
|
|
||||||
|
|
||||||
let(:dossier) { create(:dossier, procedure: procedure) }
|
|
||||||
|
|
||||||
before { dossier.champs_private.first.update(value: 'quinoa') }
|
|
||||||
|
|
||||||
it { is_expected.to eq('quinoa') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#sorted_ids' do
|
describe '#sorted_ids' do
|
||||||
let(:instructeur) { create(:instructeur) }
|
let(:instructeur) { create(:instructeur) }
|
||||||
let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) }
|
let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) }
|
||||||
|
@ -755,136 +635,6 @@ describe ProcedurePresentation do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#eager_load_displayed_fields' do
|
|
||||||
let(:procedure_presentation) { create(:procedure_presentation, procedure: procedure, assign_to: assign_to, displayed_fields: [{ 'table' => table, 'column' => column }]) }
|
|
||||||
let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
|
||||||
let(:displayed_dossier) { procedure_presentation.eager_load_displayed_fields(procedure.dossiers).first }
|
|
||||||
|
|
||||||
context 'for type de champ' do
|
|
||||||
let(:table) { 'type_de_champ' }
|
|
||||||
let(:column) { procedure.types_de_champ.first.stable_id.to_s }
|
|
||||||
|
|
||||||
it 'preloads the champs relation' do
|
|
||||||
# Ideally, we would only preload the champs for the matching column
|
|
||||||
|
|
||||||
expect(displayed_dossier.association(:champs)).to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:groupe_instructeur)).not_to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for type de champ private' do
|
|
||||||
let(:table) { 'type_de_champ_private' }
|
|
||||||
let(:column) { procedure.types_de_champ_private.first.stable_id.to_s }
|
|
||||||
|
|
||||||
it 'preloads the champs relation' do
|
|
||||||
# Ideally, we would only preload the champs for the matching column
|
|
||||||
|
|
||||||
expect(displayed_dossier.association(:champs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:groupe_instructeur)).not_to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for user' do
|
|
||||||
let(:table) { 'user' }
|
|
||||||
let(:column) { 'email' }
|
|
||||||
|
|
||||||
it 'preloads the user relation' do
|
|
||||||
expect(displayed_dossier.association(:champs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:groupe_instructeur)).not_to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for individual' do
|
|
||||||
let(:procedure) { create(:procedure, :for_individual, :with_type_de_champ, :with_type_de_champ_private) }
|
|
||||||
let(:table) { 'individual' }
|
|
||||||
let(:column) { 'nom' }
|
|
||||||
|
|
||||||
it 'preloads the individual relation' do
|
|
||||||
expect(displayed_dossier.association(:champs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:groupe_instructeur)).not_to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for etablissement' do
|
|
||||||
let(:table) { 'etablissement' }
|
|
||||||
let(:column) { 'siret' }
|
|
||||||
|
|
||||||
it 'preloads the etablissement relation' do
|
|
||||||
expect(displayed_dossier.association(:champs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:groupe_instructeur)).not_to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for followers_instructeurs' do
|
|
||||||
let(:table) { 'followers_instructeurs' }
|
|
||||||
let(:column) { 'email' }
|
|
||||||
|
|
||||||
it 'preloads the followers_instructeurs relation' do
|
|
||||||
expect(displayed_dossier.association(:champs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).to be_loaded
|
|
||||||
expect(displayed_dossier.association(:groupe_instructeur)).not_to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for groupe_instructeur' do
|
|
||||||
let(:table) { 'groupe_instructeur' }
|
|
||||||
let(:column) { 'label' }
|
|
||||||
|
|
||||||
it 'preloads the groupe_instructeur relation' do
|
|
||||||
expect(displayed_dossier.association(:champs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:groupe_instructeur)).to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'for self' do
|
|
||||||
let(:table) { 'self' }
|
|
||||||
let(:column) { 'created_at' }
|
|
||||||
|
|
||||||
it 'does not preload anything' do
|
|
||||||
expect(displayed_dossier.association(:champs)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:champs_private)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:user)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:individual)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:etablissement)).not_to be_loaded
|
|
||||||
expect(displayed_dossier.association(:followers_instructeurs)).not_to be_loaded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#human_value_for_filter" do
|
describe "#human_value_for_filter" do
|
||||||
let(:filters) { { "suivis" => [{ "label" => "label1", "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "true" }] } }
|
let(:filters) { { "suivis" => [{ "label" => "label1", "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "true" }] } }
|
||||||
|
|
||||||
|
|
157
spec/services/dossier_projection_service_spec.rb
Normal file
157
spec/services/dossier_projection_service_spec.rb
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
describe DossierProjectionService do
|
||||||
|
describe '#project' do
|
||||||
|
subject { described_class.project(dossiers_ids, fields) }
|
||||||
|
|
||||||
|
context 'with multiple dossier' do
|
||||||
|
let!(:procedure) { create(:procedure, :with_type_de_champ) }
|
||||||
|
let!(:dossier_1) { create(:dossier, procedure: procedure) }
|
||||||
|
let!(:dossier_2) { create(:dossier, procedure: procedure) }
|
||||||
|
let!(:dossier_3) { create(:dossier, procedure: procedure) }
|
||||||
|
|
||||||
|
let(:dossiers_ids) { [dossier_3.id, dossier_1.id, dossier_2.id] }
|
||||||
|
let(:fields) do
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"table" => "type_de_champ",
|
||||||
|
"column" => procedure.types_de_champ[0].stable_id.to_s
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
dossier_1.champs.first.update(value: 'champ_1')
|
||||||
|
dossier_2.champs.first.update(value: 'champ_2')
|
||||||
|
dossier_3.champs.first.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:result) { subject }
|
||||||
|
|
||||||
|
it 'respects the dossiers_ids order and returns nil for empty result' do
|
||||||
|
expect(result.length).to eq(3)
|
||||||
|
expect(result[0].dossier.id).to eq(dossier_3.id)
|
||||||
|
expect(result[1].dossier.id).to eq(dossier_1.id)
|
||||||
|
expect(result[2].dossier.id).to eq(dossier_2.id)
|
||||||
|
|
||||||
|
expect(result[0].columns[0]).to be nil
|
||||||
|
expect(result[1].columns[0]).to eq('champ_1')
|
||||||
|
expect(result[2].columns[0]).to eq('champ_2')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'attributes by attributes' do
|
||||||
|
let(:fields) { [{ "table" => table, "column" => column }] }
|
||||||
|
let(:dossiers_ids) { [dossier.id] }
|
||||||
|
|
||||||
|
subject { super()[0].columns[0] }
|
||||||
|
|
||||||
|
context 'for self table' do
|
||||||
|
let(:table) { 'self' }
|
||||||
|
|
||||||
|
context 'for created_at column' do
|
||||||
|
let(:column) { 'created_at' }
|
||||||
|
let(:dossier) { Timecop.freeze(Time.zone.local(1992, 3, 22)) { create(:dossier) } }
|
||||||
|
|
||||||
|
it { is_expected.to eq('22/03/1992') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for en_construction_at column' do
|
||||||
|
let(:column) { 'en_construction_at' }
|
||||||
|
let(:dossier) { create(:dossier, :en_construction, en_construction_at: Time.zone.local(2018, 10, 17)) }
|
||||||
|
|
||||||
|
it { is_expected.to eq('17/10/2018') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for updated_at column' do
|
||||||
|
let(:column) { 'updated_at' }
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
|
||||||
|
before { dossier.touch(time: Time.zone.local(2018, 9, 25)) }
|
||||||
|
|
||||||
|
it { is_expected.to eq('25/09/2018') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for user table' do
|
||||||
|
let(:table) { 'user' }
|
||||||
|
let(:column) { 'email' }
|
||||||
|
|
||||||
|
let(:dossier) { create(:dossier, user: create(:user, email: 'bla@yopmail.com')) }
|
||||||
|
|
||||||
|
it { is_expected.to eq('bla@yopmail.com') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for individual table' do
|
||||||
|
let(:table) { 'individual' }
|
||||||
|
let(:procedure) { create(:procedure, :for_individual, :with_type_de_champ, :with_type_de_champ_private) }
|
||||||
|
let(:dossier) { create(:dossier, procedure: procedure, individual: create(:individual, nom: 'Martin', prenom: 'Jacques', gender: 'M.')) }
|
||||||
|
|
||||||
|
context 'for prenom column' do
|
||||||
|
let(:column) { 'prenom' }
|
||||||
|
|
||||||
|
it { is_expected.to eq('Jacques') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for nom column' do
|
||||||
|
let(:column) { 'nom' }
|
||||||
|
|
||||||
|
it { is_expected.to eq('Martin') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for gender column' do
|
||||||
|
let(:column) { 'gender' }
|
||||||
|
|
||||||
|
it { is_expected.to eq('M.') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for etablissement table' do
|
||||||
|
let(:table) { 'etablissement' }
|
||||||
|
let(:column) { 'code_postal' } # All other columns work the same, no extra test required
|
||||||
|
|
||||||
|
let!(:dossier) { create(:dossier, etablissement: create(:etablissement, code_postal: '75008')) }
|
||||||
|
|
||||||
|
it { is_expected.to eq('75008') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for groupe_instructeur table' do
|
||||||
|
let(:table) { 'groupe_instructeur' }
|
||||||
|
let(:column) { 'label' }
|
||||||
|
|
||||||
|
let!(:dossier) { create(:dossier) }
|
||||||
|
|
||||||
|
it { is_expected.to eq('défaut') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for followers_instructeurs table' do
|
||||||
|
let(:table) { 'followers_instructeurs' }
|
||||||
|
let(:column) { 'email' }
|
||||||
|
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
let!(:follow1) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'user1@host')) }
|
||||||
|
let!(:follow2) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'user2@host')) }
|
||||||
|
|
||||||
|
it { is_expected.to eq "user1@host, user2@host" }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for type_de_champ table' do
|
||||||
|
let(:table) { 'type_de_champ' }
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
let(:column) { dossier.procedure.types_de_champ.first.stable_id.to_s }
|
||||||
|
|
||||||
|
before { dossier.champs.first.update(value: 'kale') }
|
||||||
|
|
||||||
|
it { is_expected.to eq('kale') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for type_de_champ_private table' do
|
||||||
|
let(:table) { 'type_de_champ_private' }
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
let(:column) { dossier.procedure.types_de_champ_private.first.stable_id.to_s }
|
||||||
|
|
||||||
|
before { dossier.champs_private.first.update(value: 'quinoa') }
|
||||||
|
|
||||||
|
it { is_expected.to eq('quinoa') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,7 @@ describe 'users/sessions/new.html.haml', type: :view do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
|
allow(view).to receive(:devise_mapping).and_return(Devise.mappings[:user])
|
||||||
allow(view).to receive(:resource_name).and_return(:user)
|
allow(view).to receive(:resource).and_return(:user)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|
|
@ -11617,9 +11617,9 @@ sshpk@^1.7.0:
|
||||||
tweetnacl "~0.14.0"
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
ssri@^6.0.1:
|
ssri@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
|
||||||
integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
|
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
figgy-pudding "^3.5.1"
|
figgy-pudding "^3.5.1"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue