diff --git a/app/components/instructeurs/column_picker_component.rb b/app/components/instructeurs/column_picker_component.rb index 92cc524da..f8c1b0fba 100644 --- a/app/components/instructeurs/column_picker_component.rb +++ b/app/components/instructeurs/column_picker_component.rb @@ -12,7 +12,7 @@ class Instructeurs::ColumnPickerComponent < ApplicationComponent def displayable_columns_for_select [ procedure.columns.filter(&:displayable).map { |column| [column.label, column.id] }, - procedure_presentation.displayed_fields.map { Column.new(**_1.deep_symbolize_keys.merge(procedure_id: procedure.id)).id } + procedure_presentation.displayed_columns.map(&:id) ] end end diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 89044e7d2..68ed3b7df 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -104,7 +104,7 @@ module Instructeurs .page(page) .per(ITEMS_PER_PAGE) - @projected_dossiers = DossierProjectionService.project(@filtered_sorted_paginated_ids, procedure_presentation.displayed_fields) + @projected_dossiers = DossierProjectionService.project(@filtered_sorted_paginated_ids, procedure_presentation.displayed_columns) @disable_checkbox_all = @projected_dossiers.all? { _1.batch_operation_id.present? } @batch_operations = BatchOperation.joins(:groupe_instructeurs) @@ -133,9 +133,9 @@ module Instructeurs end def update_displayed_fields - values = (params['values'].presence || []).reject(&:empty?) + ids = (params['values'].presence || []).reject(&:empty?) - procedure_presentation.update_displayed_fields(values) + procedure_presentation.update!(displayed_columns: ids) redirect_back(fallback_location: instructeur_procedure_url(procedure)) end diff --git a/app/controllers/recherche_controller.rb b/app/controllers/recherche_controller.rb index 98d684792..63772f839 100644 --- a/app/controllers/recherche_controller.rb +++ b/app/controllers/recherche_controller.rb @@ -3,10 +3,14 @@ class RechercheController < ApplicationController before_action :authenticate_logged_user! ITEMS_PER_PAGE = 25 + + # the columns are generally procedure specific + # but in the search context, we are looking for dossiers from multiple procedures + # so we are faking the columns with a random procedure_id PROJECTIONS = [ - { "table" => 'procedure', "column" => 'libelle' }, - { "table" => 'user', "column" => 'email' }, - { "table" => 'procedure', "column" => 'procedure_id' } + Column.new(procedure_id: 666, table: 'procedure', column: 'libelle'), + Column.new(procedure_id: 666, table: 'user', column: 'email'), + Column.new(procedure_id: 666, table: 'procedure', column: 'procedure_id') ] def nav_bar_profile diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index 1619ecf37..d4dcecc09 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -21,6 +21,8 @@ class ProcedurePresentation < ApplicationRecord validate :check_filters_max_length validate :check_filters_max_integer + attribute :displayed_columns, :column, array: true + attribute :sorted_column, :sorted_column def sorted_column = super || procedure.default_sorted_column # Dummy override to set default value @@ -42,7 +44,7 @@ class ProcedurePresentation < ApplicationRecord def displayed_fields_for_headers [ Column.new(procedure_id: procedure.id, table: 'self', column: 'id', classname: 'number-col'), - *displayed_fields.map { Column.new(**_1.deep_symbolize_keys.merge(procedure_id: procedure.id)) }, + *displayed_columns, Column.new(procedure_id: procedure.id, table: 'self', column: 'state', classname: 'state-col'), *procedure.sva_svr_columns ] diff --git a/app/services/dossier_projection_service.rb b/app/services/dossier_projection_service.rb index fbbe64da8..041969489 100644 --- a/app/services/dossier_projection_service.rb +++ b/app/services/dossier_projection_service.rb @@ -40,8 +40,10 @@ class DossierProjectionService # Those hashes are needed because: # - 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 + def self.project(dossiers_ids, columns) + fields = columns.map { |c| { TABLE => c.table, COLUMN => c.column } } + champ_value = champ_value_formatter(dossiers_ids, fields) + state_field = { TABLE => 'self', COLUMN => 'state' } archived_field = { TABLE => 'self', COLUMN => 'archived' } batch_operation_field = { TABLE => 'self', COLUMN => 'batch_operation_id' } @@ -53,7 +55,7 @@ class DossierProjectionService individual_last_name = { TABLE => 'individual', COLUMN => 'nom' } sva_svr_decision_on_field = { TABLE => 'self', COLUMN => 'sva_svr_decision_on' } dossier_corrections = { TABLE => 'dossier_corrections', COLUMN => 'resolved_at' } - champ_value = champ_value_formatter(dossiers_ids, fields) + ([state_field, archived_field, sva_svr_decision_on_field, hidden_by_user_at_field, hidden_by_administration_at_field, hidden_by_reason_field, for_tiers_field, individual_first_name, individual_last_name, batch_operation_field, dossier_corrections] + fields) .each { |f| f[:id_value_h] = {} } .group_by { |f| f[TABLE] } # one query per table diff --git a/spec/lib/tasks/deployment/20240920130741_migrate_procedure_presentation_to_columns.rake_spec.rb b/spec/lib/tasks/deployment/20240920130741_migrate_procedure_presentation_to_columns.rake_spec.rb index 0494ba5e7..795736c26 100644 --- a/spec/lib/tasks/deployment/20240920130741_migrate_procedure_presentation_to_columns.rake_spec.rb +++ b/spec/lib/tasks/deployment/20240920130741_migrate_procedure_presentation_to_columns.rake_spec.rb @@ -38,10 +38,7 @@ describe '20240920130741_migrate_procedure_presentation_to_columns.rake' do it 'populates the columns' do procedure_id = procedure.id - expect(procedure_presentation.displayed_columns).to eq([ - { "procedure_id" => procedure_id, "column_id" => "etablissement/entreprise_raison_sociale" }, - { "procedure_id" => procedure_id, "column_id" => "type_de_champ/#{stable_id}" } - ]) + expect(procedure_presentation.displayed_columns.map(&:label)).to eq(["Raison sociale", procedure.active_revision.types_de_champ.first.libelle]) order, column_id = procedure_presentation .sorted_column diff --git a/spec/models/instructeur_spec.rb b/spec/models/instructeur_spec.rb index 9523e6779..5ac18c0df 100644 --- a/spec/models/instructeur_spec.rb +++ b/spec/models/instructeur_spec.rb @@ -167,20 +167,6 @@ describe Instructeur, type: :model do it { expect(errors).to be_nil } end - context 'with invalid presentation' do - let(:procedure_id) { procedure.id } - before do - pp = ProcedurePresentation.create(assign_to: procedure_assign, displayed_fields: [{ 'table' => 'invalid', 'column' => 'random' }]) - pp.save(:validate => false) - end - - it 'recreates a valid prsentation' do - expect(procedure_presentation).to be_persisted - end - it { expect(procedure_presentation).to be_valid } - it { expect(errors).to be_present } - end - context 'with default presentation' do let(:procedure_id) { procedure_2.id } diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 315deccb0..2fd15c6e2 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -40,8 +40,11 @@ describe ProcedurePresentation do describe 'validation' 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 } + context 'of displayed columns' do + it do + pp = build(:procedure_presentation, displayed_columns: [{ table: "user", column: "reset_password_token", procedure_id: }]) + expect { pp.displayed_columns }.to raise_error(ActiveRecord::RecordNotFound) + end end context 'of filters' do @@ -795,6 +798,9 @@ describe ProcedurePresentation do end describe '#update_displayed_fields' do + let(:en_construction_column) { procedure.find_column(label: 'En construction le') } + let(:mise_a_jour_column) { procedure.find_column(label: 'Mis à jour le') } + let(:procedure_presentation) do create(:procedure_presentation, assign_to:).tap do |pp| pp.update(sorted_column: SortedColumn.new(column: procedure.find_column(label: 'Demandeur'), order: 'desc')) @@ -802,24 +808,19 @@ describe ProcedurePresentation do end subject do - procedure_presentation.update_displayed_fields([ - procedure.find_column(label: 'En construction le').id, - procedure.find_column(label: 'Mis à jour le').id + procedure_presentation.update(displayed_columns: [ + en_construction_column.id, mise_a_jour_column.id ]) end it 'should update displayed_fields' do - expect(procedure_presentation.displayed_columns).to eq([]) + expect(procedure_presentation.displayed_columns).to eq(procedure.default_displayed_columns) subject expect(procedure_presentation.displayed_columns).to eq([ - { "column_id" => "self/en_construction_at", "procedure_id" => procedure.id }, - { "column_id" => "self/updated_at", "procedure_id" => procedure.id } + en_construction_column, mise_a_jour_column ]) - - expect(procedure_presentation.sorted_column).to eq(procedure.default_sorted_column) - expect(procedure_presentation.sorted_column.order).to eq('desc') end end end diff --git a/spec/services/dossier_projection_service_spec.rb b/spec/services/dossier_projection_service_spec.rb index 1b577cf40..7671bdda5 100644 --- a/spec/services/dossier_projection_service_spec.rb +++ b/spec/services/dossier_projection_service_spec.rb @@ -2,7 +2,7 @@ describe DossierProjectionService do describe '#project' do - subject { described_class.project(dossiers_ids, fields) } + subject { described_class.project(dossiers_ids, columns) } context 'with multiple dossier' do let!(:procedure) { create(:procedure, types_de_champ_public: [{}, { type: :linked_drop_down_list }]) } @@ -11,12 +11,9 @@ describe DossierProjectionService do let!(:dossier_3) { create(:dossier, :en_instruction, procedure: procedure) } let(:dossiers_ids) { [dossier_3.id, dossier_1.id, dossier_2.id] } - let(:fields) do + let(:columns) do procedure.active_revision.types_de_champ_public.map do |type_de_champ| - { - "table" => "type_de_champ", - "column" => type_de_champ.stable_id.to_s - } + procedure.find_column(label: type_de_champ.libelle) end end @@ -55,12 +52,9 @@ describe DossierProjectionService do let!(:dossier) { create(:dossier, procedure:) } let(:dossiers_ids) { [dossier.id] } - let(:fields) do + let(:columns) do [ - { - "table" => "type_de_champ", - "column" => procedure.active_revision.types_de_champ_public[0].stable_id.to_s - } + procedure.find_column(label: procedure.active_revision.types_de_champ_public[0].libelle) ] end @@ -78,38 +72,37 @@ describe DossierProjectionService do end context 'attributes by attributes' do - let(:fields) { [{ "table" => table, "column" => column }] } + let(:procedure) { create(:procedure) } + let(:columns) { [procedure.find_column(label:)] } let(:dossiers_ids) { [dossier.id] } subject { super()[0].columns[0] } context 'for self table' do - let(:table) { 'self' } - context 'for created_at column' do - let(:column) { 'created_at' } - let(:dossier) { Timecop.freeze(Time.zone.local(1992, 3, 22)) { create(:dossier) } } + let(:label) { 'Créé le' } + let(:dossier) { Timecop.freeze(Time.zone.local(1992, 3, 22)) { create(:dossier, procedure:) } } it { is_expected.to eq('22/03/1992') } end context 'for en_construction_at column' do - let(:column) { 'en_construction_at' } - let(:dossier) { create(:dossier, :en_construction, en_construction_at: Time.zone.local(2018, 10, 17)) } + let(:label) { 'En construction le' } + let(:dossier) { create(:dossier, :en_construction, en_construction_at: Time.zone.local(2018, 10, 17), procedure:) } it { is_expected.to eq('17/10/2018') } end context 'for depose_at column' do - let(:column) { 'depose_at' } - let(:dossier) { create(:dossier, :en_construction, depose_at: Time.zone.local(2018, 10, 17)) } + let(:label) { 'Déposé le' } + let(:dossier) { create(:dossier, :en_construction, depose_at: Time.zone.local(2018, 10, 17), procedure:) } it { is_expected.to eq('17/10/2018') } end context 'for updated_at column' do - let(:column) { 'updated_at' } - let(:dossier) { create(:dossier) } + let(:label) { 'Mis à jour le' } + let(:dossier) { create(:dossier, procedure:) } before { dossier.touch(time: Time.zone.local(2018, 9, 25)) } @@ -118,61 +111,56 @@ describe DossierProjectionService do end context 'for user table' do - let(:table) { 'user' } - let(:column) { 'email' } + let(:label) { 'Demandeur' } - let(:dossier) { create(:dossier, user: create(:user, email: 'bla@yopmail.com')) } + let(:dossier) { create(:dossier, user: create(:user, email: 'bla@yopmail.com'), procedure:) } it { is_expected.to eq('bla@yopmail.com') } end context 'for individual table' do - let(:table) { 'individual' } let(:procedure) { create(:procedure, :for_individual, :with_type_de_champ, :with_type_de_champ_private) } - let(:dossier) { create(:dossier, procedure: procedure, individual: build(:individual, nom: 'Martin', prenom: 'Jacques', gender: 'M.')) } + let(:dossier) { create(:dossier, procedure:, individual: build(:individual, nom: 'Martin', prenom: 'Jacques', gender: 'M.')) } context 'for prenom column' do - let(:column) { 'prenom' } + let(:label) { 'Prénom' } it { is_expected.to eq('Jacques') } end context 'for nom column' do - let(:column) { 'nom' } + let(:label) { 'Nom' } it { is_expected.to eq('Martin') } end context 'for gender column' do - let(:column) { 'gender' } + let(:label) { 'Civilité' } it { is_expected.to eq('M.') } end end context 'for etablissement table' do - let(:table) { 'etablissement' } - let(:column) { 'code_postal' } # All other columns work the same, no extra test required + let(:label) { 'Code postal' } - let!(:dossier) { create(:dossier, etablissement: create(:etablissement, code_postal: '75008')) } + let!(:dossier) { create(:dossier, procedure:, etablissement: create(:etablissement, code_postal: '75008')) } it { is_expected.to eq('75008') } end context 'for groupe_instructeur table' do - let(:table) { 'groupe_instructeur' } - let(:column) { 'label' } + let(:label) { 'Groupe instructeur' } - let!(:dossier) { create(:dossier) } + let!(:dossier) { create(:dossier, procedure:) } it { is_expected.to eq('défaut') } end context 'for followers_instructeurs table' do - let(:table) { 'followers_instructeurs' } - let(:column) { 'email' } + let(:label) { 'Email instructeur' } - let(:dossier) { create(:dossier) } + let(:dossier) { create(:dossier, procedure:) } let!(:follow1) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'b@host.fr')) } let!(:follow2) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'a@host.fr')) } let!(:follow3) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'c@host.fr')) } @@ -181,19 +169,21 @@ describe DossierProjectionService do end context 'for type_de_champ table' do - let(:table) { 'type_de_champ' } - let(:dossier) { create(:dossier) } - let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } + let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :text }]) } + let(:dossier) { create(:dossier, procedure:) } + let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle } - before { dossier.project_champs_public.first.update(value: 'kale') } + before do + dossier.project_champs_public.first.update(value: 'kale') + end it { is_expected.to eq('kale') } end context 'for type_de_champ_private table' do - let(:table) { 'type_de_champ_private' } - let(:dossier) { create(:dossier) } - let(:column) { dossier.procedure.active_revision.types_de_champ_private.first.stable_id.to_s } + let(:procedure) { create(:procedure, types_de_champ_private: [{ type: :text }]) } + let(:dossier) { create(:dossier, procedure:) } + let(:label) { dossier.procedure.active_revision.types_de_champ_private.first.libelle } before { dossier.project_champs_private.first.update(value: 'quinoa') } @@ -201,10 +191,9 @@ describe DossierProjectionService do end context 'for type_de_champ table and value to.s' do - let(:table) { 'type_de_champ' } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }]) } - let(:dossier) { create(:dossier, procedure: procedure) } - let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } + let(:dossier) { create(:dossier, procedure:) } + let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle } before { dossier.project_champs_public.first.update(value: 'true') } @@ -212,10 +201,9 @@ describe DossierProjectionService do end context 'for type_de_champ table and value to.s which needs data field' do - let(:table) { 'type_de_champ' } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :address }]) } - let(:dossier) { create(:dossier, procedure: procedure) } - let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } + let(:dossier) { create(:dossier, procedure:) } + let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle } before { dossier.project_champs_public.first.update(value: '18 a la bonne rue', data: { 'label' => '18 a la bonne rue', 'departement' => 'd' }) } @@ -223,10 +211,9 @@ describe DossierProjectionService do end context 'for type_de_champ table: type_de_champ pays which needs external_id field' do - let(:table) { 'type_de_champ' } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :pays }]) } - let(:dossier) { create(:dossier, procedure: procedure) } - let(:column) { dossier.procedure.active_revision.types_de_champ_public.first.stable_id.to_s } + let(:dossier) { create(:dossier, procedure:) } + let(:label) { dossier.procedure.active_revision.types_de_champ_public.first.libelle } around do |example| I18n.with_locale(:fr) do @@ -254,8 +241,10 @@ describe DossierProjectionService do context 'for dossier corrections table' do let(:table) { 'dossier_corrections' } let(:column) { 'resolved_at' } - let(:dossier) { create(:dossier, :en_construction) } - subject { described_class.project(dossiers_ids, fields)[0] } + let(:procedure) { create(:procedure) } + let(:columns) { [Column.new(procedure_id: procedure.id, table:, column:)] } # should somehow be present in column concern + let(:dossier) { create(:dossier, :en_construction, procedure:) } + subject { described_class.project(dossiers_ids, columns)[0] } context "when dossier has pending correction" do before { create(:dossier_correction, dossier:) }