Merge pull request #10850 from demarches-simplifiees/add_id_to_column

Tech: utilise des colonnes id dans les `ProcedurePresentation`
This commit is contained in:
LeSim 2024-10-08 07:41:48 +00:00 committed by GitHub
commit c072ee7226
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 342 additions and 93 deletions

View file

@ -1,4 +1,4 @@
= form_tag update_sort_instructeur_procedure_path(procedure_id: @procedure.id, column_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: @procedure.notifications_column.id, order: opposite_order), method: :get, data: { controller: 'autosubmit' } do
.fr-fieldset__element.fr-m-0 .fr-fieldset__element.fr-m-0
.fr-checkbox-group.fr-checkbox-group--sm .fr-checkbox-group.fr-checkbox-group--sm
= check_box_tag :order, opposite_order, active? = check_box_tag :order, opposite_order, active?

View file

@ -12,7 +12,7 @@ class Instructeurs::ColumnPickerComponent < ApplicationComponent
def displayable_columns_for_select def displayable_columns_for_select
[ [
procedure.columns.filter(&:displayable).map { |column| [column.label, column.id] }, procedure.columns.filter(&:displayable).map { |column| [column.label, column.id] },
procedure_presentation.displayed_fields.map { Column.new(**_1.deep_symbolize_keys).id } procedure_presentation.displayed_fields.map { Column.new(**_1.deep_symbolize_keys.merge(procedure_id: procedure.id)).id }
] ]
end end
end end

View file

@ -158,7 +158,7 @@ module Instructeurs
@statut = statut @statut = statut
@procedure = procedure @procedure = procedure
@procedure_presentation = procedure_presentation @procedure_presentation = procedure_presentation
@column = procedure.find_column(id: params[:column]) @column = procedure.find_column(h_id: JSON.parse(params[:column], symbolize_names: true))
end end
def remove_filter def remove_filter

View file

