diff --git a/app/components/dossiers/export_dropdown_component.rb b/app/components/dossiers/export_dropdown_component.rb
index b9215ce9911..928cd35fe59 100644
--- a/app/components/dossiers/export_dropdown_component.rb
+++ b/app/components/dossiers/export_dropdown_component.rb
@@ -6,11 +6,12 @@ class Dossiers::ExportDropdownComponent < ApplicationComponent
attr_reader :wrapper
attr_reader :export_templates
- def initialize(procedure:, export_templates: nil, statut: nil, count: nil, class_btn: nil, export_url: nil, show_export_template_tab: true, wrapper: :div)
+ def initialize(procedure:, export_templates: nil, statut: nil, count: nil, archived_count: 0, class_btn: nil, export_url: nil, show_export_template_tab: true, wrapper: :div)
@procedure = procedure
@export_templates = export_templates
@statut = statut
@count = count
+ @archived_count = archived_count
@class_btn = class_btn
@export_url = export_url
@show_export_template_tab = show_export_template_tab
@@ -29,6 +30,18 @@ def allowed_format?(item)
item.fetch(:format) != :json || @procedure.active_revision.carte?
end
+ def can_include_archived?
+ @statut == 'tous' && @archived_count > 0
+ end
+
+ def include_archived_title
+ if @archived_count > 1
+ "Inclure les #{@archived_count} dossiers « archivés »"
+ else
+ "Inclure le dossier « archivé »"
+ end
+ end
+
def download_export_path(export_format: nil, export_template_id: nil, no_progress_notification: nil)
@export_url.call(@procedure,
export_format:,
diff --git a/app/components/dossiers/export_dropdown_component/export_dropdown_component.html.haml b/app/components/dossiers/export_dropdown_component/export_dropdown_component.html.haml
index b17752c4384..ad1962f281a 100644
--- a/app/components/dossiers/export_dropdown_component/export_dropdown_component.html.haml
+++ b/app/components/dossiers/export_dropdown_component/export_dropdown_component.html.haml
@@ -15,12 +15,19 @@
.fr-tabs__panel.fr-pb-8w.fr-tabs__panel--selected{ id: "tabpanel-standard#{@count}-panel", role: "tabpanel", "aria-labelledby": "tabpanel-standard#{@count}", tabindex: "0" }
= form_with url: download_export_path, namespace: "export#{@count}", data: { turbo_method: :post, turbo: true } do |f|
+
+ - if can_include_archived?
+ .fr-pb-2w
+ = render Dsfr::ToggleComponent.new(form: f,
+ target: :include_archived,
+ html_title: include_archived_title)
+
= f.hidden_field :statut, value: @statut
%fieldset.fr-fieldset#radio-hint{ "aria-labelledby": "radio-hint-legend" }
%legend.fr-fieldset__legend--regular.fr-fieldset__legend#radio-hint-legend Sélectionner le format de l'export
.fr-fieldset__element
.fr-radio-group
- = f.radio_button :export_format, 'xlsx'
+ = f.radio_button :export_format, 'xlsx', checked: true
= f.label :export_format_xlsx, 'Fichier xlsx'
.fr-fieldset__element
.fr-radio-group
@@ -57,6 +64,12 @@
.fr-tabs__panel.fr-pr-3w.fr-pb-8w{ id: "tabpanel-template#{@count}-panel", role: "tabpanel", "aria-labelledby": "tabpanel-template", tabindex: "0" }
= form_with url: download_export_path, namespace: "export_template_#{@count}", data: { turbo_method: :post, turbo: true } do |f|
= f.hidden_field :statut, value: @statut
+ - if can_include_archived?
+ .fr-pb-2w
+ = render Dsfr::ToggleComponent.new(form: f,
+ target: :include_archived,
+ html_title: include_archived_title)
+
.fr-select-group
- if export_templates.present?
%label.fr-label{ for: 'select' }
diff --git a/app/components/dsfr/toggle_component.rb b/app/components/dsfr/toggle_component.rb
index 43084c6f0b9..adb13898fc8 100644
--- a/app/components/dsfr/toggle_component.rb
+++ b/app/components/dsfr/toggle_component.rb
@@ -3,16 +3,18 @@
class Dsfr::ToggleComponent < ApplicationComponent
attr_reader :target
attr_reader :title
+ attr_reader :html_title
attr_reader :hint
attr_reader :toggle_labels
attr_reader :disabled
attr_reader :data
attr_reader :extra_class_names
- def initialize(form:, target:, title:, disabled: nil, hint: nil, toggle_labels: { checked: 'Activé', unchecked: 'Désactivé' }, opt: nil, extra_class_names: nil)
+ def initialize(form:, target:, title: nil, html_title: nil, disabled: nil, hint: nil, toggle_labels: { checked: 'Activé', unchecked: 'Désactivé' }, opt: nil, extra_class_names: nil)
@form = form
@target = target
@title = title
+ @html_title = html_title
@hint = hint
@disabled = disabled
@toggle_labels = toggle_labels
@@ -22,7 +24,19 @@ def initialize(form:, target:, title:, disabled: nil, hint: nil, toggle_labels:
private
+ def label_for
+ return input_id if @form.object.present?
+
+ return "#{@form.options[:namespace]}_#{target}" if @form.options[:namespace].present?
+
+ target.to_s
+ end
+
def input_id
- dom_id(@form.object, target)
+ if @form.object.present?
+ dom_id(@form.object, target)
+ else
+ target.to_s
+ end
end
end
diff --git a/app/components/dsfr/toggle_component/toggle_component.html.haml b/app/components/dsfr/toggle_component/toggle_component.html.haml
index 90fa244330b..e4582803832 100644
--- a/app/components/dsfr/toggle_component/toggle_component.html.haml
+++ b/app/components/dsfr/toggle_component/toggle_component.html.haml
@@ -1,8 +1,8 @@
%div{ class: "fr-toggle fr-toggle--label-left #{extra_class_names}" }
= @form.check_box target, class: 'fr-toggle__input', disabled:, data:, id: input_id
= @form.label target,
- title,
- for: input_id,
+ title || html_title&.html_safe,
+ for: label_for,
data: { 'fr-checked-label': toggle_labels[:checked], 'fr-unchecked-label': toggle_labels[:unchecked] },
class: 'fr-toggle__label'
diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb
index 06297a87386..1ab08f32016 100644
--- a/app/controllers/instructeurs/procedures_controller.rb
+++ b/app/controllers/instructeurs/procedures_controller.rb
@@ -106,6 +106,12 @@ def show
page = params[:page].presence || 1
@dossiers_count = @filtered_sorted_ids.size
+ @archived_dossiers_count = if statut == 'tous'
+ @counts[:archives]
+ else
+ 0
+ end
+
@filtered_sorted_paginated_ids = Kaminari
.paginate_array(@filtered_sorted_ids)
.page(page)
@@ -317,6 +323,7 @@ def export_options
time_span_type: params[:time_span_type],
statut: params[:statut],
export_template:,
+ include_archived: params[:include_archived],
procedure_presentation: params[:statut].present? ? procedure_presentation : nil
}.compact
end
diff --git a/app/models/dossier.rb b/app/models/dossier.rb
index cad644dfdc7..8631a72b897 100644
--- a/app/models/dossier.rb
+++ b/app/models/dossier.rb
@@ -244,7 +244,7 @@ def classer_sans_suite(motivation: nil, instructeur: nil, processed_at: Time.zon
scope :with_type_de_champ, -> (stable_id) { joins(:champs).where(champs: { stream: 'main', stable_id: }) }
- scope :all_state, -> { not_archived.state_not_brouillon }
+ scope :all_state, -> (include_archived: false) { include_archived ? state_not_brouillon : not_archived.state_not_brouillon }
scope :en_construction, -> { not_archived.state_en_construction }
scope :en_instruction, -> { not_archived.state_en_instruction }
scope :termine, -> { not_archived.state_termine }
@@ -385,7 +385,7 @@ def classer_sans_suite(motivation: nil, instructeur: nil, processed_at: Time.zon
.distinct
end
- scope :by_statut, -> (statut, instructeur = nil) do
+ scope :by_statut, -> (statut, instructeur: nil, include_archived: false) do
case statut
when 'a-suivre'
visible_by_administration
@@ -399,7 +399,7 @@ def classer_sans_suite(motivation: nil, instructeur: nil, processed_at: Time.zon
when 'traites'
visible_by_administration.termine
when 'tous'
- visible_by_administration.all_state
+ visible_by_administration.all_state(include_archived:)
when 'supprimes'
hidden_by_administration.state_termine.or(hidden_by_expired)
when 'archives'
diff --git a/app/models/export.rb b/app/models/export.rb
index 7cce92ca9eb..bcc2c671f54 100644
--- a/app/models/export.rb
+++ b/app/models/export.rb
@@ -69,7 +69,7 @@ def since
time_span_type == Export.time_span_types.fetch(:monthly) ? 30.days.ago : nil
end
- def self.find_or_create_fresh_export(format, groupe_instructeurs, user_profile, time_span_type: time_span_types.fetch(:everything), statut: statuts.fetch(:tous), procedure_presentation: nil, export_template: nil)
+ def self.find_or_create_fresh_export(format, groupe_instructeurs, user_profile, time_span_type: time_span_types.fetch(:everything), statut: statuts.fetch(:tous), procedure_presentation: nil, export_template: nil, include_archived: false)
filtered_columns = Array.wrap(procedure_presentation&.filters_for(statut))
sorted_column = procedure_presentation&.sorted_column
@@ -78,6 +78,7 @@ def self.find_or_create_fresh_export(format, groupe_instructeurs, user_profile,
export_template:,
time_span_type:,
statut:,
+ include_archived:,
key: generate_cache_key(groupe_instructeurs.map(&:id), filtered_columns, sorted_column)
}
@@ -136,7 +137,7 @@ def dossiers_for_export
dossiers.visible_by_administration.where('dossiers.depose_at > ?', since)
elsif filtered_columns.present? || sorted_column.present?
instructeur = instructeur_from(user_profile)
- filtered_sorted_ids = DossierFilterService.filtered_sorted_ids(dossiers, statut, filtered_columns, sorted_column, instructeur)
+ filtered_sorted_ids = DossierFilterService.filtered_sorted_ids(dossiers, statut, filtered_columns, sorted_column, instructeur, include_archived: include_archived)
dossiers.where(id: filtered_sorted_ids)
else
diff --git a/app/services/dossier_filter_service.rb b/app/services/dossier_filter_service.rb
index b8bdb3d8152..5f8679611fd 100644
--- a/app/services/dossier_filter_service.rb
+++ b/app/services/dossier_filter_service.rb
@@ -3,8 +3,8 @@
class DossierFilterService
TYPE_DE_CHAMP = 'type_de_champ'
- def self.filtered_sorted_ids(dossiers, statut, filters, sorted_column, instructeur, count: nil)
- dossiers_by_statut = dossiers.by_statut(statut, instructeur)
+ def self.filtered_sorted_ids(dossiers, statut, filters, sorted_column, instructeur, count: nil, include_archived: false)
+ dossiers_by_statut = dossiers.by_statut(statut, instructeur:, include_archived:)
dossiers_sorted_ids = self.sorted_ids(dossiers_by_statut, sorted_column, instructeur, count || dossiers_by_statut.size)
if filters.present?
diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml
index 003f8a3cbe0..5e836a06853 100644
--- a/app/views/instructeurs/procedures/show.html.haml
+++ b/app/views/instructeurs/procedures/show.html.haml
@@ -63,7 +63,7 @@
.fr-ml-auto
- if @dossiers_count > 0
%ul.fr-btns-group.fr-btns-group--right.fr-btns-group--sm.fr-btns-group--inline-md.fr-btns-group--icon-left
- = render Dossiers::ExportDropdownComponent.new(wrapper: :li, procedure: @procedure, export_templates: current_instructeur.export_templates_for(@procedure), statut: @statut, count: @dossiers_count,
+ = render Dossiers::ExportDropdownComponent.new(wrapper: :li, procedure: @procedure, export_templates: current_instructeur.export_templates_for(@procedure), statut: @statut, count: @dossiers_count, archived_count: @archived_dossiers_count,
class_btn: 'fr-btn--secondary fr-icon-download-line', export_url: method(:download_export_instructeur_procedure_path))
= render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-btn--tertiary', 'fr-icon-settings-5-line'] }, menu_options: { id: 'custom-menu' }) do |menu|
diff --git a/db/migrate/20241203154714_add_include_archived_dossiers_in_export.rb b/db/migrate/20241203154714_add_include_archived_dossiers_in_export.rb
new file mode 100644
index 00000000000..b5c8395a5a5
--- /dev/null
+++ b/db/migrate/20241203154714_add_include_archived_dossiers_in_export.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddIncludeArchivedDossiersInExport < ActiveRecord::Migration[7.0]
+ def change
+ add_column :exports, :include_archived, :boolean, default: false, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8a6de1a8998..9f0c33c87c9 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[7.0].define(version: 2024_11_26_145420) do
+ActiveRecord::Schema[7.0].define(version: 2024_12_03_154714) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_buffercache"
enable_extension "pg_stat_statements"
@@ -643,6 +643,7 @@
t.bigint "export_template_id"
t.jsonb "filtered_columns", default: [], null: false, array: true
t.string "format", null: false
+ t.boolean "include_archived", default: false, null: false
t.bigint "instructeur_id"
t.string "job_status", default: "pending", null: false
t.text "key", null: false
diff --git a/spec/components/dossiers/export_dropdown_component_spec.rb b/spec/components/dossiers/export_dropdown_component_spec.rb
new file mode 100644
index 00000000000..5192a4aaab9
--- /dev/null
+++ b/spec/components/dossiers/export_dropdown_component_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Dossiers::ExportDropdownComponent, type: :component do
+ subject(:component) { described_class.new(**params) }
+
+ describe '#include_archived_title' do
+ let(:procedure) { double('Procedure') }
+
+ context 'when archived_count is greater than 1' do
+ it 'returns the pluralized archived title' do
+ component = Dossiers::ExportDropdownComponent.new(
+ procedure: procedure,
+ archived_count: 3
+ )
+ expect(component.include_archived_title).to eq("Inclure les 3 dossiers « archivés »")
+ end
+ end
+
+ context 'when archived_count is 1 or less' do
+ it 'returns the singular archived title' do
+ component = Dossiers::ExportDropdownComponent.new(
+ procedure: procedure,
+ archived_count: 1
+ )
+ expect(component.include_archived_title).to eq("Inclure le dossier « archivé »")
+ end
+ end
+ end
+end
diff --git a/spec/services/dossier_filter_service_spec.rb b/spec/services/dossier_filter_service_spec.rb
index 28d16a283fe..9ae26622ce6 100644
--- a/spec/services/dossier_filter_service_spec.rb
+++ b/spec/services/dossier_filter_service_spec.rb
@@ -9,20 +9,30 @@ def to_filter((label, filter)) = FilteredColumn.new(column: procedure.find_colum
let(:dossiers) { procedure.dossiers }
let(:statut) { 'suivis' }
let(:filters) { [] }
+ let(:include_archived) { false }
let(:sorted_columns) { procedure.default_sorted_column }
- subject { described_class.filtered_sorted_ids(dossiers, statut, filters, sorted_columns, instructeur) }
+ subject { described_class.filtered_sorted_ids(dossiers, statut, filters, sorted_columns, instructeur, include_archived:) }
context 'with no filters' do
let(:en_construction_dossier) { create(:dossier, :en_construction, procedure:) }
let(:accepte_dossier) { create(:dossier, :accepte, procedure:) }
+ let(:archived_dossier) { create(:dossier, :accepte, :archived, procedure:) }
before do
create(:follow, dossier: en_construction_dossier, instructeur:)
create(:follow, dossier: accepte_dossier, instructeur:)
+ create(:follow, dossier: archived_dossier, instructeur:)
end
it { is_expected.to contain_exactly(en_construction_dossier.id) }
+
+ context 'when include_archived is true' do
+ let(:include_archived) { true }
+ let(:statut) { 'tous' }
+
+ it { is_expected.to contain_exactly(en_construction_dossier.id, accepte_dossier.id, archived_dossier.id) }
+ end
end
context 'with mocked sorted_ids' do