From e7bc7c478330e2fda3f0349ba168ba1b982ffc71 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 22 Aug 2024 12:37:10 +0200 Subject: [PATCH] feat(procedure): can filter from repetion content --- app/models/column.rb | 2 + .../concerns/addressable_column_concern.rb | 41 +++------ app/models/concerns/columns_concern.rb | 10 +-- app/models/procedure.rb | 83 ++++++++++--------- app/models/type_de_champ.rb | 2 +- .../repetition_type_de_champ.rb | 6 ++ .../types_de_champ/type_de_champ_base.rb | 13 ++- app/services/procedure_export_service.rb | 6 +- spec/models/dossier_spec.rb | 9 +- ...ocedure_presentation_and_revisions_spec.rb | 4 +- spec/models/procedure_presentation_spec.rb | 2 +- spec/models/procedure_revision_spec.rb | 4 +- .../instructeurs/procedure_filters_spec.rb | 8 ++ 13 files changed, 96 insertions(+), 94 deletions(-) diff --git a/app/models/column.rb b/app/models/column.rb index 0c97e9217..583aa56aa 100644 --- a/app/models/column.rb +++ b/app/models/column.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Column + TYPE_DE_CHAMP_TABLE = 'type_de_champ' + attr_reader :table, :column, :label, :classname, :type, :scope, :value_column, :filterable, :displayable def initialize(table:, column:, label: nil, type: :text, value_column: :value, filterable: true, displayable: true, classname: '', scope: '') diff --git a/app/models/concerns/addressable_column_concern.rb b/app/models/concerns/addressable_column_concern.rb index 42974f69c..f6fbbed5d 100644 --- a/app/models/concerns/addressable_column_concern.rb +++ b/app/models/concerns/addressable_column_concern.rb @@ -4,41 +4,22 @@ module AddressableColumnConcern extend ActiveSupport::Concern included do - def columns(table:) + def columns(displayable: true, prefix: nil) super.concat([ + ["code postal (5 chiffres)", ['postal_code'], :text], + ["commune", ['city_name'], :text], + ["département", ['departement_code'], :enum], + ["region", ['region_name'], :enum] + ].map do |(label, value_column, type)| Columns::JSONPathColumn.new( - table:, - displayable: false, + table: Column::TYPE_DE_CHAMP_TABLE, column: stable_id, - label: "#{libelle} – code postal (5 chiffres)", - type: :text, - value_column: ['postal_code'] - ), - Columns::JSONPathColumn.new( - table:, + label: "#{libelle_with_prefix(prefix)} – #{label}", displayable: false, - column: stable_id, - label: "#{libelle} – commune", - type: :text, - value_column: ['city_name'] - ), - Columns::JSONPathColumn.new( - table:, - displayable: false, - column: stable_id, - label: "#{libelle} – département", - type: :enum, - value_column: ['departement_code'] - ), - Columns::JSONPathColumn.new( - table:, - displayable: false, - column: stable_id, - label: "#{libelle} – région", - type: :enum, - value_column: ['region_name'] + type:, + value_column: ) - ]) + end) end end end diff --git a/app/models/concerns/columns_concern.rb b/app/models/concerns/columns_concern.rb index 1fb72c2af..732b27695 100644 --- a/app/models/concerns/columns_concern.rb +++ b/app/models/concerns/columns_concern.rb @@ -4,8 +4,6 @@ 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 @@ -75,13 +73,7 @@ module ColumnsConcern 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 + all_revisions_types_de_champ.flat_map(&:columns) end end end diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 4bf782525..b8e16abdf 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -74,7 +74,7 @@ class Procedure < ApplicationRecord brouillon? ? draft_revision : published_revision end - def types_de_champ_for_procedure_presentation(parent = nil) + def all_revisions_types_de_champ(parent: nil) if brouillon? if parent.nil? TypeDeChamp.fillable @@ -85,45 +85,15 @@ class Procedure < ApplicationRecord draft_revision.children_of(parent) end else - # all published revisions - revision_ids = revisions.ids - [draft_revision_id] - # fetch all parent types de champ - parent_ids = if parent.present? - ProcedureRevisionTypeDeChamp - .where(revision_id: revision_ids) - .joins(:type_de_champ) - .where(type_de_champ: { stable_id: parent.stable_id }) - .ids - end - - # fetch all type_de_champ.stable_id for all the revisions expect draft - # and for each stable_id take the bigger (more recent) type_de_champ.id - recent_ids = TypeDeChamp - .fillable - .joins(:revision_types_de_champ) - .where(revision_types_de_champ: { revision_id: revision_ids, parent_id: parent_ids }) - .group(:stable_id).select('MAX(types_de_champ.id)') - - # fetch the more recent procedure_revision_types_de_champ - # which includes recents_ids - recents_prtdc = ProcedureRevisionTypeDeChamp - .where(type_de_champ_id: recent_ids) - .where.not(revision_id: draft_revision_id) - .group(:type_de_champ_id) - .select('MAX(id)') - - TypeDeChamp - .joins(:revision_types_de_champ) - .where(revision_types_de_champ: { id: recents_prtdc }).then do |relation| - if feature_enabled?(:export_order_by_revision) # Fonds Verts, en attente d'exports personnalisables - relation.order(:private, 'revision_types_de_champ.revision_id': :desc, position: :asc) - else - relation.order(:private, :position, 'revision_types_de_champ.revision_id': :desc) - end - end + cache_key = ['all_revisions_types_de_champ', published_revision, parent].compact + Rails.cache.fetch(cache_key, expires_in: 1.month) { published_revisions_types_de_champ(parent) } end end + def types_de_champ_for_procedure_export + all_revisions_types_de_champ.not_repetition + end + def types_de_champ_for_tags TypeDeChamp .fillable @@ -1034,6 +1004,45 @@ class Procedure < ApplicationRecord private + def published_revisions_types_de_champ(parent = nil) + # all published revisions + revision_ids = revisions.ids - [draft_revision_id] + # fetch all parent types de champ + parent_ids = if parent.present? + ProcedureRevisionTypeDeChamp + .where(revision_id: revision_ids) + .joins(:type_de_champ) + .where(type_de_champ: { stable_id: parent.stable_id }) + .ids + end + + # fetch all type_de_champ.stable_id for all the revisions expect draft + # and for each stable_id take the bigger (more recent) type_de_champ.id + recent_ids = TypeDeChamp + .fillable + .joins(:revision_types_de_champ) + .where(revision_types_de_champ: { revision_id: revision_ids, parent_id: parent_ids }) + .group(:stable_id).select('MAX(types_de_champ.id)') + + # fetch the more recent procedure_revision_types_de_champ + # which includes recents_ids + recents_prtdc = ProcedureRevisionTypeDeChamp + .where(type_de_champ_id: recent_ids) + .where.not(revision_id: draft_revision_id) + .group(:type_de_champ_id) + .select('MAX(id)') + + TypeDeChamp + .joins(:revision_types_de_champ) + .where(revision_types_de_champ: { id: recents_prtdc }).then do |relation| + if feature_enabled?(:export_order_by_revision) # Fonds Verts, en attente d'exports personnalisables + relation.order(:private, 'revision_types_de_champ.revision_id': :desc, position: :asc) + else + relation.order(:private, :position, 'revision_types_de_champ.revision_id': :desc) + end + end + end + def validates_associated_draft_revision_with_context return if draft_revision.blank? return if draft_revision.validate(validation_context) diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index d56321a14..0fd8333df 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -148,7 +148,7 @@ class TypeDeChamp < ApplicationRecord has_one :revision, through: :revision_type_de_champ has_one :procedure, through: :revision - delegate :estimated_fill_duration, :estimated_read_duration, :tags_for_template, :libelles_for_export, :libelle_for_export, :primary_options, :secondary_options, to: :dynamic_type + delegate :estimated_fill_duration, :estimated_read_duration, :tags_for_template, :libelles_for_export, :libelle_for_export, :primary_options, :secondary_options, :columns, to: :dynamic_type delegate :used_by_routing_rules?, to: :revision_type_de_champ class WithIndifferentAccess diff --git a/app/models/types_de_champ/repetition_type_de_champ.rb b/app/models/types_de_champ/repetition_type_de_champ.rb index 740e572b1..163470308 100644 --- a/app/models/types_de_champ/repetition_type_de_champ.rb +++ b/app/models/types_de_champ/repetition_type_de_champ.rb @@ -26,4 +26,10 @@ class TypesDeChamp::RepetitionTypeDeChamp < TypesDeChamp::TypeDeChampBase # /\*?[] are invalid Excel worksheet characters ActiveStorage::Filename.new(str.delete('[]*?')).sanitized end + + def columns(displayable: true, prefix: nil) + @type_de_champ.procedure + .all_revisions_types_de_champ(parent: @type_de_champ) + .flat_map { _1.columns(displayable: false, prefix: libelle) } + 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 04df7b4c6..3b75b05da 100644 --- a/app/models/types_de_champ/type_de_champ_base.rb +++ b/app/models/types_de_champ/type_de_champ_base.rb @@ -98,20 +98,25 @@ class TypesDeChamp::TypeDeChampBase end end - def columns(table:) + def columns(displayable: true, prefix: nil) [ Column.new( - table:, + table: Column::TYPE_DE_CHAMP_TABLE, column: stable_id.to_s, - label: libelle, + label: libelle_with_prefix(prefix), type: TypeDeChamp.filter_hash_type(type_champ), - value_column: TypeDeChamp.filter_hash_value_column(type_champ) + value_column: TypeDeChamp.filter_hash_value_column(type_champ), + displayable: ) ] end private + def libelle_with_prefix(prefix) + [prefix, libelle].compact.join(' – ') + end + def paths [ { diff --git a/app/services/procedure_export_service.rb b/app/services/procedure_export_service.rb index 1e6918bdb..001ee7ef7 100644 --- a/app/services/procedure_export_service.rb +++ b/app/services/procedure_export_service.rb @@ -107,10 +107,10 @@ class ProcedureExportService .group_by(&:stable_id) procedure - .types_de_champ_for_procedure_presentation + .all_revisions_types_de_champ .repetition .filter_map do |type_de_champ_repetition| - types_de_champ = procedure.types_de_champ_for_procedure_presentation(type_de_champ_repetition).to_a + types_de_champ = procedure.all_revisions_types_de_champ(parent: type_de_champ_repetition).to_a rows = champs_by_stable_id.fetch(type_de_champ_repetition.stable_id, []).flat_map(&:rows_for_export) if types_de_champ.present? && rows.present? @@ -151,7 +151,7 @@ class ProcedureExportService end def spreadsheet_columns(format) - types_de_champ = procedure.types_de_champ_for_procedure_presentation.not_repetition.to_a + types_de_champ = procedure.types_de_champ_for_procedure_export.to_a Proc.new do |instance| instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ) diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index 7564568cf..41f21b271 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -2005,7 +2005,7 @@ describe Dossier, type: :model do expect { integer_number_type_de_champ.update(type_champ: :decimal_number) procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision) - }.to change { dossier.reload.champs_for_export(procedure.types_de_champ_for_procedure_presentation.not_repetition.to_a) } + }.to change { dossier.reload.champs_for_export(procedure.all_revisions_types_de_champ.not_repetition.to_a) } .from([["c1", 42]]).to([["c1", 42.0]]) end end @@ -2032,8 +2032,8 @@ describe Dossier, type: :model do let(:repetition_second_revision_champ) { dossier_second_revision.champs_public.find(&:repetition?) } let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier_second_revision) { create(:dossier, procedure: procedure) } - let(:dossier_champs_for_export) { dossier.champs_for_export(procedure.types_de_champ_for_procedure_presentation.not_repetition) } - let(:dossier_second_revision_champs_for_export) { dossier_second_revision.champs_for_export(procedure.types_de_champ_for_procedure_presentation.not_repetition) } + let(:dossier_champs_for_export) { dossier.champs_for_export(procedure.types_de_champ_for_procedure_export) } + let(:dossier_second_revision_champs_for_export) { dossier_second_revision.champs_for_export(procedure.types_de_champ_for_procedure_export) } context "when procedure published" do before do @@ -2066,8 +2066,7 @@ describe Dossier, type: :model do draft.add_type_de_champ(type_champ: :communes, libelle: "communes", parent_stable_id: tdc_repetition.stable_id) dossier_test = create(:dossier, procedure: proc_test) - repetition = proc_test.types_de_champ_for_procedure_presentation.repetition.first - type_champs = proc_test.types_de_champ_for_procedure_presentation(repetition).to_a + type_champs = proc_test.all_revisions_types_de_champ(parent: tdc_repetition).to_a expect(type_champs.size).to eq(1) expect(dossier.champs_for_export(type_champs).size).to eq(3) end diff --git a/spec/models/procedure_presentation_and_revisions_spec.rb b/spec/models/procedure_presentation_and_revisions_spec.rb index d2920a826..62cdc8bc5 100644 --- a/spec/models/procedure_presentation_and_revisions_spec.rb +++ b/spec/models/procedure_presentation_and_revisions_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true describe ProcedurePresentation do - describe "#types_de_champ_for_procedure_presentation" do - subject { procedure.types_de_champ_for_procedure_presentation.not_repetition.pluck(:libelle) } + describe "#types_de_champ_for_procedure_export" do + subject { procedure.types_de_champ_for_procedure_export.pluck(:libelle) } context 'for a draft procedure' do let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :number, libelle: 'libelle 1' }]) } diff --git a/spec/models/procedure_presentation_spec.rb b/spec/models/procedure_presentation_spec.rb index 266d2c23e..66e8584b0 100644 --- a/spec/models/procedure_presentation_spec.rb +++ b/spec/models/procedure_presentation_spec.rb @@ -568,7 +568,7 @@ describe ProcedurePresentation do context 'for type_de_champ using AddressableColumnConcern' do let(:types_de_champ_public) { [{ type: :rna, stable_id: 1 }] } let(:type_de_champ) { procedure.active_revision.types_de_champ.first } - let(:available_columns) { type_de_champ.dynamic_type.columns(table: 'type_de_champ') } + let(:available_columns) { type_de_champ.columns } let(:column) { available_columns.find { _1.value_column == value_column_searched } } let(:filter) { [column.to_json.merge({ "value" => value })] } let(:kept_dossier) { create(:dossier, procedure: procedure) } diff --git a/spec/models/procedure_revision_spec.rb b/spec/models/procedure_revision_spec.rb index 2f3ef9fbc..592187aaa 100644 --- a/spec/models/procedure_revision_spec.rb +++ b/spec/models/procedure_revision_spec.rb @@ -123,7 +123,7 @@ describe ProcedureRevision do draft.reload expect(draft.revision_types_de_champ_public.map(&:position)).to eq([0, 1, 2, 3]) expect(draft.types_de_champ_public.index(type_de_champ_public)).to eq(2) - expect(draft.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(type_de_champ_public)).to eq(2) + expect(draft.procedure.types_de_champ_for_procedure_export.index(type_de_champ_public)).to eq(2) end it 'move up' do @@ -132,7 +132,7 @@ describe ProcedureRevision do draft.reload expect(draft.revision_types_de_champ_public.map(&:position)).to eq([0, 1, 2, 3]) expect(draft.types_de_champ_public.index(last_type_de_champ)).to eq(0) - expect(draft.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(last_type_de_champ)).to eq(0) + expect(draft.procedure.types_de_champ_for_procedure_export.index(last_type_de_champ)).to eq(0) end end diff --git a/spec/system/instructeurs/procedure_filters_spec.rb b/spec/system/instructeurs/procedure_filters_spec.rb index 6b2314b44..860caf186 100644 --- a/spec/system/instructeurs/procedure_filters_spec.rb +++ b/spec/system/instructeurs/procedure_filters_spec.rb @@ -109,6 +109,14 @@ describe "procedure filters" do end end + describe 'with repetition' do + let(:types_de_champ_public) { [{ type: :repetition, libelle: 'Enfants', children: [{ libelle: 'Nom' }] }] } + + scenario "should be able to user custom fiters", js: true do + add_filter('Enfants – Nom', 'Greer') + end + end + describe 'with a vcr cached cassette' do describe 'departements' do let(:types_de_champ_public) { [{ type: :departements }] }