@ -5,7 +5,8 @@ class Column
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(procedure_id:, table:, column:, label: nil, type: :text, value_column: :value, filterable: true, displayable: true, classname: '', scope: '')
@procedure_id = procedure_id
@table = table @table = table
@column = column @column = column
@label = label || I18n.t(column, scope: [:activerecord, :attributes, :procedure_presentation, :fields, table]) @label = label || I18n.t(column, scope: [:activerecord, :attributes, :procedure_presentation, :fields, table])
@ -14,21 +15,12 @@ class Column
@scope = scope @scope = scope
@value_column = value_column @value_column = value_column
@filterable = filterable @filterable = filterable
# We need this for backward compatibility
@displayable = displayable @displayable = displayable
end end
def id def id = h_id.to_json
"#{table}/#{column}" def h_id = { procedure_id: @procedure_id, column_id: "#{table}/#{column}" }
end def ==(other) = h_id == other.h_id # using h_id instead of id to avoid inversion of keys
def self.make_id(table, column)
"#{table}/#{column}"
end
def ==(other)
other.to_json == to_json
end
def to_json def to_json
{ {

View file

@ -4,7 +4,7 @@ module AddressableColumnConcern
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
def columns(displayable: true, prefix: nil) def columns(procedure_id:, displayable: true, prefix: nil)
super.concat([ super.concat([
["code postal (5 chiffres)", ['postal_code'], :text], ["code postal (5 chiffres)", ['postal_code'], :text],
["commune", ['city_name'], :text], ["commune", ['city_name'], :text],
@ -12,6 +12,7 @@ module AddressableColumnConcern
["region", ['region_name'], :enum] ["region", ['region_name'], :enum]
].map do |(label, value_column, type)| ].map do |(label, value_column, type)|
Columns::JSONPathColumn.new( Columns::JSONPathColumn.new(
procedure_id:,
table: Column::TYPE_DE_CHAMP_TABLE, table: Column::TYPE_DE_CHAMP_TABLE,
column: stable_id, column: stable_id,
label: "#{libelle_with_prefix(prefix)} #{label}", label: "#{libelle_with_prefix(prefix)} #{label}",

View file

@ -4,7 +4,10 @@ module ColumnsConcern
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
def find_column(id:) = columns.find { |f| f.id == id } def find_column(h_id: nil, label: nil)
return columns.find { _1.h_id == h_id } if h_id.present?
return columns.find { _1.label == label } if label.present?
end
def columns def columns
columns = dossier_columns columns = dossier_columns
@ -14,16 +17,24 @@ module ColumnsConcern
columns.concat(types_de_champ_columns) columns.concat(types_de_champ_columns)
end end
def dossier_id_column
Column.new(procedure_id: id, table: 'self', column: 'id', classname: 'number-col', type: :number)
end
def notifications_column
Column.new(procedure_id: id, table: 'notifications', column: 'notifications', label: "notifications", filterable: false)
end
def dossier_columns def dossier_columns
common = [Column.new(table: 'self', column: 'id', classname: 'number-col', type: :number), Column.new(table: 'notifications', column: 'notifications', label: "notifications", filterable: false)] common = [dossier_id_column, notifications_column]
dates = ['created_at', 'updated_at', 'depose_at', 'en_construction_at', 'en_instruction_at', 'processed_at'] dates = ['created_at', 'updated_at', 'depose_at', 'en_construction_at', 'en_instruction_at', 'processed_at']
.map { |column| Column.new(table: 'self', column:, type: :date) } .map { |column| Column.new(procedure_id: id, table: 'self', column:, type: :date) }
non_displayable_dates = ['updated_since', 'depose_since', 'en_construction_since', 'en_instruction_since', 'processed_since'] non_displayable_dates = ['updated_since', 'depose_since', 'en_construction_since', 'en_instruction_since', 'processed_since']
.map { |column| Column.new(table: 'self', column:, type: :date, displayable: false) } .map { |column| Column.new(procedure_id: id, table: 'self', column:, type: :date, displayable: false) }
states = [Column.new(table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', displayable: false)] states = [Column.new(procedure_id: id, table: 'self', column: 'state', type: :enum, scope: 'instructeurs.dossiers.filterable_state', displayable: false)]
[common, dates, sva_svr_columns(for_filters: true), non_displayable_dates, states].flatten.compact [common, dates, sva_svr_columns(for_filters: true), non_displayable_dates, states].flatten.compact
end end
@ -34,12 +45,12 @@ module ColumnsConcern
scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self] scope = [:activerecord, :attributes, :procedure_presentation, :fields, :self]
columns = [ columns = [
Column.new(table: 'self', column: 'sva_svr_decision_on', type: :date, Column.new(procedure_id: id, table: 'self', column: 'sva_svr_decision_on', type: :date,
label: I18n.t("#{sva_svr_decision}_decision_on", scope:), classname: for_filters ? '' : 'sva-col') label: I18n.t("#{sva_svr_decision}_decision_on", scope:), classname: for_filters ? '' : 'sva-col')
] ]
if for_filters if for_filters
columns << Column.new(table: 'self', column: 'sva_svr_decision_before', type: :date, displayable: false, columns << Column.new(procedure_id: id, table: 'self', column: 'sva_svr_decision_before', type: :date, displayable: false,
label: I18n.t("#{sva_svr_decision}_decision_before", scope:)) label: I18n.t("#{sva_svr_decision}_decision_before", scope:))
end end
@ -50,30 +61,30 @@ module ColumnsConcern
def standard_columns def standard_columns
[ [
Column.new(table: 'user', column: 'email'), Column.new(procedure_id: id, table: 'user', column: 'email'),
Column.new(table: 'followers_instructeurs', column: 'email'), Column.new(procedure_id: id, table: 'followers_instructeurs', column: 'email'),
Column.new(table: 'groupe_instructeur', column: 'id', type: :enum), Column.new(procedure_id: id, table: 'groupe_instructeur', column: 'id', type: :enum),
Column.new(table: 'avis', column: 'question_answer', filterable: false) # not filterable ? Column.new(procedure_id: id, table: 'avis', column: 'question_answer', filterable: false) # not filterable ?
] ]
end end
def individual_columns def individual_columns
['nom', 'prenom', 'gender'].map { |column| Column.new(table: 'individual', column:) } ['nom', 'prenom', 'gender'].map { |column| Column.new(procedure_id: id, table: 'individual', column:) }
end end
def moral_columns def moral_columns
etablissements = ['entreprise_siren', 'entreprise_forme_juridique', 'entreprise_nom_commercial', 'entreprise_raison_sociale', 'entreprise_siret_siege_social'] etablissements = ['entreprise_siren', 'entreprise_forme_juridique', 'entreprise_nom_commercial', 'entreprise_raison_sociale', 'entreprise_siret_siege_social']
.map { |column| Column.new(table: 'etablissement', column:) } .map { |column| Column.new(procedure_id: id, table: 'etablissement', column:) }
etablissement_dates = ['entreprise_date_creation'].map { |column| Column.new(table: 'etablissement', column:, type: :date) } etablissement_dates = ['entreprise_date_creation'].map { |column| Column.new(procedure_id: id, table: 'etablissement', column:, type: :date) }
other = ['siret', 'libelle_naf', 'code_postal'].map { |column| Column.new(table: 'etablissement', column:) } other = ['siret', 'libelle_naf', 'code_postal'].map { |column| Column.new(procedure_id: id, table: 'etablissement', column:) }
[etablissements, etablissement_dates, other].flatten [etablissements, etablissement_dates, other].flatten
end end
def types_de_champ_columns def types_de_champ_columns
all_revisions_types_de_champ.flat_map(&:columns) all_revisions_types_de_champ.flat_map { _1.columns(procedure_id: id) }
end end
end end
end end

View file

@ -613,14 +613,6 @@ class Procedure < ApplicationRecord
end end
end end
def self.default_sort
{
'table' => 'self',
'column' => 'id',
'order' => 'desc'
}
end
def whitelist! def whitelist!
touch(:whitelisted_at) touch(:whitelisted_at)
end end

View file

@ -29,11 +29,28 @@ class ProcedurePresentation < ApplicationRecord
validate :check_filters_max_length validate :check_filters_max_length
validate :check_filters_max_integer validate :check_filters_max_integer
attribute :sorted_column, :jsonb
attribute :a_suivre_filters, :jsonb, array: true
attribute :suivis_filters, :jsonb, array: true
attribute :traites_filters, :jsonb, array: true
attribute :tous_filters, :jsonb, array: true
attribute :supprimes_filters, :jsonb, array: true
attribute :supprimes_recemment_filters, :jsonb, array: true
attribute :expirant_filters, :jsonb, array: true
attribute :archives_filters, :jsonb, array: true
def filters_for(statut)
send(filters_name_for(statut))
end
def filters_name_for(statut) = statut.tr('-', '_').then { "#{_1}_filters" }
def displayed_fields_for_headers def displayed_fields_for_headers
[ [
Column.new(table: 'self', column: 'id', classname: 'number-col'), Column.new(procedure_id: procedure.id, table: 'self', column: 'id', classname: 'number-col'),
*displayed_fields.map { Column.new(**_1.deep_symbolize_keys) }, *displayed_fields.map { Column.new(**_1.deep_symbolize_keys.merge(procedure_id: procedure.id)) },
Column.new(table: 'self', column: 'state', classname: 'state-col'), Column.new(procedure_id: procedure.id, table: 'self', column: 'state', classname: 'state-col'),
*procedure.sva_svr_columns *procedure.sva_svr_columns
] ]
end end
@ -81,8 +98,10 @@ class ProcedurePresentation < ApplicationRecord
end end
def add_filter(statut, column_id, value) def add_filter(statut, column_id, value)
h_id = JSON.parse(column_id, symbolize_names: true)
if value.present? if value.present?
column = procedure.find_column(id: column_id) column = procedure.find_column(h_id:)
case column.table case column.table
when TYPE_DE_CHAMP when TYPE_DE_CHAMP
@ -98,40 +117,57 @@ class ProcedurePresentation < ApplicationRecord
'value' => value 'value' => value
} }
filters_for(statut) << { id: h_id, filter: value }
update(filters: updated_filters) update(filters: updated_filters)
end end
end end
def remove_filter(statut, column_id, value) def remove_filter(statut, column_id, value)
column = procedure.find_column(id: column_id) h_id = JSON.parse(column_id, symbolize_names: true)
column = procedure.find_column(h_id:)
updated_filters = filters.dup updated_filters = filters.dup
updated_filters[statut] = filters[statut].reject do |filter| updated_filters[statut] = filters[statut].reject do |filter|
filter.values_at(TABLE, COLUMN, 'value') == [column.table, column.column, value] filter.values_at(TABLE, COLUMN, 'value') == [column.table, column.column, value]
end end
collection = filters_for(statut)
collection.delete(collection.find { sym_h = _1.deep_symbolize_keys; sym_h[:id] == h_id && sym_h[:filter] == value })
update!(filters: updated_filters) update!(filters: updated_filters)
end end
def update_displayed_fields(column_ids) def update_displayed_fields(column_ids)
column_ids = Array.wrap(column_ids) h_ids = Array.wrap(column_ids).map { |id| JSON.parse(id, symbolize_names: true) }
columns = column_ids.map { |id| procedure.find_column(id:) } columns = h_ids.map { |h_id| procedure.find_column(h_id:) }
update!(displayed_fields: columns) update!(
displayed_fields: columns,
displayed_columns: columns.map(&:h_id)
)
if !sort_to_column_id(sort).in?(column_ids) if !sort_to_column_id(sort).in?(column_ids)
update!(sort: Procedure.default_sort) default_column_id = procedure.dossier_id_column.id
update_sort(default_column_id, "desc")
end end
end end
def update_sort(column_id, order) def update_sort(column_id, order)
column = procedure.find_column(id: column_id) h_id = JSON.parse(column_id, symbolize_names: true)
column = procedure.find_column(h_id:)
order = order.presence || opposite_order_for(column.table, column.column)
update!(sort: { update!(
sort: {
TABLE => column.table, TABLE => column.table,
COLUMN => column.column, COLUMN => column.column,
ORDER => order.presence || opposite_order_for(column.table, column.column) ORDER => order
}) },
sorted_column: {
order:,
id: h_id
}
)
end end
def opposite_order_for(table, column) def opposite_order_for(table, column)
@ -201,7 +237,7 @@ class ProcedurePresentation < ApplicationRecord
.map do |(table, column), filters| .map do |(table, column), filters|
values = filters.pluck('value') values = filters.pluck('value')
value_column = filters.pluck('value_column').compact.first || :value value_column = filters.pluck('value_column').compact.first || :value
dossier_column = procedure.find_column(id: Column.make_id(table, column)) # hack to find json path columns dossier_column = procedure.find_column(h_id: { procedure_id: procedure.id, column_id: "#{table}/#{column}" }) # hack to find json path columns
if dossier_column.is_a?(Columns::JSONPathColumn) if dossier_column.is_a?(Columns::JSONPathColumn)
dossier_column.filtered_ids(dossiers, values) dossier_column.filtered_ids(dossiers, values)
else else

View file

@ -27,9 +27,9 @@ class TypesDeChamp::RepetitionTypeDeChamp < TypesDeChamp::TypeDeChampBase
ActiveStorage::Filename.new(str.delete('[]*?')).sanitized ActiveStorage::Filename.new(str.delete('[]*?')).sanitized
end end
def columns(displayable: true, prefix: nil) def columns(procedure_id:, displayable: true, prefix: nil)
@type_de_champ.procedure @type_de_champ.procedure
.all_revisions_types_de_champ(parent: @type_de_champ) .all_revisions_types_de_champ(parent: @type_de_champ)
.flat_map { _1.columns(displayable: false, prefix: libelle) } .flat_map { _1.columns(procedure_id:, displayable: false, prefix: libelle) }
end end
end end

View file

@ -98,9 +98,10 @@ class TypesDeChamp::TypeDeChampBase
end end
end end
def columns(displayable: true, prefix: nil) def columns(procedure_id:, displayable: true, prefix: nil)
[ [
Column.new( Column.new(
procedure_id:,
table: Column::TYPE_DE_CHAMP_TABLE, table: Column::TYPE_DE_CHAMP_TABLE,
column: stable_id.to_s, column: stable_id.to_s,
label: libelle_with_prefix(prefix), label: libelle_with_prefix(prefix),

View file

@ -12,13 +12,13 @@ class TypesDeChamp::YesNoTypeDeChamp < TypesDeChamp::CheckboxTypeDeChamp
end end
def human_to_filter(human_value) def human_to_filter(human_value)
human_value.downcase! downcased = human_value.downcase
if human_value == "oui" if downcased == "oui"
"true" "true"
elsif human_value == "non" elsif downcased == "non"
"false" "false"
else else
human_value downcased
end end
end end

View file

@ -6,6 +6,6 @@
- filters.each_with_index do |filter, i| - filters.each_with_index do |filter, i|
- if i > 0 - if i > 0
= " ou " = " ou "
= link_to remove_filter_instructeur_procedure_path(procedure, { statut: statut, column: "#{filter['table']}/#{filter['column']}", value: filter['value'] }), = link_to remove_filter_instructeur_procedure_path(procedure, { statut: statut, column: { procedure_id: procedure.id, column_id: filter['table'] + "/" + filter['column'] }.to_json, value: filter['value'] }),
class: "fr-tag fr-tag--dismiss fr-my-1w", aria: { label: "Retirer le filtre #{filter['column']}" } do 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)}" = "#{filter['label'].truncate(50)} : #{procedure_presentation.human_value_for_filter(filter)}"

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddColumnIdsToProcedurePresentations < ActiveRecord::Migration[7.0]
def change
add_column :procedure_presentations, :displayed_columns, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :tous_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :suivis_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :traites_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :a_suivre_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :archives_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :expirant_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :supprimes_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :supprimes_recemment_filters, :jsonb, array: true, default: [], null: false
add_column :procedure_presentations, :sorted_column, :jsonb
end
end

View file

@ -866,11 +866,21 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_23_125619) do
end end
create_table "procedure_presentations", id: :serial, force: :cascade do |t| create_table "procedure_presentations", id: :serial, force: :cascade do |t|
t.jsonb "a_suivre_filters", default: [], null: false, array: true
t.jsonb "archives_filters", default: [], null: false, array: true
t.integer "assign_to_id" t.integer "assign_to_id"
t.datetime "created_at", precision: nil t.datetime "created_at", precision: nil
t.jsonb "displayed_columns", default: [], null: false, array: true
t.jsonb "displayed_fields", default: [{"label"=>"Demandeur", "table"=>"user", "column"=>"email"}], null: false t.jsonb "displayed_fields", default: [{"label"=>"Demandeur", "table"=>"user", "column"=>"email"}], null: false
t.jsonb "expirant_filters", default: [], null: false, array: true
t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[], "expirant"=>[], "supprimes"=>[]}, null: false t.jsonb "filters", default: {"tous"=>[], "suivis"=>[], "traites"=>[], "a-suivre"=>[], "archives"=>[], "expirant"=>[], "supprimes"=>[]}, null: false
t.jsonb "sort", default: {"order"=>"desc", "table"=>"notifications", "column"=>"notifications"}, null: false t.jsonb "sort", default: {"order"=>"desc", "table"=>"notifications", "column"=>"notifications"}, null: false
t.jsonb "sorted_column"
t.jsonb "suivis_filters", default: [], null: false, array: true
t.jsonb "supprimes_filters", default: [], null: false, array: true
t.jsonb "supprimes_recemment_filters", default: [], null: false, array: true
t.jsonb "tous_filters", default: [], null: false, array: true
t.jsonb "traites_filters", default: [], null: false, array: true
t.datetime "updated_at", precision: nil t.datetime "updated_at", precision: nil
t.index ["assign_to_id"], name: "index_procedure_presentations_on_assign_to_id", unique: true t.index ["assign_to_id"], name: "index_procedure_presentations_on_assign_to_id", unique: true
end end

View file

@ -0,0 +1,49 @@
# frozen_string_literal: true
namespace :after_party do
desc 'Deployment task: migrate_procedure_presentation_to_columns'
task migrate_procedure_presentation_to_columns: :environment do
total = ProcedurePresentation.count
progress = ProgressReport.new(total)
ProcedurePresentation.find_each do |presentation|
procedure_id = presentation.procedure.id
presentation.displayed_columns = presentation.displayed_fields
.filter(&:present?)
.map { Column.new(**_1.deep_symbolize_keys.merge(procedure_id:)) }
.map(&:h_id)
sort = presentation.sort
presentation.sorted_column = {
'order' => sort['order'],
'id' => make_id(procedure_id, sort['table'], sort['column'])
}
presentation.filters.each do |key, filters|
raw_columns = filters.map do
{
id: make_id(procedure_id, _1['table'], _1['column']),
filter: _1['value']
}
end
presentation.send("#{presentation.filters_name_for(key)}=", raw_columns)
end
presentation.save!(validate: false)
progress.inc
end
AfterParty::TaskRecord
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
private
def make_id(procedure_id, table, column)
{ procedure_id:, column_id: "#{table}/#{column}" }
end
end

View file

@ -5,6 +5,7 @@ describe Instructeurs::ColumnFilterComponent, type: :component do
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, instructeurs: [instructeur]) } let(:procedure) { create(:procedure, instructeurs: [instructeur]) }
let(:procedure_id) { procedure.id }
let(:procedure_presentation) { nil } let(:procedure_presentation) { nil }
let(:statut) { nil } let(:statut) { nil }
@ -17,8 +18,8 @@ describe Instructeurs::ColumnFilterComponent, type: :component do
let(:column) { nil } let(:column) { nil }
let(:included_displayable_field) do let(:included_displayable_field) do
[ [
Column.new(label: 'email', table: 'user', column: 'email'), Column.new(procedure_id:, label: 'email', table: 'user', column: 'email'),
Column.new(label: "depose_since", table: "self", column: "depose_since", displayable: false) Column.new(procedure_id:, label: "depose_since", table: "self", column: "depose_since", displayable: false)
] ]
end end
@ -26,7 +27,7 @@ describe Instructeurs::ColumnFilterComponent, type: :component do
subject { component.filterable_columns_options } subject { component.filterable_columns_options }
it { is_expected.to eq([["email", "user/email"], ["depose_since", "self/depose_since"]]) } it { is_expected.to eq([["email", included_displayable_field.first.id], ["depose_since", included_displayable_field.second.id]]) }
end end
end end
@ -45,7 +46,7 @@ describe Instructeurs::ColumnFilterComponent, type: :component do
let(:types_de_champ_public) { [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }] } 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(:procedure) { create(:procedure, :published, types_de_champ_public:) }
let(:drop_down_stable_id) { procedure.active_revision.types_de_champ.first.stable_id } let(:drop_down_stable_id) { procedure.active_revision.types_de_champ.first.stable_id }
let(:column) { Column.new(table: 'type_de_champ', scope: nil, column: drop_down_stable_id) } let(:column) { Column.new(procedure_id:, table: 'type_de_champ', scope: nil, column: drop_down_stable_id) }
it 'find most recent tdc' do it 'find most recent tdc' do
is_expected.to eq(['Paris', 'Lyon', 'Marseille']) is_expected.to eq(['Paris', 'Lyon', 'Marseille'])

View file

@ -4,13 +4,15 @@ describe Instructeurs::ColumnPickerComponent, type: :component do
let(:component) { described_class.new(procedure:, procedure_presentation:) } let(:component) { described_class.new(procedure:, procedure_presentation:) }
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
let(:procedure_id) { procedure.id }
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) } let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) }
let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) }
describe "#displayable_columns_for_select" do describe "#displayable_columns_for_select" do
let(:default_user_email) { Column.new(label: 'email', table: 'user', column: 'email') } let(:default_user_email) { Column.new(procedure_id:, label: 'email', table: 'user', column: 'email') }
let(:excluded_displayable_field) { Column.new(label: "label1", table: "table1", column: "column1", displayable: false) } let(:excluded_displayable_field) { Column.new(procedure_id:, label: "label1", table: "table1", column: "column1", displayable: false) }
let(:email_column_id) { default_user_email.id }
subject { component.displayable_columns_for_select } subject { component.displayable_columns_for_select }
@ -21,6 +23,6 @@ describe Instructeurs::ColumnPickerComponent, type: :component do
]) ])
end end
it { is_expected.to eq([[["email", "user/email"]], ["user/email"]]) } it { is_expected.to eq([[["email", email_column_id]], [email_column_id]]) }
end end
end end

