Merge pull request #6131 from betagouv/more_instructeur_show_love
ameloration des perfs instructeur show procedure
This commit is contained in:
commit
f45a7a83fe
21 changed files with 201 additions and 161 deletions
|
@ -85,10 +85,10 @@ module Instructeurs
|
|||
@archived_dossiers
|
||||
end
|
||||
|
||||
@has_en_cours_notifications = current_instructeur.notifications_for_procedure(@procedure, :en_cours).exists?
|
||||
@has_termine_notifications = current_instructeur.notifications_for_procedure(@procedure, :termine).exists?
|
||||
|
||||
@not_archived_notifications_dossier_ids = current_instructeur.notifications_for_procedure(@procedure, :not_archived).pluck(:id)
|
||||
notifications = current_instructeur.notifications_for_groupe_instructeurs(groupe_instructeur_ids)
|
||||
@has_en_cours_notifications = notifications[:en_cours].present?
|
||||
@has_termine_notifications = notifications[:termines].present?
|
||||
@not_archived_notifications_dossier_ids = notifications[:en_cours] + notifications[:termines]
|
||||
|
||||
sorted_ids = procedure_presentation.sorted_ids(@dossiers, current_instructeur)
|
||||
|
||||
|
@ -101,18 +101,12 @@ module Instructeurs
|
|||
|
||||
page = params[:page].presence || 1
|
||||
|
||||
filtered_sorted_paginated_ids = Kaminari
|
||||
@filtered_sorted_paginated_ids = Kaminari
|
||||
.paginate_array(filtered_sorted_ids)
|
||||
.page(page)
|
||||
.per(ITEMS_PER_PAGE)
|
||||
|
||||
@dossiers = @dossiers.where(id: filtered_sorted_paginated_ids)
|
||||
|
||||
@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)
|
||||
@projected_dossiers = DossierProjectionService.project(@filtered_sorted_paginated_ids, procedure_presentation.displayed_fields)
|
||||
|
||||
assign_exports
|
||||
end
|
||||
|
@ -156,6 +150,11 @@ module Instructeurs
|
|||
.groupe_instructeurs
|
||||
.where(procedure: procedure)
|
||||
|
||||
@dossier_count = current_instructeur
|
||||
.dossiers_count_summary(groupe_instructeur_ids)
|
||||
.fetch_values('tous', 'archives')
|
||||
.sum
|
||||
|
||||
export = Export.find_or_create_export(export_format, groupe_instructeurs)
|
||||
|
||||
if export.ready? && export.old? && params[:force_export]
|
||||
|
@ -220,10 +219,7 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def assign_exports
|
||||
groupe_instructeurs_for_procedure = current_instructeur.groupe_instructeurs.where(procedure: procedure)
|
||||
@xlsx_export = Export.find_for_format_and_groupe_instructeurs(:xlsx, groupe_instructeurs_for_procedure)
|
||||
@csv_export = Export.find_for_format_and_groupe_instructeurs(:csv, groupe_instructeurs_for_procedure)
|
||||
@ods_export = Export.find_for_format_and_groupe_instructeurs(:ods, groupe_instructeurs_for_procedure)
|
||||
@xlsx_export, @csv_export, @ods_export = Export.find_for_groupe_instructeurs(groupe_instructeur_ids)
|
||||
end
|
||||
|
||||
def assign_to
|
||||
|
@ -251,7 +247,9 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def procedure
|
||||
Procedure.find(procedure_id)
|
||||
Procedure
|
||||
.with_attached_logo
|
||||
.find(procedure_id)
|
||||
end
|
||||
|
||||
def ensure_ownership!
|
||||
|
@ -282,25 +280,5 @@ module Instructeurs
|
|||
def current_filters
|
||||
@current_filters ||= procedure_presentation.filters[statut]
|
||||
end
|
||||
|
||||
def kaminarize(current_page, total)
|
||||
@dossiers.instance_eval <<-EVAL
|
||||
def current_page
|
||||
#{current_page}
|
||||
end
|
||||
def total_pages
|
||||
(#{total} / #{ITEMS_PER_PAGE}.to_f).ceil
|
||||
end
|
||||
def limit_value
|
||||
#{ITEMS_PER_PAGE}
|
||||
end
|
||||
def first_page?
|
||||
current_page == 1
|
||||
end
|
||||
def last_page?
|
||||
current_page == total_pages
|
||||
end
|
||||
EVAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,4 +21,17 @@ class Expert < ApplicationRecord
|
|||
def self.by_email(email)
|
||||
Expert.eager_load(:user).find_by(users: { email: email })
|
||||
end
|
||||
|
||||
def avis_summary
|
||||
if @avis_summary.present?
|
||||
@avis_summary
|
||||
else
|
||||
query = <<~EOF
|
||||
COUNT(*) FILTER (where answer IS NULL) AS unanswered,
|
||||
COUNT(*) AS total
|
||||
EOF
|
||||
result = avis.select(query)[0]
|
||||
@avis_summary = { unanswered: result.unanswered, total: result.total }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,15 +51,18 @@ class Export < ApplicationRecord
|
|||
|
||||
def self.find_or_create_export(format, groupe_instructeurs)
|
||||
create_with(groupe_instructeurs: groupe_instructeurs)
|
||||
.create_or_find_by(format: format, key: generate_cache_key(groupe_instructeurs))
|
||||
.create_or_find_by(format: format, key: generate_cache_key(groupe_instructeurs.map(&:id)))
|
||||
end
|
||||
|
||||
def self.find_for_format_and_groupe_instructeurs(format, groupe_instructeurs)
|
||||
find_by(format: format, key: generate_cache_key(groupe_instructeurs))
|
||||
def self.find_for_groupe_instructeurs(groupe_instructeurs_ids)
|
||||
exports = where(key: generate_cache_key(groupe_instructeurs_ids))
|
||||
|
||||
['xlsx', 'csv', 'ods']
|
||||
.map { |format| exports.find { |export| export.format == format } }
|
||||
end
|
||||
|
||||
def self.generate_cache_key(groupe_instructeurs)
|
||||
groupe_instructeurs.map(&:id).sort.join('-')
|
||||
def self.generate_cache_key(groupe_instructeurs_ids)
|
||||
groupe_instructeurs_ids.sort.join('-')
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -134,14 +134,21 @@ class Instructeur < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def notifications_for_procedure(procedure, scope)
|
||||
target_groupes = groupe_instructeurs.where(procedure: procedure)
|
||||
|
||||
def notifications_for_groupe_instructeurs(groupe_instructeurs)
|
||||
Dossier
|
||||
.where(groupe_instructeur: target_groupes)
|
||||
.send(scope) # :en_cours or :termine or :not_archived (or any other Dossier scope)
|
||||
.not_archived
|
||||
.where(groupe_instructeur: groupe_instructeurs)
|
||||
.merge(followed_dossiers)
|
||||
.with_notifications
|
||||
.pluck(:state, :id)
|
||||
.reduce({ termines: [], en_cours: [] }) do |acc, e|
|
||||
if Dossier::TERMINE.include?(e[0])
|
||||
acc[:termines] << e[1]
|
||||
elsif Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(e[0])
|
||||
acc[:en_cours] << e[1]
|
||||
end
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
def procedure_ids_with_notifications(scope)
|
||||
|
@ -165,11 +172,14 @@ class Instructeur < ApplicationRecord
|
|||
.reduce([]) do |acc, groupe|
|
||||
procedure = groupe.procedure
|
||||
|
||||
notifications = notifications_for_groupe_instructeurs([groupe.id])
|
||||
nb_notification = notifications[:en_cours].count + notifications[:termines].count
|
||||
|
||||
h = {
|
||||
nb_en_construction: groupe.dossiers.en_construction.count,
|
||||
nb_en_instruction: groupe.dossiers.en_instruction.count,
|
||||
nb_accepted: Traitement.where(dossier: groupe.dossiers.accepte, processed_at: Time.zone.yesterday.beginning_of_day..Time.zone.yesterday.end_of_day).count,
|
||||
nb_notification: notifications_for_procedure(procedure, :not_archived).count
|
||||
nb_notification: nb_notification
|
||||
}
|
||||
|
||||
if h[:nb_en_construction] > 0 || h[:nb_notification] > 0
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class DossierProjectionService
|
||||
class DossierProjection < Struct.new(:dossier, :columns)
|
||||
class DossierProjection < Struct.new(:dossier_id, :state, :archived, :columns)
|
||||
end
|
||||
|
||||
TABLE = 'table'
|
||||
|
@ -18,57 +18,64 @@ class DossierProjectionService
|
|||
# - 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]) }
|
||||
state_field = { TABLE => 'self', COLUMN => 'state' }
|
||||
archived_field = { TABLE => 'self', COLUMN => 'archived' }
|
||||
|
||||
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]
|
||||
([state_field, archived_field] + fields) # the view needs state and archived dossier attributes
|
||||
.each { |f| f[:id_value_h] = {} }
|
||||
.group_by { |f| f[TABLE] } # one query per table
|
||||
.each do |table, fields|
|
||||
case table
|
||||
when 'type_de_champ', 'type_de_champ_private'
|
||||
Champ
|
||||
.includes(:type_de_champ)
|
||||
.where(
|
||||
types_de_champ: { stable_id: 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 = fields.find { |f| f[COLUMN] == stable_id.to_s }
|
||||
field[:id_value_h] = champs.to_h { |c| [c.dossier_id, c.to_s] }
|
||||
end
|
||||
when 'self'
|
||||
Dossier
|
||||
.where(id: dossiers_ids)
|
||||
.pluck(:id, field[COLUMN].to_sym)
|
||||
.to_h { |id, col| [id, col&.strftime('%d/%m/%Y')] }
|
||||
.pluck(:id, *fields.map { |f| f[COLUMN].to_sym })
|
||||
.each do |id, *columns|
|
||||
fields.zip(columns).each do |field, value|
|
||||
if [state_field, archived_field].include?(field)
|
||||
field[:id_value_h][id] = value
|
||||
else
|
||||
field[:id_value_h][id] = value&.strftime('%d/%m/%Y') # other fields are datetime
|
||||
end
|
||||
end
|
||||
end
|
||||
when 'individual'
|
||||
Individual
|
||||
.where(dossier_id: dossiers_ids)
|
||||
.pluck(:dossier_id, *fields.map { |f| f[COLUMN].to_sym })
|
||||
.each { |id, *columns| fields.zip(columns).each { |field, value| field[:id_value_h][id] = value } }
|
||||
when 'etablissement'
|
||||
Etablissement
|
||||
.where(dossier_id: dossiers_ids)
|
||||
.pluck(:dossier_id, *fields.map { |f| f[COLUMN].to_sym })
|
||||
.each { |id, *columns| fields.zip(columns).each { |field, value| field[:id_value_h][id] = value } }
|
||||
when 'user'
|
||||
Dossier
|
||||
fields[0][:id_value_h] = Dossier # there is only one field available for user table
|
||||
.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
|
||||
fields[0][:id_value_h] = Dossier
|
||||
.joins(:groupe_instructeur)
|
||||
.where(id: dossiers_ids)
|
||||
.pluck('dossiers.id, groupe_instructeurs.label')
|
||||
.to_h
|
||||
when 'followers_instructeurs'
|
||||
Follow
|
||||
fields[0][:id_value_h] = Follow
|
||||
.active
|
||||
.joins(instructeur: :user)
|
||||
.where(dossier_id: dossiers_ids)
|
||||
|
@ -78,11 +85,13 @@ class DossierProjectionService
|
|||
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] })
|
||||
dossiers_ids.map do |dossier_id|
|
||||
DossierProjection.new(
|
||||
dossier_id,
|
||||
state_field[:id_value_h][dossier_id],
|
||||
archived_field[:id_value_h][dossier_id],
|
||||
fields.map { |f| f[:id_value_h][dossier_id] }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,13 @@
|
|||
- else
|
||||
%p.menu-item Le téléchargement des pièces jointes est désactivé pour les dossiers de plus de #{number_to_human_size Dossier::TAILLE_MAX_ZIP}.
|
||||
|
||||
= render partial: "instructeurs/procedures/dossier_actions", locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: current_instructeur&.follow?(dossier) }
|
||||
= render partial: "instructeurs/procedures/dossier_actions",
|
||||
locals: { procedure_id: dossier.procedure.id,
|
||||
dossier_id: dossier.id,
|
||||
state: dossier.state,
|
||||
archived: dossier.archived,
|
||||
dossier_is_followed: current_instructeur&.follow?(dossier) }
|
||||
|
||||
|
||||
.state-button
|
||||
= render partial: "state_button", locals: { dossier: dossier }
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
- if dossier.en_construction_ou_instruction?
|
||||
- if Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state)
|
||||
- if dossier_is_followed
|
||||
= link_to unfollow_instructeur_dossier_path(procedure, dossier), method: :patch, class: 'button' do
|
||||
= link_to unfollow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
|
||||
%span.icon.unfollow>
|
||||
Ne plus suivre
|
||||
- else
|
||||
= link_to follow_instructeur_dossier_path(procedure, dossier), method: :patch, class: 'button' do
|
||||
= link_to follow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
|
||||
%span.icon.follow>
|
||||
Suivre le dossier
|
||||
|
||||
- elsif dossier.termine?
|
||||
- if dossier.archived
|
||||
= link_to unarchive_instructeur_dossier_path(procedure, dossier), method: :patch, class: 'button' do
|
||||
- elsif Dossier::TERMINE.include?(state)
|
||||
- if archived
|
||||
= link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
|
||||
%span.icon.unarchive>
|
||||
Désarchiver le dossier
|
||||
- else
|
||||
= link_to archive_instructeur_dossier_path(procedure, dossier), method: :patch, class: 'button' do
|
||||
= link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
|
||||
%span.icon.archive>
|
||||
Archiver le dossier
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- if procedure.dossiers.state_not_brouillon.any?
|
||||
- if dossier_count > 0
|
||||
%span.dropdown
|
||||
%button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'download-menu' }
|
||||
Télécharger tous les dossiers
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
= field['label']
|
||||
- if @procedure_presentation.sort['table'] == field['table'] && @procedure_presentation.sort['column'] == field['column']
|
||||
- if @procedure_presentation.sort['order'] == 'asc'
|
||||
%img.caret-icon{ src: image_url("table/up_caret.svg") }
|
||||
%img.caret-icon{ src: image_url("table/up_caret.svg"), width: 10, height: 6, loading: 'lazy' }
|
||||
- else
|
||||
%img.caret-icon{ src: image_url("table/down_caret.svg") }
|
||||
%img.caret-icon{ src: image_url("table/down_caret.svg"), width: 10, height: 6, loading: 'lazy' }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%= render_to_element('.procedure-actions', partial: "download_dossiers",
|
||||
locals: { procedure: @procedure, xlsx_export: @xlsx_export, csv_export: @csv_export, ods_export: @ods_export }) %>
|
||||
locals: { procedure: @procedure, xlsx_export: @xlsx_export, csv_export: @csv_export, ods_export: @ods_export, dossier_count: @dossier_count }) %>
|
||||
|
||||
<% [[@xlsx_export, :xlsx], [@csv_export, :csv], [@ods_export, :ods]].each do |(export, format)| %>
|
||||
<% if export && !export.ready? %>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
.procedure-actions
|
||||
= render partial: "download_dossiers",
|
||||
locals: { procedure: @procedure, xlsx_export: @xlsx_export, csv_export: @csv_export, ods_export: @ods_export }
|
||||
locals: { procedure: @procedure, xlsx_export: @xlsx_export, csv_export: @csv_export, ods_export: @ods_export, dossier_count: @tous_count + @archives_count }
|
||||
|
||||
.container
|
||||
- if @statut == 'a-suivre'
|
||||
|
@ -75,8 +75,9 @@
|
|||
%span.icon.delete
|
||||
Afficher les dossiers supprimés
|
||||
|
||||
- if @dossiers.present? || @current_filters.count > 0
|
||||
= paginate @dossiers
|
||||
- if @filtered_sorted_paginated_ids.present? || @current_filters.count > 0
|
||||
- pagination = paginate @filtered_sorted_paginated_ids
|
||||
= pagination
|
||||
%span.dropdown
|
||||
%button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'filter-menu' }
|
||||
Filtrer
|
||||
|
@ -130,27 +131,32 @@
|
|||
|
||||
%tbody
|
||||
- @projected_dossiers.each do |p|
|
||||
- dossier = p.dossier
|
||||
- path = instructeur_dossier_path(@procedure, dossier.id)
|
||||
- path = instructeur_dossier_path(@procedure, p.dossier_id)
|
||||
|
||||
%tr
|
||||
%td.folder-col
|
||||
%a.cell-link{ href: path }
|
||||
%span.icon.folder
|
||||
- if @not_archived_notifications_dossier_ids.include?(dossier.id)
|
||||
- if @not_archived_notifications_dossier_ids.include?(p.dossier_id)
|
||||
%span.notifications{ 'aria-label': 'notifications' }
|
||||
|
||||
%td.number-col
|
||||
%a.cell-link{ href: path }= dossier.id
|
||||
%a.cell-link{ href: path }= p.dossier_id
|
||||
|
||||
- p.columns.each do |column|
|
||||
%td
|
||||
%a.cell-link{ href: path }= column
|
||||
|
||||
%td.status-col
|
||||
%a.cell-link{ href: path }= status_badge(dossier.state)
|
||||
%a.cell-link{ href: path }= status_badge(p.state)
|
||||
|
||||
%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
|
||||
%td.action-col.follow-col= render partial: 'dossier_actions',
|
||||
locals: { procedure_id: @procedure.id,
|
||||
dossier_id: p.dossier_id,
|
||||
state: p.state,
|
||||
archived: p.archived,
|
||||
dossier_is_followed: @followed_dossiers_id.include?(p.dossier_id) }
|
||||
|
||||
= pagination
|
||||
- else
|
||||
%h2.empty-text Aucun dossier
|
||||
|
|
|
@ -31,6 +31,12 @@
|
|||
%td.status-col
|
||||
= link_to(dossier_linked_path(current_instructeur, dossier), class: 'cell-link') do
|
||||
= status_badge(dossier.state)
|
||||
%td.action-col.follow-col= render partial: 'instructeurs/procedures/dossier_actions', locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
|
||||
%td.action-col.follow-col= render partial: "instructeurs/procedures/dossier_actions",
|
||||
locals: { procedure_id: dossier.procedure.id,
|
||||
dossier_id: dossier.id,
|
||||
state: dossier.state,
|
||||
archived: dossier.archived,
|
||||
dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
|
||||
|
||||
- else
|
||||
%h2 Aucun dossier correspondant à votre recherche n'a été trouvé
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.dropdown.header-menu-opener
|
||||
%button.button.dropdown-button.icon-only.header-menu-button{ title: "Mon compte", 'aria-expanded' => 'false', 'aria-controls' => 'mon_compte_menu' }
|
||||
.hidden Mon compte
|
||||
= image_tag "icons/account-circle.svg", alt: 'Mon compte'
|
||||
= image_tag "icons/account-circle.svg", alt: 'Mon compte', width: 24, height: 24, loading: 'lazy'
|
||||
%ul.header-menu.dropdown-content#mon_compte_menu
|
||||
%li
|
||||
.menu-item{ title: current_email }
|
||||
|
|
|
@ -21,30 +21,28 @@
|
|||
- if nav_bar_profile == :instructeur && instructeur_signed_in?
|
||||
- current_url = request.path_info
|
||||
%ul.header-tabs
|
||||
- if current_instructeur.procedures.count > 0
|
||||
- if current_instructeur.procedures.any?
|
||||
%li
|
||||
= active_link_to "Démarches", instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'tab-link'
|
||||
- if current_instructeur.user.expert && current_expert.avis.count > 0
|
||||
- if current_instructeur.user.expert && current_expert.avis_summary[:total] > 0
|
||||
%li
|
||||
= active_link_to expert_all_avis_path, active: controller_name == 'avis', class: 'tab-link' do
|
||||
Avis
|
||||
- avis_counter = current_expert.avis.without_answer.count
|
||||
- if avis_counter > 0
|
||||
%span.badge.warning= avis_counter
|
||||
- if current_expert.avis_summary[:unanswered] > 0
|
||||
%span.badge.warning= current_expert.avis_summary[:unanswered]
|
||||
|
||||
- if nav_bar_profile == :expert && expert_signed_in?
|
||||
%ul.header-tabs
|
||||
- if current_expert.user.instructeur && current_instructeur.procedures.count > 0
|
||||
- if current_expert.user.instructeur && current_instructeur.procedures.any?
|
||||
%li
|
||||
= active_link_to "Démarches", instructeur_procedures_path, active: ['dossiers','procedures'].include?(controller_name), class: 'tab-link'
|
||||
|
||||
- if current_expert.avis.count > 0
|
||||
- if current_expert.avis_summary[:total] > 0
|
||||
%li
|
||||
= active_link_to expert_all_avis_path, active: controller_name == 'avis', class: 'tab-link' do
|
||||
Avis
|
||||
- avis_counter = current_expert.avis.without_answer.count
|
||||
- if avis_counter > 0
|
||||
%span.badge.warning= avis_counter
|
||||
- if current_expert.avis_summary[:unanswered] > 0
|
||||
%span.badge.warning= current_expert.avis_summary[:unanswered]
|
||||
|
||||
- if nav_bar_profile == :user
|
||||
%ul.header-tabs
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
= label_tag :q, "Numéro de dossier", class: 'hidden'
|
||||
= text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: "Rechercher un dossier"
|
||||
%button{ title: "Rechercher" }
|
||||
= image_tag "icons/search-blue.svg", alt: 'Rechercher', 'aria-hidden':'true'
|
||||
= image_tag "icons/search-blue.svg", alt: 'Rechercher', 'aria-hidden':'true', width: 24, height: 24, loading: 'lazy'
|
||||
|
|
|
@ -4,7 +4,7 @@ FactoryBot.define do
|
|||
groupe_instructeurs { [association(:groupe_instructeur)] }
|
||||
|
||||
after(:build) do |export, _evaluator|
|
||||
export.key = Export.generate_cache_key(export.groupe_instructeurs)
|
||||
export.key = Export.generate_cache_key(export.groupe_instructeurs.map(&:id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,9 +48,9 @@ RSpec.describe Export, type: :model do
|
|||
context 'when an export is made for one groupe instructeur' do
|
||||
let!(:export) { create(:export, groupe_instructeurs: [gi_1, gi_2]) }
|
||||
|
||||
it { expect(Export.find_for_format_and_groupe_instructeurs(:csv, [gi_1])).to eq(nil) }
|
||||
it { expect(Export.find_for_format_and_groupe_instructeurs(:csv, [gi_2, gi_1])).to eq(export) }
|
||||
it { expect(Export.find_for_format_and_groupe_instructeurs(:csv, [gi_1, gi_2, gi_3])).to eq(nil) }
|
||||
it { expect(Export.find_for_groupe_instructeurs([gi_1.id])[1]).to eq(nil) }
|
||||
it { expect(Export.find_for_groupe_instructeurs([gi_2.id, gi_1.id])[1]).to eq(export) }
|
||||
it { expect(Export.find_for_groupe_instructeurs([gi_1.id, gi_2.id, gi_3.id])[1]).to eq(nil) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -255,29 +255,35 @@ describe Instructeur, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#notifications_for_procedure' do
|
||||
describe '#notifications_for_groupe_instructeurs' do
|
||||
# one procedure, one group, 2 instructeurs
|
||||
let(:procedure) { create(:simple_procedure, :routee, :with_type_de_champ_private) }
|
||||
let!(:dossier) { create(:dossier, :followed, groupe_instructeur: procedure.groupe_instructeurs.last, state: Dossier.states.fetch(:en_construction)) }
|
||||
let(:gi_p1) { procedure.groupe_instructeurs.last }
|
||||
let!(:dossier) { create(:dossier, :followed, groupe_instructeur: gi_p1, state: Dossier.states.fetch(:en_construction)) }
|
||||
let(:instructeur) { dossier.follows.first.instructeur }
|
||||
let!(:instructeur_2) { create(:instructeur, groupe_instructeurs: [procedure.groupe_instructeurs.last]) }
|
||||
let!(:instructeur_2) { create(:instructeur, groupe_instructeurs: [gi_p1]) }
|
||||
|
||||
# one other procedure, dossier followed by a third instructeur
|
||||
let!(:dossier_on_procedure_2) { create(:dossier, :followed, state: Dossier.states.fetch(:en_construction)) }
|
||||
let!(:instructeur_on_procedure_2) { dossier_on_procedure_2.follows.first.instructeur }
|
||||
let(:gi_p2) { dossier.groupe_instructeur }
|
||||
|
||||
let(:now) { Time.zone.parse("14/09/1867") }
|
||||
let(:follow) { instructeur.follows.find_by(dossier: dossier) }
|
||||
let(:follow2) { instructeur_2.follows.find_by(dossier: dossier) }
|
||||
|
||||
let(:seen_at_instructeur) { now - 1.hour }
|
||||
let(:seen_at_instructeur2) { now - 1.hour }
|
||||
|
||||
before do
|
||||
procedure.groupe_instructeurs.last.instructeurs << instructeur
|
||||
gi_p1.instructeurs << instructeur
|
||||
instructeur_2.followed_dossiers << dossier
|
||||
Timecop.freeze(now)
|
||||
end
|
||||
|
||||
after { Timecop.return }
|
||||
|
||||
subject { instructeur.notifications_for_procedure(procedure, :en_cours) }
|
||||
subject { instructeur.notifications_for_groupe_instructeurs(gi_p1)[:en_cours] }
|
||||
|
||||
context 'when the instructeur has just followed the dossier' do
|
||||
it { is_expected.to match([]) }
|
||||
|
@ -290,14 +296,14 @@ describe Instructeur, type: :model do
|
|||
follow2.update_attribute('demande_seen_at', seen_at_instructeur2)
|
||||
end
|
||||
|
||||
it { is_expected.to match([dossier]) }
|
||||
it { expect(instructeur_2.notifications_for_procedure(procedure, :en_cours)).to match([dossier]) }
|
||||
it { expect(instructeur_on_procedure_2.notifications_for_procedure(procedure, :en_cours)).to match([]) }
|
||||
it { is_expected.to match([dossier.id]) }
|
||||
it { expect(instructeur_2.notifications_for_groupe_instructeurs(gi_p1)[:en_cours]).to match([dossier.id]) }
|
||||
it { expect(instructeur_on_procedure_2.notifications_for_groupe_instructeurs(gi_p2)[:en_cours]).to match([]) }
|
||||
|
||||
context 'and there is a modification on private champs' do
|
||||
before { dossier.champs_private.first.update_attribute('value', 'toto') }
|
||||
|
||||
it { is_expected.to match([dossier]) }
|
||||
it { is_expected.to match([dossier.id]) }
|
||||
end
|
||||
|
||||
context 'when instructeur update it s public champs last seen' do
|
||||
|
@ -305,7 +311,7 @@ describe Instructeur, type: :model do
|
|||
let(:seen_at_instructeur2) { now - 1.hour }
|
||||
|
||||
it { is_expected.to match([]) }
|
||||
it { expect(instructeur_2.notifications_for_procedure(procedure, :en_cours)).to match([dossier]) }
|
||||
it { expect(instructeur_2.notifications_for_groupe_instructeurs(gi_p1)[:en_cours]).to match([dossier.id]) }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -321,7 +327,7 @@ describe Instructeur, type: :model do
|
|||
follow.update_attribute('annotations_privees_seen_at', seen_at_instructeur)
|
||||
end
|
||||
|
||||
it { is_expected.to match([dossier]) }
|
||||
it { is_expected.to match([dossier.id]) }
|
||||
end
|
||||
|
||||
context 'when there is a modification on avis' do
|
||||
|
@ -330,7 +336,7 @@ describe Instructeur, type: :model do
|
|||
follow.update_attribute('avis_seen_at', seen_at_instructeur)
|
||||
end
|
||||
|
||||
it { is_expected.to match([dossier]) }
|
||||
it { is_expected.to match([dossier.id]) }
|
||||
end
|
||||
|
||||
context 'the messagerie' do
|
||||
|
@ -340,7 +346,7 @@ describe Instructeur, type: :model do
|
|||
follow.update_attribute('messagerie_seen_at', seen_at_instructeur)
|
||||
end
|
||||
|
||||
it { is_expected.to match([dossier]) }
|
||||
it { is_expected.to match([dossier.id]) }
|
||||
end
|
||||
|
||||
context 'when there is a new commentaire issued by tps' do
|
||||
|
@ -431,9 +437,9 @@ describe Instructeur, type: :model do
|
|||
|
||||
context 'when a notification exists' do
|
||||
before do
|
||||
allow(instructeur).to receive(:notifications_for_procedure)
|
||||
.with(procedure_to_assign, :not_archived)
|
||||
.and_return([1, 2, 3])
|
||||
allow(instructeur).to receive(:notifications_for_groupe_instructeurs)
|
||||
.with([procedure_to_assign.groupe_instructeurs.first.id])
|
||||
.and_return(en_cours: [1, 2, 3], termines: [])
|
||||
end
|
||||
|
||||
it do
|
||||
|
|
|
@ -5,8 +5,8 @@ describe DossierProjectionService do
|
|||
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!(:dossier_2) { create(:dossier, :en_construction, :archived, procedure: procedure) }
|
||||
let!(:dossier_3) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||
|
||||
let(:dossiers_ids) { [dossier_3.id, dossier_1.id, dossier_2.id] }
|
||||
let(:fields) do
|
||||
|
@ -26,11 +26,20 @@ describe DossierProjectionService do
|
|||
|
||||
let(:result) { subject }
|
||||
|
||||
it 'respects the dossiers_ids order and returns nil for empty result' do
|
||||
it 'respects the dossiers_ids order, returns state, archived and 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].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].state).to eq('en_instruction')
|
||||
expect(result[1].state).to eq('brouillon')
|
||||
expect(result[2].state).to eq('en_construction')
|
||||
|
||||
expect(result[0].archived).to be false
|
||||
expect(result[1].archived).to be false
|
||||
expect(result[2].archived).to be true
|
||||
|
||||
expect(result[0].columns[0]).to be nil
|
||||
expect(result[1].columns[0]).to eq('champ_1')
|
||||
|
|
|
@ -92,8 +92,8 @@ describe NotificationService do
|
|||
|
||||
context 'when there is a notification on this procedure' do
|
||||
before do
|
||||
allow_any_instance_of(Instructeur).to receive(:notifications_for_procedure)
|
||||
.and_return([12])
|
||||
allow_any_instance_of(Instructeur).to receive(:notifications_for_groupe_instructeurs)
|
||||
.and_return(en_cours: [12], termines: [])
|
||||
end
|
||||
|
||||
it do
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
describe 'instructeurs/procedures/_download_dossiers.html.haml', type: :view do
|
||||
let(:current_instructeur) { create(:instructeur) }
|
||||
let(:procedure) { create(:procedure) }
|
||||
let(:dossier_count) { 0 }
|
||||
|
||||
subject { render 'instructeurs/procedures/download_dossiers.html.haml', procedure: procedure, xlsx_export: nil, csv_export: nil, ods_export: nil }
|
||||
subject { render 'instructeurs/procedures/download_dossiers.html.haml', procedure: procedure, dossier_count: dossier_count, xlsx_export: nil, csv_export: nil, ods_export: nil }
|
||||
|
||||
context "when procedure has 0 dossier" do
|
||||
it { is_expected.not_to include("Télécharger tous les dossiers") }
|
||||
end
|
||||
|
||||
context "when procedure has 1 dossier brouillon" do
|
||||
let!(:dossier) { create(:dossier, procedure: procedure) }
|
||||
it { is_expected.not_to include("Télécharger tous les dossiers") }
|
||||
end
|
||||
|
||||
context "when procedure has at least 1 dossier en construction" do
|
||||
let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
context "when procedure has at least 1 dossier" do
|
||||
let(:dossier_count) { 1 }
|
||||
it { is_expected.to include("Télécharger tous les dossiers") }
|
||||
|
||||
context "With zip archive enabled" do
|
||||
|
|
Loading…
Add table
Reference in a new issue