feat(procedure): can filter from repetion content

This commit is contained in:
Paul Chavard 2024-08-22 12:37:10 +02:00
parent 4bd518f85e
commit e7bc7c4783
No known key found for this signature in database
13 changed files with 96 additions and 94 deletions

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Column class Column
TYPE_DE_CHAMP_TABLE = 'type_de_champ'
attr_reader :table, :column, :label, :classname, :type, :scope, :value_column, :filterable, :displayable 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: '') def initialize(table:, column:, label: nil, type: :text, value_column: :value, filterable: true, displayable: true, classname: '', scope: '')

View file

@ -4,41 +4,22 @@ module AddressableColumnConcern
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
def columns(table:) def columns(displayable: true, prefix: nil)
super.concat([ 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( Columns::JSONPathColumn.new(
table:, table: Column::TYPE_DE_CHAMP_TABLE,
displayable: false,
column: stable_id, column: stable_id,
label: "#{libelle} code postal (5 chiffres)", label: "#{libelle_with_prefix(prefix)} #{label}",
type: :text,
value_column: ['postal_code']
),
Columns::JSONPathColumn.new(
table:,
displayable: false, displayable: false,
column: stable_id, type:,
label: "#{libelle} commune", value_column:
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']
) )
]) end)
end end
end end
end end

View file

@ -4,8 +4,6 @@ module ColumnsConcern
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
TYPE_DE_CHAMP = 'type_de_champ'
def find_column(id:) = columns.find { |f| f.id == id } def find_column(id:) = columns.find { |f| f.id == id }
def columns def columns
@ -75,13 +73,7 @@ module ColumnsConcern
end end
def types_de_champ_columns def types_de_champ_columns
types_de_champ_for_procedure_presentation all_revisions_types_de_champ.flat_map(&:columns)
.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 end
end end

View file

@ -74,7 +74,7 @@ class Procedure < ApplicationRecord
brouillon? ? draft_revision : published_revision brouillon? ? draft_revision : published_revision
end end
def types_de_champ_for_procedure_presentation(parent = nil) def all_revisions_types_de_champ(parent: nil)
if brouillon? if brouillon?
if parent.nil? if parent.nil?
TypeDeChamp.fillable TypeDeChamp.fillable
@ -85,45 +85,15 @@ class Procedure < ApplicationRecord
draft_revision.children_of(parent) draft_revision.children_of(parent)
end end
else else
# all published revisions cache_key = ['all_revisions_types_de_champ', published_revision, parent].compact
revision_ids = revisions.ids - [draft_revision_id] Rails.cache.fetch(cache_key, expires_in: 1.month) { published_revisions_types_de_champ(parent) }
# 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 end
end end
def types_de_champ_for_procedure_export
all_revisions_types_de_champ.not_repetition
end
def types_de_champ_for_tags def types_de_champ_for_tags
TypeDeChamp TypeDeChamp
.fillable .fillable
@ -1034,6 +1004,45 @@ class Procedure < ApplicationRecord
private 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 def validates_associated_draft_revision_with_context
return if draft_revision.blank? return if draft_revision.blank?
return if draft_revision.validate(validation_context) return if draft_revision.validate(validation_context)

View file

@ -148,7 +148,7 @@ class TypeDeChamp < ApplicationRecord
has_one :revision, through: :revision_type_de_champ has_one :revision, through: :revision_type_de_champ
has_one :procedure, through: :revision 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 delegate :used_by_routing_rules?, to: :revision_type_de_champ
class WithIndifferentAccess class WithIndifferentAccess

View file

@ -26,4 +26,10 @@ class TypesDeChamp::RepetitionTypeDeChamp < TypesDeChamp::TypeDeChampBase
# /\*?[] are invalid Excel worksheet characters # /\*?[] are invalid Excel worksheet characters
ActiveStorage::Filename.new(str.delete('[]*?')).sanitized ActiveStorage::Filename.new(str.delete('[]*?')).sanitized
end 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 end

View file