View file

@ -886,12 +886,14 @@ describe Instructeurs::ProceduresController, type: :controller do
end end
it 'can change order' do it 'can change order' do
expect { get :update_sort, params: { procedure_id: procedure.id, column_id: "individual/nom", order: 'asc' } } column_id = procedure.find_column(label: "Nom").id
expect { get :update_sort, params: { procedure_id: procedure.id, column_id:, order: 'asc' } }
.to change { procedure_presentation.sort } .to change { procedure_presentation.sort }
.from({ "column" => "notifications", "order" => "desc", "table" => "notifications" }) .from({ "column" => "notifications", "order" => "desc", "table" => "notifications" })
.to({ "column" => "nom", "order" => "asc", "table" => "individual" }) .to({ "column" => "nom", "order" => "asc", "table" => "individual" })
end end
end end
describe '#add_filter' do describe '#add_filter' do
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let(:procedure) { create(:procedure, :for_individual) } let(:procedure) { create(:procedure, :for_individual) }
@ -903,7 +905,8 @@ describe Instructeurs::ProceduresController, type: :controller do
end end
subject do subject do
post :add_filter, params: { procedure_id: procedure.id, column: "individual/nom", value: "n" * 110, statut: "a-suivre" } column = procedure.find_column(label: "Nom").id
post :add_filter, params: { procedure_id: procedure.id, column:, value: "n" * 110, statut: "a-suivre" }
end end
it 'should render the error' do it 'should render the error' do

