diff --git a/app/controllers/concerns/create_avis_concern.rb b/app/controllers/concerns/create_avis_concern.rb
index 8b7e55865..74965976c 100644
--- a/app/controllers/concerns/create_avis_concern.rb
+++ b/app/controllers/concerns/create_avis_concern.rb
@@ -17,6 +17,9 @@ module CreateAvisConcern
allowed_dossiers += dossier.linked_dossiers_for(instructeur_or_expert)
end
+ if (instructeur_or_expert.is_a?(Instructeur)) && !instructeur_or_expert.follows.exists?(dossier: dossier)
+ instructeur_or_expert.follow(dossier)
+ end
create_results = Avis.create(
expert_emails.flat_map do |email|
user = User.create_or_promote_to_expert(email, SecureRandom.hex)
diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb
index 7dbdc2365..172891bc8 100644
--- a/app/controllers/instructeurs/procedures_controller.rb
+++ b/app/controllers/instructeurs/procedures_controller.rb
@@ -76,10 +76,11 @@ module Instructeurs
@has_termine_notifications = notifications[:termines].present?
@not_archived_notifications_dossier_ids = notifications[:en_cours] + notifications[:termines]
- filtered_sorted_ids = procedure_presentation.filtered_sorted_ids(dossiers, dossiers_count, statut)
+ filtered_sorted_ids = procedure_presentation.filtered_sorted_ids(dossiers, statut, count: dossiers_count)
page = params[:page].presence || 1
+ @dossiers_count = filtered_sorted_ids.size
@filtered_sorted_paginated_ids = Kaminari
.paginate_array(filtered_sorted_ids)
.page(page)
@@ -137,8 +138,6 @@ module Instructeurs
end
def download_export
- export_format = params[:export_format]
- time_span_type = params[:time_span_type] || Export.time_span_types.fetch(:everything)
groupe_instructeurs = current_instructeur
.groupe_instructeurs
.where(procedure: procedure)
@@ -148,17 +147,19 @@ module Instructeurs
.visible_by_administration
.exists?(groupe_instructeur_id: groupe_instructeur_ids)
- export = Export.find_or_create_export(export_format, time_span_type, groupe_instructeurs)
+ export = Export.find_or_create_export(export_format, groupe_instructeurs, **export_options)
- if export.ready? && export.old? && params[:force_export]
+ if export.ready? && export.old? && force_export?
export.destroy
- export = Export.find_or_create_export(export_format, time_span_type, groupe_instructeurs)
+ export = Export.find_or_create_export(export_format, groupe_instructeurs, **export_options)
end
if export.ready?
respond_to do |format|
format.js do
@procedure = procedure
+ @statut = export_options[:statut]
+ @dossiers_count = export.count
assign_exports
flash.notice = "L’export au format \"#{export_format}\" est prêt. Vous pouvez le télécharger"
end
@@ -173,6 +174,8 @@ module Instructeurs
format.js do
@procedure = procedure
+ @statut = export_options[:statut]
+ @dossiers_count = export.count
assign_exports
if !params[:no_progress_notification]
flash.notice = notice_message
@@ -261,7 +264,7 @@ module Instructeurs
end
def assign_exports
- @exports = Export.find_for_groupe_instructeurs(groupe_instructeur_ids)
+ @exports = Export.find_for_groupe_instructeurs(groupe_instructeur_ids, procedure_presentation)
end
def assign_tos
@@ -280,6 +283,22 @@ module Instructeurs
@statut ||= (params[:statut].presence || 'a-suivre')
end
+ def export_format
+ @export_format ||= params[:export_format]
+ end
+
+ def force_export?
+ @force_export ||= params[:force_export].present?
+ end
+
+ def export_options
+ @export_options ||= {
+ time_span_type: params[:time_span_type],
+ statut: params[:statut],
+ procedure_presentation: params[:statut].present? ? procedure_presentation : nil
+ }.compact
+ end
+
def procedure_id
params[:procedure_id]
end
diff --git a/app/helpers/dossier_helper.rb b/app/helpers/dossier_helper.rb
index 8a2582100..a0d9f1905 100644
--- a/app/helpers/dossier_helper.rb
+++ b/app/helpers/dossier_helper.rb
@@ -109,9 +109,23 @@ module DossierHelper
"#{base_url}/entreprise/#{siren}"
end
- def exports_list(exports)
- Export::FORMATS.map do |(format, time_span_type)|
- [format, time_span_type, exports[format] && exports[format][time_span_type]]
+ def exports_list(exports, statut = nil)
+ if statut
+ Export::FORMATS.map do |item|
+ export = exports
+ .fetch(item.fetch(:format))
+ .fetch(:statut)
+ .fetch(statut, nil)
+ item.merge(export: export)
+ end
+ else
+ Export::FORMATS_WITH_TIME_SPAN.map do |item|
+ export = exports
+ .fetch(item.fetch(:format))
+ .fetch(:time_span_type)
+ .fetch(item.fetch(:time_span_type), nil)
+ item.merge(export: export)
+ end
end
end
end
diff --git a/app/models/export.rb b/app/models/export.rb
index caef9ce48..a62872c5b 100644
--- a/app/models/export.rb
+++ b/app/models/export.rb
@@ -2,12 +2,15 @@
#
# Table name: exports
#
-# id :bigint not null, primary key
-# format :string not null
-# key :text not null
-# time_span_type :string default("everything"), not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :bigint not null, primary key
+# format :string not null
+# key :text not null
+# procedure_presentation_snapshot :jsonb
+# statut :string default("tous")
+# time_span_type :string default("everything"), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# procedure_presentation_id :bigint
#
class Export < ApplicationRecord
MAX_DUREE_CONSERVATION_EXPORT = 3.hours
@@ -23,7 +26,18 @@ class Export < ApplicationRecord
monthly: 'monthly'
}
+ enum statut: {
+ 'a-suivre': 'a-suivre',
+ suivis: 'suivis',
+ traites: 'traites',
+ tous: 'tous',
+ supprimes_recemment: 'supprimes_recemment',
+ archives: 'archives',
+ expirant: 'expirant'
+ }
+
has_and_belongs_to_many :groupe_instructeurs
+ belongs_to :procedure_presentation, optional: true
has_one_attached :file
@@ -33,19 +47,24 @@ class Export < ApplicationRecord
after_create_commit :compute_async
- FORMATS = [:xlsx, :ods, :csv].flat_map do |format|
- Export.time_span_types.values.map do |time_span_type|
- [format, time_span_type]
+ FORMATS_WITH_TIME_SPAN = [:xlsx, :ods, :csv].flat_map do |format|
+ time_span_types.keys.map do |time_span_type|
+ { format: format.to_sym, time_span_type: time_span_type }
end
end
+ FORMATS = [:xlsx, :ods, :csv].map do |format|
+ { format: format.to_sym }
+ end
def compute_async
ExportJob.perform_later(self)
end
def compute
+ load_snapshot!
+
file.attach(
- io: io(since: since),
+ io: io,
filename: filename,
content_type: content_type,
# We generate the exports ourselves, so they are safe
@@ -62,47 +81,109 @@ class Export < ApplicationRecord
end
def old?
- updated_at < 20.minutes.ago
+ updated_at < 20.minutes.ago || filters_changed?
end
- def self.find_or_create_export(format, time_span_type, groupe_instructeurs)
- create_with(groupe_instructeurs: groupe_instructeurs)
+ def filters_changed?
+ procedure_presentation&.snapshot != procedure_presentation_snapshot
+ end
+
+ def filtered?
+ procedure_presentation_id.present?
+ end
+
+ def xlsx?
+ format == self.class.formats.fetch(:xlsx)
+ end
+
+ def ods?
+ format == self.class.formats.fetch(:ods)
+ end
+
+ def csv?
+ format == self.class.formats.fetch(:csv)
+ end
+
+ def self.find_or_create_export(format, groupe_instructeurs, time_span_type: time_span_types.fetch(:everything), statut: statuts.fetch(:tous), procedure_presentation: nil)
+ create_with(groupe_instructeurs: groupe_instructeurs, procedure_presentation: procedure_presentation, procedure_presentation_snapshot: procedure_presentation&.snapshot)
+ .includes(:procedure_presentation)
.create_or_find_by(format: format,
time_span_type: time_span_type,
- key: generate_cache_key(groupe_instructeurs.map(&:id)))
+ statut: statut,
+ key: generate_cache_key(groupe_instructeurs.map(&:id), procedure_presentation&.id))
end
- def self.find_for_groupe_instructeurs(groupe_instructeurs_ids)
- exports = where(key: generate_cache_key(groupe_instructeurs_ids))
+ def self.find_for_groupe_instructeurs(groupe_instructeurs_ids, procedure_presentation)
+ exports = if procedure_presentation.present?
+ where(key: generate_cache_key(groupe_instructeurs_ids))
+ .or(where(key: generate_cache_key(groupe_instructeurs_ids, procedure_presentation.id)))
+ else
+ where(key: generate_cache_key(groupe_instructeurs_ids))
+ end
+ filtered, not_filtered = exports.partition(&:filtered?)
- [:xlsx, :csv, :ods].map do |format|
- [
- format,
- Export.time_span_types.values.map do |time_span_type|
- [time_span_type, exports.find { |export| export.format == format.to_s && export.time_span_type == time_span_type }]
- end.filter { |(_, export)| export.present? }.to_h
- ]
- end.filter { |(_, exports)| exports.present? }.to_h
+ {
+ xlsx: {
+ time_span_type: not_filtered.filter(&:xlsx?).index_by(&:time_span_type),
+ statut: filtered.filter(&:xlsx?).index_by(&:statut)
+ },
+ ods: {
+ time_span_type: not_filtered.filter(&:ods?).index_by(&:time_span_type),
+ statut: filtered.filter(&:ods?).index_by(&:statut)
+ },
+ csv: {
+ time_span_type: not_filtered.filter(&:csv?).index_by(&:time_span_type),
+ statut: filtered.filter(&:csv?).index_by(&:statut)
+ }
+ }
end
- def self.generate_cache_key(groupe_instructeurs_ids)
- groupe_instructeurs_ids.sort.join('-')
+ def self.generate_cache_key(groupe_instructeurs_ids, procedure_presentation_id = nil)
+ if procedure_presentation_id.present?
+ "#{groupe_instructeurs_ids.sort.join('-')}--#{procedure_presentation_id}"
+ else
+ groupe_instructeurs_ids.sort.join('-')
+ end
+ end
+
+ def count
+ if procedure_presentation_id.present?
+ dossiers_for_export.size
+ end
end
private
- def filename
- procedure_identifier = procedure.path || "procedure-#{procedure.id}"
- "dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}"
+ def load_snapshot!
+ if procedure_presentation_snapshot.present?
+ procedure_presentation.attributes = procedure_presentation_snapshot
+ end
end
- def io(since: nil)
- dossiers = Dossier.visible_by_administration
- .where(groupe_instructeur: groupe_instructeurs)
- if since.present?
- dossiers = dossiers.where('dossiers.depose_at > ?', since)
+ def dossiers_for_export
+ @dossiers_for_export ||= begin
+ dossiers = Dossier.where(groupe_instructeur: groupe_instructeurs)
+
+ if since.present?
+ dossiers.visible_by_administration.where('dossiers.depose_at > ?', since)
+ elsif procedure_presentation.present?
+ filtered_sorted_ids = procedure_presentation
+ .filtered_sorted_ids(dossiers, statut)
+
+ dossiers.where(id: filtered_sorted_ids)
+ else
+ dossiers
+ end
end
- service = ProcedureExportService.new(procedure, dossiers)
+ end
+
+ def filename
+ procedure_identifier = procedure.path || "procedure-#{procedure.id}"
+ "dossiers_#{procedure_identifier}_#{statut}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}"
+ end
+
+ def io
+ service = ProcedureExportService.new(procedure, dossiers_for_export)
case format.to_sym
when :csv
diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb
index 1c39669d5..c2edecc70 100644
--- a/app/models/procedure_presentation.rb
+++ b/app/models/procedure_presentation.rb
@@ -25,6 +25,8 @@ class ProcedurePresentation < ApplicationRecord
FILTERS_VALUE_MAX_LENGTH = 100
belongs_to :assign_to, optional: false
+ has_many :exports, dependent: :destroy
+
delegate :procedure, :instructeur, to: :assign_to
validate :check_allowed_displayed_fields
@@ -180,9 +182,9 @@ class ProcedurePresentation < ApplicationRecord
end.reduce(:&)
end
- def filtered_sorted_ids(dossiers, count, statut)
+ def filtered_sorted_ids(dossiers, statut, count: nil)
dossiers_by_statut = dossiers.by_statut(instructeur, statut)
- dossiers_sorted_ids = self.sorted_ids(dossiers_by_statut, count)
+ dossiers_sorted_ids = self.sorted_ids(dossiers_by_statut, count || dossiers_by_statut.size)
if filters[statut].present?
filtered_ids(dossiers_by_statut, statut).intersection(dossiers_sorted_ids)
@@ -261,6 +263,10 @@ class ProcedurePresentation < ApplicationRecord
})
end
+ def snapshot
+ slice(:filters, :sort, :displayed_fields)
+ end
+
private
def field_id(field)
diff --git a/app/views/instructeurs/procedures/_dossiers_export.html.haml b/app/views/instructeurs/procedures/_dossiers_export.html.haml
new file mode 100644
index 000000000..45f0754b5
--- /dev/null
+++ b/app/views/instructeurs/procedures/_dossiers_export.html.haml
@@ -0,0 +1,19 @@
+%span.dropdown
+ %button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'download-menu' }
+ = t(".download", count: count)
+ #download-menu.dropdown-content.fade-in-down{ style: 'width: 450px' }
+ %ul.dropdown-items
+ - exports_list(exports, statut).each do |item|
+ - format = item[:format]
+ - export = item[:export]
+ %li
+ - if export.nil?
+ = link_to t(".everything_#{format}_html"), download_export_instructeur_procedure_path(procedure, statut: statut, export_format: format), remote: true
+ - elsif export.ready?
+ = link_to t(".everything_ready_html", export_time: time_ago_in_words(export.updated_at), export_format: ".#{format}"), export.file.service_url, target: "_blank", rel: "noopener"
+ - if export.old?
+ = button_to download_export_instructeur_procedure_path(procedure, export_format: format, statut: statut, force_export: true), class: "button small", style: "padding-right: 2px", title: t(".everything_short", export_format: ".#{format}"), remote: true, method: :get, params: { export_format: format, statut: statut, force_export: true } do
+ .icon.retry
+ - else
+ %span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, statut: statut, no_progress_notification: true) }
+ = t(".everything_pending_html", export_time: time_ago_in_words(export.created_at), export_format: ".#{format}")
diff --git a/app/views/instructeurs/procedures/_dossiers_filter.html.haml b/app/views/instructeurs/procedures/_dossiers_filter.html.haml
new file mode 100644
index 000000000..ac7c88839
--- /dev/null
+++ b/app/views/instructeurs/procedures/_dossiers_filter.html.haml
@@ -0,0 +1,24 @@
+%span.dropdown
+ %button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'filter-menu' }
+ Filtrer
+ #filter-menu.dropdown-content.left-aligned.fade-in-down
+ = form_tag add_filter_instructeur_procedure_path(procedure), method: :post, class: 'dropdown-form large' do
+ = label_tag :field, "Colonne"
+ = select_tag :field, options_for_select(displayed_fields_options)
+ %br
+ = label_tag :value, "Valeur"
+ = text_field_tag :value, nil, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH
+ = hidden_field_tag :statut, statut
+ %br
+ = submit_tag "Ajouter le filtre", class: 'button'
+
+- current_filters.group_by { |filter| filter['table'] }.each_with_index do |(table, filters), i|
+ - if i > 0
+ et
+ - filters.each_with_index do |filter, i|
+ - if i > 0
+ ou
+ %span.filter
+ = link_to remove_filter_instructeur_procedure_path(procedure, { statut: statut, field: "#{filter['table']}/#{filter['column']}", value: filter['value'] }) do
+ %img.close-icon{ src: image_url("close.svg") }
+ = "#{filter['label'].truncate(50)} : #{procedure_presentation.human_value_for_filter(filter)}"
diff --git a/app/views/instructeurs/procedures/_download_dossiers.html.haml b/app/views/instructeurs/procedures/_download_dossiers.html.haml
index c07b51811..11e83b75e 100644
--- a/app/views/instructeurs/procedures/_download_dossiers.html.haml
+++ b/app/views/instructeurs/procedures/_download_dossiers.html.haml
@@ -3,7 +3,10 @@
Télécharger tous les dossiers
#download-menu.dropdown-content.fade-in-down{ style: 'width: 450px' }
%ul.dropdown-items
- - exports_list(exports).each do |(format, time_span_type, export)|
+ - exports_list(exports).each do |item|
+ - format = item[:format]
+ - time_span_type = item[:time_span_type]
+ - export = item[:export]
%li
- if export.nil?
= link_to t("#{time_span_type}_#{format}_html", scope: [:instructeurs, :procedure, :export_stale]), download_export_instructeur_procedure_path(procedure, time_span_type: time_span_type, export_format: format), remote: true
@@ -13,7 +16,7 @@
= button_to download_export_instructeur_procedure_path(procedure, export_format: format, time_span_type: time_span_type, force_export: true), class: "button small", style: "padding-right: 2px", title: t("#{time_span_type}_short", export_format: ".#{format}", scope: [:instructeurs, :procedure, :export_stale]), remote: true, method: :get, params: { export_format: format, time_span_type: time_span_type, force_export: true } do
.icon.retry
- else
- %span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, no_progress_notification: true) }
+ %span{ 'data-export-poll-url': download_export_instructeur_procedure_path(procedure, export_format: format, time_span_type: time_span_type, no_progress_notification: true) }
= t("export_#{time_span_type}_pending_html", export_time: time_ago_in_words(export.created_at), export_format: ".#{format}", scope: [:instructeurs, :procedure])
%li
= link_to t(:download_archive, scope: [:instructeurs, :procedure]), instructeur_archives_path(procedure)
diff --git a/app/views/instructeurs/procedures/download_export.js.erb b/app/views/instructeurs/procedures/download_export.js.erb
index aab5dbf9f..ab489f92d 100644
--- a/app/views/instructeurs/procedures/download_export.js.erb
+++ b/app/views/instructeurs/procedures/download_export.js.erb
@@ -1,11 +1,22 @@
<% if @can_download_dossiers %>
- <%= render_to_element('.procedure-actions', partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports }) %>
+ <% if @statut.present? %>
+ <%= render_to_element('.dossiers-export', partial: "dossiers_export", locals: { procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count }) %>
+ <% else %>
+ <%= render_to_element('.procedure-actions', partial: "download_dossiers", locals: { procedure: @procedure, exports: @exports }) %>
+ <% end %>
<% end %>
-<% @exports.each do |format, exports| %>
- <% exports.each do |time_span_type, export| %>
- <% if !export.ready? %>
- <%= fire_event('export:update', { url: download_export_instructeur_procedure_path(@procedure, export_format: format, time_span_type: time_span_type, no_progress_notification: true) }.to_json) %>
+<% @exports.values.each do |exports| %>
+ <% if @statut.present? %>
+ <% export = exports[:statut][@statut] %>
+ <% if export && !export.ready? %>
+ <%= fire_event('export:update', { url: download_export_instructeur_procedure_path(@procedure, export_format: export.format, statut: export.statut, no_progress_notification: true) }.to_json) %>
+ <% end %>
+ <% else %>
+ <% exports[:time_span_type].values.each do |export| %>
+ <% if !export.ready? %>
+ <%= fire_event('export:update', { url: download_export_instructeur_procedure_path(@procedure, export_format: export.format, time_span_type: export.time_span_type, no_progress_notification: true) }.to_json) %>
+ <% end %>
<% end %>
<% end %>
<% end %>
diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml
index ac166b19d..feae02087 100644
--- a/app/views/instructeurs/procedures/show.html.haml
+++ b/app/views/instructeurs/procedures/show.html.haml
@@ -52,34 +52,16 @@
- if @statut == 'expirant'
%p.explication-onglet Les dossiers n'expireront pas avant la période de conservation des données.
-
- 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
- #filter-menu.dropdown-content.left-aligned.fade-in-down
- = form_tag add_filter_instructeur_procedure_path(@procedure), method: :post, class: 'dropdown-form large' do
- = label_tag :field, "Colonne"
- = select_tag :field, options_for_select(@displayed_fields_options)
- %br
- = label_tag :value, "Valeur"
- = text_field_tag :value, nil, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH
- = hidden_field_tag :statut, @statut
- %br
- = submit_tag "Ajouter le filtre", class: 'button'
+ .flex
+ .flex-grow
+ = render partial: "dossiers_filter", locals: { procedure: @procedure, procedure_presentation: @procedure_presentation, current_filters: @current_filters, statut: @statut, displayed_fields_options: @displayed_fields_options }
+ - if @dossiers_count > 0
+ .dossiers-export
+ = render partial: "dossiers_export", locals: { procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count }
- - @current_filters.group_by { |filter| filter['table'] }.each_with_index do |(table, filters), i|
- - if i > 0
- et
- - filters.each_with_index do |filter, i|
- - if i > 0
- ou
- %span.filter
- = link_to remove_filter_instructeur_procedure_path(@procedure, { statut: @statut, field: "#{filter['table']}/#{filter['column']}", value: filter['value'] }) do
- %img.close-icon{ src: image_url("close.svg") }
- = "#{filter['label'].truncate(50)} : #{@procedure_presentation.human_value_for_filter(filter)}"
%table.table.dossiers-table.hoverable
%thead
%tr
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index ac201cf2d..692e07c58 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -15,7 +15,7 @@
"type": "controller",
"class": "Users::DossiersController",
"method": "merci",
- "line": 196,
+ "line": 201,
"file": "app/controllers/users/dossiers_controller.rb",
"rendered": {
"name": "users/dossiers/merci",
@@ -115,24 +115,24 @@
{
"warning_type": "Redirect",
"warning_code": 18,
- "fingerprint": "c46b5c9cd6474ffae789f39a2280ba6b5a5a74d3ffa8a38cf8a409f9a027ed0e",
+ "fingerprint": "eae88be293b3849e07be81a18de99c04b08c7b2c045cc4a43ca3624ea178d965",
"check_name": "Redirect",
"message": "Possible unprotected redirect",
"file": "app/controllers/instructeurs/procedures_controller.rb",
- "line": 202,
+ "line": 191,
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
- "code": "redirect_to(Export.find_or_create_export(params[:export_format], (params[:time_span_type] or \"everything\"), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url)",
+ "code": "redirect_to(Export.find_or_create_export(export_format, current_instructeur.groupe_instructeurs.where(:procedure => procedure), **export_options).file.service_url)",
"render_path": null,
"location": {
"type": "method",
"class": "Instructeurs::ProceduresController",
"method": "download_export"
},
- "user_input": "Export.find_or_create_export(params[:export_format], (params[:time_span_type] or \"everything\"), current_instructeur.groupe_instructeurs.where(:procedure => procedure)).file.service_url",
+ "user_input": "Export.find_or_create_export(export_format, current_instructeur.groupe_instructeurs.where(:procedure => procedure), **export_options).file.service_url",
"confidence": "High",
"note": ""
}
],
- "updated": "2022-02-22 15:46:39 +0100",
+ "updated": "2022-04-05 14:21:07 +0200",
"brakeman_version": "5.1.1"
}
diff --git a/config/initializers/legit_admin_domains.rb b/config/initializers/legit_admin_domains.rb
index c064dfe4b..5df4e341f 100644
--- a/config/initializers/legit_admin_domains.rb
+++ b/config/initializers/legit_admin_domains.rb
@@ -1,2 +1,2 @@
-domains = ["gouv.fr", "sante.fr", "cnafmail.fr", "cnamts.fr", "cci.fr", "caf.fr", "msa.fr"]
+domains = ["gouv.fr", "sante.fr", "cnafmail.fr", "cnamts.fr", "cci.fr", "caf.fr", "msa.fr", "assurance-maladie.fr"]
LEGIT_ADMIN_DOMAINS = ENV["LEGIT_ADMIN_DOMAINS"]&.split(';') || domains
diff --git a/config/locales/views/instructeurs/procedures/fr.yml b/config/locales/views/instructeurs/procedures/fr.yml
new file mode 100644
index 000000000..9cf758507
--- /dev/null
+++ b/config/locales/views/instructeurs/procedures/fr.yml
@@ -0,0 +1,13 @@
+fr:
+ instructeurs:
+ procedures:
+ dossiers_export:
+ everything_csv_html: Demander un export au format .csv
(uniquement les dossiers, sans les champs répétables)
+ everything_xlsx_html: Demander un export au format .xlsx
+ everything_ods_html: Demander un export au format .ods
+ everything_short: Demander un export au format %{export_format}
+ everything_pending_html: Un export au format %{export_format} est en train d’être généré
(demandé il y a %{export_time})
+ everything_ready_html: Télécharger l’export au format %{export_format}
(généré il y a %{export_time})
+ download:
+ one: Télécharger un dossier
+ other: Télécharger %{count} dossiers
diff --git a/db/migrate/20220323143325_add_procedure_presentation_and_state_to_exports.rb b/db/migrate/20220323143325_add_procedure_presentation_and_state_to_exports.rb
new file mode 100644
index 000000000..3b2ac9406
--- /dev/null
+++ b/db/migrate/20220323143325_add_procedure_presentation_and_state_to_exports.rb
@@ -0,0 +1,11 @@
+class AddProcedurePresentationAndStateToExports < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ def change
+ add_reference :exports, :procedure_presentation, null: true, index: { algorithm: :concurrently }
+ add_column :exports, :statut, :string
+ change_column_default :exports, :statut, "tous"
+ remove_index :exports, [:format, :time_span_type, :key]
+ add_index :exports, [:format, :time_span_type, :statut, :key], unique: true, algorithm: :concurrently
+ end
+end
diff --git a/db/migrate/20220405163206_add_procedure_presentation_snapshot_to_exports.rb b/db/migrate/20220405163206_add_procedure_presentation_snapshot_to_exports.rb
new file mode 100644
index 000000000..2ac3dd06b
--- /dev/null
+++ b/db/migrate/20220405163206_add_procedure_presentation_snapshot_to_exports.rb
@@ -0,0 +1,5 @@
+class AddProcedurePresentationSnapshotToExports < ActiveRecord::Migration[6.1]
+ def change
+ add_column :exports, :procedure_presentation_snapshot, :jsonb
+ end
+end
diff --git a/db/migrate/20220407081538_backfill_export_status.rb b/db/migrate/20220407081538_backfill_export_status.rb
new file mode 100644
index 000000000..721789437
--- /dev/null
+++ b/db/migrate/20220407081538_backfill_export_status.rb
@@ -0,0 +1,10 @@
+class BackfillExportStatus < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ def change
+ Export.in_batches do |relation|
+ relation.update_all statut: "tous"
+ sleep(0.01)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 11feb6627..d59c95dd9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_04_05_100354) do
+ActiveRecord::Schema.define(version: 2022_04_07_081538) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -431,9 +431,13 @@ ActiveRecord::Schema.define(version: 2022_04_05_100354) do
t.datetime "created_at", null: false
t.string "format", null: false
t.text "key", null: false
+ t.bigint "procedure_presentation_id"
+ t.jsonb "procedure_presentation_snapshot"
+ t.string "statut", default: "tous"
t.string "time_span_type", default: "everything", null: false
t.datetime "updated_at", null: false
- t.index ["format", "time_span_type", "key"], name: "index_exports_on_format_and_time_span_type_and_key", unique: true
+ t.index ["format", "time_span_type", "statut", "key"], name: "index_exports_on_format_and_time_span_type_and_statut_and_key", unique: true
+ t.index ["procedure_presentation_id"], name: "index_exports_on_procedure_presentation_id"
end
create_table "exports_groupe_instructeurs", force: :cascade do |t|
diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb
index bd3c80f20..4962ac9d4 100644
--- a/spec/controllers/instructeurs/dossiers_controller_spec.rb
+++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb
@@ -509,6 +509,13 @@ describe Instructeurs::DossiersController, type: :controller do
end
end
+ context 'as an instructeur, i auto follow the dossier so I get the notifications' do
+ it 'works' do
+ subject
+ expect(instructeur.followed_dossiers).to match_array([dossier])
+ end
+ end
+
context 'email sending' do
before do
subject
diff --git a/spec/factories/export.rb b/spec/factories/export.rb
index 3069ca238..c7ebda9b4 100644
--- a/spec/factories/export.rb
+++ b/spec/factories/export.rb
@@ -1,11 +1,12 @@
FactoryBot.define do
factory :export do
- format { :csv }
+ format { Export.formats.fetch(:csv) }
+ statut { Export.statuts.fetch(:tous) }
time_span_type { Export.time_span_types.fetch(:everything) }
groupe_instructeurs { [association(:groupe_instructeur)] }
after(:build) do |export, _evaluator|
- export.key = Export.generate_cache_key(export.groupe_instructeurs.map(&:id))
+ export.key = Export.generate_cache_key(export.groupe_instructeurs.map(&:id), export.procedure_presentation&.id)
end
end
end
diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb
index e074999b7..2bfdec18a 100644
--- a/spec/models/export_spec.rb
+++ b/spec/models/export_spec.rb
@@ -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_groupe_instructeurs([gi_1.id])).to eq({}) }
- it { expect(Export.find_for_groupe_instructeurs([gi_2.id, gi_1.id])).to eq({ csv: { 'everything' => export } }) }
- it { expect(Export.find_for_groupe_instructeurs([gi_1.id, gi_2.id, gi_3.id])).to eq({}) }
+ it { expect(Export.find_for_groupe_instructeurs([gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} } }) }
+ it { expect(Export.find_for_groupe_instructeurs([gi_2.id, gi_1.id], nil)).to eq({ csv: { statut: {}, time_span_type: { 'everything' => export } }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} } }) }
+ it { expect(Export.find_for_groupe_instructeurs([gi_1.id, gi_2.id, gi_3.id], nil)).to eq({ csv: { statut: {}, time_span_type: {} }, xlsx: { statut: {}, time_span_type: {} }, ods: { statut: {}, time_span_type: {} } }) }
end
end
end
diff --git a/spec/system/instructeurs/instruction_spec.rb b/spec/system/instructeurs/instruction_spec.rb
index 032397f8e..9af7c53aa 100644
--- a/spec/system/instructeurs/instruction_spec.rb
+++ b/spec/system/instructeurs/instruction_spec.rb
@@ -105,7 +105,9 @@ describe 'Instructing a dossier:', js: true do
assert_performed_jobs 1
click_on "Télécharger tous les dossiers"
- click_on "Demander un export au format .xlsx"
+ within(:css, '.procedure-actions') do
+ click_on "Demander un export au format .xlsx"
+ end
expect(page).to have_text('Nous générons cet export.')
expect(page).to have_text('Un export au format .xlsx est en train d’être généré')
@@ -114,13 +116,25 @@ describe 'Instructing a dossier:', js: true do
expect(page).to have_text('Nous générons cet export.')
expect(page).to have_text('Un export des 30 derniers jours au format .xlsx est en train d’être généré')
+ click_on "Télécharger un dossier"
+ within(:css, '.dossiers-export') do
+ click_on "Demander un export au format .csv"
+ end
+ expect(page).to have_text('Nous générons cet export.')
+ expect(page).to have_text('Un export au format .csv est en train d’être généré')
+
perform_enqueued_jobs(only: ExportJob)
- assert_performed_jobs 3
+ assert_performed_jobs 4
page.driver.browser.navigate.refresh
click_on "Télécharger tous les dossiers"
expect(page).to have_text('Télécharger l’export au format .xlsx')
expect(page).to have_text('Télécharger l’export des 30 derniers jours au format .xlsx')
+ # close dropdown menu
+ click_on "Télécharger tous les dossiers"
+
+ click_on "Télécharger un dossier"
+ expect(page).to have_text('Télécharger l’export au format .xlsx')
end
scenario 'A instructeur can see the personnes impliquées' do