From b3cb3e974ed1ffecca7ff661298e6bba8f04209b Mon Sep 17 00:00:00 2001 From: mfo Date: Fri, 19 Jul 2024 11:16:40 +0200 Subject: [PATCH 01/25] refactor(Facet): extract Facet from ProcedurePresentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: LeSim clean(code): no comment 🎶 --- app/models/facet.rb | 25 +++ app/models/procedure_presentation.rb | 71 +++++---- .../procedures/_header_field.html.haml | 11 +- .../instructeurs/procedures/show.html.haml | 2 +- spec/models/procedure_presentation_spec.rb | 148 ++++++++++-------- 5 files changed, 155 insertions(+), 102 deletions(-) create mode 100644 app/models/facet.rb diff --git a/app/models/facet.rb b/app/models/facet.rb new file mode 100644 index 000000000..ca50225bc --- /dev/null +++ b/app/models/facet.rb @@ -0,0 +1,25 @@ +class Facet + def initialize(table:, column:, label: nil, virtual: false, type: :text, value_column: :value, filterable: true, classname: nil, scope: '') + @table = table + @column = column + @label = label || I18n.t(column, scope: [:activerecord, :attributes, :procedure_presentation, :fields, table]) + @classname = classname + @virtual = virtual + @type = type + @scope = scope + @value_column = value_column + @filterable = filterable + end + + attr_reader :table, :column, :label, :classname, :virtual, :type, :scope, :value_column, :filterable + + def ==(other) + other.to_json == to_json + end + + def to_json + { + table:, column:, label:, classname:, virtual:, type:, scope:, value_column:, filterable: + } + end +end diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index b5e981a45..553c2dd4b 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -94,24 +94,24 @@ class ProcedurePresentation < ApplicationRecord def displayable_fields_for_select [ - fields.reject { |field| field['virtual'] } - .map { |field| [field['label'], field_id(field)] }, - displayed_fields.map { |field| field_id(field) } + fields.reject(&:virtual) + .map { |field| [field.label, field_id(field)] }, + displayed_fields.map { field_id(Facet.new(**_1.deep_symbolize_keys)) } ] end def filterable_fields_options fields.filter_map do |field| - next if field['filterable'] == false + next if field.filterable == false - [field['label'], field_id(field)] + [field.label, field_id(field)] end end def displayed_fields_for_headers [ field_hash('self', 'id', classname: 'number-col'), - *displayed_fields, + *displayed_fields.map { Facet.new(**_1.deep_symbolize_keys) }, field_hash('self', 'state', classname: 'state-col'), *sva_svr_fields ] @@ -201,15 +201,15 @@ class ProcedurePresentation < ApplicationRecord value_column = filters.pluck('value_column').compact.first || :value case table when 'self' - field = self_fields.find { |h| h['column'] == column } - if field['type'] == :date + field = self_fields.find { |h| h.column == column } + if field.type == :date dates = values .filter_map { |v| Time.zone.parse(v).beginning_of_day rescue nil } dossiers.filter_by_datetimes(column, dates) - elsif field['column'] == "state" && values.include?("pending_correction") + elsif field.column == "state" && values.include?("pending_correction") dossiers.joins(:corrections).where(corrections: DossierCorrection.pending) - elsif field['column'] == "state" && values.include?("en_construction") + elsif field.column == "state" && values.include?("en_construction") dossiers.where("dossiers.#{column} IN (?)", values).includes(:corrections).where.not(corrections: DossierCorrection.pending) else dossiers.where("dossiers.#{column} IN (?)", values) @@ -284,7 +284,7 @@ class ProcedurePresentation < ApplicationRecord else field = find_field(filter[TABLE], filter[COLUMN]) - if field["type"] == :date + if field.type == :date parsed_date = safe_parse_date(filter['value']) return parsed_date.present? ? I18n.l(parsed_date) : nil @@ -303,7 +303,8 @@ class ProcedurePresentation < ApplicationRecord def add_filter(statut, field, value) if value.present? table, column = field.split(SLASH) - label, value_column = find_field(table, column).values_at('label', 'value_column') + label = find_field(table, column).label + value_column = find_field(table, column).value_column case table when TYPE_DE_CHAMP, TYPE_DE_CHAMP_PRIVATE @@ -371,26 +372,27 @@ class ProcedurePresentation < ApplicationRecord end def field_type(field_id) - find_field(*field_id.split(SLASH))['type'] + find_field(*field_id.split(SLASH)).type end def field_enum(field_id) field = find_field(*field_id.split(SLASH)) - if field['scope'].present? - I18n.t(field['scope']).map(&:to_a).map(&:reverse) - elsif field['table'] == 'groupe_instructeur' + if field.scope.present? + I18n.t(field.scope).map(&:to_a).map(&:reverse) + elsif field.table == 'groupe_instructeur' instructeur.groupe_instructeurs.filter_map do if _1.procedure_id == procedure.id [_1.label, _1.id] end end else - find_type_de_champ(field['column']).options_for_select + find_type_de_champ(field.column).options_for_select end end def sortable?(field) - sort['table'] == field['table'] && sort['column'] == field['column'] + sort['table'] == field.table && + sort['column'] == field.column end def aria_sort(order, field) @@ -407,12 +409,23 @@ class ProcedurePresentation < ApplicationRecord private - def field_id(field) - field.values_at(TABLE, COLUMN).join(SLASH) + # type_de_champ/4373429 + def field_id(field_or_sort) + if field_or_sort.is_a?(Hash) + sort = field_or_sort + [sort[TABLE], sort[COLUMN]].join(SLASH) + else + field = field_or_sort + if field.label == 'rna – commune' + "#{[field.table, field.column].join(SLASH)}->#{field.value_column}" + else + [field.table, field.column].join(SLASH) + end + end end def find_field(table, column) - fields.find { |field| field.values_at(TABLE, COLUMN) == [table, column] } + fields.find { [_1.table, _1.column] == [table, column] } end def find_type_de_champ(column) @@ -466,17 +479,7 @@ class ProcedurePresentation < ApplicationRecord end def field_hash(table, column, label: nil, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) - { - 'label' => label || I18n.t(column, scope: [:activerecord, :attributes, :procedure_presentation, :fields, table]), - TABLE => table, - COLUMN => column, - 'classname' => classname, - 'virtual' => virtual, - 'type' => type, - 'scope' => scope, - 'value_column' => value_column, - 'filterable' => filterable - } + Facet.new(table:, column:, label:, classname:, virtual:, type:, scope:, value_column:, filterable:) end def field_hash_for_type_de_champ_public(type_champ, libelle, stable_id) @@ -500,8 +503,8 @@ class ProcedurePresentation < ApplicationRecord def valid_columns_for_table(table) @column_whitelist ||= fields - .group_by { |field| field[TABLE] } - .transform_values { |fields| Set.new(fields.pluck(COLUMN)) } + .group_by(&:table) + .transform_values { |facets| Set.new(facets.map(&:column)) } @column_whitelist[table] || [] end diff --git a/app/views/instructeurs/procedures/_header_field.html.haml b/app/views/instructeurs/procedures/_header_field.html.haml index e4a54d673..e66d2f683 100644 --- a/app/views/instructeurs/procedures/_header_field.html.haml +++ b/app/views/instructeurs/procedures/_header_field.html.haml @@ -1,10 +1,9 @@ -%th{ @procedure_presentation.aria_sort(@procedure_presentation.sort['order'], field), scope: "col", class: classname } - - = link_to update_sort_instructeur_procedure_path(@procedure, table: field['table'], column: field['column'], order: @procedure_presentation.opposite_order_for(field['table'], field['column'])) do +%th{ @procedure_presentation.aria_sort(@procedure_presentation.sort['order'], field), scope: "col", class: field.classname } + = link_to update_sort_instructeur_procedure_path(@procedure, table: field.table, column: field.column, order: @procedure_presentation.opposite_order_for(field.table, field.column)) do - if @procedure_presentation.sortable?(field) - if @procedure_presentation.sort['order'] == 'asc' - #{field['label']} ↑ + #{field.label} ↑ - else - #{field['label']} ↓ + #{field.label} ↓ - else - #{field['label']} + #{field.label} diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index d73df00cd..a9fa7783f 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -99,7 +99,7 @@ %input{ type: "checkbox", disabled: @disable_checkbox_all, checked: @disable_checkbox_all, data: { action: "batch-operation#onCheckAll" }, id: dom_id(BatchOperation.new, :checkbox_all), aria: { label: t('views.instructeurs.dossiers.select_all') } } - @procedure_presentation.displayed_fields_for_headers.each do |field| - = render partial: "header_field", locals: { field: field, classname: field['classname'] } + = render partial: "header_field", locals: { field: field } %th.follow-col Actions diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 498d6b817..27cc13b27 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -10,10 +10,10 @@ describe ProcedurePresentation do create(:procedure_presentation, assign_to: assign_to, displayed_fields: [ - { "label" => "test1", "table" => "user", "column" => "email" }, - { "label" => "test2", "table" => "type_de_champ", "column" => first_type_de_champ_id } + { label: "test1", table: "user", column: "email" }, + { label: "test2", table: "type_de_champ", column: first_type_de_champ_id } ], - sort: { "table" => "user", "column" => "email", "order" => "asc" }, + sort: { table: "user", column: "email", "order" => "asc" }, filters: filters) } let(:procedure_presentation_id) { procedure_presentation.id } @@ -35,19 +35,19 @@ describe ProcedurePresentation do it { expect(build(:procedure_presentation)).to be_valid } context 'of displayed fields' do - it { expect(build(:procedure_presentation, displayed_fields: [{ "table" => "user", "column" => "reset_password_token", "order" => "asc" }])).to be_invalid } + it { expect(build(:procedure_presentation, displayed_fields: [{ table: "user", column: "reset_password_token", "order" => "asc" }])).to be_invalid } end context 'of sort' do - it { expect(build(:procedure_presentation, sort: { "table" => "notifications", "column" => "notifications", "order" => "asc" })).to be_valid } - it { expect(build(:procedure_presentation, sort: { "table" => "self", "column" => "id", "order" => "asc" })).to be_valid } - it { expect(build(:procedure_presentation, sort: { "table" => "self", "column" => "state", "order" => "asc" })).to be_valid } - it { expect(build(:procedure_presentation, sort: { "table" => "user", "column" => "reset_password_token", "order" => "asc" })).to be_invalid } + it { expect(build(:procedure_presentation, sort: { table: "notifications", column: "notifications", "order" => "asc" })).to be_valid } + it { expect(build(:procedure_presentation, sort: { table: "self", column: "id", "order" => "asc" })).to be_valid } + it { expect(build(:procedure_presentation, sort: { table: "self", column: "state", "order" => "asc" })).to be_valid } + it { expect(build(:procedure_presentation, sort: { table: "user", column: "reset_password_token", "order" => "asc" })).to be_invalid } end context 'of filters' do - it { expect(build(:procedure_presentation, filters: { "suivis" => [{ "table" => "user", "column" => "reset_password_token", "order" => "asc" }] })).to be_invalid } - it { expect(build(:procedure_presentation, filters: { "suivis" => [{ "table" => "user", "column" => "email", "value" => "exceedingly long filter value" * 10 }] })).to be_invalid } + it { expect(build(:procedure_presentation, filters: { "suivis" => [{ table: "user", column: "reset_password_token", "order" => "asc" }] })).to be_invalid } + it { expect(build(:procedure_presentation, filters: { "suivis" => [{ table: "user", column: "email", "value" => "exceedingly long filter value" * 10 }] })).to be_invalid } end end @@ -64,36 +64,36 @@ describe ProcedurePresentation do let(:tdc_private_2) { procedure.active_revision.types_de_champ_private[1] } let(:expected) { [ - { "label" => 'Créé le', "table" => 'self', "column" => 'created_at', 'classname' => '', 'virtual' => false, 'type' => :date, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Mis à jour le', "table" => 'self', "column" => 'updated_at', 'classname' => '', 'virtual' => false, 'type' => :date, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Déposé le', "table" => 'self', "column" => 'depose_at', 'classname' => '', 'virtual' => false, 'type' => :date, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'En construction le', "table" => 'self', "column" => 'en_construction_at', 'classname' => '', 'virtual' => false, 'type' => :date, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'En instruction le', "table" => 'self', "column" => 'en_instruction_at', 'classname' => '', 'virtual' => false, 'type' => :date, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Terminé le', "table" => 'self', "column" => 'processed_at', 'classname' => '', 'virtual' => false, 'type' => :date, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => "Mis à jour depuis", "table" => "self", "column" => "updated_since", "classname" => "", 'virtual' => true, 'type' => :date, 'scope' => '', "value_column" => :value, 'filterable' => true }, - { "label" => "Déposé depuis", "table" => "self", "column" => "depose_since", "classname" => "", 'virtual' => true, 'type' => :date, 'scope' => '', "value_column" => :value, 'filterable' => true }, - { "label" => "En construction depuis", "table" => "self", "column" => "en_construction_since", "classname" => "", 'virtual' => true, 'type' => :date, 'scope' => '', "value_column" => :value, 'filterable' => true }, - { "label" => "En instruction depuis", "table" => "self", "column" => "en_instruction_since", "classname" => "", 'virtual' => true, 'type' => :date, 'scope' => '', "value_column" => :value, 'filterable' => true }, - { "label" => "Terminé depuis", "table" => "self", "column" => "processed_since", "classname" => "", 'virtual' => true, 'type' => :date, 'scope' => '', "value_column" => :value, 'filterable' => true }, - { "label" => "Statut", "table" => "self", "column" => "state", "classname" => "", 'virtual' => true, 'scope' => 'instructeurs.dossiers.filterable_state', 'type' => :enum, "value_column" => :value, 'filterable' => true }, - { "label" => 'Demandeur', "table" => 'user', "column" => 'email', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Email instructeur', "table" => 'followers_instructeurs', "column" => 'email', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Groupe instructeur', "table" => 'groupe_instructeur', "column" => 'id', 'classname' => '', 'virtual' => false, 'type' => :enum, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Avis oui/non', "table" => 'avis', "column" => 'question_answer', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => false }, - { "label" => 'SIREN', "table" => 'etablissement', "column" => 'entreprise_siren', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Forme juridique', "table" => 'etablissement', "column" => 'entreprise_forme_juridique', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Nom commercial', "table" => 'etablissement', "column" => 'entreprise_nom_commercial', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Raison sociale', "table" => 'etablissement', "column" => 'entreprise_raison_sociale', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'SIRET siège social', "table" => 'etablissement', "column" => 'entreprise_siret_siege_social', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Date de création', "table" => 'etablissement', "column" => 'entreprise_date_creation', 'classname' => '', 'virtual' => false, 'type' => :date, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'SIRET', "table" => 'etablissement', "column" => 'siret', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Libellé NAF', "table" => 'etablissement', "column" => 'libelle_naf', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => 'Code postal', "table" => 'etablissement', "column" => 'code_postal', 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => tdc_1.libelle, "table" => 'type_de_champ', "column" => tdc_1.stable_id.to_s, 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => tdc_2.libelle, "table" => 'type_de_champ', "column" => tdc_2.stable_id.to_s, 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => tdc_private_1.libelle, "table" => 'type_de_champ_private', "column" => tdc_private_1.stable_id.to_s, 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true }, - { "label" => tdc_private_2.libelle, "table" => 'type_de_champ_private', "column" => tdc_private_2.stable_id.to_s, 'classname' => '', 'virtual' => false, 'type' => :text, "scope" => '', "value_column" => :value, 'filterable' => true } - ] + { label: 'Créé le', table: 'self', column: 'created_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'Mis à jour le', table: 'self', column: 'updated_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'Déposé le', table: 'self', column: 'depose_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'En construction le', table: 'self', column: 'en_construction_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'En instruction le', table: 'self', column: 'en_instruction_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'Terminé le', table: 'self', column: 'processed_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Mis à jour depuis", table: "self", column: "updated_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Déposé depuis", table: "self", column: "depose_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "En construction depuis", table: "self", column: "en_construction_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "En instruction depuis", table: "self", column: "en_instruction_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Terminé depuis", table: "self", column: "processed_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Statut", table: "self", column: "state", classname: "", virtual: true, scope: 'instructeurs.dossiers.filterable_state', type: :enum, value_column: :value, filterable: true }, + { label: 'Demandeur', table: 'user', column: 'email', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Email instructeur', table: 'followers_instructeurs', column: 'email', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Groupe instructeur', table: 'groupe_instructeur', column: 'id', classname: '', virtual: false, type: :enum, scope: '', value_column: :value, filterable: true }, + { label: 'Avis oui/non', table: 'avis', column: 'question_answer', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: false }, + { label: 'SIREN', table: 'etablissement', column: 'entreprise_siren', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Forme juridique', table: 'etablissement', column: 'entreprise_forme_juridique', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Nom commercial', table: 'etablissement', column: 'entreprise_nom_commercial', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Raison sociale', table: 'etablissement', column: 'entreprise_raison_sociale', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'SIRET siège social', table: 'etablissement', column: 'entreprise_siret_siege_social', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Date de création', table: 'etablissement', column: 'entreprise_date_creation', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'SIRET', table: 'etablissement', column: 'siret', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Libellé NAF', table: 'etablissement', column: 'libelle_naf', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Code postal', table: 'etablissement', column: 'code_postal', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_1.libelle, table: 'type_de_champ', column: tdc_1.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_2.libelle, table: 'type_de_champ', column: tdc_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_private_1.libelle, table: 'type_de_champ_private', column: tdc_private_1.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_private_2.libelle, table: 'type_de_champ_private', column: tdc_private_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true } + ].map { Facet.new(**_1) } } before do @@ -105,13 +105,29 @@ describe ProcedurePresentation do subject { create(:procedure_presentation, assign_to: assign_to) } - it { expect(subject.fields).to eq(expected) } + context 'with explication/header_sections' do + let(:types_de_champ_public) { Array.new(4) { { type: :text } } } + let(:types_de_champ_private) { Array.new(4) { { type: :text } } } + before do + procedure.active_revision.types_de_champ_public[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) + procedure.active_revision.types_de_champ_public[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) + procedure.active_revision.types_de_champ_private[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) + procedure.active_revision.types_de_champ_private[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) + end + it { expect(subject.fields).to eq(expected) } + end + + context 'with rna' do + let(:types_de_champ_public) { [{ type: :rna, libelle: 'rna' }] } + let(:types_de_champ_private) { [] } + xit { expect(subject.fields.map(&:label)).to include('rna – commune') } + end end context 'when the procedure is for individuals' do - let(:name_field) { { "label" => "Prénom", "table" => "individual", "column" => "prenom", 'classname' => '', 'virtual' => false, "type" => :text, "scope" => '', "value_column" => :value, 'filterable' => true } } - let(:surname_field) { { "label" => "Nom", "table" => "individual", "column" => "nom", 'classname' => '', 'virtual' => false, "type" => :text, "scope" => '', "value_column" => :value, 'filterable' => true } } - let(:gender_field) { { "label" => "Civilité", "table" => "individual", "column" => "gender", 'classname' => '', 'virtual' => false, "type" => :text, "scope" => '', "value_column" => :value, 'filterable' => true } } + let(:name_field) { Facet.new(label: "Prénom", table: "individual", column: "prenom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:surname_field) { Facet.new(label: "Nom", table: "individual", column: "nom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:gender_field) { Facet.new(label: "Civilité", table: "individual", column: "gender", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } let(:procedure) { create(:procedure, :for_individual) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } @@ -124,8 +140,8 @@ describe ProcedurePresentation do let(:procedure) { create(:procedure, :for_individual, :sva) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - let(:decision_on) { { "label" => "Date décision SVA", "table" => "self", "column" => "sva_svr_decision_on", 'classname' => '', 'virtual' => false, "type" => :date, "scope" => '', "value_column" => :value, 'filterable' => true } } - let(:decision_before_field) { { "label" => "Date décision SVA avant", "table" => "self", "column" => "sva_svr_decision_before", 'classname' => '', 'virtual' => true, "type" => :date, "scope" => '', "value_column" => :value, 'filterable' => true } } + let(:decision_on) { Facet.new(label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_before_field) { Facet.new(label: "Date décision SVA avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } subject { procedure_presentation.fields } @@ -136,8 +152,8 @@ describe ProcedurePresentation do let(:procedure) { create(:procedure, :for_individual, :svr) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - let(:decision_on) { { "label" => "Date décision SVR", "table" => "self", "column" => "sva_svr_decision_on", 'classname' => '', 'virtual' => false, "type" => :date, "scope" => '', "value_column" => :value, 'filterable' => true } } - let(:decision_before_field) { { "label" => "Date décision SVR avant", "table" => "self", "column" => "sva_svr_decision_before", 'classname' => '', 'virtual' => true, "type" => :date, "scope" => '', "value_column" => :value, 'filterable' => true } } + let(:decision_on) { Facet.new(label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_before_field) { Facet.new(label: "Date décision SVR avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } subject { procedure_presentation.fields } @@ -147,8 +163,8 @@ describe ProcedurePresentation do describe "#displayable_fields_for_select" do subject { create(:procedure_presentation, assign_to: assign_to) } - let(:excluded_displayable_field) { { "label" => "depose_since", "table" => "self", "column" => "depose_since", 'virtual' => true } } - let(:included_displayable_field) { { "label" => "label1", "table" => "table1", "column" => "column1", 'virtual' => false } } + let(:excluded_displayable_field) { Facet.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) } + let(:included_displayable_field) { Facet.new(label: "label1", table: "table1", column: "column1", virtual: false) } before do allow(subject).to receive(:fields).and_return([ @@ -161,15 +177,25 @@ describe ProcedurePresentation do end describe "#filterable_fields_options" do subject { create(:procedure_presentation, assign_to: assign_to) } - let(:included_displayable_field) do - [ - { "label" => "label1", "table" => "table1", "column" => "column1", 'virtual' => false }, - { "label" => "depose_since", "table" => "self", "column" => "depose_since", 'virtual' => true } - ] - end - before do - allow(subject).to receive(:fields).and_return(included_displayable_field) + context 'filders' do + let(:included_displayable_field) do + [ + Facet.new(label: "label1", table: "table1", column: "column1", virtual: false), + Facet.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) + ] + end + + before do + allow(subject).to receive(:fields).and_return(included_displayable_field) + end + + it { expect(subject.filterable_fields_options).to eq([["label1", "table1/column1"], ["depose_since", "self/depose_since"]]) } + end + context 'with rna' do + let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :rna, libelle: 'rna', stable_id: 1 }]) } + it { expect(subject.filterable_fields_options.map { _1[0] }).to include('rna – commune') } + it { expect(subject.filterable_fields_options.map { _1[1] }).to include('type_de_champ/1->data.commune') } end it { expect(subject.filterable_fields_options).to eq([["label1", "table1/column1"], ["depose_since", "self/depose_since"]]) } @@ -859,7 +885,7 @@ describe ProcedurePresentation do end 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" }] } } subject { procedure_presentation.human_value_for_filter(procedure_presentation.filters["suivis"].first) } @@ -878,7 +904,7 @@ describe ProcedurePresentation do end context 'when filter is state' do - let(:filters) { { "suivis" => [{ "table" => "self", "column" => "state", "value" => "en_construction" }] } } + let(:filters) { { "suivis" => [{ table: "self", column: "state", "value" => "en_construction" }] } } it 'should get i18n value' do expect(subject).to eq("En construction") @@ -886,7 +912,7 @@ describe ProcedurePresentation do end context 'when filter is a date' do - let(:filters) { { "suivis" => [{ "table" => "self", "column" => "en_instruction_at", "value" => "15/06/2023" }] } } + let(:filters) { { "suivis" => [{ table: "self", column: "en_instruction_at", "value" => "15/06/2023" }] } } it 'should get formatted value' do expect(subject).to eq("15/06/2023") From 41eb99ce5a3b9a8ef5146c0ad33aa88d6ea292bd Mon Sep 17 00:00:00 2001 From: mfo Date: Fri, 19 Jul 2024 12:00:23 +0200 Subject: [PATCH 02/25] fix(dossier_projection_service.rb): do not fwd internal id_value_h to the view by deep duping fields --- app/services/dossier_projection_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/dossier_projection_service.rb b/app/services/dossier_projection_service.rb index e7680f936..92089b1e6 100644 --- a/app/services/dossier_projection_service.rb +++ b/app/services/dossier_projection_service.rb @@ -39,6 +39,7 @@ 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) + fields = fields.deep_dup state_field = { TABLE => 'self', COLUMN => 'state' } archived_field = { TABLE => 'self', COLUMN => 'archived' } batch_operation_field = { TABLE => 'self', COLUMN => 'batch_operation_id' } From 5c095812286b9c323e1b0d24e4e19292c45a14e0 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 15:06:52 +0200 Subject: [PATCH 03/25] move id to facet and search_by facet_id --- app/models/procedure_presentation.rb | 76 +++++++++++++--------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 553c2dd4b..b272c425a 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -94,17 +94,16 @@ class ProcedurePresentation < ApplicationRecord def displayable_fields_for_select [ - fields.reject(&:virtual) - .map { |field| [field.label, field_id(field)] }, - displayed_fields.map { field_id(Facet.new(**_1.deep_symbolize_keys)) } + fields.reject(&:virtual).map { |facet| [facet.label, facet.id] }, + displayed_fields.map { Facet.new(**_1.deep_symbolize_keys).id } ] end def filterable_fields_options - fields.filter_map do |field| - next if field.filterable == false + fields.filter_map do |facet| + next if facet.filterable == false - [field.label, field_id(field)] + [facet.label, facet.id] end end @@ -282,9 +281,9 @@ class ProcedurePresentation < ApplicationRecord instructeur.groupe_instructeurs .find { _1.id == filter['value'].to_i }&.label || filter['value'] else - field = find_field(filter[TABLE], filter[COLUMN]) + facet = fields.find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } - if field.type == :date + if facet.type == :date parsed_date = safe_parse_date(filter['value']) return parsed_date.present? ? I18n.l(parsed_date) : nil @@ -300,11 +299,13 @@ class ProcedurePresentation < ApplicationRecord nil end - def add_filter(statut, field, value) + def add_filter(statut, facet_id, value) if value.present? - table, column = field.split(SLASH) - label = find_field(table, column).label - value_column = find_field(table, column).value_column + facet = find_facet(facet_id) + label = facet.label + column = facet.column + table = facet.table + value_column = facet.value_column case table when TYPE_DE_CHAMP, TYPE_DE_CHAMP_PRIVATE @@ -324,8 +325,9 @@ class ProcedurePresentation < ApplicationRecord end end - def remove_filter(statut, field, value) - table, column = field.split(SLASH) + def remove_filter(statut, facet_id, value) + facet = find_facet(facet_id) + table, column = facet.table, facet.column updated_filters = filters.dup updated_filters[statut] = filters[statut].reject do |filter| @@ -335,16 +337,16 @@ class ProcedurePresentation < ApplicationRecord update!(filters: updated_filters) end - def update_displayed_fields(values) - if values.nil? - values = [] + def update_displayed_fields(facet_ids) + if facet_ids.nil? + facet_ids = [] end - fields = values.map { |value| find_field(*value.split(SLASH)) } + facets = facet_ids.map { |id| find_facet(id) } - update!(displayed_fields: fields) + update!(displayed_fields: facets) - if !values.include?(field_id(sort)) + if !sort_to_facet_id(sort).in?(facet_ids) update!(sort: Procedure.default_sort) end end @@ -371,22 +373,22 @@ class ProcedurePresentation < ApplicationRecord slice(:filters, :sort, :displayed_fields) end - def field_type(field_id) - find_field(*field_id.split(SLASH)).type + def field_type(facet_id) + find_facet(facet_id).type end - def field_enum(field_id) - field = find_field(*field_id.split(SLASH)) - if field.scope.present? - I18n.t(field.scope).map(&:to_a).map(&:reverse) - elsif field.table == 'groupe_instructeur' + def field_enum(facet_id) + facet = find_facet(facet_id) + if facet.scope.present? + I18n.t(facet.scope).map(&:to_a).map(&:reverse) + elsif facet.table == 'groupe_instructeur' instructeur.groupe_instructeurs.filter_map do if _1.procedure_id == procedure.id [_1.label, _1.id] end end else - find_type_de_champ(field.column).options_for_select + find_type_de_champ(facet.column).options_for_select end end @@ -410,22 +412,12 @@ class ProcedurePresentation < ApplicationRecord private # type_de_champ/4373429 - def field_id(field_or_sort) - if field_or_sort.is_a?(Hash) - sort = field_or_sort - [sort[TABLE], sort[COLUMN]].join(SLASH) - else - field = field_or_sort - if field.label == 'rna – commune' - "#{[field.table, field.column].join(SLASH)}->#{field.value_column}" - else - [field.table, field.column].join(SLASH) - end - end + def sort_to_facet_id(sort) + [sort[TABLE], sort[COLUMN]].join(SLASH) end - def find_field(table, column) - fields.find { [_1.table, _1.column] == [table, column] } + def find_facet(facet_id) + fields.find { _1.id == facet_id } end def find_type_de_champ(column) From 49eaf10bce46b3d20c75c0fc3f6841e70a2f392a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 15:10:30 +0200 Subject: [PATCH 04/25] fields -> facets --- app/models/procedure_presentation.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index b272c425a..5ba8d31fd 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -43,10 +43,10 @@ class ProcedurePresentation < ApplicationRecord ].compact_blank end - def fields - fields = self_fields + def facets + facets = self_fields - fields.push( + facets.push( field_hash('user', 'email', type: :text), field_hash('followers_instructeurs', 'email', type: :text), field_hash('groupe_instructeur', 'id', type: :enum), @@ -54,7 +54,7 @@ class ProcedurePresentation < ApplicationRecord ) if procedure.for_individual - fields.push( + facets.push( field_hash("individual", "prenom", type: :text), field_hash("individual", "nom", type: :text), field_hash("individual", "gender", type: :text) @@ -62,7 +62,7 @@ class ProcedurePresentation < ApplicationRecord end if !procedure.for_individual - fields.push( + facets.push( field_hash('etablissement', 'entreprise_siren', type: :text), field_hash('etablissement', 'entreprise_forme_juridique', type: :text), field_hash('etablissement', 'entreprise_nom_commercial', type: :text), @@ -71,14 +71,14 @@ class ProcedurePresentation < ApplicationRecord field_hash('etablissement', 'entreprise_date_creation', type: :date) ) - fields.push( + facets.push( field_hash('etablissement', 'siret', type: :text), field_hash('etablissement', 'libelle_naf', type: :text), field_hash('etablissement', 'code_postal', type: :text) ) end - fields.concat(procedure.types_de_champ_for_procedure_presentation + facets.concat(procedure.types_de_champ_for_procedure_presentation .pluck(:type_champ, :libelle, :private, :stable_id) .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } .map do |(type_champ, libelle, is_private, stable_id)| @@ -89,18 +89,18 @@ class ProcedurePresentation < ApplicationRecord end end) - fields + facets end def displayable_fields_for_select [ - fields.reject(&:virtual).map { |facet| [facet.label, facet.id] }, + facets.reject(&:virtual).map { |facet| [facet.label, facet.id] }, displayed_fields.map { Facet.new(**_1.deep_symbolize_keys).id } ] end def filterable_fields_options - fields.filter_map do |facet| + facets.filter_map do |facet| next if facet.filterable == false [facet.label, facet.id] @@ -281,7 +281,7 @@ class ProcedurePresentation < ApplicationRecord instructeur.groupe_instructeurs .find { _1.id == filter['value'].to_i }&.label || filter['value'] else - facet = fields.find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } + facet = facets.find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } if facet.type == :date parsed_date = safe_parse_date(filter['value']) @@ -417,7 +417,7 @@ class ProcedurePresentation < ApplicationRecord end def find_facet(facet_id) - fields.find { _1.id == facet_id } + facets.find { _1.id == facet_id } end def find_type_de_champ(column) @@ -494,7 +494,7 @@ class ProcedurePresentation < ApplicationRecord end def valid_columns_for_table(table) - @column_whitelist ||= fields + @column_whitelist ||= facets .group_by(&:table) .transform_values { |facets| Set.new(facets.map(&:column)) } From 2239172fac3ea87f2fea866aa9b69f0b8e82d269 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 15:40:43 +0200 Subject: [PATCH 05/25] move facets list to facets --- app/models/facet.rb | 119 ++++++++++++++++++++++++++- app/models/procedure_presentation.rb | 101 ++--------------------- 2 files changed, 125 insertions(+), 95 deletions(-) diff --git a/app/models/facet.rb b/app/models/facet.rb index ca50225bc..3d4514021 100644 --- a/app/models/facet.rb +++ b/app/models/facet.rb @@ -1,5 +1,8 @@ class Facet - def initialize(table:, column:, label: nil, virtual: false, type: :text, value_column: :value, filterable: true, classname: nil, scope: '') + TYPE_DE_CHAMP = 'type_de_champ' + TYPE_DE_CHAMP_PRIVATE = 'type_de_champ_private' + + def initialize(table:, column:, label: nil, virtual: false, type: :text, value_column: :value, filterable: true, classname: '', scope: '') @table = table @column = column @label = label || I18n.t(column, scope: [:activerecord, :attributes, :procedure_presentation, :fields, table]) @@ -22,4 +25,118 @@ class Facet table:, column:, label:, classname:, virtual:, type:, scope:, value_column:, filterable: } end + + def self.dossier_facets(procedure:) + [ + new(table: 'self', column: 'created_at', type: :date), + new(table: 'self', column: 'updated_at', type: :date), + new(table: 'self', column: 'depose_at', type: :date), + new(table: 'self', column: 'en_construction_at', type: :date), + new(table: 'self', column: 'en_instruction_at', type: :date), + new(table: 'self', column: 'processed_at', type: :date), + *sva_svr_facets(procedure:, for_filters: true), + new(table: 'self', column: 'updated_since', type: :date, virtual: true), + new(table: 'self', column: 'depose_since', type: :date, virtual: true), + new(table: 'self', column: 'en_construction_since', type: :date, virtual: true), + new(table: 'self', column: 'en_instruction_since', type: :date, virtual: true), + new(table: 'self', column: 'processed_since', type: :date, virtual: true), + new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true) + ].compact_blank + end + + def self.facets(procedure:) + facets = Facet.dossier_facets(procedure:) + + facets.push( + new(table: 'user', column: 'email', type: :text), + new(table: 'followers_instructeurs', column: 'email', type: :text), + new(table: 'groupe_instructeur', column: 'id', type: :enum), + new(table: 'avis', column: 'question_answer', filterable: false) + ) + + if procedure.for_individual + facets.push( + new(table: "individual", column: "prenom", type: :text), + new(table: "individual", column: "nom", type: :text), + new(table: "individual", column: "gender", type: :text) + ) + end + + if !procedure.for_individual + facets.push( + new(table: 'etablissement', column: 'entreprise_siren', type: :text), + new(table: 'etablissement', column: 'entreprise_forme_juridique', type: :text), + new(table: 'etablissement', column: 'entreprise_nom_commercial', type: :text), + new(table: 'etablissement', column: 'entreprise_raison_sociale', type: :text), + new(table: 'etablissement', column: 'entreprise_siret_siege_social', type: :text), + new(table: 'etablissement', column: 'entreprise_date_creation', type: :date) + ) + + facets.push( + new(table: 'etablissement', column: 'siret', type: :text), + new(table: 'etablissement', column: 'libelle_naf', type: :text), + new(table: 'etablissement', column: 'code_postal', type: :text) + ) + end + + facets.concat(procedure.types_de_champ_for_procedure_presentation + .pluck(:type_champ, :libelle, :private, :stable_id) + .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } + .flat_map do |(type_champ, libelle, is_private, stable_id)| + tdc = TypeDeChamp.new(type_champ:, libelle:, private: is_private, stable_id:) + if is_private + facets_for_type_de_champ_private(tdc) + else + facets_for_type_de_champ_public(tdc) + end + end) + + facets + end + + def self.facets_for_type_de_champ_public(tdc) + tdc.dynamic_type.search_paths.map do |path_struct| + new( + table: TYPE_DE_CHAMP, + column: tdc.stable_id.to_s, + label: path_struct[:libelle], + type: TypeDeChamp.filter_hash_type(tdc.type_champ), + value_column: path_struct[:path] + ) + end + end + + def self.facets_for_type_de_champ_private(tdc) + tdc.dynamic_type.search_paths.map do |path_struct| + new( + table: TYPE_DE_CHAMP_PRIVATE, + column: tdc.stable_id.to_s, + label: path_struct[:libelle], + type: TypeDeChamp.filter_hash_type(tdc.type_champ), + value_column: path_struct[:path] + ) + end + end + + + + def self.sva_svr_facets(procedure:, for_filters: false) + return if !procedure.sva_svr_enabled? + + i18n_scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] + + facets = [] + facets << new(table: 'self', column: 'sva_svr_decision_on', + type: :date, + label: I18n.t("#{procedure.sva_svr_decision}_decision_on", scope: i18n_scope), + classname: for_filters ? '' : 'sva-col') + + if for_filters + facets << new(table: 'self', column: 'sva_svr_decision_before', + label: I18n.t("#{procedure.sva_svr_decision}_decision_before", scope: i18n_scope), + type: :date, virtual: true) + end + + facets + end end diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 5ba8d31fd..625d94678 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -25,82 +25,15 @@ class ProcedurePresentation < ApplicationRecord validate :check_allowed_filter_columns validate :check_filters_max_length - def self_fields - [ - field_hash('self', 'created_at', type: :date), - field_hash('self', 'updated_at', type: :date), - field_hash('self', 'depose_at', type: :date), - field_hash('self', 'en_construction_at', type: :date), - field_hash('self', 'en_instruction_at', type: :date), - field_hash('self', 'processed_at', type: :date), - *sva_svr_fields(for_filters: true), - field_hash('self', 'updated_since', type: :date, virtual: true), - field_hash('self', 'depose_since', type: :date, virtual: true), - field_hash('self', 'en_construction_since', type: :date, virtual: true), - field_hash('self', 'en_instruction_since', type: :date, virtual: true), - field_hash('self', 'processed_since', type: :date, virtual: true), - field_hash('self', 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true) - ].compact_blank - end - - def facets - facets = self_fields - - facets.push( - field_hash('user', 'email', type: :text), - field_hash('followers_instructeurs', 'email', type: :text), - field_hash('groupe_instructeur', 'id', type: :enum), - field_hash('avis', 'question_answer', filterable: false) - ) - - if procedure.for_individual - facets.push( - field_hash("individual", "prenom", type: :text), - field_hash("individual", "nom", type: :text), - field_hash("individual", "gender", type: :text) - ) - end - - if !procedure.for_individual - facets.push( - field_hash('etablissement', 'entreprise_siren', type: :text), - field_hash('etablissement', 'entreprise_forme_juridique', type: :text), - field_hash('etablissement', 'entreprise_nom_commercial', type: :text), - field_hash('etablissement', 'entreprise_raison_sociale', type: :text), - field_hash('etablissement', 'entreprise_siret_siege_social', type: :text), - field_hash('etablissement', 'entreprise_date_creation', type: :date) - ) - - facets.push( - field_hash('etablissement', 'siret', type: :text), - field_hash('etablissement', 'libelle_naf', type: :text), - field_hash('etablissement', 'code_postal', type: :text) - ) - end - - facets.concat(procedure.types_de_champ_for_procedure_presentation - .pluck(:type_champ, :libelle, :private, :stable_id) - .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } - .map do |(type_champ, libelle, is_private, stable_id)| - if is_private - field_hash_for_type_de_champ_private(type_champ, libelle, stable_id) - else - field_hash_for_type_de_champ_public(type_champ, libelle, stable_id) - end - end) - - facets - end - def displayable_fields_for_select [ - facets.reject(&:virtual).map { |facet| [facet.label, facet.id] }, + Facet.facets(procedure:).reject(&:virtual).map { |facet| [facet.label, facet.id] }, displayed_fields.map { Facet.new(**_1.deep_symbolize_keys).id } ] end def filterable_fields_options - facets.filter_map do |facet| + Facet.facets(procedure:).filter_map do |facet| next if facet.filterable == false [facet.label, facet.id] @@ -112,30 +45,10 @@ class ProcedurePresentation < ApplicationRecord field_hash('self', 'id', classname: 'number-col'), *displayed_fields.map { Facet.new(**_1.deep_symbolize_keys) }, field_hash('self', 'state', classname: 'state-col'), - *sva_svr_fields + *Facet.sva_svr_facets(procedure:) ] end - def sva_svr_fields(for_filters: false) - return if !procedure.sva_svr_enabled? - - i18n_scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] - - fields = [] - fields << field_hash('self', 'sva_svr_decision_on', - type: :date, - label: I18n.t("#{procedure.sva_svr_decision}_decision_on", scope: i18n_scope), - classname: for_filters ? '' : 'sva-col') - - if for_filters - fields << field_hash('self', 'sva_svr_decision_before', - label: I18n.t("#{procedure.sva_svr_decision}_decision_before", scope: i18n_scope), - type: :date, virtual: true) - end - - fields - end - def sorted_ids(dossiers, count) table, column, order = sort.values_at(TABLE, COLUMN, 'order') @@ -200,7 +113,7 @@ class ProcedurePresentation < ApplicationRecord value_column = filters.pluck('value_column').compact.first || :value case table when 'self' - field = self_fields.find { |h| h.column == column } + field = Facet.dossier_facets(procedure:).find { |h| h.column == column } if field.type == :date dates = values .filter_map { |v| Time.zone.parse(v).beginning_of_day rescue nil } @@ -281,7 +194,7 @@ class ProcedurePresentation < ApplicationRecord instructeur.groupe_instructeurs .find { _1.id == filter['value'].to_i }&.label || filter['value'] else - facet = facets.find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } + facet = Facet.facets(procedure:).find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } if facet.type == :date parsed_date = safe_parse_date(filter['value']) @@ -417,7 +330,7 @@ class ProcedurePresentation < ApplicationRecord end def find_facet(facet_id) - facets.find { _1.id == facet_id } + Facet.facets(procedure:).find { _1.id == facet_id } end def find_type_de_champ(column) @@ -494,7 +407,7 @@ class ProcedurePresentation < ApplicationRecord end def valid_columns_for_table(table) - @column_whitelist ||= facets + @column_whitelist ||= Facet.facets(procedure:) .group_by(&:table) .transform_values { |facets| Set.new(facets.map(&:column)) } From 305b8c13c75a711a85521d0b19659293d8d92805 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 16:53:22 +0200 Subject: [PATCH 06/25] ! BREAKING : break previous filter by `type_de_champ_private` simplify type_de_champ_private -> type_de_champ, fix spec, add facet_spec --- app/models/facet.rb | 60 +++------ app/models/procedure_presentation.rb | 19 +-- spec/models/facet_spec.rb | 105 ++++++++++++++++ spec/models/procedure_presentation_spec.rb | 140 +++------------------ 4 files changed, 142 insertions(+), 182 deletions(-) create mode 100644 spec/models/facet_spec.rb diff --git a/app/models/facet.rb b/app/models/facet.rb index 3d4514021..252796965 100644 --- a/app/models/facet.rb +++ b/app/models/facet.rb @@ -1,6 +1,5 @@ class Facet TYPE_DE_CHAMP = 'type_de_champ' - TYPE_DE_CHAMP_PRIVATE = 'type_de_champ_private' def initialize(table:, column:, label: nil, virtual: false, type: :text, value_column: :value, filterable: true, classname: '', scope: '') @table = table @@ -69,57 +68,38 @@ class Facet new(table: 'etablissement', column: 'entreprise_nom_commercial', type: :text), new(table: 'etablissement', column: 'entreprise_raison_sociale', type: :text), new(table: 'etablissement', column: 'entreprise_siret_siege_social', type: :text), - new(table: 'etablissement', column: 'entreprise_date_creation', type: :date) - ) - - facets.push( + new(table: 'etablissement', column: 'entreprise_date_creation', type: :date), new(table: 'etablissement', column: 'siret', type: :text), new(table: 'etablissement', column: 'libelle_naf', type: :text), new(table: 'etablissement', column: 'code_postal', type: :text) ) end - facets.concat(procedure.types_de_champ_for_procedure_presentation - .pluck(:type_champ, :libelle, :private, :stable_id) - .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } - .flat_map do |(type_champ, libelle, is_private, stable_id)| - tdc = TypeDeChamp.new(type_champ:, libelle:, private: is_private, stable_id:) - if is_private - facets_for_type_de_champ_private(tdc) - else - facets_for_type_de_champ_public(tdc) - end - end) + facets.concat(types_de_champ_facets(procedure)) facets end - def self.facets_for_type_de_champ_public(tdc) - tdc.dynamic_type.search_paths.map do |path_struct| - new( - table: TYPE_DE_CHAMP, - column: tdc.stable_id.to_s, - label: path_struct[:libelle], - type: TypeDeChamp.filter_hash_type(tdc.type_champ), - value_column: path_struct[:path] - ) - end + def self.types_de_champ_facets(procedure) + procedure + .types_de_champ_for_procedure_presentation + .pluck(:type_champ, :libelle, :stable_id) + .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } + .flat_map do |(type_champ, libelle, stable_id)| + tdc = TypeDeChamp.new(type_champ:, libelle:, stable_id:) + + tdc.dynamic_type.search_paths.map do |path_struct| + new( + table: TYPE_DE_CHAMP, + column: tdc.stable_id.to_s, + label: path_struct[:libelle], + type: TypeDeChamp.filter_hash_type(tdc.type_champ), + value_column: path_struct[:path] + ) + end + end end - def self.facets_for_type_de_champ_private(tdc) - tdc.dynamic_type.search_paths.map do |path_struct| - new( - table: TYPE_DE_CHAMP_PRIVATE, - column: tdc.stable_id.to_s, - label: path_struct[:libelle], - type: TypeDeChamp.filter_hash_type(tdc.type_champ), - value_column: path_struct[:path] - ) - end - end - - - def self.sva_svr_facets(procedure:, for_filters: false) return if !procedure.sva_svr_enabled? diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 625d94678..0b658780b 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -10,7 +10,6 @@ class ProcedurePresentation < ApplicationRecord SLASH = '/' TYPE_DE_CHAMP = 'type_de_champ' - TYPE_DE_CHAMP_PRIVATE = 'type_de_champ_private' FILTERS_VALUE_MAX_LENGTH = 100 @@ -73,17 +72,6 @@ class ProcedurePresentation < ApplicationRecord else ids end - when TYPE_DE_CHAMP_PRIVATE - ids = dossiers - .with_type_de_champ(column) - .order("champs.value #{order}") - .pluck(:id) - if ids.size != count - rest = dossiers.where.not(id: ids).order(id: order).pluck(:id) - order == 'asc' ? ids + rest : rest + ids - else - ids - end when 'followers_instructeurs' assert_supported_column(table, column) # LEFT OUTER JOIN allows to keep dossiers without assigned instructeurs yet @@ -129,9 +117,6 @@ class ProcedurePresentation < ApplicationRecord when TYPE_DE_CHAMP dossiers.with_type_de_champ(column) .filter_ilike(:champs, value_column, values) - when TYPE_DE_CHAMP_PRIVATE - dossiers.with_type_de_champ(column) - .filter_ilike(:champs, value_column, values) when 'etablissement' if column == 'entreprise_date_creation' dates = values @@ -182,7 +167,7 @@ class ProcedurePresentation < ApplicationRecord end def human_value_for_filter(filter) - if [TYPE_DE_CHAMP, TYPE_DE_CHAMP_PRIVATE].include?(filter[TABLE]) + if filter[TABLE] == TYPE_DE_CHAMP find_type_de_champ(filter[COLUMN]).dynamic_type.filter_to_human(filter['value']) elsif filter['column'] == 'state' if filter['value'] == 'pending_correction' @@ -221,7 +206,7 @@ class ProcedurePresentation < ApplicationRecord value_column = facet.value_column case table - when TYPE_DE_CHAMP, TYPE_DE_CHAMP_PRIVATE + when TYPE_DE_CHAMP value = find_type_de_champ(column).dynamic_type.human_to_filter(value) end diff --git a/spec/models/facet_spec.rb b/spec/models/facet_spec.rb new file mode 100644 index 000000000..77b951062 --- /dev/null +++ b/spec/models/facet_spec.rb @@ -0,0 +1,105 @@ +describe Facet do + describe "#facets" do + context 'when the procedure can have a SIRET number' do + let(:procedure) do + create(:procedure, + types_de_champ_public: Array.new(4) { { type: :text } }, + types_de_champ_private: Array.new(4) { { type: :text } }) + end + let(:tdc_1) { procedure.active_revision.types_de_champ_public[0] } + let(:tdc_2) { procedure.active_revision.types_de_champ_public[1] } + let(:tdc_private_1) { procedure.active_revision.types_de_champ_private[0] } + let(:tdc_private_2) { procedure.active_revision.types_de_champ_private[1] } + let(:expected) { + [ + { label: 'Créé le', table: 'self', column: 'created_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'Mis à jour le', table: 'self', column: 'updated_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'Déposé le', table: 'self', column: 'depose_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'En construction le', table: 'self', column: 'en_construction_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'En instruction le', table: 'self', column: 'en_instruction_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'Terminé le', table: 'self', column: 'processed_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Mis à jour depuis", table: "self", column: "updated_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Déposé depuis", table: "self", column: "depose_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "En construction depuis", table: "self", column: "en_construction_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "En instruction depuis", table: "self", column: "en_instruction_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Terminé depuis", table: "self", column: "processed_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, + { label: "Statut", table: "self", column: "state", classname: "", virtual: true, scope: 'instructeurs.dossiers.filterable_state', type: :enum, value_column: :value, filterable: true }, + { label: 'Demandeur', table: 'user', column: 'email', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Email instructeur', table: 'followers_instructeurs', column: 'email', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Groupe instructeur', table: 'groupe_instructeur', column: 'id', classname: '', virtual: false, type: :enum, scope: '', value_column: :value, filterable: true }, + { label: 'Avis oui/non', table: 'avis', column: 'question_answer', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: false }, + { label: 'SIREN', table: 'etablissement', column: 'entreprise_siren', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Forme juridique', table: 'etablissement', column: 'entreprise_forme_juridique', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Nom commercial', table: 'etablissement', column: 'entreprise_nom_commercial', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Raison sociale', table: 'etablissement', column: 'entreprise_raison_sociale', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'SIRET siège social', table: 'etablissement', column: 'entreprise_siret_siege_social', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Date de création', table: 'etablissement', column: 'entreprise_date_creation', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, + { label: 'SIRET', table: 'etablissement', column: 'siret', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Libellé NAF', table: 'etablissement', column: 'libelle_naf', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'Code postal', table: 'etablissement', column: 'code_postal', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_1.libelle, table: 'type_de_champ', column: tdc_1.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_2.libelle, table: 'type_de_champ', column: tdc_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_private_1.libelle, table: 'type_de_champ', column: tdc_private_1.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: tdc_private_2.libelle, table: 'type_de_champ', column: tdc_private_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true } + ].map { Facet.new(**_1) } + } + + subject { Facet.facets(procedure:) } + + context 'with explication/header_sections' do + let(:types_de_champ_public) { Array.new(4) { { type: :text } } } + let(:types_de_champ_private) { Array.new(4) { { type: :text } } } + before do + procedure.active_revision.types_de_champ_public[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) + procedure.active_revision.types_de_champ_public[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) + procedure.active_revision.types_de_champ_private[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) + procedure.active_revision.types_de_champ_private[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) + end + + it { expect(subject).to eq(expected) } + end + + xcontext 'with rna' do + let(:types_de_champ_public) { [{ type: :rna, libelle: 'rna' }] } + let(:types_de_champ_private) { [] } + xit { expect(subject.map(&:label)).to include('rna – commune') } + end + end + + context 'when the procedure is for individuals' do + let(:name_field) { Facet.new(label: "Prénom", table: "individual", column: "prenom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:surname_field) { Facet.new(label: "Nom", table: "individual", column: "nom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:gender_field) { Facet.new(label: "Civilité", table: "individual", column: "gender", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:procedure) { create(:procedure, :for_individual) } + let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } + + subject { Facet.facets(procedure:) } + + it { is_expected.to include(name_field, surname_field, gender_field) } + end + + context 'when the procedure is sva' do + let(:procedure) { create(:procedure, :for_individual, :sva) } + let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } + + let(:decision_on) { Facet.new(label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_before_field) { Facet.new(label: "Date décision SVA avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } + + subject { Facet.facets(procedure:) } + + it { is_expected.to include(decision_on, decision_before_field) } + end + + context 'when the procedure is svr' do + let(:procedure) { create(:procedure, :for_individual, :svr) } + let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } + + let(:decision_on) { Facet.new(label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_before_field) { Facet.new(label: "Date décision SVR avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } + + subject { Facet.facets(procedure:) } + + it { is_expected.to include(decision_on, decision_before_field) } + end + end +end diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 27cc13b27..0c15fabfc 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -51,129 +51,20 @@ describe ProcedurePresentation do end end - describe "#fields" do - context 'when the procedure can have a SIRET number' do - let(:procedure) do - create(:procedure, - types_de_champ_public: Array.new(4) { { type: :text } }, - types_de_champ_private: Array.new(4) { { type: :text } }) - end - let(:tdc_1) { procedure.active_revision.types_de_champ_public[0] } - let(:tdc_2) { procedure.active_revision.types_de_champ_public[1] } - let(:tdc_private_1) { procedure.active_revision.types_de_champ_private[0] } - let(:tdc_private_2) { procedure.active_revision.types_de_champ_private[1] } - let(:expected) { - [ - { label: 'Créé le', table: 'self', column: 'created_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, - { label: 'Mis à jour le', table: 'self', column: 'updated_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, - { label: 'Déposé le', table: 'self', column: 'depose_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, - { label: 'En construction le', table: 'self', column: 'en_construction_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, - { label: 'En instruction le', table: 'self', column: 'en_instruction_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, - { label: 'Terminé le', table: 'self', column: 'processed_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, - { label: "Mis à jour depuis", table: "self", column: "updated_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, - { label: "Déposé depuis", table: "self", column: "depose_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, - { label: "En construction depuis", table: "self", column: "en_construction_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, - { label: "En instruction depuis", table: "self", column: "en_instruction_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, - { label: "Terminé depuis", table: "self", column: "processed_since", classname: "", virtual: true, type: :date, scope: '', value_column: :value, filterable: true }, - { label: "Statut", table: "self", column: "state", classname: "", virtual: true, scope: 'instructeurs.dossiers.filterable_state', type: :enum, value_column: :value, filterable: true }, - { label: 'Demandeur', table: 'user', column: 'email', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Email instructeur', table: 'followers_instructeurs', column: 'email', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Groupe instructeur', table: 'groupe_instructeur', column: 'id', classname: '', virtual: false, type: :enum, scope: '', value_column: :value, filterable: true }, - { label: 'Avis oui/non', table: 'avis', column: 'question_answer', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: false }, - { label: 'SIREN', table: 'etablissement', column: 'entreprise_siren', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Forme juridique', table: 'etablissement', column: 'entreprise_forme_juridique', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Nom commercial', table: 'etablissement', column: 'entreprise_nom_commercial', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Raison sociale', table: 'etablissement', column: 'entreprise_raison_sociale', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'SIRET siège social', table: 'etablissement', column: 'entreprise_siret_siege_social', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Date de création', table: 'etablissement', column: 'entreprise_date_creation', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, - { label: 'SIRET', table: 'etablissement', column: 'siret', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Libellé NAF', table: 'etablissement', column: 'libelle_naf', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: 'Code postal', table: 'etablissement', column: 'code_postal', classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: tdc_1.libelle, table: 'type_de_champ', column: tdc_1.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: tdc_2.libelle, table: 'type_de_champ', column: tdc_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: tdc_private_1.libelle, table: 'type_de_champ_private', column: tdc_private_1.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, - { label: tdc_private_2.libelle, table: 'type_de_champ_private', column: tdc_private_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true } - ].map { Facet.new(**_1) } - } - - before do - procedure.active_revision.types_de_champ_public[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) - procedure.active_revision.types_de_champ_public[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) - procedure.active_revision.types_de_champ_private[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) - procedure.active_revision.types_de_champ_private[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) - end - - subject { create(:procedure_presentation, assign_to: assign_to) } - - context 'with explication/header_sections' do - let(:types_de_champ_public) { Array.new(4) { { type: :text } } } - let(:types_de_champ_private) { Array.new(4) { { type: :text } } } - before do - procedure.active_revision.types_de_champ_public[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) - procedure.active_revision.types_de_champ_public[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) - procedure.active_revision.types_de_champ_private[2].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:header_section)) - procedure.active_revision.types_de_champ_private[3].update_attribute(:type_champ, TypeDeChamp.type_champs.fetch(:explication)) - end - it { expect(subject.fields).to eq(expected) } - end - - context 'with rna' do - let(:types_de_champ_public) { [{ type: :rna, libelle: 'rna' }] } - let(:types_de_champ_private) { [] } - xit { expect(subject.fields.map(&:label)).to include('rna – commune') } - end - end - - context 'when the procedure is for individuals' do - let(:name_field) { Facet.new(label: "Prénom", table: "individual", column: "prenom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } - let(:surname_field) { Facet.new(label: "Nom", table: "individual", column: "nom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } - let(:gender_field) { Facet.new(label: "Civilité", table: "individual", column: "gender", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } - let(:procedure) { create(:procedure, :for_individual) } - let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - - subject { procedure_presentation.fields } - - it { is_expected.to include(name_field, surname_field, gender_field) } - end - - context 'when the procedure is sva' do - let(:procedure) { create(:procedure, :for_individual, :sva) } - let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - - let(:decision_on) { Facet.new(label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } - let(:decision_before_field) { Facet.new(label: "Date décision SVA avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } - - subject { procedure_presentation.fields } - - it { is_expected.to include(decision_on, decision_before_field) } - end - - context 'when the procedure is svr' do - let(:procedure) { create(:procedure, :for_individual, :svr) } - let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - - let(:decision_on) { Facet.new(label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } - let(:decision_before_field) { Facet.new(label: "Date décision SVR avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } - - subject { procedure_presentation.fields } - - it { is_expected.to include(decision_on, decision_before_field) } - end - end - describe "#displayable_fields_for_select" do subject { create(:procedure_presentation, assign_to: assign_to) } - let(:excluded_displayable_field) { Facet.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) } - let(:included_displayable_field) { Facet.new(label: "label1", table: "table1", column: "column1", virtual: false) } + + let(:default_user_email) { Facet.new(label: 'email', table: 'user', column: 'email') } + let(:excluded_displayable_field) { Facet.new(label: "label1", table: "table1", column: "column1", virtual: true) } before do - allow(subject).to receive(:fields).and_return([ - excluded_displayable_field, - included_displayable_field + allow(Facet).to receive(:facets).and_return([ + default_user_email, + excluded_displayable_field ]) end - it { expect(subject.displayable_fields_for_select).to eq([[["label1", "table1/column1"]], ["user/email"]]) } + it { expect(subject.displayable_fields_for_select).to eq([[["email", "user/email"]], ["user/email"]]) } end describe "#filterable_fields_options" do subject { create(:procedure_presentation, assign_to: assign_to) } @@ -181,13 +72,13 @@ describe ProcedurePresentation do context 'filders' do let(:included_displayable_field) do [ - Facet.new(label: "label1", table: "table1", column: "column1", virtual: false), + Facet.new(label: 'email', table: 'user', column: 'email'), Facet.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) ] end before do - allow(subject).to receive(:fields).and_return(included_displayable_field) + allow(Facet).to receive(:facets).and_return(included_displayable_field) end it { expect(subject.filterable_fields_options).to eq([["label1", "table1/column1"], ["depose_since", "self/depose_since"]]) } @@ -196,9 +87,8 @@ describe ProcedurePresentation do let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :rna, libelle: 'rna', stable_id: 1 }]) } it { expect(subject.filterable_fields_options.map { _1[0] }).to include('rna – commune') } it { expect(subject.filterable_fields_options.map { _1[1] }).to include('type_de_champ/1->data.commune') } + it { expect(subject.filterable_fields_options).to eq([["email", "user/email"], ["depose_since", "self/depose_since"]]) } end - - it { expect(subject.filterable_fields_options).to eq([["label1", "table1/column1"], ["depose_since", "self/depose_since"]]) } end describe '#sorted_ids' do @@ -336,7 +226,7 @@ describe ProcedurePresentation do context 'for type_de_champ_private table' do context 'with no revisions' do - let(:table) { 'type_de_champ_private' } + let(:table) { 'type_de_champ' } let(:column) { procedure.active_revision.types_de_champ_private.first.stable_id.to_s } let(:biere_dossier) { create(:dossier, procedure: procedure) } @@ -362,7 +252,7 @@ describe ProcedurePresentation do context 'with a revision adding a new type_de_champ' do let!(:tdc) { { type_champ: :text, private: true, libelle: 'nouveau champ' } } - let(:table) { 'type_de_champ_private' } + let(:table) { 'type_de_champ' } let(:column) { procedure.active_revision.types_de_champ_private.last.stable_id.to_s } let(:nothing_dossier) { create(:dossier, procedure: procedure) } @@ -675,7 +565,7 @@ describe ProcedurePresentation do end context 'for type_de_champ_private table' do - let(:filter) { [{ 'table' => 'type_de_champ_private', 'column' => type_de_champ_private.stable_id.to_s, 'value' => 'keep' }] } + let(:filter) { [{ 'table' => 'type_de_champ', 'column' => type_de_champ_private.stable_id.to_s, 'value' => 'keep' }] } let(:kept_dossier) { create(:dossier, procedure: procedure) } let(:discarded_dossier) { create(:dossier, procedure: procedure) } @@ -691,8 +581,8 @@ describe ProcedurePresentation do context 'with multiple search values' do let(:filter) do [ - { 'table' => 'type_de_champ_private', 'column' => type_de_champ_private.stable_id.to_s, 'value' => 'keep' }, - { 'table' => 'type_de_champ_private', 'column' => type_de_champ_private.stable_id.to_s, 'value' => 'and' } + { 'table' => 'type_de_champ', 'column' => type_de_champ_private.stable_id.to_s, 'value' => 'keep' }, + { 'table' => 'type_de_champ', 'column' => type_de_champ_private.stable_id.to_s, 'value' => 'and' } ] end From 969282e4537b78e1614d3c24c60593661ccf510e Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 17:05:08 +0200 Subject: [PATCH 07/25] remove last field_hash --- app/models/procedure_presentation.rb | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 0b658780b..7c0ad678a 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -41,9 +41,9 @@ class ProcedurePresentation < ApplicationRecord def displayed_fields_for_headers [ - field_hash('self', 'id', classname: 'number-col'), + Facet.new(table: 'self', column: 'id', classname: 'number-col'), *displayed_fields.map { Facet.new(**_1.deep_symbolize_keys) }, - field_hash('self', 'state', classname: 'state-col'), + Facet.new(table: 'self', column: 'state', classname: 'state-col'), *Facet.sva_svr_facets(procedure:) ] end @@ -368,24 +368,6 @@ class ProcedurePresentation < ApplicationRecord end end - def field_hash(table, column, label: nil, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) - Facet.new(table:, column:, label:, classname:, virtual:, type:, scope:, value_column:, filterable:) - end - - def field_hash_for_type_de_champ_public(type_champ, libelle, stable_id) - field_hash(TYPE_DE_CHAMP, stable_id.to_s, - label: libelle, - type: TypeDeChamp.filter_hash_type(type_champ), - value_column: TypeDeChamp.filter_hash_value_column(type_champ)) - end - - def field_hash_for_type_de_champ_private(type_champ, libelle, stable_id) - field_hash(TYPE_DE_CHAMP_PRIVATE, stable_id.to_s, - label: libelle, - type: TypeDeChamp.filter_hash_type(type_champ), - value_column: TypeDeChamp.filter_hash_value_column(type_champ)) - end - def valid_column?(table, column, extra_columns = {}) valid_columns_for_table(table).include?(column) || extra_columns[table]&.include?(column) From 437e632f6ad125d45596df1e257c61cadf378291 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 17:28:20 +0200 Subject: [PATCH 08/25] move facet_find to Facet.find --- app/models/facet.rb | 4 ++++ app/models/procedure_presentation.rb | 14 +++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/facet.rb b/app/models/facet.rb index 252796965..747eda1e1 100644 --- a/app/models/facet.rb +++ b/app/models/facet.rb @@ -25,6 +25,10 @@ class Facet } end + def self.find(procedure:, id:) + facets(procedure:).find { _1.id == id } + end + def self.dossier_facets(procedure:) [ new(table: 'self', column: 'created_at', type: :date), diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 7c0ad678a..64db2927f 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -199,7 +199,7 @@ class ProcedurePresentation < ApplicationRecord def add_filter(statut, facet_id, value) if value.present? - facet = find_facet(facet_id) + facet = Facet.find(procedure:, id: facet_id) label = facet.label column = facet.column table = facet.table @@ -224,7 +224,7 @@ class ProcedurePresentation < ApplicationRecord end def remove_filter(statut, facet_id, value) - facet = find_facet(facet_id) + facet = Facet.find(procedure:, id: facet_id) table, column = facet.table, facet.column updated_filters = filters.dup @@ -240,7 +240,7 @@ class ProcedurePresentation < ApplicationRecord facet_ids = [] end - facets = facet_ids.map { |id| find_facet(id) } + facets = facet_ids.map { |id| Facet.find(procedure:, id:) } update!(displayed_fields: facets) @@ -272,11 +272,11 @@ class ProcedurePresentation < ApplicationRecord end def field_type(facet_id) - find_facet(facet_id).type + Facet.find(procedure:, id: facet_id).type end def field_enum(facet_id) - facet = find_facet(facet_id) + facet = Facet.find(procedure:, id: facet_id) if facet.scope.present? I18n.t(facet.scope).map(&:to_a).map(&:reverse) elsif facet.table == 'groupe_instructeur' @@ -314,10 +314,6 @@ class ProcedurePresentation < ApplicationRecord [sort[TABLE], sort[COLUMN]].join(SLASH) end - def find_facet(facet_id) - Facet.facets(procedure:).find { _1.id == facet_id } - end - def find_type_de_champ(column) TypeDeChamp .joins(:revision_types_de_champ) From 73293b0d0620c22acf4b2453bedaecffd4b6919e Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 17:39:55 +0200 Subject: [PATCH 09/25] give facet object instead of field_id to instructeur_filter_component --- .../dossiers/instructeur_filter_component.rb | 13 +++++-------- .../instructeur_filter_component.html.haml | 4 ++-- .../instructeurs/procedures_controller.rb | 2 +- app/models/procedure_presentation.rb | 4 ---- .../procedures/_dossiers_filter_dropdown.html.haml | 2 +- .../procedures/update_filter.turbo_stream.haml | 2 +- 6 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/components/dossiers/instructeur_filter_component.rb b/app/components/dossiers/instructeur_filter_component.rb index 7e841f5c4..eecef1897 100644 --- a/app/components/dossiers/instructeur_filter_component.rb +++ b/app/components/dossiers/instructeur_filter_component.rb @@ -1,17 +1,14 @@ class Dossiers::InstructeurFilterComponent < ApplicationComponent - def initialize(procedure:, procedure_presentation:, statut:, field_id: nil) + def initialize(procedure:, procedure_presentation:, statut:, facet: nil) @procedure = procedure @procedure_presentation = procedure_presentation @statut = statut - @field_id = field_id + @facet = facet end - attr_reader :procedure, :procedure_presentation, :statut, :field_id + attr_reader :procedure, :procedure_presentation, :statut, :facet - def field_type - return :text if field_id.nil? - procedure_presentation.field_type(field_id) - end + def facet_type = facet.present? ? facet.type : :text def options_for_select_of_field procedure_presentation.field_enum(field_id) @@ -19,7 +16,7 @@ class Dossiers::InstructeurFilterComponent < ApplicationComponent def filter_react_props { - selected_key: @field_id || '', + selected_key: facet.present? ? facet.id : '', items: procedure_presentation.filterable_fields_options, name: :field, id: 'search-filter', diff --git a/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml b/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml index f98e93700..f347727f5 100644 --- a/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml +++ b/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml @@ -7,10 +7,10 @@ %input.hidden{ type: 'submit', formaction: update_filter_instructeur_procedure_path(procedure), data: { autosubmit_target: 'submitter' } } = label_tag :value, t('.value'), for: 'value', class: 'fr-label' - - if field_type == :enum + - if facet_type == :enum = select_tag :value, options_for_select(options_for_select_of_field), id: 'value', name: 'value', class: 'fr-select', data: { no_autosubmit: true } - else - %input#value.fr-input{ type: field_type, name: :value, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH, disabled: field_id.nil? ? true : false, data: { no_autosubmit: true } } + %input#value.fr-input{ type: facet_type, name: :value, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH, disabled: facet.nil? ? true : false, data: { no_autosubmit: true } } = hidden_field_tag :statut, statut = submit_tag t('.add_filter'), class: 'fr-btn fr-btn--secondary fr-mt-2w' diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 6de8cfdee..8d0e66b7f 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -159,7 +159,7 @@ module Instructeurs @statut = statut @procedure = procedure @procedure_presentation = procedure_presentation - @field = params[:field] + @facet = Facet.find(procedure: , id: params[:field]) end def remove_filter diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 64db2927f..d63f851c0 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -271,10 +271,6 @@ class ProcedurePresentation < ApplicationRecord slice(:filters, :sort, :displayed_fields) end - def field_type(facet_id) - Facet.find(procedure:, id: facet_id).type - end - def field_enum(facet_id) facet = Facet.find(procedure:, id: facet_id) if facet.scope.present? diff --git a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml index 4bcafb3ed..3cd95da34 100644 --- a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml +++ b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml @@ -3,4 +3,4 @@ = t('views.instructeurs.dossiers.filters.title') - menu.with_form do - = render Dossiers::InstructeurFilterComponent.new(procedure: procedure, procedure_presentation: @procedure_presentation, statut: statut) + = render Dossiers::InstructeurFilterComponent.new(procedure:, procedure_presentation: @procedure_presentation, statut:) diff --git a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml index a5cd42c91..4d88e0852 100644 --- a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml +++ b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml @@ -1,2 +1,2 @@ = turbo_stream.replace 'filter-component' do - = render Dossiers::InstructeurFilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, field_id: @field) + = render Dossiers::InstructeurFilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, facet: @facet) From 772424ff5e16e67897ef55bb6983b701060e22ed Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 17:53:04 +0200 Subject: [PATCH 10/25] move field_enum to instructeur filter component --- app/components/application_component.rb | 4 +++ .../dossiers/instructeur_filter_component.rb | 26 ++++++++++++-- app/models/procedure_presentation.rb | 15 -------- .../instructeur_filter_component_spec.rb | 35 +++++++++++++++++++ spec/models/procedure_presentation_spec.rb | 27 -------------- 5 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 spec/components/dossiers/instructeur_filter_component_spec.rb diff --git a/app/components/application_component.rb b/app/components/application_component.rb index da1e79249..b5b400157 100644 --- a/app/components/application_component.rb +++ b/app/components/application_component.rb @@ -8,6 +8,10 @@ class ApplicationComponent < ViewComponent::Base controller.current_user end + def current_instructeur + controller.current_instructeur + end + def current_administrateur controller.current_administrateur end diff --git a/app/components/dossiers/instructeur_filter_component.rb b/app/components/dossiers/instructeur_filter_component.rb index eecef1897..6337b1bd5 100644 --- a/app/components/dossiers/instructeur_filter_component.rb +++ b/app/components/dossiers/instructeur_filter_component.rb @@ -1,4 +1,6 @@ class Dossiers::InstructeurFilterComponent < ApplicationComponent + attr_reader :procedure, :procedure_presentation, :statut, :facet + def initialize(procedure:, procedure_presentation:, statut:, facet: nil) @procedure = procedure @procedure_presentation = procedure_presentation @@ -6,12 +8,20 @@ class Dossiers::InstructeurFilterComponent < ApplicationComponent @facet = facet end - attr_reader :procedure, :procedure_presentation, :statut, :facet - def facet_type = facet.present? ? facet.type : :text def options_for_select_of_field - procedure_presentation.field_enum(field_id) + if facet.scope.present? + I18n.t(facet.scope).map(&:to_a).map(&:reverse) + elsif facet.table == 'groupe_instructeur' + current_instructeur.groupe_instructeurs.filter_map do + if _1.procedure_id == procedure.id + [_1.label, _1.id] + end + end + else + find_type_de_champ(facet.column).options_for_select + end end def filter_react_props @@ -25,4 +35,14 @@ class Dossiers::InstructeurFilterComponent < ApplicationComponent data: { no_autosubmit: 'input blur', no_autosubmit_on_empty: 'true', autosubmit_target: 'input' } } end + + private + + def find_type_de_champ(column) + TypeDeChamp + .joins(:revision_types_de_champ) + .where(revision_types_de_champ: { revision_id: procedure.revisions }) + .order(created_at: :desc) + .find_by(stable_id: column) + end end diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index d63f851c0..406b4f14c 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -271,21 +271,6 @@ class ProcedurePresentation < ApplicationRecord slice(:filters, :sort, :displayed_fields) end - def field_enum(facet_id) - facet = Facet.find(procedure:, id: facet_id) - if facet.scope.present? - I18n.t(facet.scope).map(&:to_a).map(&:reverse) - elsif facet.table == 'groupe_instructeur' - instructeur.groupe_instructeurs.filter_map do - if _1.procedure_id == procedure.id - [_1.label, _1.id] - end - end - else - find_type_de_champ(facet.column).options_for_select - end - end - def sortable?(field) sort['table'] == field.table && sort['column'] == field.column diff --git a/spec/components/dossiers/instructeur_filter_component_spec.rb b/spec/components/dossiers/instructeur_filter_component_spec.rb new file mode 100644 index 000000000..5cd6d9fe0 --- /dev/null +++ b/spec/components/dossiers/instructeur_filter_component_spec.rb @@ -0,0 +1,35 @@ +describe Dossiers::InstructeurFilterComponent, type: :component do + let(:component) { described_class.new(procedure:, procedure_presentation:, statut:, facet:) } + + let(:instructeur) { create(:instructeur) } + let(:procedure) { create(:procedure, instructeurs: [instructeur]) } + let(:procedure_presentation) { nil } + let(:statut) { nil } + + before do + allow(component).to receive(:current_instructeur).and_return(instructeur) + end + + describe '.options_for_select_of_field' do + subject { component.options_for_select_of_field } + + context "facet is groupe_instructeur" do + let(:facet) { double("Facet", scope: nil, table: 'groupe_instructeur') } + let!(:gi_2) { instructeur.groupe_instructeurs.create(label: 'gi2', procedure:) } + let!(:gi_3) { instructeur.groupe_instructeurs.create(label: 'gi3', procedure: create(:procedure)) } + + it { is_expected.to eq([['défaut', procedure.defaut_groupe_instructeur.id], ['gi2', gi_2.id]]) } + end + + context 'when facet is dropdown' do + let(:types_de_champ_public) { [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }] } + let(:procedure) { create(:procedure, :published, types_de_champ_public:) } + let(:drop_down_stable_id) { procedure.active_revision.types_de_champ.first.stable_id } + let(:facet) { Facet.new(table: 'type_de_champ', scope: nil, column: drop_down_stable_id) } + + it 'find most recent tdc' do + is_expected.to eq(['Paris', 'Lyon', 'Marseille']) + end + end + end +end diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 0c15fabfc..55d486892 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -904,31 +904,4 @@ describe ProcedurePresentation do end end end - - describe '#field_enum' do - context "field is groupe_instructeur" do - let!(:gi_2) { instructeur.groupe_instructeurs.create(label: 'gi2', procedure:) } - let!(:gi_3) { instructeur.groupe_instructeurs.create(label: 'gi3', procedure: create(:procedure)) } - - subject { procedure_presentation.field_enum('groupe_instructeur/id') } - - it { is_expected.to eq([['défaut', procedure.defaut_groupe_instructeur.id], ['gi2', gi_2.id]]) } - end - - context 'when field is dropdown' do - let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :text }], types_de_champ_private: [{}]) } - let(:tdc) { procedure.published_revision.types_de_champ_public.first } - before do - procedure.draft_revision - .find_and_ensure_exclusive_use(tdc.stable_id) - .update(type_champ: :drop_down_list, - drop_down_list_value: "Paris\nLyon\nMarseille") - procedure.publish_revision! - end - subject { procedure_presentation.field_enum("type_de_champ/#{tdc.id}") } - it 'find most recent tdc' do - expect(subject).to eq(["Paris", "Lyon", "Marseille"]) - end - end - end end From 7a1c25271d874cf9078526a25abab15f5237f10a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 18:03:15 +0200 Subject: [PATCH 11/25] small refactors --- app/models/facet.rb | 6 +++++- app/models/procedure_presentation.rb | 5 +---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/facet.rb b/app/models/facet.rb index 747eda1e1..d068d831e 100644 --- a/app/models/facet.rb +++ b/app/models/facet.rb @@ -1,4 +1,6 @@ class Facet + attr_reader :table, :column, :label, :classname, :virtual, :type, :scope, :value_column, :filterable + TYPE_DE_CHAMP = 'type_de_champ' def initialize(table:, column:, label: nil, virtual: false, type: :text, value_column: :value, filterable: true, classname: '', scope: '') @@ -13,7 +15,9 @@ class Facet @filterable = filterable end - attr_reader :table, :column, :label, :classname, :virtual, :type, :scope, :value_column, :filterable + def id + "#{table}/#{column}" + end def ==(other) other.to_json == to_json diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 406b4f14c..6c204a969 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -236,10 +236,7 @@ class ProcedurePresentation < ApplicationRecord end def update_displayed_fields(facet_ids) - if facet_ids.nil? - facet_ids = [] - end - + facet_ids = Array.wrap(facet_ids) facets = facet_ids.map { |id| Facet.find(procedure:, id:) } update!(displayed_fields: facets) From c5c2d5782d64e47b43a04abe63d572c8e8139bde Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 18:13:26 +0200 Subject: [PATCH 12/25] lets keep it private --- app/models/procedure_presentation.rb | 214 ++++++++++----------- spec/models/procedure_presentation_spec.rb | 4 +- 2 files changed, 109 insertions(+), 109 deletions(-) diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 6c204a969..07b8437a5 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -48,113 +48,6 @@ class ProcedurePresentation < ApplicationRecord ] end - def sorted_ids(dossiers, count) - table, column, order = sort.values_at(TABLE, COLUMN, 'order') - - case table - when 'notifications' - dossiers_id_with_notification = dossiers.merge(instructeur.followed_dossiers).with_notifications.ids - if order == 'desc' - dossiers_id_with_notification + - (dossiers.order('dossiers.updated_at desc').ids - dossiers_id_with_notification) - else - (dossiers.order('dossiers.updated_at asc').ids - dossiers_id_with_notification) + - dossiers_id_with_notification - end - when TYPE_DE_CHAMP - ids = dossiers - .with_type_de_champ(column) - .order("champs.value #{order}") - .pluck(:id) - if ids.size != count - rest = dossiers.where.not(id: ids).order(id: order).pluck(:id) - order == 'asc' ? ids + rest : rest + ids - else - ids - end - when 'followers_instructeurs' - assert_supported_column(table, column) - # LEFT OUTER JOIN allows to keep dossiers without assigned instructeurs yet - dossiers - .includes(:followers_instructeurs) - .joins('LEFT OUTER JOIN users instructeurs_users ON instructeurs_users.id = instructeurs.user_id') - .order("instructeurs_users.email #{order}") - .pluck(:id) - .uniq - when 'avis' - dossiers.includes(table) - .order("#{self.class.sanitized_column(table, column)} #{order}") - .pluck(:id) - .uniq - when 'self', 'user', 'individual', 'etablissement', 'groupe_instructeur' - (table == 'self' ? dossiers : dossiers.includes(table)) - .order("#{self.class.sanitized_column(table, column)} #{order}") - .pluck(:id) - end - end - - def filtered_ids(dossiers, statut) - filters.fetch(statut) - .group_by { |filter| filter.values_at(TABLE, COLUMN) } - .map do |(table, column), filters| - values = filters.pluck('value') - value_column = filters.pluck('value_column').compact.first || :value - case table - when 'self' - field = Facet.dossier_facets(procedure:).find { |h| h.column == column } - if field.type == :date - dates = values - .filter_map { |v| Time.zone.parse(v).beginning_of_day rescue nil } - - dossiers.filter_by_datetimes(column, dates) - elsif field.column == "state" && values.include?("pending_correction") - dossiers.joins(:corrections).where(corrections: DossierCorrection.pending) - elsif field.column == "state" && values.include?("en_construction") - dossiers.where("dossiers.#{column} IN (?)", values).includes(:corrections).where.not(corrections: DossierCorrection.pending) - else - dossiers.where("dossiers.#{column} IN (?)", values) - end - when TYPE_DE_CHAMP - dossiers.with_type_de_champ(column) - .filter_ilike(:champs, value_column, values) - when 'etablissement' - if column == 'entreprise_date_creation' - dates = values - .filter_map { |v| v.to_date rescue nil } - - dossiers - .includes(table) - .where(table.pluralize => { column => dates }) - else - dossiers - .includes(table) - .filter_ilike(table, column, values) - end - when 'followers_instructeurs' - assert_supported_column(table, column) - dossiers - .includes(:followers_instructeurs) - .joins('INNER JOIN users instructeurs_users ON instructeurs_users.id = instructeurs.user_id') - .filter_ilike('instructeurs_users', :email, values) - when 'user', 'individual', 'avis' - dossiers - .includes(table) - .filter_ilike(table, column, values) - when 'groupe_instructeur' - assert_supported_column(table, column) - if column == 'label' - dossiers - .joins(:groupe_instructeur) - .filter_ilike(table, column, values) - else - dossiers - .joins(:groupe_instructeur) - .where(groupe_instructeur_id: values) - end - end.pluck(:id) - end.reduce(:&) - end - def filtered_sorted_ids(dossiers, statut, count: nil) dossiers_by_statut = dossiers.by_statut(statut, instructeur) dossiers_sorted_ids = self.sorted_ids(dossiers_by_statut, count || dossiers_by_statut.size) @@ -287,6 +180,113 @@ class ProcedurePresentation < ApplicationRecord private + def sorted_ids(dossiers, count) + table, column, order = sort.values_at(TABLE, COLUMN, 'order') + + case table + when 'notifications' + dossiers_id_with_notification = dossiers.merge(instructeur.followed_dossiers).with_notifications.ids + if order == 'desc' + dossiers_id_with_notification + + (dossiers.order('dossiers.updated_at desc').ids - dossiers_id_with_notification) + else + (dossiers.order('dossiers.updated_at asc').ids - dossiers_id_with_notification) + + dossiers_id_with_notification + end + when TYPE_DE_CHAMP + ids = dossiers + .with_type_de_champ(column) + .order("champs.value #{order}") + .pluck(:id) + if ids.size != count + rest = dossiers.where.not(id: ids).order(id: order).pluck(:id) + order == 'asc' ? ids + rest : rest + ids + else + ids + end + when 'followers_instructeurs' + assert_supported_column(table, column) + # LEFT OUTER JOIN allows to keep dossiers without assigned instructeurs yet + dossiers + .includes(:followers_instructeurs) + .joins('LEFT OUTER JOIN users instructeurs_users ON instructeurs_users.id = instructeurs.user_id') + .order("instructeurs_users.email #{order}") + .pluck(:id) + .uniq + when 'avis' + dossiers.includes(table) + .order("#{self.class.sanitized_column(table, column)} #{order}") + .pluck(:id) + .uniq + when 'self', 'user', 'individual', 'etablissement', 'groupe_instructeur' + (table == 'self' ? dossiers : dossiers.includes(table)) + .order("#{self.class.sanitized_column(table, column)} #{order}") + .pluck(:id) + end + end + + def filtered_ids(dossiers, statut) + filters.fetch(statut) + .group_by { |filter| filter.values_at(TABLE, COLUMN) } + .map do |(table, column), filters| + values = filters.pluck('value') + value_column = filters.pluck('value_column').compact.first || :value + case table + when 'self' + field = Facet.dossier_facets(procedure:).find { |h| h.column == column } + if field.type == :date + dates = values + .filter_map { |v| Time.zone.parse(v).beginning_of_day rescue nil } + + dossiers.filter_by_datetimes(column, dates) + elsif field.column == "state" && values.include?("pending_correction") + dossiers.joins(:corrections).where(corrections: DossierCorrection.pending) + elsif field.column == "state" && values.include?("en_construction") + dossiers.where("dossiers.#{column} IN (?)", values).includes(:corrections).where.not(corrections: DossierCorrection.pending) + else + dossiers.where("dossiers.#{column} IN (?)", values) + end + when TYPE_DE_CHAMP + dossiers.with_type_de_champ(column) + .filter_ilike(:champs, value_column, values) + when 'etablissement' + if column == 'entreprise_date_creation' + dates = values + .filter_map { |v| v.to_date rescue nil } + + dossiers + .includes(table) + .where(table.pluralize => { column => dates }) + else + dossiers + .includes(table) + .filter_ilike(table, column, values) + end + when 'followers_instructeurs' + assert_supported_column(table, column) + dossiers + .includes(:followers_instructeurs) + .joins('INNER JOIN users instructeurs_users ON instructeurs_users.id = instructeurs.user_id') + .filter_ilike('instructeurs_users', :email, values) + when 'user', 'individual', 'avis' + dossiers + .includes(table) + .filter_ilike(table, column, values) + when 'groupe_instructeur' + assert_supported_column(table, column) + if column == 'label' + dossiers + .joins(:groupe_instructeur) + .filter_ilike(table, column, values) + else + dossiers + .joins(:groupe_instructeur) + .where(groupe_instructeur_id: values) + end + end.pluck(:id) + end.reduce(:&) + end + # type_de_champ/4373429 def sort_to_facet_id(sort) [sort[TABLE], sort[COLUMN]].join(SLASH) diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 55d486892..546721eb8 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -97,7 +97,7 @@ describe ProcedurePresentation do let(:sort) { { 'table' => table, 'column' => column, 'order' => order } } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to, sort: sort) } - subject { procedure_presentation.sorted_ids(procedure.dossiers, procedure.dossiers.count) } + subject { procedure_presentation.send(:sorted_ids, procedure.dossiers, procedure.dossiers.count) } context 'for notifications table' do let(:table) { 'notifications' } @@ -361,7 +361,7 @@ describe ProcedurePresentation do describe '#filtered_ids' do let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to, filters: { "suivis" => filter }) } - subject { procedure_presentation.filtered_ids(procedure.dossiers.joins(:user), 'suivis') } + subject { procedure_presentation.send(:filtered_ids, procedure.dossiers.joins(:user), 'suivis') } context 'for self table' do context 'for created_at column' do From 14c5c26bb626f4a90b0cedff9074c07f06532324 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 22:33:17 +0200 Subject: [PATCH 13/25] rubocop --- app/controllers/instructeurs/procedures_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 8d0e66b7f..6332107b5 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -159,7 +159,7 @@ module Instructeurs @statut = statut @procedure = procedure @procedure_presentation = procedure_presentation - @facet = Facet.find(procedure: , id: params[:field]) + @facet = Facet.find(procedure:, id: params[:field]) end def remove_filter From e19f1bd8c2a5ea7fd91676e79b28182b7e16bf6b Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Fri, 19 Jul 2024 22:59:07 +0200 Subject: [PATCH 14/25] sortable? -> sorted_by --- app/models/procedure_presentation.rb | 8 ++++---- app/views/instructeurs/procedures/_header_field.html.haml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 07b8437a5..fb1c1d87c 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -161,13 +161,13 @@ class ProcedurePresentation < ApplicationRecord slice(:filters, :sort, :displayed_fields) end - def sortable?(field) - sort['table'] == field.table && - sort['column'] == field.column + def sorted_by?(facet) + sort['table'] == facet.table && + sort['column'] == facet.column end def aria_sort(order, field) - if sortable?(field) + if sorted_by?(field) if order == 'asc' { "aria-sort": "ascending" } elsif order == 'desc' diff --git a/app/views/instructeurs/procedures/_header_field.html.haml b/app/views/instructeurs/procedures/_header_field.html.haml index e66d2f683..2d46b77b5 100644 --- a/app/views/instructeurs/procedures/_header_field.html.haml +++ b/app/views/instructeurs/procedures/_header_field.html.haml @@ -1,6 +1,6 @@ %th{ @procedure_presentation.aria_sort(@procedure_presentation.sort['order'], field), scope: "col", class: field.classname } = link_to update_sort_instructeur_procedure_path(@procedure, table: field.table, column: field.column, order: @procedure_presentation.opposite_order_for(field.table, field.column)) do - - if @procedure_presentation.sortable?(field) + - if @procedure_presentation.sorted_by?(field) - if @procedure_presentation.sort['order'] == 'asc' #{field.label} ↑ - else From 379c9fb81293c4e19a200d8bf014389e077af9d0 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Sat, 20 Jul 2024 09:23:46 +0200 Subject: [PATCH 15/25] procedure should provide its facets --- .../instructeurs/procedures_controller.rb | 2 +- app/models/concerns/facets_concern.rb | 107 ++++++++++++++++++ app/models/facet.rb | 101 ----------------- app/models/procedure.rb | 1 + app/models/procedure_presentation.rb | 18 +-- .../facets_concern_spec.rb} | 12 +- spec/models/procedure_presentation_spec.rb | 4 +- 7 files changed, 123 insertions(+), 122 deletions(-) create mode 100644 app/models/concerns/facets_concern.rb rename spec/models/{facet_spec.rb => concerns/facets_concern_spec.rb} (97%) diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 6332107b5..96824488d 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -159,7 +159,7 @@ module Instructeurs @statut = statut @procedure = procedure @procedure_presentation = procedure_presentation - @facet = Facet.find(procedure:, id: params[:field]) + @facet = procedure.find_facet(id: params[:field]) end def remove_filter diff --git a/app/models/concerns/facets_concern.rb b/app/models/concerns/facets_concern.rb new file mode 100644 index 000000000..1ac45dbb8 --- /dev/null +++ b/app/models/concerns/facets_concern.rb @@ -0,0 +1,107 @@ +module FacetsConcern + extend ActiveSupport::Concern + + included do + TYPE_DE_CHAMP = 'type_de_champ' + + def find_facet(id:) + facets.find { |f| f.id == id } + end + + def facets + facets = dossier_facets + + facets.push( + Facet.new(table: 'user', column: 'email', type: :text), + Facet.new(table: 'followers_instructeurs', column: 'email', type: :text), + Facet.new(table: 'groupe_instructeur', column: 'id', type: :enum), + Facet.new(table: 'avis', column: 'question_answer', filterable: false) + ) + + if for_individual + facets.push( + Facet.new(table: "individual", column: "prenom", type: :text), + Facet.new(table: "individual", column: "nom", type: :text), + Facet.new(table: "individual", column: "gender", type: :text) + ) + end + + if !for_individual + facets.push( + Facet.new(table: 'etablissement', column: 'entreprise_siren', type: :text), + Facet.new(table: 'etablissement', column: 'entreprise_forme_juridique', type: :text), + Facet.new(table: 'etablissement', column: 'entreprise_nom_commercial', type: :text), + Facet.new(table: 'etablissement', column: 'entreprise_raison_sociale', type: :text), + Facet.new(table: 'etablissement', column: 'entreprise_siret_siege_social', type: :text), + Facet.new(table: 'etablissement', column: 'entreprise_date_creation', type: :date), + Facet.new(table: 'etablissement', column: 'siret', type: :text), + Facet.new(table: 'etablissement', column: 'libelle_naf', type: :text), + Facet.new(table: 'etablissement', column: 'code_postal', type: :text) + ) + end + + facets.concat(types_de_champ_facets) + + facets + end + + def dossier_facets + [ + Facet.new(table: 'self', column: 'created_at', type: :date), + Facet.new(table: 'self', column: 'updated_at', type: :date), + Facet.new(table: 'self', column: 'depose_at', type: :date), + Facet.new(table: 'self', column: 'en_construction_at', type: :date), + Facet.new(table: 'self', column: 'en_instruction_at', type: :date), + Facet.new(table: 'self', column: 'processed_at', type: :date), + *sva_svr_facets(for_filters: true), + Facet.new(table: 'self', column: 'updated_since', type: :date, virtual: true), + Facet.new(table: 'self', column: 'depose_since', type: :date, virtual: true), + Facet.new(table: 'self', column: 'en_construction_since', type: :date, virtual: true), + Facet.new(table: 'self', column: 'en_instruction_since', type: :date, virtual: true), + Facet.new(table: 'self', column: 'processed_since', type: :date, virtual: true), + Facet.new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true) + ].compact_blank + end + + def sva_svr_facets(for_filters: false) + return if !sva_svr_enabled? + + i18n_scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] + + facets = [] + facets << Facet.new(table: 'self', column: 'sva_svr_decision_on', + type: :date, + label: I18n.t("#{sva_svr_decision}_decision_on", scope: i18n_scope), + classname: for_filters ? '' : 'sva-col') + + if for_filters + facets << Facet.new(table: 'self', column: 'sva_svr_decision_before', + label: I18n.t("#{sva_svr_decision}_decision_before", scope: i18n_scope), + type: :date, virtual: true) + end + + facets + end + + private + + def types_de_champ_facets + types_de_champ_for_procedure_presentation + .pluck(:type_champ, :libelle, :stable_id) + .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } + .flat_map do |(type_champ, libelle, stable_id)| + tdc = TypeDeChamp.new(type_champ:, libelle:, stable_id:) + + tdc.dynamic_type.search_paths.map do |path_struct| + Facet.new( + table: TYPE_DE_CHAMP, + column: tdc.stable_id.to_s, + label: path_struct[:libelle], + type: TypeDeChamp.filter_hash_type(tdc.type_champ), + value_column: path_struct[:path] + ) + end + end + end + end +end diff --git a/app/models/facet.rb b/app/models/facet.rb index d068d831e..910106f7f 100644 --- a/app/models/facet.rb +++ b/app/models/facet.rb @@ -1,8 +1,6 @@ class Facet attr_reader :table, :column, :label, :classname, :virtual, :type, :scope, :value_column, :filterable - TYPE_DE_CHAMP = 'type_de_champ' - def initialize(table:, column:, label: nil, virtual: false, type: :text, value_column: :value, filterable: true, classname: '', scope: '') @table = table @column = column @@ -28,103 +26,4 @@ class Facet table:, column:, label:, classname:, virtual:, type:, scope:, value_column:, filterable: } end - - def self.find(procedure:, id:) - facets(procedure:).find { _1.id == id } - end - - def self.dossier_facets(procedure:) - [ - new(table: 'self', column: 'created_at', type: :date), - new(table: 'self', column: 'updated_at', type: :date), - new(table: 'self', column: 'depose_at', type: :date), - new(table: 'self', column: 'en_construction_at', type: :date), - new(table: 'self', column: 'en_instruction_at', type: :date), - new(table: 'self', column: 'processed_at', type: :date), - *sva_svr_facets(procedure:, for_filters: true), - new(table: 'self', column: 'updated_since', type: :date, virtual: true), - new(table: 'self', column: 'depose_since', type: :date, virtual: true), - new(table: 'self', column: 'en_construction_since', type: :date, virtual: true), - new(table: 'self', column: 'en_instruction_since', type: :date, virtual: true), - new(table: 'self', column: 'processed_since', type: :date, virtual: true), - new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true) - ].compact_blank - end - - def self.facets(procedure:) - facets = Facet.dossier_facets(procedure:) - - facets.push( - new(table: 'user', column: 'email', type: :text), - new(table: 'followers_instructeurs', column: 'email', type: :text), - new(table: 'groupe_instructeur', column: 'id', type: :enum), - new(table: 'avis', column: 'question_answer', filterable: false) - ) - - if procedure.for_individual - facets.push( - new(table: "individual", column: "prenom", type: :text), - new(table: "individual", column: "nom", type: :text), - new(table: "individual", column: "gender", type: :text) - ) - end - - if !procedure.for_individual - facets.push( - new(table: 'etablissement', column: 'entreprise_siren', type: :text), - new(table: 'etablissement', column: 'entreprise_forme_juridique', type: :text), - new(table: 'etablissement', column: 'entreprise_nom_commercial', type: :text), - new(table: 'etablissement', column: 'entreprise_raison_sociale', type: :text), - new(table: 'etablissement', column: 'entreprise_siret_siege_social', type: :text), - new(table: 'etablissement', column: 'entreprise_date_creation', type: :date), - new(table: 'etablissement', column: 'siret', type: :text), - new(table: 'etablissement', column: 'libelle_naf', type: :text), - new(table: 'etablissement', column: 'code_postal', type: :text) - ) - end - - facets.concat(types_de_champ_facets(procedure)) - - facets - end - - def self.types_de_champ_facets(procedure) - procedure - .types_de_champ_for_procedure_presentation - .pluck(:type_champ, :libelle, :stable_id) - .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } - .flat_map do |(type_champ, libelle, stable_id)| - tdc = TypeDeChamp.new(type_champ:, libelle:, stable_id:) - - tdc.dynamic_type.search_paths.map do |path_struct| - new( - table: TYPE_DE_CHAMP, - column: tdc.stable_id.to_s, - label: path_struct[:libelle], - type: TypeDeChamp.filter_hash_type(tdc.type_champ), - value_column: path_struct[:path] - ) - end - end - end - - def self.sva_svr_facets(procedure:, for_filters: false) - return if !procedure.sva_svr_enabled? - - i18n_scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] - - facets = [] - facets << new(table: 'self', column: 'sva_svr_decision_on', - type: :date, - label: I18n.t("#{procedure.sva_svr_decision}_decision_on", scope: i18n_scope), - classname: for_filters ? '' : 'sva-col') - - if for_filters - facets << new(table: 'self', column: 'sva_svr_decision_before', - label: I18n.t("#{procedure.sva_svr_decision}_decision_before", scope: i18n_scope), - type: :date, virtual: true) - end - - facets - end end diff --git a/app/models/procedure.rb b/app/models/procedure.rb index c336dcce5..d86f30b6d 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -6,6 +6,7 @@ class Procedure < ApplicationRecord include ProcedureSVASVRConcern include ProcedureChorusConcern include PiecesJointesListConcern + include FacetsConcern include Discard::Model self.discard_column = :hidden_at diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index fb1c1d87c..5fc4095b0 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -26,13 +26,13 @@ class ProcedurePresentation < ApplicationRecord def displayable_fields_for_select [ - Facet.facets(procedure:).reject(&:virtual).map { |facet| [facet.label, facet.id] }, + procedure.facets.reject(&:virtual).map { |facet| [facet.label, facet.id] }, displayed_fields.map { Facet.new(**_1.deep_symbolize_keys).id } ] end def filterable_fields_options - Facet.facets(procedure:).filter_map do |facet| + procedure.facets.filter_map do |facet| next if facet.filterable == false [facet.label, facet.id] @@ -44,7 +44,7 @@ class ProcedurePresentation < ApplicationRecord Facet.new(table: 'self', column: 'id', classname: 'number-col'), *displayed_fields.map { Facet.new(**_1.deep_symbolize_keys) }, Facet.new(table: 'self', column: 'state', classname: 'state-col'), - *Facet.sva_svr_facets(procedure:) + *procedure.sva_svr_facets ] end @@ -72,7 +72,7 @@ class ProcedurePresentation < ApplicationRecord instructeur.groupe_instructeurs .find { _1.id == filter['value'].to_i }&.label || filter['value'] else - facet = Facet.facets(procedure:).find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } + facet = procedure.facets.find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } if facet.type == :date parsed_date = safe_parse_date(filter['value']) @@ -92,7 +92,7 @@ class ProcedurePresentation < ApplicationRecord def add_filter(statut, facet_id, value) if value.present? - facet = Facet.find(procedure:, id: facet_id) + facet = procedure.find_facet(id: facet_id) label = facet.label column = facet.column table = facet.table @@ -117,7 +117,7 @@ class ProcedurePresentation < ApplicationRecord end def remove_filter(statut, facet_id, value) - facet = Facet.find(procedure:, id: facet_id) + facet = procedure.find_facet(id: facet_id) table, column = facet.table, facet.column updated_filters = filters.dup @@ -130,7 +130,7 @@ class ProcedurePresentation < ApplicationRecord def update_displayed_fields(facet_ids) facet_ids = Array.wrap(facet_ids) - facets = facet_ids.map { |id| Facet.find(procedure:, id:) } + facets = facet_ids.map { |id| procedure.find_facet(id:) } update!(displayed_fields: facets) @@ -233,7 +233,7 @@ class ProcedurePresentation < ApplicationRecord value_column = filters.pluck('value_column').compact.first || :value case table when 'self' - field = Facet.dossier_facets(procedure:).find { |h| h.column == column } + field = procedure.dossier_facets.find { |h| h.column == column } if field.type == :date dates = values .filter_map { |v| Time.zone.parse(v).beginning_of_day rescue nil } @@ -348,7 +348,7 @@ class ProcedurePresentation < ApplicationRecord end def valid_columns_for_table(table) - @column_whitelist ||= Facet.facets(procedure:) + @column_whitelist ||= procedure.facets .group_by(&:table) .transform_values { |facets| Set.new(facets.map(&:column)) } diff --git a/spec/models/facet_spec.rb b/spec/models/concerns/facets_concern_spec.rb similarity index 97% rename from spec/models/facet_spec.rb rename to spec/models/concerns/facets_concern_spec.rb index 77b951062..5ba738441 100644 --- a/spec/models/facet_spec.rb +++ b/spec/models/concerns/facets_concern_spec.rb @@ -1,5 +1,7 @@ -describe Facet do +describe FacetsConcern do describe "#facets" do + subject { procedure.facets } + context 'when the procedure can have a SIRET number' do let(:procedure) do create(:procedure, @@ -44,8 +46,6 @@ describe Facet do ].map { Facet.new(**_1) } } - subject { Facet.facets(procedure:) } - context 'with explication/header_sections' do let(:types_de_champ_public) { Array.new(4) { { type: :text } } } let(:types_de_champ_private) { Array.new(4) { { type: :text } } } @@ -73,8 +73,6 @@ describe Facet do let(:procedure) { create(:procedure, :for_individual) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - subject { Facet.facets(procedure:) } - it { is_expected.to include(name_field, surname_field, gender_field) } end @@ -85,8 +83,6 @@ describe Facet do let(:decision_on) { Facet.new(label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } let(:decision_before_field) { Facet.new(label: "Date décision SVA avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } - subject { Facet.facets(procedure:) } - it { is_expected.to include(decision_on, decision_before_field) } end @@ -97,8 +93,6 @@ describe Facet do let(:decision_on) { Facet.new(label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } let(:decision_before_field) { Facet.new(label: "Date décision SVR avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } - subject { Facet.facets(procedure:) } - it { is_expected.to include(decision_on, decision_before_field) } end end diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 546721eb8..926d476f9 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -58,7 +58,7 @@ describe ProcedurePresentation do let(:excluded_displayable_field) { Facet.new(label: "label1", table: "table1", column: "column1", virtual: true) } before do - allow(Facet).to receive(:facets).and_return([ + allow(procedure).to receive(:facets).and_return([ default_user_email, excluded_displayable_field ]) @@ -78,7 +78,7 @@ describe ProcedurePresentation do end before do - allow(Facet).to receive(:facets).and_return(included_displayable_field) + allow(procedure).to receive(:facets).and_return(included_displayable_field) end it { expect(subject.filterable_fields_options).to eq([["label1", "table1/column1"], ["depose_since", "self/depose_since"]]) } From 41ea39abb166d103710fdf6d03e3de1c5df7194b Mon Sep 17 00:00:00 2001 From: mfo Date: Sat, 20 Jul 2024 07:43:44 +0200 Subject: [PATCH 16/25] ProcedurePresentation.filterable_fields_options -> InstructeurFilterComponent.filterable_fields_options --- .../dossiers/instructeur_filter_component.rb | 10 +++++++- app/models/procedure_presentation.rb | 8 ------- .../instructeur_filter_component_spec.rb | 19 +++++++++++++++ spec/models/procedure_presentation_spec.rb | 24 ------------------- 4 files changed, 28 insertions(+), 33 deletions(-) diff --git a/app/components/dossiers/instructeur_filter_component.rb b/app/components/dossiers/instructeur_filter_component.rb index 6337b1bd5..66796b4da 100644 --- a/app/components/dossiers/instructeur_filter_component.rb +++ b/app/components/dossiers/instructeur_filter_component.rb @@ -27,7 +27,7 @@ class Dossiers::InstructeurFilterComponent < ApplicationComponent def filter_react_props { selected_key: facet.present? ? facet.id : '', - items: procedure_presentation.filterable_fields_options, + items: filterable_fields_options, name: :field, id: 'search-filter', 'aria-describedby': 'instructeur-filter-combo-label', @@ -36,6 +36,14 @@ class Dossiers::InstructeurFilterComponent < ApplicationComponent } end + def filterable_fields_options + procedure.facets.filter_map do |facet| + next if facet.filterable == false + + [facet.label, facet.id] + end + end + private def find_type_de_champ(column) diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 5fc4095b0..cd6853a68 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -31,14 +31,6 @@ class ProcedurePresentation < ApplicationRecord ] end - def filterable_fields_options - procedure.facets.filter_map do |facet| - next if facet.filterable == false - - [facet.label, facet.id] - end - end - def displayed_fields_for_headers [ Facet.new(table: 'self', column: 'id', classname: 'number-col'), diff --git a/spec/components/dossiers/instructeur_filter_component_spec.rb b/spec/components/dossiers/instructeur_filter_component_spec.rb index 5cd6d9fe0..801433961 100644 --- a/spec/components/dossiers/instructeur_filter_component_spec.rb +++ b/spec/components/dossiers/instructeur_filter_component_spec.rb @@ -10,6 +10,25 @@ describe Dossiers::InstructeurFilterComponent, type: :component do allow(component).to receive(:current_instructeur).and_return(instructeur) end + describe ".filterable_fields_options" do + context 'filders' do + let(:facet) { nil } + let(:included_displayable_field) do + [ + Facet.new(label: 'email', table: 'user', column: 'email'), + Facet.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) + ] + end + + before do + allow(Facet).to receive(:facets).and_return(included_displayable_field) + end + subject { component.filterable_fields_options } + + it { is_expected.to eq([["email", "user/email"], ["depose_since", "self/depose_since"]]) } + end + end + describe '.options_for_select_of_field' do subject { component.options_for_select_of_field } diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 926d476f9..a778655b6 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -66,30 +66,6 @@ describe ProcedurePresentation do it { expect(subject.displayable_fields_for_select).to eq([[["email", "user/email"]], ["user/email"]]) } end - describe "#filterable_fields_options" do - subject { create(:procedure_presentation, assign_to: assign_to) } - - context 'filders' do - let(:included_displayable_field) do - [ - Facet.new(label: 'email', table: 'user', column: 'email'), - Facet.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) - ] - end - - before do - allow(procedure).to receive(:facets).and_return(included_displayable_field) - end - - it { expect(subject.filterable_fields_options).to eq([["label1", "table1/column1"], ["depose_since", "self/depose_since"]]) } - end - context 'with rna' do - let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :rna, libelle: 'rna', stable_id: 1 }]) } - it { expect(subject.filterable_fields_options.map { _1[0] }).to include('rna – commune') } - it { expect(subject.filterable_fields_options.map { _1[1] }).to include('type_de_champ/1->data.commune') } - it { expect(subject.filterable_fields_options).to eq([["email", "user/email"], ["depose_since", "self/depose_since"]]) } - end - end describe '#sorted_ids' do let(:instructeur) { create(:instructeur) } From 60c6c86d708233753732418dbf1b07728f8ab195 Mon Sep 17 00:00:00 2001 From: mfo Date: Sat, 20 Jul 2024 08:08:23 +0200 Subject: [PATCH 17/25] ProcedurePresentation.displayable_fields_for_select -> InstructeurFacetPickerComponent.displayable_fields_for_select --- .../instructeur_facet_picker_component.rb | 16 ++++++++++++++++ .../instructeur_facet_picker_component.en.yml | 3 +++ .../instructeur_facet_picker_component.fr.yml | 3 +++ .../instructeur_facet_picker_component.html.haml | 5 +++++ .../instructeurs/procedures_controller.rb | 1 - app/models/procedure_presentation.rb | 7 ------- app/views/instructeurs/procedures/show.html.haml | 7 +------ config/locales/en.yml | 1 - config/locales/fr.yml | 1 - .../instructeur_filter_component_spec.rb | 5 ++--- spec/models/procedure_presentation_spec.rb | 16 ---------------- 11 files changed, 30 insertions(+), 35 deletions(-) create mode 100644 app/components/dossiers/instructeur_facet_picker_component.rb create mode 100644 app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.en.yml create mode 100644 app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.fr.yml create mode 100644 app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.html.haml diff --git a/app/components/dossiers/instructeur_facet_picker_component.rb b/app/components/dossiers/instructeur_facet_picker_component.rb new file mode 100644 index 000000000..0b2b25a0f --- /dev/null +++ b/app/components/dossiers/instructeur_facet_picker_component.rb @@ -0,0 +1,16 @@ +class Dossiers::InstructeurFacetPickerComponent < ApplicationComponent + attr_reader :procedure, :procedure_presentation + + def initialize(procedure:, procedure_presentation:) + @procedure = procedure + @procedure_presentation = procedure_presentation + @displayable_fields_for_select, @displayable_fields_selected = displayable_fields_for_select + end + + def displayable_fields_for_select + [ + procedure.facets.reject(&:virtual).map { |facet| [facet.label, facet.id] }, + procedure_presentation.displayed_fields.map { Facet.new(**_1.deep_symbolize_keys).id } + ] + end +end diff --git a/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.en.yml b/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.en.yml new file mode 100644 index 000000000..4c5d618b4 --- /dev/null +++ b/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.en.yml @@ -0,0 +1,3 @@ +--- +en: + save: 'Save' diff --git a/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.fr.yml b/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.fr.yml new file mode 100644 index 000000000..8403f9691 --- /dev/null +++ b/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.fr.yml @@ -0,0 +1,3 @@ +--- +fr: + save: 'Enregistrer' diff --git a/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.html.haml b/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.html.haml new file mode 100644 index 000000000..e0a6cd958 --- /dev/null +++ b/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.html.haml @@ -0,0 +1,5 @@ += form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do + %react-fragment + = render ReactComponent.new "ComboBox/MultiComboBox", items: @displayable_fields_for_select, selected_keys: @displayable_fields_selected, name: 'values[]', 'aria-label': 'Colonne à afficher' + + = submit_tag t('.save'), class: 'fr-btn fr-btn--secondary' diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 96824488d..c7bd254b7 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -72,7 +72,6 @@ module Instructeurs @procedure_presentation = procedure_presentation @current_filters = current_filters - @displayable_fields_for_select, @displayable_fields_selected = procedure_presentation.displayable_fields_for_select @counts = current_instructeur .dossiers_count_summary(groupe_instructeur_ids) .symbolize_keys diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index cd6853a68..f3638529e 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -24,13 +24,6 @@ class ProcedurePresentation < ApplicationRecord validate :check_allowed_filter_columns validate :check_filters_max_length - def displayable_fields_for_select - [ - procedure.facets.reject(&:virtual).map { |facet| [facet.label, facet.id] }, - displayed_fields.map { Facet.new(**_1.deep_symbolize_keys).id } - ] - end - def displayed_fields_for_headers [ Facet.new(table: 'self', column: 'id', classname: 'number-col'), diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index a9fa7783f..e87989352 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -109,12 +109,7 @@ - menu.with_button_inner_html do = t('views.instructeurs.dossiers.personalize') - menu.with_form do - = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do - %react-fragment - = render ReactComponent.new "ComboBox/MultiComboBox", items: @displayable_fields_for_select, selected_keys: @displayable_fields_selected, name: 'values[]', 'aria-label': 'Colonne à afficher' - - = submit_tag t('views.instructeurs.dossiers.save'), class: 'fr-btn fr-btn--secondary' - + = render Dossiers::InstructeurFacetPickerComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation) %tbody = render Dossiers::BatchSelectMoreComponent.new(dossiers_count: @dossiers_count, filtered_sorted_ids: @filtered_sorted_ids) diff --git a/config/locales/en.yml b/config/locales/en.yml index e97328dee..50e1c8448 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -414,7 +414,6 @@ en: repasser_en_construction: Revert to submitted show_deleted_dossiers: Show deleted files follow_file: Follow-up the file - save: Save stop_follow: No longer follow no_file: No file dossier_synthesis: Summary of files diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 71dfd008a..7aba7889d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -419,7 +419,6 @@ fr: repasser_en_construction: Repasser en construction follow_file: Suivre le dossier stop_follow: Ne plus suivre - save: Enregistrer no_file: Aucun dossier dossier_synthesis: Synthèse des dossiers passer_en_instruction_blocked_by_pending_correction: | diff --git a/spec/components/dossiers/instructeur_filter_component_spec.rb b/spec/components/dossiers/instructeur_filter_component_spec.rb index 801433961..f7e9aee3f 100644 --- a/spec/components/dossiers/instructeur_filter_component_spec.rb +++ b/spec/components/dossiers/instructeur_filter_component_spec.rb @@ -20,9 +20,8 @@ describe Dossiers::InstructeurFilterComponent, type: :component do ] end - before do - allow(Facet).to receive(:facets).and_return(included_displayable_field) - end + before { allow(procedure).to receive(:facets).and_return(included_displayable_field) } + subject { component.filterable_fields_options } it { is_expected.to eq([["email", "user/email"], ["depose_since", "self/depose_since"]]) } diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index a778655b6..f3367e91e 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -51,22 +51,6 @@ describe ProcedurePresentation do end end - describe "#displayable_fields_for_select" do - subject { create(:procedure_presentation, assign_to: assign_to) } - - let(:default_user_email) { Facet.new(label: 'email', table: 'user', column: 'email') } - let(:excluded_displayable_field) { Facet.new(label: "label1", table: "table1", column: "column1", virtual: true) } - - before do - allow(procedure).to receive(:facets).and_return([ - default_user_email, - excluded_displayable_field - ]) - end - - it { expect(subject.displayable_fields_for_select).to eq([[["email", "user/email"]], ["user/email"]]) } - end - describe '#sorted_ids' do let(:instructeur) { create(:instructeur) } let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) } From ef63579e2e2debc0cfed2133617e116f7cc2b96a Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Sun, 21 Jul 2024 17:51:06 +0200 Subject: [PATCH 18/25] clean facets_concern --- app/models/concerns/facets_concern.rb | 103 +++++++++++--------------- 1 file changed, 44 insertions(+), 59 deletions(-) diff --git a/app/models/concerns/facets_concern.rb b/app/models/concerns/facets_concern.rb index 1ac45dbb8..53f8d819a 100644 --- a/app/models/concerns/facets_concern.rb +++ b/app/models/concerns/facets_concern.rb @@ -4,80 +4,41 @@ module FacetsConcern included do TYPE_DE_CHAMP = 'type_de_champ' - def find_facet(id:) - facets.find { |f| f.id == id } - end + def find_facet(id:) = facets.find { |f| f.id == id } def facets facets = dossier_facets - - facets.push( - Facet.new(table: 'user', column: 'email', type: :text), - Facet.new(table: 'followers_instructeurs', column: 'email', type: :text), - Facet.new(table: 'groupe_instructeur', column: 'id', type: :enum), - Facet.new(table: 'avis', column: 'question_answer', filterable: false) - ) - - if for_individual - facets.push( - Facet.new(table: "individual", column: "prenom", type: :text), - Facet.new(table: "individual", column: "nom", type: :text), - Facet.new(table: "individual", column: "gender", type: :text) - ) - end - - if !for_individual - facets.push( - Facet.new(table: 'etablissement', column: 'entreprise_siren', type: :text), - Facet.new(table: 'etablissement', column: 'entreprise_forme_juridique', type: :text), - Facet.new(table: 'etablissement', column: 'entreprise_nom_commercial', type: :text), - Facet.new(table: 'etablissement', column: 'entreprise_raison_sociale', type: :text), - Facet.new(table: 'etablissement', column: 'entreprise_siret_siege_social', type: :text), - Facet.new(table: 'etablissement', column: 'entreprise_date_creation', type: :date), - Facet.new(table: 'etablissement', column: 'siret', type: :text), - Facet.new(table: 'etablissement', column: 'libelle_naf', type: :text), - Facet.new(table: 'etablissement', column: 'code_postal', type: :text) - ) - end - + facets.concat(standard_facets) + facets.concat(individual_facets) if for_individual + facets.concat(moral_facets) if !for_individual facets.concat(types_de_champ_facets) - - facets end def dossier_facets - [ - Facet.new(table: 'self', column: 'created_at', type: :date), - Facet.new(table: 'self', column: 'updated_at', type: :date), - Facet.new(table: 'self', column: 'depose_at', type: :date), - Facet.new(table: 'self', column: 'en_construction_at', type: :date), - Facet.new(table: 'self', column: 'en_instruction_at', type: :date), - Facet.new(table: 'self', column: 'processed_at', type: :date), - *sva_svr_facets(for_filters: true), - Facet.new(table: 'self', column: 'updated_since', type: :date, virtual: true), - Facet.new(table: 'self', column: 'depose_since', type: :date, virtual: true), - Facet.new(table: 'self', column: 'en_construction_since', type: :date, virtual: true), - Facet.new(table: 'self', column: 'en_instruction_since', type: :date, virtual: true), - Facet.new(table: 'self', column: 'processed_since', type: :date, virtual: true), - Facet.new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true) - ].compact_blank + dates = ['created_at', 'updated_at', 'depose_at', 'en_construction_at', 'en_instruction_at', 'processed_at'] + .map { |column| Facet.new(table: 'self', column:, type: :date) } + + virtual_dates = ['updated_since', 'depose_since', 'en_construction_since', 'en_instruction_since', 'processed_since'] + .map { |column| Facet.new(table: 'self', column:, type: :date, virtual: true) } + + states = [Facet.new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true)] + + [dates, sva_svr_facets(for_filters: true), virtual_dates, states].flatten.compact end def sva_svr_facets(for_filters: false) return if !sva_svr_enabled? - i18n_scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] + scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] - facets = [] - facets << Facet.new(table: 'self', column: 'sva_svr_decision_on', - type: :date, - label: I18n.t("#{sva_svr_decision}_decision_on", scope: i18n_scope), - classname: for_filters ? '' : 'sva-col') + facets = [ + Facet.new(table: 'self', column: 'sva_svr_decision_on', type: :date, + label: I18n.t("#{sva_svr_decision}_decision_on", scope:), classname: for_filters ? '' : 'sva-col') + ] if for_filters - facets << Facet.new(table: 'self', column: 'sva_svr_decision_before', - label: I18n.t("#{sva_svr_decision}_decision_before", scope: i18n_scope), - type: :date, virtual: true) + facets << Facet.new(table: 'self', column: 'sva_svr_decision_before', type: :date, virtual: true, + label: I18n.t("#{sva_svr_decision}_decision_before", scope:)) end facets @@ -85,6 +46,30 @@ module FacetsConcern private + def standard_facets + [ + Facet.new(table: 'user', column: 'email'), + Facet.new(table: 'followers_instructeurs', column: 'email'), + Facet.new(table: 'groupe_instructeur', column: 'id', type: :enum), + Facet.new(table: 'avis', column: 'question_answer', filterable: false) + ] + end + + def individual_facets + ['nom', 'prenom', 'gender'].map { |column| Facet.new(table: 'individual', column:) } + end + + def moral_facets + etablissements = ['entreprise_siren', 'entreprise_forme_juridique', 'entreprise_nom_commercial', 'entreprise_raison_sociale', 'entreprise_siret_siege_social'] + .map { |column| Facet.new(table: 'etablissement', column:) } + + etablissement_dates = ['entreprise_date_creation'].map { |column| Facet.new(table: 'etablissement', column:, type: :date) } + + other = ['siret', 'libelle_naf', 'code_postal'].map { |column| Facet.new(table: 'etablissement', column:) } + + [etablissements, etablissement_dates, other].flatten + end + def types_de_champ_facets types_de_champ_for_procedure_presentation .pluck(:type_champ, :libelle, :stable_id) From 61051771a7832e38e9c02a418b421b00b29ee3f9 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Sun, 21 Jul 2024 21:11:44 +0200 Subject: [PATCH 19/25] stay the old way for the moment ? --- app/models/concerns/facets_concern.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/facets_concern.rb b/app/models/concerns/facets_concern.rb index 53f8d819a..f041f2c8b 100644 --- a/app/models/concerns/facets_concern.rb +++ b/app/models/concerns/facets_concern.rb @@ -83,7 +83,7 @@ module FacetsConcern column: tdc.stable_id.to_s, label: path_struct[:libelle], type: TypeDeChamp.filter_hash_type(tdc.type_champ), - value_column: path_struct[:path] + value_column: TypeDeChamp.filter_hash_value_column(type_champ) ) end end From 31255b69cc271f9b62235e5309f835b7ad1bca20 Mon Sep 17 00:00:00 2001 From: mfo Date: Mon, 22 Jul 2024 09:22:05 +0200 Subject: [PATCH 20/25] move/rename faceting components within their instructeur ns --- .../column_filter_component.rb} | 2 +- .../column_filter_component.en.yml} | 0 .../column_filter_component.fr.yml} | 0 .../column_filter_component.html.haml} | 0 .../column_picker_component.rb} | 2 +- .../column_picker_component.en.yml} | 0 .../column_picker_component.fr.yml} | 0 .../column_picker_component.html.haml} | 0 .../_dossiers_filter_dropdown.html.haml | 2 +- .../instructeurs/procedures/show.html.haml | 2 +- .../update_filter.turbo_stream.haml | 2 +- .../column_filter_component_spec.rb} | 2 +- .../column_picker_component_spec.rb | 24 +++++++++++++++++++ 13 files changed, 30 insertions(+), 6 deletions(-) rename app/components/{dossiers/instructeur_filter_component.rb => instructeurs/column_filter_component.rb} (95%) rename app/components/{dossiers/instructeur_filter_component/instructeur_filter_component.en.yml => instructeurs/column_filter_component/column_filter_component.en.yml} (100%) rename app/components/{dossiers/instructeur_filter_component/instructeur_filter_component.fr.yml => instructeurs/column_filter_component/column_filter_component.fr.yml} (100%) rename app/components/{dossiers/instructeur_filter_component/instructeur_filter_component.html.haml => instructeurs/column_filter_component/column_filter_component.html.haml} (100%) rename app/components/{dossiers/instructeur_facet_picker_component.rb => instructeurs/column_picker_component.rb} (88%) rename app/components/{dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.en.yml => instructeurs/column_picker_component/column_picker_component.en.yml} (100%) rename app/components/{dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.fr.yml => instructeurs/column_picker_component/column_picker_component.fr.yml} (100%) rename app/components/{dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.html.haml => instructeurs/column_picker_component/column_picker_component.html.haml} (100%) rename spec/components/{dossiers/instructeur_filter_component_spec.rb => instructeurs/column_filter_component_spec.rb} (96%) create mode 100644 spec/components/instructeurs/column_picker_component_spec.rb diff --git a/app/components/dossiers/instructeur_filter_component.rb b/app/components/instructeurs/column_filter_component.rb similarity index 95% rename from app/components/dossiers/instructeur_filter_component.rb rename to app/components/instructeurs/column_filter_component.rb index 66796b4da..7a2b40e6c 100644 --- a/app/components/dossiers/instructeur_filter_component.rb +++ b/app/components/instructeurs/column_filter_component.rb @@ -1,4 +1,4 @@ -class Dossiers::InstructeurFilterComponent < ApplicationComponent +class Instructeurs::ColumnFilterComponent < ApplicationComponent attr_reader :procedure, :procedure_presentation, :statut, :facet def initialize(procedure:, procedure_presentation:, statut:, facet: nil) diff --git a/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.en.yml b/app/components/instructeurs/column_filter_component/column_filter_component.en.yml similarity index 100% rename from app/components/dossiers/instructeur_filter_component/instructeur_filter_component.en.yml rename to app/components/instructeurs/column_filter_component/column_filter_component.en.yml diff --git a/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.fr.yml b/app/components/instructeurs/column_filter_component/column_filter_component.fr.yml similarity index 100% rename from app/components/dossiers/instructeur_filter_component/instructeur_filter_component.fr.yml rename to app/components/instructeurs/column_filter_component/column_filter_component.fr.yml diff --git a/app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml b/app/components/instructeurs/column_filter_component/column_filter_component.html.haml similarity index 100% rename from app/components/dossiers/instructeur_filter_component/instructeur_filter_component.html.haml rename to app/components/instructeurs/column_filter_component/column_filter_component.html.haml diff --git a/app/components/dossiers/instructeur_facet_picker_component.rb b/app/components/instructeurs/column_picker_component.rb similarity index 88% rename from app/components/dossiers/instructeur_facet_picker_component.rb rename to app/components/instructeurs/column_picker_component.rb index 0b2b25a0f..b609167db 100644 --- a/app/components/dossiers/instructeur_facet_picker_component.rb +++ b/app/components/instructeurs/column_picker_component.rb @@ -1,4 +1,4 @@ -class Dossiers::InstructeurFacetPickerComponent < ApplicationComponent +class Instructeurs::ColumnPickerComponent < ApplicationComponent attr_reader :procedure, :procedure_presentation def initialize(procedure:, procedure_presentation:) diff --git a/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.en.yml b/app/components/instructeurs/column_picker_component/column_picker_component.en.yml similarity index 100% rename from app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.en.yml rename to app/components/instructeurs/column_picker_component/column_picker_component.en.yml diff --git a/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.fr.yml b/app/components/instructeurs/column_picker_component/column_picker_component.fr.yml similarity index 100% rename from app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.fr.yml rename to app/components/instructeurs/column_picker_component/column_picker_component.fr.yml diff --git a/app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.html.haml b/app/components/instructeurs/column_picker_component/column_picker_component.html.haml similarity index 100% rename from app/components/dossiers/instructeur_facet_picker_component/instructeur_facet_picker_component.html.haml rename to app/components/instructeurs/column_picker_component/column_picker_component.html.haml diff --git a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml index 3cd95da34..e301a5c53 100644 --- a/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml +++ b/app/views/instructeurs/procedures/_dossiers_filter_dropdown.html.haml @@ -3,4 +3,4 @@ = t('views.instructeurs.dossiers.filters.title') - menu.with_form do - = render Dossiers::InstructeurFilterComponent.new(procedure:, procedure_presentation: @procedure_presentation, statut:) + = render Instructeurs::ColumnFilterComponent.new(procedure:, procedure_presentation: @procedure_presentation, statut:) diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index e87989352..69e33b9cf 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -109,7 +109,7 @@ - menu.with_button_inner_html do = t('views.instructeurs.dossiers.personalize') - menu.with_form do - = render Dossiers::InstructeurFacetPickerComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation) + = render Instructeurs::ColumnPickerComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation) %tbody = render Dossiers::BatchSelectMoreComponent.new(dossiers_count: @dossiers_count, filtered_sorted_ids: @filtered_sorted_ids) diff --git a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml index 4d88e0852..20127f210 100644 --- a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml +++ b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml @@ -1,2 +1,2 @@ = turbo_stream.replace 'filter-component' do - = render Dossiers::InstructeurFilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, facet: @facet) + = render Instructeurs::ColumnFilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, facet: @facet) diff --git a/spec/components/dossiers/instructeur_filter_component_spec.rb b/spec/components/instructeurs/column_filter_component_spec.rb similarity index 96% rename from spec/components/dossiers/instructeur_filter_component_spec.rb rename to spec/components/instructeurs/column_filter_component_spec.rb index f7e9aee3f..98f3f5035 100644 --- a/spec/components/dossiers/instructeur_filter_component_spec.rb +++ b/spec/components/instructeurs/column_filter_component_spec.rb @@ -1,4 +1,4 @@ -describe Dossiers::InstructeurFilterComponent, type: :component do +describe Instructeurs::ColumnFilterComponent, type: :component do let(:component) { described_class.new(procedure:, procedure_presentation:, statut:, facet:) } let(:instructeur) { create(:instructeur) } diff --git a/spec/components/instructeurs/column_picker_component_spec.rb b/spec/components/instructeurs/column_picker_component_spec.rb new file mode 100644 index 000000000..55e5983b1 --- /dev/null +++ b/spec/components/instructeurs/column_picker_component_spec.rb @@ -0,0 +1,24 @@ +describe Instructeurs::ColumnPickerComponent, type: :component do + let(:component) { described_class.new(procedure:, procedure_presentation:) } + + let(:procedure) { create(:procedure) } + let(:instructeur) { create(:instructeur) } + let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) } + let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } + + describe "#displayable_fields_for_select" do + let(:default_user_email) { Facet.new(label: 'email', table: 'user', column: 'email') } + let(:excluded_displayable_field) { Facet.new(label: "label1", table: "table1", column: "column1", virtual: true) } + + subject { component.displayable_fields_for_select } + + before do + allow(Facet).to receive(:facets).and_return([ + default_user_email, + excluded_displayable_field + ]) + end + + it { is_expected.to eq([[["email", "user/email"]], ["user/email"]]) } + end +end From 743ff8c8a9a0fccf7bf5b18ac96d5f7ec86ab7a0 Mon Sep 17 00:00:00 2001 From: mfo Date: Mon, 22 Jul 2024 09:37:15 +0200 Subject: [PATCH 21/25] extract Instructeurs::FacetTableHeaderComponent wrapping sort logic --- .../column_table_header_component.rb | 40 +++++++++++++++++++ .../column_table_header_component.html.haml | 9 +++++ app/models/procedure_presentation.rb | 17 -------- .../instructeurs/procedures/show.html.haml | 4 +- 4 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 app/components/instructeurs/column_table_header_component.rb create mode 100644 app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml diff --git a/app/components/instructeurs/column_table_header_component.rb b/app/components/instructeurs/column_table_header_component.rb new file mode 100644 index 000000000..9c2d2df36 --- /dev/null +++ b/app/components/instructeurs/column_table_header_component.rb @@ -0,0 +1,40 @@ +class Instructeurs::ColumnTableHeaderComponent < ApplicationComponent + attr_reader :procedure_presentation, :facet + # maybe extract a FacetSorter class? + + def initialize(procedure_presentation:, facet:) + @procedure_presentation = procedure_presentation + @facet = facet + end + + def sorted_by_current_facet? + procedure_presentation.sort['table'] == facet.table && + procedure_presentation.sort['column'] == facet.column + end + + def sorted_ascending? + current_sort_order == 'asc' + end + + def sorted_descending? + current_sort_order == 'desc' + end + + def aria_sort + if sorted_by_current_facet? + if sorted_ascending? + { "aria-sort": "ascending" } + elsif sorted_descending? + { "aria-sort": "descending" } + end + else + {} + end + end + + private + + def current_sort_order + procedure_presentation.sort['order'] + end +end diff --git a/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml b/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml new file mode 100644 index 000000000..faacf92a0 --- /dev/null +++ b/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml @@ -0,0 +1,9 @@ +%th{ aria_sort, scope: "col", class: facet.classname } + = link_to update_sort_instructeur_procedure_path(@procedure_presentation.procedure, table: facet.table, column: facet.column, order: @procedure_presentation.opposite_order_for(facet.table, facet.column)) do + - if sorted_by_current_facet? + - if sorted_ascending? + #{facet.label} ↑ + - else + #{facet.label} ↓ + - else + #{facet.label} diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index f3638529e..03ae96513 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -146,23 +146,6 @@ class ProcedurePresentation < ApplicationRecord slice(:filters, :sort, :displayed_fields) end - def sorted_by?(facet) - sort['table'] == facet.table && - sort['column'] == facet.column - end - - def aria_sort(order, field) - if sorted_by?(field) - if order == 'asc' - { "aria-sort": "ascending" } - elsif order == 'desc' - { "aria-sort": "descending" } - end - else - {} - end - end - private def sorted_ids(dossiers, count) diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 69e33b9cf..43e617a65 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -98,8 +98,8 @@ %th.text-center %input{ type: "checkbox", disabled: @disable_checkbox_all, checked: @disable_checkbox_all, data: { action: "batch-operation#onCheckAll" }, id: dom_id(BatchOperation.new, :checkbox_all), aria: { label: t('views.instructeurs.dossiers.select_all') } } - - @procedure_presentation.displayed_fields_for_headers.each do |field| - = render partial: "header_field", locals: { field: field } + - @procedure_presentation.displayed_fields_for_headers.each do |facet| + = render Instructeurs::ColumnTableHeaderComponent.new(procedure_presentation: @procedure_presentation, facet:) %th.follow-col Actions From b6464c1963526b88eb1155a86e530558fab06876 Mon Sep 17 00:00:00 2001 From: mfo Date: Mon, 22 Jul 2024 11:32:07 +0200 Subject: [PATCH 22/25] tech: use facet_id to sort fields --- .../notified_toggle_component.html.haml | 2 +- .../column_table_header_component.rb | 5 +++++ .../column_table_header_component.html.haml | 2 +- .../instructeurs/procedures_controller.rb | 2 +- app/models/concerns/facets_concern.rb | 4 +++- app/models/procedure_presentation.rb | 10 ++++++---- .../procedures/_header_field.html.haml | 9 --------- config/routes.rb | 2 +- .../instructeurs/procedures_controller_spec.rb | 18 ++++++++++++++++++ spec/models/concerns/facets_concern_spec.rb | 2 ++ 10 files changed, 38 insertions(+), 18 deletions(-) delete mode 100644 app/views/instructeurs/procedures/_header_field.html.haml diff --git a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml index 4f6eca594..2b87518fd 100644 --- a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml +++ b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml @@ -1,4 +1,4 @@ -= form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, table: 'notifications', column: 'notifications', order: opposite_order), method: :get, data: { controller: 'autosubmit' } do += form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, facet_id: 'notifications/notifications', order: opposite_order), method: :get, data: { controller: 'autosubmit' } do .fr-fieldset__element.fr-m-0 .fr-checkbox-group.fr-checkbox-group--sm = check_box_tag :order, opposite_order, active? diff --git a/app/components/instructeurs/column_table_header_component.rb b/app/components/instructeurs/column_table_header_component.rb index 9c2d2df36..9d210bb37 100644 --- a/app/components/instructeurs/column_table_header_component.rb +++ b/app/components/instructeurs/column_table_header_component.rb @@ -1,12 +1,17 @@ class Instructeurs::ColumnTableHeaderComponent < ApplicationComponent attr_reader :procedure_presentation, :facet # maybe extract a FacetSorter class? + # def initialize(procedure_presentation:, facet:) @procedure_presentation = procedure_presentation @facet = facet end + def facet_id + facet.id + end + def sorted_by_current_facet? procedure_presentation.sort['table'] == facet.table && procedure_presentation.sort['column'] == facet.column diff --git a/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml b/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml index faacf92a0..fee5caf74 100644 --- a/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml +++ b/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml @@ -1,5 +1,5 @@ %th{ aria_sort, scope: "col", class: facet.classname } - = link_to update_sort_instructeur_procedure_path(@procedure_presentation.procedure, table: facet.table, column: facet.column, order: @procedure_presentation.opposite_order_for(facet.table, facet.column)) do + = link_to update_sort_instructeur_procedure_path(@procedure_presentation.procedure, facet_id:, order: @procedure_presentation.opposite_order_for(facet.table, facet.column)) do - if sorted_by_current_facet? - if sorted_ascending? #{facet.label} ↑ diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index c7bd254b7..61ca05d9f 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -141,7 +141,7 @@ module Instructeurs end def update_sort - procedure_presentation.update_sort(params[:table], params[:column], params[:order]) + procedure_presentation.update_sort(params[:facet_id], params[:order]) redirect_back(fallback_location: instructeur_procedure_url(procedure)) end diff --git a/app/models/concerns/facets_concern.rb b/app/models/concerns/facets_concern.rb index f041f2c8b..417ab1d64 100644 --- a/app/models/concerns/facets_concern.rb +++ b/app/models/concerns/facets_concern.rb @@ -15,6 +15,8 @@ module FacetsConcern end def dossier_facets + common = [Facet.new(table: 'self', column: 'id', classname: 'number-col'), Facet.new(table: 'notifications', column: 'notifications', label: "notifications", filterable: false)] + dates = ['created_at', 'updated_at', 'depose_at', 'en_construction_at', 'en_instruction_at', 'processed_at'] .map { |column| Facet.new(table: 'self', column:, type: :date) } @@ -23,7 +25,7 @@ module FacetsConcern states = [Facet.new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true)] - [dates, sva_svr_facets(for_filters: true), virtual_dates, states].flatten.compact + [common, dates, sva_svr_facets(for_filters: true), virtual_dates, states].flatten.compact end def sva_svr_facets(for_filters: false) diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 03ae96513..9ebe1535f 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -124,11 +124,13 @@ class ProcedurePresentation < ApplicationRecord end end - def update_sort(table, column, order) + def update_sort(facet_id, order) + facet = procedure.find_facet(id: facet_id) + update!(sort: { - TABLE => table, - COLUMN => column, - ORDER => order.presence || opposite_order_for(table, column) + TABLE => facet.table, + COLUMN => facet.column, + ORDER => order.presence || opposite_order_for(facet.table, facet.column) }) end diff --git a/app/views/instructeurs/procedures/_header_field.html.haml b/app/views/instructeurs/procedures/_header_field.html.haml deleted file mode 100644 index 2d46b77b5..000000000 --- a/app/views/instructeurs/procedures/_header_field.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -%th{ @procedure_presentation.aria_sort(@procedure_presentation.sort['order'], field), scope: "col", class: field.classname } - = link_to update_sort_instructeur_procedure_path(@procedure, table: field.table, column: field.column, order: @procedure_presentation.opposite_order_for(field.table, field.column)) do - - if @procedure_presentation.sorted_by?(field) - - if @procedure_presentation.sort['order'] == 'asc' - #{field.label} ↑ - - else - #{field.label} ↓ - - else - #{field.label} diff --git a/config/routes.rb b/config/routes.rb index cc1f9f7b3..502307d7c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -473,7 +473,7 @@ Rails.application.routes.draw do end patch 'update_displayed_fields' - get 'update_sort/:table/:column' => 'procedures#update_sort', as: 'update_sort' + get 'update_sort/:facet_id' => 'procedures#update_sort', as: 'update_sort' post 'add_filter' post 'update_filter' get 'remove_filter' diff --git a/spec/controllers/instructeurs/procedures_controller_spec.rb b/spec/controllers/instructeurs/procedures_controller_spec.rb index 8aad3822d..c729d0b6c 100644 --- a/spec/controllers/instructeurs/procedures_controller_spec.rb +++ b/spec/controllers/instructeurs/procedures_controller_spec.rb @@ -872,6 +872,24 @@ describe Instructeurs::ProceduresController, type: :controller do end end + describe '#update_filter' do + let(:instructeur) { create(:instructeur) } + let(:procedure) { create(:procedure, :for_individual) } + def procedure_presentation = instructeur.assign_to.first.procedure_presentation_or_default_and_errors.first + + before do + create(:assign_to, instructeur:, groupe_instructeur: build(:groupe_instructeur, procedure:)) + + sign_in(instructeur.user) + end + + it 'can change order' do + expect { get :update_sort, params: { procedure_id: procedure.id, facet_id: "individual/nom", order: 'asc' } } + .to change { procedure_presentation.sort } + .from({ "column" => "notifications", "order" => "desc", "table" => "notifications" }) + .to({ "column" => "nom", "order" => "asc", "table" => "individual" }) + end + end describe '#add_filter' do let(:instructeur) { create(:instructeur) } let(:procedure) { create(:procedure, :for_individual) } diff --git a/spec/models/concerns/facets_concern_spec.rb b/spec/models/concerns/facets_concern_spec.rb index 5ba738441..acf82c6ae 100644 --- a/spec/models/concerns/facets_concern_spec.rb +++ b/spec/models/concerns/facets_concern_spec.rb @@ -14,6 +14,8 @@ describe FacetsConcern do let(:tdc_private_2) { procedure.active_revision.types_de_champ_private[1] } let(:expected) { [ + { label: 'Nº dossier', table: 'self', column: 'id', classname: 'number-col', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, + { label: 'notifications', table: 'notifications', column: 'notifications', virtual: false, type: :text, scope: '', value_column: :value, filterable: false }, { label: 'Créé le', table: 'self', column: 'created_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, { label: 'Mis à jour le', table: 'self', column: 'updated_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, { label: 'Déposé le', table: 'self', column: 'depose_at', classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true }, From 2a0537134d9c33961c8e7720c53dd8cbe9531777 Mon Sep 17 00:00:00 2001 From: mfo Date: Mon, 22 Jul 2024 14:01:39 +0200 Subject: [PATCH 23/25] fix(spec): should have been updated with facet concern sig --- spec/components/instructeurs/column_picker_component_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/components/instructeurs/column_picker_component_spec.rb b/spec/components/instructeurs/column_picker_component_spec.rb index 55e5983b1..7b340a96e 100644 --- a/spec/components/instructeurs/column_picker_component_spec.rb +++ b/spec/components/instructeurs/column_picker_component_spec.rb @@ -13,7 +13,7 @@ describe Instructeurs::ColumnPickerComponent, type: :component do subject { component.displayable_fields_for_select } before do - allow(Facet).to receive(:facets).and_return([ + allow(procedure).to receive(:facets).and_return([ default_user_email, excluded_displayable_field ]) From b910705353bdb1c9526a05f69c77f6e02d5fe678 Mon Sep 17 00:00:00 2001 From: mfo Date: Mon, 22 Jul 2024 14:47:27 +0200 Subject: [PATCH 24/25] move tdc facets builder to tdc --- app/models/concerns/facets_concern.rb | 11 +---------- app/models/types_de_champ/type_de_champ_base.rb | 14 +++++++++++++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/models/concerns/facets_concern.rb b/app/models/concerns/facets_concern.rb index 417ab1d64..44cdfb035 100644 --- a/app/models/concerns/facets_concern.rb +++ b/app/models/concerns/facets_concern.rb @@ -78,16 +78,7 @@ module FacetsConcern .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } .flat_map do |(type_champ, libelle, stable_id)| tdc = TypeDeChamp.new(type_champ:, libelle:, stable_id:) - - tdc.dynamic_type.search_paths.map do |path_struct| - Facet.new( - table: TYPE_DE_CHAMP, - column: tdc.stable_id.to_s, - label: path_struct[:libelle], - type: TypeDeChamp.filter_hash_type(tdc.type_champ), - value_column: TypeDeChamp.filter_hash_value_column(type_champ) - ) - end + tdc.dynamic_type.facets(table: TYPE_DE_CHAMP) end end end diff --git a/app/models/types_de_champ/type_de_champ_base.rb b/app/models/types_de_champ/type_de_champ_base.rb index 02570c8d1..cb79b8707 100644 --- a/app/models/types_de_champ/type_de_champ_base.rb +++ b/app/models/types_de_champ/type_de_champ_base.rb @@ -1,7 +1,7 @@ class TypesDeChamp::TypeDeChampBase include ActiveModel::Validations - delegate :description, :libelle, :mandatory, :mandatory?, :stable_id, :fillable?, :public?, to: :@type_de_champ + delegate :description, :libelle, :mandatory, :mandatory?, :stable_id, :fillable?, :public?, :type_champ, to: :@type_de_champ FILL_DURATION_SHORT = 10.seconds FILL_DURATION_MEDIUM = 1.minute @@ -96,6 +96,18 @@ class TypesDeChamp::TypeDeChampBase end end + def facets(table:) + [ + Facet.new( + table:, + column: stable_id.to_s, + label: libelle, + type: TypeDeChamp.filter_hash_type(type_champ), + value_column: TypeDeChamp.filter_hash_value_column(type_champ) + ) + ] + end + private def paths From dba6f9b3aafad3fc29efb0684f915eb255cc4039 Mon Sep 17 00:00:00 2001 From: mfo Date: Mon, 19 Aug 2024 14:34:36 +0200 Subject: [PATCH 25/25] =?UTF-8?q?refactor(Facet):=20to=20column=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notified_toggle_component.html.haml | 2 +- .../instructeurs/column_filter_component.rb | 32 +++---- .../column_filter_component.html.haml | 8 +- .../instructeurs/column_picker_component.rb | 8 +- .../column_picker_component.html.haml | 2 +- .../column_table_header_component.rb | 20 ++--- .../column_table_header_component.html.haml | 12 +-- .../instructeurs/procedures_controller.rb | 8 +- app/models/{facet.rb => column.rb} | 2 +- app/models/concerns/columns_concern.rb | 85 +++++++++++++++++++ app/models/concerns/facets_concern.rb | 85 ------------------- app/models/procedure.rb | 2 +- app/models/procedure_presentation.rb | 69 +++++++-------- .../types_de_champ/type_de_champ_base.rb | 4 +- .../_dossiers_filter_tags.html.haml | 2 +- .../instructeurs/procedures/show.html.haml | 4 +- .../update_filter.turbo_stream.haml | 2 +- config/routes.rb | 2 +- .../column_filter_component_spec.rb | 26 +++--- .../column_picker_component_spec.rb | 10 +-- .../procedures_controller_spec.rb | 4 +- ...oncern_spec.rb => columns_concern_spec.rb} | 22 ++--- 22 files changed, 203 insertions(+), 208 deletions(-) rename app/models/{facet.rb => column.rb} (98%) create mode 100644 app/models/concerns/columns_concern.rb delete mode 100644 app/models/concerns/facets_concern.rb rename spec/models/concerns/{facets_concern_spec.rb => columns_concern_spec.rb} (84%) diff --git a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml index 2b87518fd..0fe1c9b20 100644 --- a/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml +++ b/app/components/dossiers/notified_toggle_component/notified_toggle_component.html.haml @@ -1,4 +1,4 @@ -= form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, facet_id: 'notifications/notifications', order: opposite_order), method: :get, data: { controller: 'autosubmit' } do += form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, column_id: 'notifications/notifications', order: opposite_order), method: :get, data: { controller: 'autosubmit' } do .fr-fieldset__element.fr-m-0 .fr-checkbox-group.fr-checkbox-group--sm = check_box_tag :order, opposite_order, active? diff --git a/app/components/instructeurs/column_filter_component.rb b/app/components/instructeurs/column_filter_component.rb index 7a2b40e6c..3e242e679 100644 --- a/app/components/instructeurs/column_filter_component.rb +++ b/app/components/instructeurs/column_filter_component.rb @@ -1,34 +1,34 @@ class Instructeurs::ColumnFilterComponent < ApplicationComponent - attr_reader :procedure, :procedure_presentation, :statut, :facet + attr_reader :procedure, :procedure_presentation, :statut, :column - def initialize(procedure:, procedure_presentation:, statut:, facet: nil) + def initialize(procedure:, procedure_presentation:, statut:, column: nil) @procedure = procedure @procedure_presentation = procedure_presentation @statut = statut - @facet = facet + @column = column end - def facet_type = facet.present? ? facet.type : :text + def column_type = column.present? ? column.type : :text - def options_for_select_of_field - if facet.scope.present? - I18n.t(facet.scope).map(&:to_a).map(&:reverse) - elsif facet.table == 'groupe_instructeur' + def options_for_select_of_column + if column.scope.present? + I18n.t(column.scope).map(&:to_a).map(&:reverse) + elsif column.table == 'groupe_instructeur' current_instructeur.groupe_instructeurs.filter_map do if _1.procedure_id == procedure.id [_1.label, _1.id] end end else - find_type_de_champ(facet.column).options_for_select + find_type_de_champ(column.column).options_for_select end end def filter_react_props { - selected_key: facet.present? ? facet.id : '', - items: filterable_fields_options, - name: :field, + selected_key: column.present? ? column.id : '', + items: filterable_columns_options, + name: :column, id: 'search-filter', 'aria-describedby': 'instructeur-filter-combo-label', form: 'filter-component', @@ -36,11 +36,11 @@ class Instructeurs::ColumnFilterComponent < ApplicationComponent } end - def filterable_fields_options - procedure.facets.filter_map do |facet| - next if facet.filterable == false + def filterable_columns_options + procedure.columns.filter_map do |column| + next if column.filterable == false - [facet.label, facet.id] + [column.label, column.id] end end diff --git a/app/components/instructeurs/column_filter_component/column_filter_component.html.haml b/app/components/instructeurs/column_filter_component/column_filter_component.html.haml index f347727f5..6f43592d3 100644 --- a/app/components/instructeurs/column_filter_component/column_filter_component.html.haml +++ b/app/components/instructeurs/column_filter_component/column_filter_component.html.haml @@ -1,16 +1,16 @@ = form_tag add_filter_instructeur_procedure_path(procedure), method: :post, class: 'dropdown-form large', id: 'filter-component', data: { turbo: true, controller: 'autosubmit' } do .fr-select-group - = label_tag :field, t('.column'), class: 'fr-label fr-m-0', id: 'instructeur-filter-combo-label', for: 'search-filter' + = label_tag :column, t('.column'), class: 'fr-label fr-m-0', id: 'instructeur-filter-combo-label', for: 'search-filter' %react-fragment = render ReactComponent.new "ComboBox/SingleComboBox", **filter_react_props %input.hidden{ type: 'submit', formaction: update_filter_instructeur_procedure_path(procedure), data: { autosubmit_target: 'submitter' } } = label_tag :value, t('.value'), for: 'value', class: 'fr-label' - - if facet_type == :enum - = select_tag :value, options_for_select(options_for_select_of_field), id: 'value', name: 'value', class: 'fr-select', data: { no_autosubmit: true } + - if column_type == :enum + = select_tag :value, options_for_select(options_for_select_of_column), id: 'value', name: 'value', class: 'fr-select', data: { no_autosubmit: true } - else - %input#value.fr-input{ type: facet_type, name: :value, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH, disabled: facet.nil? ? true : false, data: { no_autosubmit: true } } + %input#value.fr-input{ type: column_type, name: :value, maxlength: ProcedurePresentation::FILTERS_VALUE_MAX_LENGTH, disabled: column.nil? ? true : false, data: { no_autosubmit: true } } = hidden_field_tag :statut, statut = submit_tag t('.add_filter'), class: 'fr-btn fr-btn--secondary fr-mt-2w' diff --git a/app/components/instructeurs/column_picker_component.rb b/app/components/instructeurs/column_picker_component.rb index b609167db..97da94190 100644 --- a/app/components/instructeurs/column_picker_component.rb +++ b/app/components/instructeurs/column_picker_component.rb @@ -4,13 +4,13 @@ class Instructeurs::ColumnPickerComponent < ApplicationComponent def initialize(procedure:, procedure_presentation:) @procedure = procedure @procedure_presentation = procedure_presentation - @displayable_fields_for_select, @displayable_fields_selected = displayable_fields_for_select + @displayable_columns_for_select, @displayable_columns_selected = displayable_columns_for_select end - def displayable_fields_for_select + def displayable_columns_for_select [ - procedure.facets.reject(&:virtual).map { |facet| [facet.label, facet.id] }, - procedure_presentation.displayed_fields.map { Facet.new(**_1.deep_symbolize_keys).id } + procedure.columns.reject(&:virtual).map { |column| [column.label, column.id] }, + procedure_presentation.displayed_fields.map { Column.new(**_1.deep_symbolize_keys).id } ] end end diff --git a/app/components/instructeurs/column_picker_component/column_picker_component.html.haml b/app/components/instructeurs/column_picker_component/column_picker_component.html.haml index e0a6cd958..74a31729e 100644 --- a/app/components/instructeurs/column_picker_component/column_picker_component.html.haml +++ b/app/components/instructeurs/column_picker_component/column_picker_component.html.haml @@ -1,5 +1,5 @@ = form_tag update_displayed_fields_instructeur_procedure_path(@procedure), method: :patch, class: 'dropdown-form large columns-form' do %react-fragment - = render ReactComponent.new "ComboBox/MultiComboBox", items: @displayable_fields_for_select, selected_keys: @displayable_fields_selected, name: 'values[]', 'aria-label': 'Colonne à afficher' + = render ReactComponent.new "ComboBox/MultiComboBox", items: @displayable_columns_for_select, selected_keys: @displayable_columns_selected, name: 'values[]', 'aria-label': 'Colonne à afficher' = submit_tag t('.save'), class: 'fr-btn fr-btn--secondary' diff --git a/app/components/instructeurs/column_table_header_component.rb b/app/components/instructeurs/column_table_header_component.rb index 9d210bb37..afaf175a0 100644 --- a/app/components/instructeurs/column_table_header_component.rb +++ b/app/components/instructeurs/column_table_header_component.rb @@ -1,20 +1,20 @@ class Instructeurs::ColumnTableHeaderComponent < ApplicationComponent - attr_reader :procedure_presentation, :facet - # maybe extract a FacetSorter class? + attr_reader :procedure_presentation, :column + # maybe extract a ColumnSorter class? # - def initialize(procedure_presentation:, facet:) + def initialize(procedure_presentation:, column:) @procedure_presentation = procedure_presentation - @facet = facet + @column = column end - def facet_id - facet.id + def column_id + column.id end - def sorted_by_current_facet? - procedure_presentation.sort['table'] == facet.table && - procedure_presentation.sort['column'] == facet.column + def sorted_by_current_column? + procedure_presentation.sort['table'] == column.table && + procedure_presentation.sort['column'] == column.column end def sorted_ascending? @@ -26,7 +26,7 @@ class Instructeurs::ColumnTableHeaderComponent < ApplicationComponent end def aria_sort - if sorted_by_current_facet? + if sorted_by_current_column? if sorted_ascending? { "aria-sort": "ascending" } elsif sorted_descending? diff --git a/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml b/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml index fee5caf74..67c762f2c 100644 --- a/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml +++ b/app/components/instructeurs/column_table_header_component/column_table_header_component.html.haml @@ -1,9 +1,9 @@ -%th{ aria_sort, scope: "col", class: facet.classname } - = link_to update_sort_instructeur_procedure_path(@procedure_presentation.procedure, facet_id:, order: @procedure_presentation.opposite_order_for(facet.table, facet.column)) do - - if sorted_by_current_facet? +%th{ aria_sort, scope: "col", class: column.classname } + = link_to update_sort_instructeur_procedure_path(@procedure_presentation.procedure, column_id:, order: @procedure_presentation.opposite_order_for(column.table, column.column)) do + - if sorted_by_current_column? - if sorted_ascending? - #{facet.label} ↑ + #{column.label} ↑ - else - #{facet.label} ↓ + #{column.label} ↓ - else - #{facet.label} + #{column.label} diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 61ca05d9f..2a37d7b5d 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -141,13 +141,13 @@ module Instructeurs end def update_sort - procedure_presentation.update_sort(params[:facet_id], params[:order]) + procedure_presentation.update_sort(params[:column_id], params[:order]) redirect_back(fallback_location: instructeur_procedure_url(procedure)) end def add_filter - if !procedure_presentation.add_filter(statut, params[:field], params[:value]) + if !procedure_presentation.add_filter(statut, params[:column], params[:value]) flash.alert = procedure_presentation.errors.full_messages end @@ -158,11 +158,11 @@ module Instructeurs @statut = statut @procedure = procedure @procedure_presentation = procedure_presentation - @facet = procedure.find_facet(id: params[:field]) + @column = procedure.find_column(id: params[:column]) end def remove_filter - procedure_presentation.remove_filter(statut, params[:field], params[:value]) + procedure_presentation.remove_filter(statut, params[:column], params[:value]) redirect_back(fallback_location: instructeur_procedure_url(procedure)) end diff --git a/app/models/facet.rb b/app/models/column.rb similarity index 98% rename from app/models/facet.rb rename to app/models/column.rb index 910106f7f..250a74051 100644 --- a/app/models/facet.rb +++ b/app/models/column.rb @@ -1,4 +1,4 @@ -class Facet +class Column attr_reader :table, :column, :label, :classname, :virtual, :type, :scope, :value_column, :filterable def initialize(table:, column:, label: nil, virtual: false, type: :text, value_column: :value, filterable: true, classname: '', scope: '') diff --git a/app/models/concerns/columns_concern.rb b/app/models/concerns/columns_concern.rb new file mode 100644 index 000000000..2ee83d82c --- /dev/null +++ b/app/models/concerns/columns_concern.rb @@ -0,0 +1,85 @@ +module ColumnsConcern + extend ActiveSupport::Concern + + included do + TYPE_DE_CHAMP = 'type_de_champ' + + def find_column(id:) = columns.find { |f| f.id == id } + + def columns + columns = dossier_columns + columns.concat(standard_columns) + columns.concat(individual_columns) if for_individual + columns.concat(moral_columns) if !for_individual + columns.concat(types_de_champ_columns) + end + + def dossier_columns + common = [Column.new(table: 'self', column: 'id', classname: 'number-col'), Column.new(table: 'notifications', column: 'notifications', label: "notifications", filterable: false)] + + dates = ['created_at', 'updated_at', 'depose_at', 'en_construction_at', 'en_instruction_at', 'processed_at'] + .map { |column| Column.new(table: 'self', column:, type: :date) } + + virtual_dates = ['updated_since', 'depose_since', 'en_construction_since', 'en_instruction_since', 'processed_since'] + .map { |column| Column.new(table: 'self', column:, type: :date, virtual: true) } + + states = [Column.new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true)] + + [common, dates, sva_svr_columns(for_filters: true), virtual_dates, states].flatten.compact + end + + def sva_svr_columns(for_filters: false) + return if !sva_svr_enabled? + + scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] + + columns = [ + Column.new(table: 'self', column: 'sva_svr_decision_on', type: :date, + label: I18n.t("#{sva_svr_decision}_decision_on", scope:), classname: for_filters ? '' : 'sva-col') + ] + + if for_filters + columns << Column.new(table: 'self', column: 'sva_svr_decision_before', type: :date, virtual: true, + label: I18n.t("#{sva_svr_decision}_decision_before", scope:)) + end + + columns + end + + private + + def standard_columns + [ + Column.new(table: 'user', column: 'email'), + Column.new(table: 'followers_instructeurs', column: 'email'), + Column.new(table: 'groupe_instructeur', column: 'id', type: :enum), + Column.new(table: 'avis', column: 'question_answer', filterable: false) + ] + end + + def individual_columns + ['nom', 'prenom', 'gender'].map { |column| Column.new(table: 'individual', column:) } + end + + def moral_columns + etablissements = ['entreprise_siren', 'entreprise_forme_juridique', 'entreprise_nom_commercial', 'entreprise_raison_sociale', 'entreprise_siret_siege_social'] + .map { |column| Column.new(table: 'etablissement', column:) } + + etablissement_dates = ['entreprise_date_creation'].map { |column| Column.new(table: 'etablissement', column:, type: :date) } + + other = ['siret', 'libelle_naf', 'code_postal'].map { |column| Column.new(table: 'etablissement', column:) } + + [etablissements, etablissement_dates, other].flatten + end + + def types_de_champ_columns + types_de_champ_for_procedure_presentation + .pluck(:type_champ, :libelle, :stable_id) + .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } + .flat_map do |(type_champ, libelle, stable_id)| + tdc = TypeDeChamp.new(type_champ:, libelle:, stable_id:) + tdc.dynamic_type.columns(table: TYPE_DE_CHAMP) + end + end + end +end diff --git a/app/models/concerns/facets_concern.rb b/app/models/concerns/facets_concern.rb deleted file mode 100644 index 44cdfb035..000000000 --- a/app/models/concerns/facets_concern.rb +++ /dev/null @@ -1,85 +0,0 @@ -module FacetsConcern - extend ActiveSupport::Concern - - included do - TYPE_DE_CHAMP = 'type_de_champ' - - def find_facet(id:) = facets.find { |f| f.id == id } - - def facets - facets = dossier_facets - facets.concat(standard_facets) - facets.concat(individual_facets) if for_individual - facets.concat(moral_facets) if !for_individual - facets.concat(types_de_champ_facets) - end - - def dossier_facets - common = [Facet.new(table: 'self', column: 'id', classname: 'number-col'), Facet.new(table: 'notifications', column: 'notifications', label: "notifications", filterable: false)] - - dates = ['created_at', 'updated_at', 'depose_at', 'en_construction_at', 'en_instruction_at', 'processed_at'] - .map { |column| Facet.new(table: 'self', column:, type: :date) } - - virtual_dates = ['updated_since', 'depose_since', 'en_construction_since', 'en_instruction_since', 'processed_since'] - .map { |column| Facet.new(table: 'self', column:, type: :date, virtual: true) } - - states = [Facet.new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', virtual: true)] - - [common, dates, sva_svr_facets(for_filters: true), virtual_dates, states].flatten.compact - end - - def sva_svr_facets(for_filters: false) - return if !sva_svr_enabled? - - scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] - - facets = [ - Facet.new(table: 'self', column: 'sva_svr_decision_on', type: :date, - label: I18n.t("#{sva_svr_decision}_decision_on", scope:), classname: for_filters ? '' : 'sva-col') - ] - - if for_filters - facets << Facet.new(table: 'self', column: 'sva_svr_decision_before', type: :date, virtual: true, - label: I18n.t("#{sva_svr_decision}_decision_before", scope:)) - end - - facets - end - - private - - def standard_facets - [ - Facet.new(table: 'user', column: 'email'), - Facet.new(table: 'followers_instructeurs', column: 'email'), - Facet.new(table: 'groupe_instructeur', column: 'id', type: :enum), - Facet.new(table: 'avis', column: 'question_answer', filterable: false) - ] - end - - def individual_facets - ['nom', 'prenom', 'gender'].map { |column| Facet.new(table: 'individual', column:) } - end - - def moral_facets - etablissements = ['entreprise_siren', 'entreprise_forme_juridique', 'entreprise_nom_commercial', 'entreprise_raison_sociale', 'entreprise_siret_siege_social'] - .map { |column| Facet.new(table: 'etablissement', column:) } - - etablissement_dates = ['entreprise_date_creation'].map { |column| Facet.new(table: 'etablissement', column:, type: :date) } - - other = ['siret', 'libelle_naf', 'code_postal'].map { |column| Facet.new(table: 'etablissement', column:) } - - [etablissements, etablissement_dates, other].flatten - end - - def types_de_champ_facets - types_de_champ_for_procedure_presentation - .pluck(:type_champ, :libelle, :stable_id) - .reject { |(type_champ)| type_champ == TypeDeChamp.type_champs.fetch(:repetition) } - .flat_map do |(type_champ, libelle, stable_id)| - tdc = TypeDeChamp.new(type_champ:, libelle:, stable_id:) - tdc.dynamic_type.facets(table: TYPE_DE_CHAMP) - end - end - end -end diff --git a/app/models/procedure.rb b/app/models/procedure.rb index d86f30b6d..00ee13b0b 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -6,7 +6,7 @@ class Procedure < ApplicationRecord include ProcedureSVASVRConcern include ProcedureChorusConcern include PiecesJointesListConcern - include FacetsConcern + include ColumnsConcern include Discard::Model self.discard_column = :hidden_at diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 9ebe1535f..6ddb0d228 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -26,10 +26,10 @@ class ProcedurePresentation < ApplicationRecord def displayed_fields_for_headers [ - Facet.new(table: 'self', column: 'id', classname: 'number-col'), - *displayed_fields.map { Facet.new(**_1.deep_symbolize_keys) }, - Facet.new(table: 'self', column: 'state', classname: 'state-col'), - *procedure.sva_svr_facets + Column.new(table: 'self', column: 'id', classname: 'number-col'), + *displayed_fields.map { Column.new(**_1.deep_symbolize_keys) }, + Column.new(table: 'self', column: 'state', classname: 'state-col'), + *procedure.sva_svr_columns ] end @@ -57,9 +57,9 @@ class ProcedurePresentation < ApplicationRecord instructeur.groupe_instructeurs .find { _1.id == filter['value'].to_i }&.label || filter['value'] else - facet = procedure.facets.find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } + column = procedure.columns.find { _1.table == filter[TABLE] && _1.column == filter[COLUMN] } - if facet.type == :date + if column.type == :date parsed_date = safe_parse_date(filter['value']) return parsed_date.present? ? I18n.l(parsed_date) : nil @@ -75,25 +75,21 @@ class ProcedurePresentation < ApplicationRecord nil end - def add_filter(statut, facet_id, value) + def add_filter(statut, column_id, value) if value.present? - facet = procedure.find_facet(id: facet_id) - label = facet.label - column = facet.column - table = facet.table - value_column = facet.value_column + column = procedure.find_column(id: column_id) - case table + case column.table when TYPE_DE_CHAMP - value = find_type_de_champ(column).dynamic_type.human_to_filter(value) + value = find_type_de_champ(column.column).dynamic_type.human_to_filter(value) end updated_filters = filters.dup updated_filters[statut] << { - 'label' => label, - TABLE => table, - COLUMN => column, - 'value_column' => value_column, + 'label' => column.label, + TABLE => column.table, + COLUMN => column.column, + 'value_column' => column.value_column, 'value' => value } @@ -101,36 +97,35 @@ class ProcedurePresentation < ApplicationRecord end end - def remove_filter(statut, facet_id, value) - facet = procedure.find_facet(id: facet_id) - table, column = facet.table, facet.column - + def remove_filter(statut, column_id, value) + column = procedure.find_column(id: column_id) updated_filters = filters.dup + updated_filters[statut] = filters[statut].reject do |filter| - filter.values_at(TABLE, COLUMN, 'value') == [table, column, value] + filter.values_at(TABLE, COLUMN, 'value') == [column.table, column.column, value] end update!(filters: updated_filters) end - def update_displayed_fields(facet_ids) - facet_ids = Array.wrap(facet_ids) - facets = facet_ids.map { |id| procedure.find_facet(id:) } + def update_displayed_fields(column_ids) + column_ids = Array.wrap(column_ids) + columns = column_ids.map { |id| procedure.find_column(id:) } - update!(displayed_fields: facets) + update!(displayed_fields: columns) - if !sort_to_facet_id(sort).in?(facet_ids) + if !sort_to_column_id(sort).in?(column_ids) update!(sort: Procedure.default_sort) end end - def update_sort(facet_id, order) - facet = procedure.find_facet(id: facet_id) + def update_sort(column_id, order) + column = procedure.find_column(id: column_id) update!(sort: { - TABLE => facet.table, - COLUMN => facet.column, - ORDER => order.presence || opposite_order_for(facet.table, facet.column) + TABLE => column.table, + COLUMN => column.column, + ORDER => order.presence || opposite_order_for(column.table, column.column) }) end @@ -203,7 +198,7 @@ class ProcedurePresentation < ApplicationRecord value_column = filters.pluck('value_column').compact.first || :value case table when 'self' - field = procedure.dossier_facets.find { |h| h.column == column } + field = procedure.dossier_columns.find { |h| h.column == column } if field.type == :date dates = values .filter_map { |v| Time.zone.parse(v).beginning_of_day rescue nil } @@ -258,7 +253,7 @@ class ProcedurePresentation < ApplicationRecord end # type_de_champ/4373429 - def sort_to_facet_id(sort) + def sort_to_column_id(sort) [sort[TABLE], sort[COLUMN]].join(SLASH) end @@ -318,9 +313,9 @@ class ProcedurePresentation < ApplicationRecord end def valid_columns_for_table(table) - @column_whitelist ||= procedure.facets + @column_whitelist ||= procedure.columns .group_by(&:table) - .transform_values { |facets| Set.new(facets.map(&:column)) } + .transform_values { |columns| Set.new(columns.map(&:column)) } @column_whitelist[table] || [] end diff --git a/app/models/types_de_champ/type_de_champ_base.rb b/app/models/types_de_champ/type_de_champ_base.rb index cb79b8707..0d188a61a 100644 --- a/app/models/types_de_champ/type_de_champ_base.rb +++ b/app/models/types_de_champ/type_de_champ_base.rb @@ -96,9 +96,9 @@ class TypesDeChamp::TypeDeChampBase end end - def facets(table:) + def columns(table:) [ - Facet.new( + Column.new( table:, column: stable_id.to_s, label: libelle, diff --git a/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml b/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml index f3db643a3..dadfb4478 100644 --- a/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml +++ b/app/views/instructeurs/procedures/_dossiers_filter_tags.html.haml @@ -6,6 +6,6 @@ - filters.each_with_index do |filter, i| - if i > 0 = " ou " - = link_to remove_filter_instructeur_procedure_path(procedure, { statut: statut, field: "#{filter['table']}/#{filter['column']}", value: filter['value'] }), + = link_to remove_filter_instructeur_procedure_path(procedure, { statut: statut, column: "#{filter['table']}/#{filter['column']}", value: filter['value'] }), class: "fr-tag fr-tag--dismiss fr-my-1w", aria: { label: "Retirer le filtre #{filter['column']}" } do = "#{filter['label'].truncate(50)} : #{procedure_presentation.human_value_for_filter(filter)}" diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 43e617a65..c62535608 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -98,8 +98,8 @@ %th.text-center %input{ type: "checkbox", disabled: @disable_checkbox_all, checked: @disable_checkbox_all, data: { action: "batch-operation#onCheckAll" }, id: dom_id(BatchOperation.new, :checkbox_all), aria: { label: t('views.instructeurs.dossiers.select_all') } } - - @procedure_presentation.displayed_fields_for_headers.each do |facet| - = render Instructeurs::ColumnTableHeaderComponent.new(procedure_presentation: @procedure_presentation, facet:) + - @procedure_presentation.displayed_fields_for_headers.each do |column| + = render Instructeurs::ColumnTableHeaderComponent.new(procedure_presentation: @procedure_presentation, column:) %th.follow-col Actions diff --git a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml index 20127f210..790b268bf 100644 --- a/app/views/instructeurs/procedures/update_filter.turbo_stream.haml +++ b/app/views/instructeurs/procedures/update_filter.turbo_stream.haml @@ -1,2 +1,2 @@ = turbo_stream.replace 'filter-component' do - = render Instructeurs::ColumnFilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, facet: @facet) + = render Instructeurs::ColumnFilterComponent.new(procedure: @procedure, procedure_presentation: @procedure_presentation, statut: @statut, column: @column) diff --git a/config/routes.rb b/config/routes.rb index 502307d7c..1fddada7d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -473,7 +473,7 @@ Rails.application.routes.draw do end patch 'update_displayed_fields' - get 'update_sort/:facet_id' => 'procedures#update_sort', as: 'update_sort' + get 'update_sort/:column_id' => 'procedures#update_sort', as: 'update_sort' post 'add_filter' post 'update_filter' get 'remove_filter' diff --git a/spec/components/instructeurs/column_filter_component_spec.rb b/spec/components/instructeurs/column_filter_component_spec.rb index 98f3f5035..cb3cda3b0 100644 --- a/spec/components/instructeurs/column_filter_component_spec.rb +++ b/spec/components/instructeurs/column_filter_component_spec.rb @@ -1,5 +1,5 @@ describe Instructeurs::ColumnFilterComponent, type: :component do - let(:component) { described_class.new(procedure:, procedure_presentation:, statut:, facet:) } + let(:component) { described_class.new(procedure:, procedure_presentation:, statut:, column:) } let(:instructeur) { create(:instructeur) } let(:procedure) { create(:procedure, instructeurs: [instructeur]) } @@ -10,40 +10,40 @@ describe Instructeurs::ColumnFilterComponent, type: :component do allow(component).to receive(:current_instructeur).and_return(instructeur) end - describe ".filterable_fields_options" do + describe ".filterable_columns_options" do context 'filders' do - let(:facet) { nil } + let(:column) { nil } let(:included_displayable_field) do [ - Facet.new(label: 'email', table: 'user', column: 'email'), - Facet.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) + Column.new(label: 'email', table: 'user', column: 'email'), + Column.new(label: "depose_since", table: "self", column: "depose_since", virtual: true) ] end - before { allow(procedure).to receive(:facets).and_return(included_displayable_field) } + before { allow(procedure).to receive(:columns).and_return(included_displayable_field) } - subject { component.filterable_fields_options } + subject { component.filterable_columns_options } it { is_expected.to eq([["email", "user/email"], ["depose_since", "self/depose_since"]]) } end end - describe '.options_for_select_of_field' do - subject { component.options_for_select_of_field } + describe '.options_for_select_of_column' do + subject { component.options_for_select_of_column } - context "facet is groupe_instructeur" do - let(:facet) { double("Facet", scope: nil, table: 'groupe_instructeur') } + context "column is groupe_instructeur" do + let(:column) { double("Column", scope: nil, table: 'groupe_instructeur') } let!(:gi_2) { instructeur.groupe_instructeurs.create(label: 'gi2', procedure:) } let!(:gi_3) { instructeur.groupe_instructeurs.create(label: 'gi3', procedure: create(:procedure)) } it { is_expected.to eq([['défaut', procedure.defaut_groupe_instructeur.id], ['gi2', gi_2.id]]) } end - context 'when facet is dropdown' do + context 'when column is dropdown' do let(:types_de_champ_public) { [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }] } let(:procedure) { create(:procedure, :published, types_de_champ_public:) } let(:drop_down_stable_id) { procedure.active_revision.types_de_champ.first.stable_id } - let(:facet) { Facet.new(table: 'type_de_champ', scope: nil, column: drop_down_stable_id) } + let(:column) { Column.new(table: 'type_de_champ', scope: nil, column: drop_down_stable_id) } it 'find most recent tdc' do is_expected.to eq(['Paris', 'Lyon', 'Marseille']) diff --git a/spec/components/instructeurs/column_picker_component_spec.rb b/spec/components/instructeurs/column_picker_component_spec.rb index 7b340a96e..8b609903c 100644 --- a/spec/components/instructeurs/column_picker_component_spec.rb +++ b/spec/components/instructeurs/column_picker_component_spec.rb @@ -6,14 +6,14 @@ describe Instructeurs::ColumnPickerComponent, type: :component do let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - describe "#displayable_fields_for_select" do - let(:default_user_email) { Facet.new(label: 'email', table: 'user', column: 'email') } - let(:excluded_displayable_field) { Facet.new(label: "label1", table: "table1", column: "column1", virtual: true) } + describe "#displayable_columns_for_select" do + let(:default_user_email) { Column.new(label: 'email', table: 'user', column: 'email') } + let(:excluded_displayable_field) { Column.new(label: "label1", table: "table1", column: "column1", virtual: true) } - subject { component.displayable_fields_for_select } + subject { component.displayable_columns_for_select } before do - allow(procedure).to receive(:facets).and_return([ + allow(procedure).to receive(:columns).and_return([ default_user_email, excluded_displayable_field ]) diff --git a/spec/controllers/instructeurs/procedures_controller_spec.rb b/spec/controllers/instructeurs/procedures_controller_spec.rb index c729d0b6c..10937fa07 100644 --- a/spec/controllers/instructeurs/procedures_controller_spec.rb +++ b/spec/controllers/instructeurs/procedures_controller_spec.rb @@ -884,7 +884,7 @@ describe Instructeurs::ProceduresController, type: :controller do end it 'can change order' do - expect { get :update_sort, params: { procedure_id: procedure.id, facet_id: "individual/nom", order: 'asc' } } + expect { get :update_sort, params: { procedure_id: procedure.id, column_id: "individual/nom", order: 'asc' } } .to change { procedure_presentation.sort } .from({ "column" => "notifications", "order" => "desc", "table" => "notifications" }) .to({ "column" => "nom", "order" => "asc", "table" => "individual" }) @@ -901,7 +901,7 @@ describe Instructeurs::ProceduresController, type: :controller do end subject do - post :add_filter, params: { procedure_id: procedure.id, field: "individual/nom", value: "n" * 110, statut: "a-suivre" } + post :add_filter, params: { procedure_id: procedure.id, column: "individual/nom", value: "n" * 110, statut: "a-suivre" } end it 'should render the error' do diff --git a/spec/models/concerns/facets_concern_spec.rb b/spec/models/concerns/columns_concern_spec.rb similarity index 84% rename from spec/models/concerns/facets_concern_spec.rb rename to spec/models/concerns/columns_concern_spec.rb index acf82c6ae..94a9ea077 100644 --- a/spec/models/concerns/facets_concern_spec.rb +++ b/spec/models/concerns/columns_concern_spec.rb @@ -1,6 +1,6 @@ -describe FacetsConcern do - describe "#facets" do - subject { procedure.facets } +describe ColumnsConcern do + describe "#columns" do + subject { procedure.columns } context 'when the procedure can have a SIRET number' do let(:procedure) do @@ -45,7 +45,7 @@ describe FacetsConcern do { label: tdc_2.libelle, table: 'type_de_champ', column: tdc_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, { label: tdc_private_1.libelle, table: 'type_de_champ', column: tdc_private_1.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true }, { label: tdc_private_2.libelle, table: 'type_de_champ', column: tdc_private_2.stable_id.to_s, classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true } - ].map { Facet.new(**_1) } + ].map { Column.new(**_1) } } context 'with explication/header_sections' do @@ -69,9 +69,9 @@ describe FacetsConcern do end context 'when the procedure is for individuals' do - let(:name_field) { Facet.new(label: "Prénom", table: "individual", column: "prenom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } - let(:surname_field) { Facet.new(label: "Nom", table: "individual", column: "nom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } - let(:gender_field) { Facet.new(label: "Civilité", table: "individual", column: "gender", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:name_field) { Column.new(label: "Prénom", table: "individual", column: "prenom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:surname_field) { Column.new(label: "Nom", table: "individual", column: "nom", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } + let(:gender_field) { Column.new(label: "Civilité", table: "individual", column: "gender", classname: '', virtual: false, type: :text, scope: '', value_column: :value, filterable: true) } let(:procedure) { create(:procedure, :for_individual) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } @@ -82,8 +82,8 @@ describe FacetsConcern do let(:procedure) { create(:procedure, :for_individual, :sva) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - let(:decision_on) { Facet.new(label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } - let(:decision_before_field) { Facet.new(label: "Date décision SVA avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_on) { Column.new(label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_before_field) { Column.new(label: "Date décision SVA avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } it { is_expected.to include(decision_on, decision_before_field) } end @@ -92,8 +92,8 @@ describe FacetsConcern do let(:procedure) { create(:procedure, :for_individual, :svr) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } - let(:decision_on) { Facet.new(label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } - let(:decision_before_field) { Facet.new(label: "Date décision SVR avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_on) { Column.new(label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', virtual: false, type: :date, scope: '', value_column: :value, filterable: true) } + let(:decision_before_field) { Column.new(label: "Date décision SVR avant", table: "self", column: "sva_svr_decision_before", classname: '', virtual: true, type: :date, scope: '', value_column: :value, filterable: true) } it { is_expected.to include(decision_on, decision_before_field) } end