View file

@ -0,0 +1,60 @@
# frozen_string_literal: true
describe '20240920130741_migrate_procedure_presentation_to_columns.rake' do
let(:rake_task) { Rake::Task['after_party:migrate_procedure_presentation_to_columns'] }
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :text }]) }
let(:instructeur) { create(:instructeur) }
let(:assign_to) { create(:assign_to, procedure: procedure, instructeur: instructeur) }
let(:stable_id) { procedure.active_revision.types_de_champ.first.stable_id }
let!(:procedure_presentation) do
displayed_fields = [
{ "table" => "etablissement", "column" => "entreprise_raison_sociale" },
{ "table" => "type_de_champ", "column" => stable_id.to_s }
]
sort = { "order" => "desc", "table" => "self", "column" => "en_construction_at" }
filters = {
"tous" => [],
"suivis" => [],
"traites" => [{ "label" => "Libellé NAF", "table" => "etablissement", "value" => "Administration publique générale", "column" => "libelle_naf", "value_column" => "value" }],
"a-suivre" => [],
"archives" => [],
"expirant" => [],
"supprimes" => [],
"supprimes_recemment" => []
}
create(:procedure_presentation, assign_to:, displayed_fields:, filters:, sort:)
end
before do
rake_task.invoke
procedure_presentation.reload
end
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}" }
])
order, column_id = procedure_presentation
.sorted_column
.then { |sorted| [sorted['order'], sorted['id']] }
expect(order).to eq('desc')
expect(column_id).to eq("procedure_id" => procedure_id, "column_id" => "self/en_construction_at")
expect(procedure_presentation.tous_filters).to eq([])
traites = procedure_presentation.traites_filters
.map { [_1['id'], _1['filter']] }
expect(traites).to eq([[{ "column_id" => "etablissement/libelle_naf", "procedure_id" => procedure_id }, "Administration publique générale"]])
end
end

