diff --git a/app/models/column.rb b/app/models/column.rb index 22fa01a6f..5131c77c6 100644 --- a/app/models/column.rb +++ b/app/models/column.rb @@ -47,6 +47,9 @@ class Column procedure.find_column(h_id: h_id) end + def dossier_column? = false + def champ_column? = false + private def column_id = "#{table}/#{column}" diff --git a/app/models/columns/champ_column.rb b/app/models/columns/champ_column.rb index c032b3a35..3cdb05a92 100644 --- a/app/models/columns/champ_column.rb +++ b/app/models/columns/champ_column.rb @@ -45,6 +45,8 @@ class Columns::ChampColumn < Column end end + def champ_column? = true + private def column_id = "type_de_champ/#{stable_id}" diff --git a/app/models/columns/dossier_column.rb b/app/models/columns/dossier_column.rb index 72ec97405..82730b2a9 100644 --- a/app/models/columns/dossier_column.rb +++ b/app/models/columns/dossier_column.rb @@ -15,4 +15,6 @@ class Columns::DossierColumn < Column dossier.followers_instructeurs.map(&:email).join(' ') end end + + def dossier_column? = true end diff --git a/app/models/export_template.rb b/app/models/export_template.rb index 0e6e503e4..0328e16b4 100644 --- a/app/models/export_template.rb +++ b/app/models/export_template.rb @@ -9,7 +9,7 @@ class ExportTemplate < ApplicationRecord has_one :procedure, through: :groupe_instructeur has_many :exports, dependent: :nullify - enum kind: { zip: "zip" }, _prefix: :template + enum kind: { zip: 'zip', csv: 'csv', xlsx: 'xlsx', ods: 'ods' }, _prefix: :template attribute :dossier_folder, :export_item attribute :export_pdf, :export_item @@ -30,6 +30,7 @@ class ExportTemplate < ApplicationRecord end def self.default(name: nil, kind: 'zip', groupe_instructeur:) + # TODO: remove default values for tabular export dossier_folder = ExportItem.default(prefix: 'dossier') export_pdf = ExportItem.default(prefix: 'export') pjs = groupe_instructeur.procedure.exportables_pieces_jointes.map { |tdc| ExportItem.default_pj(tdc) } @@ -37,6 +38,10 @@ class ExportTemplate < ApplicationRecord new(name:, kind:, groupe_instructeur:, dossier_folder:, export_pdf:, pjs:) end + def tabular? + kind != 'zip' + end + def tags tags_categorized.slice(:individual, :etablissement, :dossier).values.flatten end @@ -60,6 +65,19 @@ class ExportTemplate < ApplicationRecord File.join(dossier_folder.path(dossier), file_path) if file_path.present? end + def dossier_exported_columns = exported_columns.filter { _1.column.dossier_column? } + + def columns_for_stable_id(stable_id) + exported_columns + .filter { _1.column.champ_column? } + .filter { _1.column.stable_id == stable_id } + end + + def in_export?(exported_column) + @template_exported_columns ||= exported_columns.map(&:column) + @template_exported_columns.include?(exported_column.column) + end + private def ensure_pjs_are_legit diff --git a/app/types/export_item_type.rb b/app/types/export_item_type.rb index e2d1f7014..04c37c6ef 100644 --- a/app/types/export_item_type.rb +++ b/app/types/export_item_type.rb @@ -29,7 +29,10 @@ class ExportItemType < ActiveRecord::Type::Value # ruby -> db def serialize(value) - if value.is_a?(ExportItem) + case value + in NilClass + nil + in ExportItem JSON.generate({ template: value.template, enabled: value.enabled, diff --git a/app/views/instructeurs/procedures/exports.html.haml b/app/views/instructeurs/procedures/exports.html.haml index e63eb4472..3e92cd894 100644 --- a/app/views/instructeurs/procedures/exports.html.haml +++ b/app/views/instructeurs/procedures/exports.html.haml @@ -6,41 +6,59 @@ [t('.title')]] } .fr-container - %h1= t('.title') - = render Dsfr::CalloutComponent.new(title: nil) do |c| - - c.with_body do - %p= t('.export_description', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i) + .fr-tabs.mb-3 + %ul.fr-tabs__list{ role: 'tablist' } + %li{ role: 'presentation' } + %button.fr-tabs__tab.fr-tabs__tab--icon-left{ id: "tabpanel-exports", tabindex: "0", role: "tab", "aria-selected": "true", "aria-controls": "tabpanel-exports-panel" } Liste des exports + %li{ role: 'presentation' } + %button.fr-tabs__tab.fr-tabs__tab--icon-left{ id: "tabpanel-export-templates", tabindex: "-1", role: "tab", "aria-selected": "false", "aria-controls": "tabpanel-export-templates-panel" } Modèles d'export - - if @exports.present? - %div{ data: @exports.any?(&:pending?) ? { controller: "turbo-poll", turbo_poll_url_value: "", turbo_poll_interval_value: 10_000, turbo_poll_max_checks_value: 6 } : {} } - = render Dossiers::ExportLinkComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, class_btn: 'fr-btn--tertiary', export_url: method(:download_export_instructeur_procedure_path)) - - - if @exports.any?{_1.format == Export.formats.fetch(:zip)} - = render Dsfr::AlertComponent.new(title: t('.title_zip'), state: :info, extra_class_names: 'fr-mb-3w') do |c| + .fr-tabs__panel.fr-tabs__panel--selected{ id: "tabpanel-exports-panel", role: "tabpanel", "aria-labelledby": "tabpanel-exports", tabindex: "0" } + = render Dsfr::CalloutComponent.new(title: nil) do |c| - c.with_body do - %p= t('.export_description_zip_html') + %p= t('.export_description', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i) - - else - = t('.no_export_html', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i ) + - if @exports.present? + %div{ data: @exports.any?(&:pending?) ? { controller: "turbo-poll", turbo_poll_url_value: "", turbo_poll_interval_value: 10_000, turbo_poll_max_checks_value: 6 } : {} } + = render Dossiers::ExportLinkComponent.new(procedure: @procedure, exports: @exports, statut: @statut, count: @dossiers_count, class_btn: 'fr-btn--tertiary', export_url: method(:download_export_instructeur_procedure_path)) - - if @procedure.feature_enabled?(:export_template) - %h2.fr-mb-1w.fr-mt-8w - Liste des modèles d'export - %p.fr-hint-text - Un modèle d'export permet de personnaliser le nom des fichiers (pour un export au format Zip) - - if @export_templates.any? - .fr-table.fr-table--no-caption.fr-mt-5w - %table - %thead - %tr - %th{ scope: 'col' } Nom du modèle - %th{ scope: 'col' }= "Groupe instructeur" if @procedure.groupe_instructeurs.many? - %tbody - - @export_templates.each do |export_template| - %tr - %td= link_to export_template.name, [:edit, :instructeur, @procedure, export_template] - %td= export_template.groupe_instructeur.label if @procedure.groupe_instructeurs.many? + - if @exports.any?{_1.format == Export.formats.fetch(:zip)} + = render Dsfr::AlertComponent.new(title: t('.title_zip'), state: :info, extra_class_names: 'fr-mb-3w') do |c| + - c.with_body do + %p= t('.export_description_zip_html') - %p - = link_to [:new, :instructeur, @procedure, :export_template], class: 'fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line' do - Ajouter un modèle d'export + - else + = t('.no_export_html', expiration_time: Export::MAX_DUREE_CONSERVATION_EXPORT.in_hours.to_i ) + + .fr-tabs__panel.fr-tabs__panel{ id: "tabpanel-export-templates-panel", role: "tabpanel", "aria-labelledby": "tabpanel-export-templates", tabindex: "0" } + = render Dsfr::AlertComponent.new(state: :info) do |c| + - c.with_body do + %p= t('.export_template_list_description_html') + + + .fr-mt-5w + = link_to t('.new_zip_export_template'), new_instructeur_procedure_export_template_path(@procedure), class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line fr-mr-1w" + = link_to t('.new_tabular_export_template'), new_instructeur_procedure_export_template_path(@procedure, kind: 'tabular'), class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-add-line" + + .fr-table.fr-table--bordered.fr-table--no-caption.fr-mt-5w + .fr-table__wrapper + .fr-table__container + .fr-table__content + %table + %thead + %tr + = tag.th "Nom du modèle", scope: 'col' + = tag.th "Format", scope: 'col' + = tag.th "Date de création", scope: 'col' + = tag.th "Partagé avec (groupe instructeurs)", scope: 'col' if @procedure.groupe_instructeurs.many? + = tag.th "Actions", scope: 'col' + %tbody + - @export_templates.each do |export_template| + %tr + %td= link_to export_template.name, [:edit, :instructeur, @procedure, export_template] + %td= pretty_kind(export_template.kind) + %td= l(export_template.created_at) + = tag.td export_template.groupe_instructeur.label if @procedure.groupe_instructeurs.many? + %td + = link_to "Modifier", [:edit, :instructeur, @procedure, export_template], class: "fr-btn fr-btn--icon-left fr-icon-edit-line fr-mr-1w" + = link_to "Supprimer", [:instructeur, @procedure, export_template], method: :delete, data: { confirm: "Voulez-vous vraiment supprimer ce modèle ? Il sera supprimé pour tous les instructeurs du groupe"}, class: "fr-btn fr-btn--secondary fr-btn--icon-left fr-icon-delete-line" diff --git a/config/locales/views/instructeurs/header/en.yml b/config/locales/views/instructeurs/header/en.yml index dbec9594d..ad343adb4 100644 --- a/config/locales/views/instructeurs/header/en.yml +++ b/config/locales/views/instructeurs/header/en.yml @@ -12,7 +12,7 @@ en: button_delay_expiration: "Keep for one more month" notification_management: notification management administrators_list: administrators list - exports_list: exports list + exports_list: exports and export templates exports_notification_label: A new export is ready to download statistics: statistics instructeurs: instructors diff --git a/config/locales/views/instructeurs/header/fr.yml b/config/locales/views/instructeurs/header/fr.yml index c13a4ded0..46b2d77c1 100644 --- a/config/locales/views/instructeurs/header/fr.yml +++ b/config/locales/views/instructeurs/header/fr.yml @@ -13,7 +13,7 @@ fr: button_delay_expiration: "Conserver un mois de plus" notification_management: Gestion des notifications administrators_list: Voir les administrateurs - exports_list: Voir les exports + exports_list: Voir les exports et modèles d'export exports_notification_label: Un nouvel export est prêt à être téléchargé statistics: Statistiques instructeurs: instructeurs diff --git a/config/locales/views/instructeurs/procedures/exports/en.yml b/config/locales/views/instructeurs/procedures/exports/en.yml index 81b0dcf8e..f1f7bc753 100644 --- a/config/locales/views/instructeurs/procedures/exports/en.yml +++ b/config/locales/views/instructeurs/procedures/exports/en.yml @@ -18,3 +18,9 @@ en: no_export_html: You have no export at the moment.
Can't find an export? It may have expired, exports are deleted after %{expiration_time} hours. + + export_template_list_description_html: | + Each instructor can configure an export template to customize exports (attachments name for a zip export, columns selection for a tabular export). It will be made available to all instructors assigned to the procedure.
+ Find out more about export template configuration + new_zip_export_template: Create zip export template + new_tabular_export_template: Create tabular export template diff --git a/config/locales/views/instructeurs/procedures/exports/fr.yml b/config/locales/views/instructeurs/procedures/exports/fr.yml index 030fa8345..fa0945652 100644 --- a/config/locales/views/instructeurs/procedures/exports/fr.yml +++ b/config/locales/views/instructeurs/procedures/exports/fr.yml @@ -17,3 +17,8 @@ fr: Vous n'arrivez pas à extraire un export au format .zip sur un réseau d'entreprise ? Essayer de renommer l'archive avec un nom plus court et ré-essayer de l'extraire. no_export_html: Vous n'avez pas d'export pour le moment.
Vous ne trouvez pas un export ? Il a peut-être expiré, les exports sont supprimés au bout de %{expiration_time} heures. + export_template_list_description_html: | + Chaque instructeur a la possibilité de configurer un modèle d'export pour personnaliser les exports (nom des pièces jointes pour un export au format zip, sélection des colonnes pour un export tabulaire). Il sera mis à disposition de l'ensemble des instructeurs affectés à la démarche
+ En savoir plus sur la configuration des modèles d'export + new_zip_export_template: Créer un modèle d'export zip + new_tabular_export_template: Créer un modèle d'export tabulaire diff --git a/spec/system/instructeurs/instruction_spec.rb b/spec/system/instructeurs/instruction_spec.rb index 098878767..2473c352e 100644 --- a/spec/system/instructeurs/instruction_spec.rb +++ b/spec/system/instructeurs/instruction_spec.rb @@ -138,7 +138,7 @@ describe 'Instructing a dossier:', js: true do expect(page).to have_text('Nous générons cet export.') - click_on "Voir les exports" + click_on "Voir les exports et modèles d'export" expect(page).to have_text("Export .csv d’un dossier « à suivre » demandé il y a moins d'une minute") expect(page).to have_text("En préparation")