@ -98,20 +98,25 @@ class TypesDeChamp::TypeDeChampBase
end end
end end
def columns(table:) def columns(displayable: true, prefix: nil)
[ [
Column.new( Column.new(
table:, table: Column::TYPE_DE_CHAMP_TABLE,
column: stable_id.to_s, column: stable_id.to_s,
label: libelle, label: libelle_with_prefix(prefix),
type: TypeDeChamp.filter_hash_type(type_champ), 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 end
private private
def libelle_with_prefix(prefix)
[prefix, libelle].compact.join(' ')
end
def paths def paths
[ [
{ {

View file

@ -107,10 +107,10 @@ class ProcedureExportService
.group_by(&:stable_id) .group_by(&:stable_id)
procedure procedure
.types_de_champ_for_procedure_presentation .all_revisions_types_de_champ
.repetition .repetition
.filter_map do |type_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) rows = champs_by_stable_id.fetch(type_de_champ_repetition.stable_id, []).flat_map(&:rows_for_export)
if types_de_champ.present? && rows.present? if types_de_champ.present? && rows.present?
@ -151,7 +151,7 @@ class ProcedureExportService
end end
def spreadsheet_columns(format) 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| Proc.new do |instance|
instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ) instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ)

View file

@ -2005,7 +2005,7 @@ describe Dossier, type: :model do
expect { expect {
integer_number_type_de_champ.update(type_champ: :decimal_number) integer_number_type_de_champ.update(type_champ: :decimal_number)
procedure.update(published_revision: procedure.draft_revision, draft_revision: procedure.create_new_revision) 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]]) .from([["c1", 42]]).to([["c1", 42.0]])
end end
end end
@ -2032,8 +2032,8 @@ describe Dossier, type: :model do
let(:repetition_second_revision_champ) { dossier_second_revision.champs_public.find(&:repetition?) } let(:repetition_second_revision_champ) { dossier_second_revision.champs_public.find(&:repetition?) }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure: procedure) }
let(:dossier_second_revision) { 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_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_presentation.not_repetition) } 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 context "when procedure published" do
before 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) draft.add_type_de_champ(type_champ: :communes, libelle: "communes", parent_stable_id: tdc_repetition.stable_id)
dossier_test = create(:dossier, procedure: proc_test) dossier_test = create(:dossier, procedure: proc_test)
repetition = proc_test.types_de_champ_for_procedure_presentation.repetition.first type_champs = proc_test.all_revisions_types_de_champ(parent: tdc_repetition).to_a
type_champs = proc_test.types_de_champ_for_procedure_presentation(repetition).to_a
expect(type_champs.size).to eq(1) expect(type_champs.size).to eq(1)
expect(dossier.champs_for_export(type_champs).size).to eq(3) expect(dossier.champs_for_export(type_champs).size).to eq(3)
end end

View file

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
describe ProcedurePresentation do describe ProcedurePresentation do
describe "#types_de_champ_for_procedure_presentation" do describe "#types_de_champ_for_procedure_export" do
subject { procedure.types_de_champ_for_procedure_presentation.not_repetition.pluck(:libelle) } subject { procedure.types_de_champ_for_procedure_export.pluck(:libelle) }
context 'for a draft procedure' do context 'for a draft procedure' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :number, libelle: 'libelle 1' }]) } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :number, libelle: 'libelle 1' }]) }

View file

@ -568,7 +568,7 @@ describe ProcedurePresentation do
context 'for type_de_champ using AddressableColumnConcern' do context 'for type_de_champ using AddressableColumnConcern' do
let(:types_de_champ_public) { [{ type: :rna, stable_id: 1 }] } let(:types_de_champ_public) { [{ type: :rna, stable_id: 1 }] }
let(:type_de_champ) { procedure.active_revision.types_de_champ.first } 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(:column) { available_columns.find { _1.value_column == value_column_searched } }
let(:filter) { [column.to_json.merge({ "value" => value })] } let(:filter) { [column.to_json.merge({ "value" => value })] }
let(:kept_dossier) { create(:dossier, procedure: procedure) } let(:kept_dossier) { create(:dossier, procedure: procedure) }

View file

@ -123,7 +123,7 @@ describe ProcedureRevision do
draft.reload draft.reload
expect(draft.revision_types_de_champ_public.map(&:position)).to eq([0, 1, 2, 3]) 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.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 end
it 'move up' do it 'move up' do
@ -132,7 +132,7 @@ describe ProcedureRevision do
draft.reload draft.reload
expect(draft.revision_types_de_champ_public.map(&:position)).to eq([0, 1, 2, 3]) 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.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
end end

View file

@ -109,6 +109,14 @@ describe "procedure filters" do
end end
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 'with a vcr cached cassette' do
describe 'departements' do describe 'departements' do
let(:types_de_champ_public) { [{ type: :departements }] } let(:types_de_champ_public) { [{ type: :departements }] }