View file

@ -6,6 +6,7 @@ describe ColumnsConcern do
context 'when the procedure can have a SIRET number' do context 'when the procedure can have a SIRET number' do
let(:procedure) { create(:procedure, types_de_champ_public:, types_de_champ_private:) } let(:procedure) { create(:procedure, types_de_champ_public:, types_de_champ_private:) }
let(:procedure_id) { procedure.id }
let(:tdc_1) { procedure.active_revision.types_de_champ_public[0] } let(:tdc_1) { procedure.active_revision.types_de_champ_public[0] }
let(:tdc_2) { procedure.active_revision.types_de_champ_public[1] } 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_1) { procedure.active_revision.types_de_champ_private[0] }
@ -43,7 +44,7 @@ describe ColumnsConcern do
{ label: tdc_2.libelle, table: 'type_de_champ', column: tdc_2.stable_id.to_s, classname: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true }, { label: tdc_2.libelle, table: 'type_de_champ', column: tdc_2.stable_id.to_s, classname: '', displayable: true, 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: '', displayable: true, 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: '', displayable: true, 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: '', displayable: true, 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: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true }
].map { Column.new(**_1) } ].map { Column.new(**_1.merge(procedure_id:)) }
} }
context 'with explication/header_sections' do context 'with explication/header_sections' do
@ -67,10 +68,11 @@ describe ColumnsConcern do
end end
context 'when the procedure is for individuals' do context 'when the procedure is for individuals' do
let(:name_field) { Column.new(label: "Prénom", table: "individual", column: "prenom", classname: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true) } let(:name_field) { Column.new(procedure_id:, label: "Prénom", table: "individual", column: "prenom", classname: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true) }
let(:surname_field) { Column.new(label: "Nom", table: "individual", column: "nom", classname: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true) } let(:surname_field) { Column.new(procedure_id:, label: "Nom", table: "individual", column: "nom", classname: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true) }
let(:gender_field) { Column.new(label: "Civilité", table: "individual", column: "gender", classname: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true) } let(:gender_field) { Column.new(procedure_id:, label: "Civilité", table: "individual", column: "gender", classname: '', displayable: true, type: :text, scope: '', value_column: :value, filterable: true) }
let(:procedure) { create(:procedure, :for_individual) } let(:procedure) { create(:procedure, :for_individual) }
let(:procedure_id) { procedure.id }
let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) }
it { is_expected.to include(name_field, surname_field, gender_field) } it { is_expected.to include(name_field, surname_field, gender_field) }
@ -78,20 +80,22 @@ describe ColumnsConcern do
context 'when the procedure is sva' do context 'when the procedure is sva' do
let(:procedure) { create(:procedure, :for_individual, :sva) } let(:procedure) { create(:procedure, :for_individual, :sva) }
let(:procedure_id) { procedure.id }
let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) }
let(:decision_on) { Column.new(label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', displayable: true, type: :date, scope: '', value_column: :value, filterable: true) } let(:decision_on) { Column.new(procedure_id:, label: "Date décision SVA", table: "self", column: "sva_svr_decision_on", classname: '', displayable: true, 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: '', displayable: false, type: :date, scope: '', value_column: :value, filterable: true) } let(:decision_before_field) { Column.new(procedure_id:, label: "Date décision SVA avant", table: "self", column: "sva_svr_decision_before", classname: '', displayable: false, type: :date, scope: '', value_column: :value, filterable: true) }
it { is_expected.to include(decision_on, decision_before_field) } it { is_expected.to include(decision_on, decision_before_field) }
end end
context 'when the procedure is svr' do context 'when the procedure is svr' do
let(:procedure) { create(:procedure, :for_individual, :svr) } let(:procedure) { create(:procedure, :for_individual, :svr) }
let(:procedure_id) { procedure.id }
let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) } let(:procedure_presentation) { create(:procedure_presentation, assign_to: assign_to) }
let(:decision_on) { Column.new(label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', displayable: true, type: :date, scope: '', value_column: :value, filterable: true) } let(:decision_on) { Column.new(procedure_id:, label: "Date décision SVR", table: "self", column: "sva_svr_decision_on", classname: '', displayable: true, 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: '', displayable: false, type: :date, scope: '', value_column: :value, filterable: true) } let(:decision_before_field) { Column.new(procedure_id:, label: "Date décision SVR avant", table: "self", column: "sva_svr_decision_before", classname: '', displayable: false, type: :date, scope: '', value_column: :value, filterable: true) }
it { is_expected.to include(decision_on, decision_before_field) } it { is_expected.to include(decision_on, decision_before_field) }
end end

View file

@ -94,7 +94,7 @@ RSpec.describe Export, type: :model do
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }
let!(:gi_1) { create(:groupe_instructeur, procedure: procedure, instructeurs: [instructeur]) } let!(:gi_1) { create(:groupe_instructeur, procedure: procedure, instructeurs: [instructeur]) }
let!(:pp) { gi_1.instructeurs.first.procedure_presentation_and_errors_for_procedure_id(procedure.id).first } let!(:pp) { gi_1.instructeurs.first.procedure_presentation_and_errors_for_procedure_id(procedure.id).first }
before { pp.add_filter('tous', 'self/created_at', '10/12/2021') } before { pp.add_filter('tous', procedure.find_column(label: 'Créé le').id, '10/12/2021') }
context 'with procedure_presentation having different filters' do context 'with procedure_presentation having different filters' do
it 'works once' do it 'works once' do
@ -105,7 +105,7 @@ RSpec.describe Export, type: :model do
it 'works once, changes procedure_presentation, recreate a new' do it 'works once, changes procedure_presentation, recreate a new' do
expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) } expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) }
.to change { Export.count }.by(1) .to change { Export.count }.by(1)
pp.add_filter('tous', 'self/updated_at', '10/12/2021') pp.add_filter('tous', procedure.find_column(label: 'Mis à jour le').id, '10/12/2021')
expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) } expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) }
.to change { Export.count }.by(1) .to change { Export.count }.by(1)
end end

View file

@ -582,7 +582,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.columns } let(:available_columns) { type_de_champ.columns(procedure_id: procedure.id) }
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) }
@ -611,6 +611,7 @@ describe ProcedurePresentation do
create(:dossier, procedure: procedure).project_champs_public.find { _1.stable_id == 1 }.update(value_json: { "departement_code" => "unknown" }) create(:dossier, procedure: procedure).project_champs_public.find { _1.stable_id == 1 }.update(value_json: { "departement_code" => "unknown" })
end end
it { is_expected.to contain_exactly(kept_dossier.id) } it { is_expected.to contain_exactly(kept_dossier.id) }
it 'describes column' do it 'describes column' do
expect(column.type).to eq(:enum) expect(column.type).to eq(:enum)
expect(column.options_for_select.first).to eq(["99 Etranger", "99"]) expect(column.options_for_select.first).to eq(["99 Etranger", "99"])
@ -849,10 +850,11 @@ describe ProcedurePresentation do
let(:filters) { { "suivis" => [] } } let(:filters) { { "suivis" => [] } }
context 'when type_de_champ yes_no' do context 'when type_de_champ yes_no' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no }]) } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :yes_no, libelle: 'oui ou non' }]) }
it 'should downcase and transform value' do it 'should downcase and transform value' do
procedure_presentation.add_filter("suivis", "type_de_champ/#{first_type_de_champ_id}", +"Oui") column_id = procedure.find_column(label: 'oui ou non').id
procedure_presentation.add_filter("suivis", column_id, "Oui")
expect(procedure_presentation.filters).to eq({ expect(procedure_presentation.filters).to eq({
"suivis" => "suivis" =>
@ -860,14 +862,19 @@ describe ProcedurePresentation do
{ "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "true", "value_column" => "value" } { "label" => first_type_de_champ.libelle, "table" => "type_de_champ", "column" => first_type_de_champ_id, "value" => "true", "value_column" => "value" }
] ]
}) })
suivis = procedure_presentation.suivis_filters.map { [_1['id'], _1['filter']] }
expect(suivis).to eq([[{ "column_id" => "type_de_champ/#{first_type_de_champ_id}", "procedure_id" => procedure.id }, "true"]])
end end
end end
context 'when type_de_champ text' do context 'when type_de_champ text' do
let(:filters) { { "suivis" => [] } } let(:filters) { { "suivis" => [] } }
let(:column_id) { procedure.find_column(label: first_type_de_champ.libelle).id }
it 'should passthrough value' do it 'should passthrough value' do
procedure_presentation.add_filter("suivis", "type_de_champ/#{first_type_de_champ_id}", "Oui") procedure_presentation.add_filter("suivis", column_id, "Oui")
expect(procedure_presentation.filters).to eq({ expect(procedure_presentation.filters).to eq({
"suivis" => [ "suivis" => [
@ -879,10 +886,11 @@ describe ProcedurePresentation do
context 'when type_de_champ departements' do context 'when type_de_champ departements' do
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :departements }]) } let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :departements }]) }
let(:column_id) { procedure.find_column(label: first_type_de_champ.libelle).id }
let(:filters) { { "suivis" => [] } } let(:filters) { { "suivis" => [] } }
it 'should set value_column' do it 'should set value_column' do
procedure_presentation.add_filter("suivis", "type_de_champ/#{first_type_de_champ_id}", "13") procedure_presentation.add_filter("suivis", column_id, "13")
expect(procedure_presentation.filters).to eq({ expect(procedure_presentation.filters).to eq({
"suivis" => [ "suivis" => [
@ -893,6 +901,26 @@ describe ProcedurePresentation do
end end
end end
describe "#remove_filter" do
let(:filters) { { "suivis" => [] } }
let(:email_column_id) { procedure.find_column(label: 'Demandeur').id }
before do
procedure_presentation.add_filter("suivis", email_column_id, "a@a.com")
end
it 'should remove filter' do
expect(procedure_presentation.filters).to eq({ "suivis" => [{ "column" => "email", "label" => "Demandeur", "table" => "user", "value" => "a@a.com", "value_column" => "value" }] })
expect(procedure_presentation.suivis_filters).to eq([{ "filter" => "a@a.com", "id" => { "column_id" => "user/email", "procedure_id" => procedure.id } }])
procedure_presentation.remove_filter("suivis", email_column_id, "a@a.com")
procedure_presentation.reload
expect(procedure_presentation.filters).to eq({ "suivis" => [] })
expect(procedure_presentation.suivis_filters).to eq([])
end
end
describe '#filtered_sorted_ids' do describe '#filtered_sorted_ids' do
let(:procedure_presentation) { create(:procedure_presentation, assign_to:) } let(:procedure_presentation) { create(:procedure_presentation, assign_to:) }
let(:dossier_1) { create(:dossier) } let(:dossier_1) { create(:dossier) }
@ -939,4 +967,51 @@ describe ProcedurePresentation do
end end
end end
end end
describe '#update_displayed_fields' do
let(:procedure_presentation) do
create(:procedure_presentation, assign_to:).tap do |pp|
pp.update_sort(procedure.find_column(label: 'Demandeur').id, 'desc')
end
end
subject do
procedure_presentation.update_displayed_fields([
procedure.find_column(label: 'En construction le').id,
procedure.find_column(label: 'Mis à jour le').id
])
end
it 'should update displayed_fields' do
expect(procedure_presentation.displayed_columns).to eq([])
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 }
])
expect(procedure_presentation.sorted_column['id']).to eq("column_id" => "self/id", "procedure_id" => procedure.id)
expect(procedure_presentation.sorted_column['order']).to eq('desc')
end
end
describe '#update_sort' do
let(:procedure_presentation) { create(:procedure_presentation, assign_to:) }
subject do
column_id = procedure.find_column(label: 'En construction le').id
procedure_presentation.update_sort(column_id, 'asc')
end
it 'should update sort and order' do
expect(procedure_presentation.sorted_column).to be_nil
subject
expect(procedure_presentation.sorted_column['id']).to eq("column_id" => "self/en_construction_at", "procedure_id" => procedure.id)
expect(procedure_presentation.sorted_column['order']).to eq('asc')
end
end
end end

View file

@ -1450,10 +1450,6 @@ describe Procedure do
end end
end end
describe ".default_sort" do
it { expect(Procedure.default_sort).to eq({ "table" => "self", "column" => "id", "order" => "desc" }) }
end
describe "#organisation_name" do describe "#organisation_name" do
subject { procedure.organisation_name } subject { procedure.organisation_name }
context 'when the procedure has a service (and no organization)' do context 'when the procedure has a service (and no organization)' do