From d03d5d0daee0281e00d2be5d839e80f583574188 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Thu, 31 Oct 2024 18:29:23 +0100 Subject: [PATCH] add ChampColumn --- app/models/column.rb | 89 --------------- app/models/columns/champ_column.rb | 105 ++++++++++++++++++ .../types_de_champ/type_de_champ_base.rb | 5 +- .../column_filter_value_component_spec.rb | 2 +- spec/models/column_spec.rb | 91 --------------- spec/models/columns/champ_column_spec.rb | 61 ++++++++++ spec/models/columns/dossier_column_spec.rb | 39 +++++++ 7 files changed, 208 insertions(+), 184 deletions(-) create mode 100644 app/models/columns/champ_column.rb create mode 100644 spec/models/columns/champ_column_spec.rb create mode 100644 spec/models/columns/dossier_column_spec.rb diff --git a/app/models/column.rb b/app/models/column.rb index c4d54b6c5..87d53aae1 100644 --- a/app/models/column.rb +++ b/app/models/column.rb @@ -52,96 +52,7 @@ class Column procedure.find_column(h_id: h_id) end - def value(champ) - return if champ.nil? - - value = typed_value(champ) - if default_column? - cast_value(value, from_type: champ.last_write_column_type, to_type: type) - else - value - end - end - private def column_id = "#{table}/#{column}" - - def typed_value(champ) - value = string_value(champ) - parse_value(value, type: champ.last_write_column_type) - end - - def string_value(champ) = champ.public_send(value_column) - def default_column? = value_column.in?([:value, :external_id]) - - def parse_value(value, type:) - return if value.blank? - - case type - when :boolean - parse_boolean(value) - when :integer - value.to_i - when :decimal - value.to_f - when :datetime - parse_datetime(value) - when :date - parse_datetime(value)&.to_date - when :enums - parse_enums(value) - else - value - end - end - - def cast_value(value, from_type:, to_type:) - return if value.blank? - return value if from_type == to_type - - case [from_type, to_type] - when [:integer, :decimal] # recast numbers automatically - value.to_f - when [:decimal, :integer] # may lose some data, but who cares ? - value.to_i - when [:integer, :text], [:decimal, :text] # number to text - value.to_s - when [:enum, :enums] # single list can become multi - [value] - when [:enum, :text] # single list can become text - value - when [:enums, :enum] # multi list can become single list - value.first - when [:enums, :text] # multi list can become text - value.join(', ') - when [:date, :datetime] # date <=> datetime - value.to_datetime - when [:datetime, :date] # may lose some data, but who cares ? - value.to_date - else - nil - end - end - - def parse_boolean(value) - case value - when 'true', 'on', '1' - true - when 'false' - false - end - end - - def parse_enums(value) - JSON.parse(value) - rescue JSON::ParserError - nil - end - - def parse_datetime(value) - Time.zone.parse(value) - rescue ArgumentError - nil - end end diff --git a/app/models/columns/champ_column.rb b/app/models/columns/champ_column.rb new file mode 100644 index 000000000..e60472284 --- /dev/null +++ b/app/models/columns/champ_column.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +class Columns::ChampColumn < Column + attr_reader :stable_id + + def initialize(procedure_id:, label:, stable_id:, displayable: true, filterable: true, type: :text, value_column: :value) + @stable_id = stable_id + + super( + procedure_id:, + table: 'type_de_champ', + column: stable_id.to_s, + label:, + type:, + value_column:, + displayable:, + filterable: + ) + end + + def value(champ) + return if champ.nil? + + value = typed_value(champ) + if default_column? + cast_value(value, from_type: champ.last_write_column_type, to_type: type) + else + value + end + end + + private + + def column_id = "type_de_champ/#{stable_id}" + + def typed_value(champ) + value = string_value(champ) + parse_value(value, type: champ.last_write_column_type) + end + + def string_value(champ) = champ.public_send(value_column) + def default_column? = value_column.in?([:value, :external_id]) + + def parse_value(value, type:) + return if value.blank? + + case type + when :boolean + parse_boolean(value) + when :integer + value.to_i + when :decimal + value.to_f + when :datetime + parse_datetime(value) + when :date + parse_datetime(value)&.to_date + when :enums + parse_enums(value) + else + value + end + end + + def cast_value(value, from_type:, to_type:) + return if value.blank? + return value if from_type == to_type + + case [from_type, to_type] + when [:integer, :decimal] # recast numbers automatically + value.to_f + when [:decimal, :integer] # may lose some data, but who cares ? + value.to_i + when [:integer, :text], [:decimal, :text] # number to text + value.to_s + when [:enum, :enums] # single list can become multi + [value] + when [:enum, :text] # single list can become text + value + when [:enums, :enum] # multi list can become single list + value.first + when [:enums, :text] # multi list can become text + value.join(', ') + when [:date, :datetime] # date <=> datetime + value.to_datetime + when [:datetime, :date] # may lose some data, but who cares ? + value.to_date + else + nil + end + end + + def parse_boolean(value) + case value + when 'true', 'on', '1' + true + when 'false' + false + end + end + + def parse_enums(value) = JSON.parse(value) rescue nil + + def parse_datetime(value) = Time.zone.parse(value) rescue nil +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 336193dea..ec5940e2a 100644 --- a/app/models/types_de_champ/type_de_champ_base.rb +++ b/app/models/types_de_champ/type_de_champ_base.rb @@ -98,10 +98,9 @@ class TypesDeChamp::TypeDeChampBase def columns(procedure_id:, displayable: true, prefix: nil) if fillable? [ - Column.new( + Columns::ChampColumn.new( procedure_id:, - table: Column::TYPE_DE_CHAMP_TABLE, - column: stable_id.to_s, + stable_id: stable_id, label: libelle_with_prefix(prefix), type: TypeDeChamp.column_type(type_champ), value_column: TypeDeChamp.value_column(type_champ), diff --git a/spec/components/instructeurs/column_filter_value_component_spec.rb b/spec/components/instructeurs/column_filter_value_component_spec.rb index fd9a1f9a9..11452b963 100644 --- a/spec/components/instructeurs/column_filter_value_component_spec.rb +++ b/spec/components/instructeurs/column_filter_value_component_spec.rb @@ -25,7 +25,7 @@ describe Instructeurs::ColumnFilterValueComponent, type: :component do let(:types_de_champ_public) { [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }] } let(:procedure) { create(:procedure, :published, types_de_champ_public:) } let(:drop_down_stable_id) { procedure.active_revision.types_de_champ.first.stable_id } - let(:column) { Column.new(procedure_id:, table: 'type_de_champ', scope: nil, column: drop_down_stable_id) } + let(:column) { procedure.find_column(label: 'Votre ville') } it 'find most recent tdc' do is_expected.to eq(['Paris', 'Lyon', 'Marseille']) diff --git a/spec/models/column_spec.rb b/spec/models/column_spec.rb index 74634c718..7b1f4b3d9 100644 --- a/spec/models/column_spec.rb +++ b/spec/models/column_spec.rb @@ -1,95 +1,4 @@ # frozen_string_literal: true describe Column do - describe 'value' do - let(:groupe_instructeur) { create(:groupe_instructeur, instructeurs: [create(:instructeur)]) } - - context 'when dossier columns' do - context 'when procedure for individual' do - let(:individual) { create(:individual, nom: "Sim", prenom: "Paul", gender: 'M.') } - let(:procedure) { create(:procedure, for_individual: true, groupe_instructeurs: [groupe_instructeur]) } - let(:dossier) { create(:dossier, individual:, mandataire_first_name: "Martin", mandataire_last_name: "Christophe", for_tiers: true) } - - it 'retrieve individual information' do - expect(procedure.find_column(label: "Prénom").value(dossier)).to eq("Paul") - expect(procedure.find_column(label: "Nom").value(dossier)).to eq("Sim") - expect(procedure.find_column(label: "Civilité").value(dossier)).to eq("M.") - end - end - - context 'when procedure for entreprise' do - let(:procedure) { create(:procedure, for_individual: false, groupe_instructeurs: [groupe_instructeur]) } - let(:dossier) { create(:dossier, :en_instruction, :with_entreprise, procedure:) } - - it 'retrieve entreprise information' do - expect(procedure.find_column(label: "Libellé NAF").value(dossier)).to eq('Transports par conduites') - end - end - - context 'when sva/svr enabled' do - let(:procedure) { create(:procedure, :sva, for_individual: true, groupe_instructeurs: [groupe_instructeur]) } - let(:dossier) { create(:dossier, :en_instruction, procedure:) } - - it 'does not fail' do - expect(procedure.find_column(label: "Date décision SVA").value(dossier)).to eq(nil) - end - end - end - - context 'when champ columns' do - let(:procedure) { create(:procedure, :with_all_champs_mandatory, groupe_instructeurs: [groupe_instructeur]) } - let(:dossier) { create(:dossier, :with_populated_champs, procedure:) } - let(:types_de_champ) { procedure.all_revisions_types_de_champ } - - it 'extracts values for columns and type de champ' do - expect_type_de_champ_values('civilite', ["M."]) - expect_type_de_champ_values('email', ['yoda@beta.gouv.fr']) - expect_type_de_champ_values('phone', ['0666666666']) - expect_type_de_champ_values('address', ["2 rue des Démarches"]) - expect_type_de_champ_values('communes', ["Coye-la-Forêt"]) - expect_type_de_champ_values('departements', ['01']) - expect_type_de_champ_values('regions', ['01']) - expect_type_de_champ_values('pays', ['France']) - expect_type_de_champ_values('epci', [nil]) - expect_type_de_champ_values('iban', [nil]) - expect_type_de_champ_values('siret', ["44011762001530", "postal_code", "city_name", "departement_code", "region_name"]) - expect_type_de_champ_values('text', ['text']) - expect_type_de_champ_values('textarea', ['textarea']) - expect_type_de_champ_values('number', ['42']) - expect_type_de_champ_values('decimal_number', [42.1]) - expect_type_de_champ_values('integer_number', [42]) - expect_type_de_champ_values('date', [Time.zone.parse('2019-07-10').to_date]) - expect_type_de_champ_values('datetime', [Time.zone.parse("1962-09-15T15:35:00+01:00")]) - expect_type_de_champ_values('checkbox', [true]) - expect_type_de_champ_values('drop_down_list', ['val1']) - expect_type_de_champ_values('multiple_drop_down_list', [["val1", "val2"]]) - expect_type_de_champ_values('linked_drop_down_list', [nil, "categorie 1", "choix 1"]) - expect_type_de_champ_values('yes_no', [true]) - expect_type_de_champ_values('annuaire_education', [nil]) - expect_type_de_champ_values('carte', []) - expect_type_de_champ_values('piece_justificative', []) - expect_type_de_champ_values('titre_identite', [true]) - expect_type_de_champ_values('cnaf', [nil]) - expect_type_de_champ_values('dgfip', [nil]) - expect_type_de_champ_values('pole_emploi', [nil]) - expect_type_de_champ_values('mesri', [nil]) - expect_type_de_champ_values('cojo', [nil]) - expect_type_de_champ_values('expression_reguliere', [nil]) - end - end - end - - private - - def expect_type_de_champ_values(type, values) - type_de_champ = types_de_champ.find { _1.type_champ == type } - champ = dossier.send(:filled_champ, type_de_champ, nil) - columns = type_de_champ.columns(procedure_id: procedure.id) - expect(columns.map { _1.value(champ) }).to eq(values) - end - - def retrieve_champ(type) - type_de_champ = types_de_champ.find { _1.type_champ == type } - dossier.send(:filled_champ, type_de_champ, nil) - end end diff --git a/spec/models/columns/champ_column_spec.rb b/spec/models/columns/champ_column_spec.rb new file mode 100644 index 000000000..effaf0914 --- /dev/null +++ b/spec/models/columns/champ_column_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +describe Columns::ChampColumn do + describe '#value' do + context 'when champ columns' do + let(:procedure) { create(:procedure, :with_all_champs_mandatory) } + let(:dossier) { create(:dossier, :with_populated_champs, procedure:) } + let(:types_de_champ) { procedure.all_revisions_types_de_champ } + + it 'extracts values for columns and type de champ' do + expect_type_de_champ_values('civilite', ["M."]) + expect_type_de_champ_values('email', ['yoda@beta.gouv.fr']) + expect_type_de_champ_values('phone', ['0666666666']) + expect_type_de_champ_values('address', ["2 rue des Démarches"]) + expect_type_de_champ_values('communes', ["Coye-la-Forêt"]) + expect_type_de_champ_values('departements', ['01']) + expect_type_de_champ_values('regions', ['01']) + expect_type_de_champ_values('pays', ['France']) + expect_type_de_champ_values('epci', [nil]) + expect_type_de_champ_values('iban', [nil]) + expect_type_de_champ_values('siret', ["44011762001530", "postal_code", "city_name", "departement_code", "region_name"]) + expect_type_de_champ_values('text', ['text']) + expect_type_de_champ_values('textarea', ['textarea']) + expect_type_de_champ_values('number', ['42']) + expect_type_de_champ_values('decimal_number', [42.1]) + expect_type_de_champ_values('integer_number', [42]) + expect_type_de_champ_values('date', [Time.zone.parse('2019-07-10').to_date]) + expect_type_de_champ_values('datetime', [Time.zone.parse("1962-09-15T15:35:00+01:00")]) + expect_type_de_champ_values('checkbox', [true]) + expect_type_de_champ_values('drop_down_list', ['val1']) + expect_type_de_champ_values('multiple_drop_down_list', [["val1", "val2"]]) + expect_type_de_champ_values('linked_drop_down_list', [nil, "categorie 1", "choix 1"]) + expect_type_de_champ_values('yes_no', [true]) + expect_type_de_champ_values('annuaire_education', [nil]) + expect_type_de_champ_values('carte', []) + expect_type_de_champ_values('piece_justificative', []) + expect_type_de_champ_values('titre_identite', [true]) + expect_type_de_champ_values('cnaf', [nil]) + expect_type_de_champ_values('dgfip', [nil]) + expect_type_de_champ_values('pole_emploi', [nil]) + expect_type_de_champ_values('mesri', [nil]) + expect_type_de_champ_values('cojo', [nil]) + expect_type_de_champ_values('expression_reguliere', [nil]) + end + end + end + + private + + def expect_type_de_champ_values(type, values) + type_de_champ = types_de_champ.find { _1.type_champ == type } + champ = dossier.send(:filled_champ, type_de_champ, nil) + columns = type_de_champ.columns(procedure_id: procedure.id) + expect(columns.map { _1.value(champ) }).to eq(values) + end + + def retrieve_champ(type) + type_de_champ = types_de_champ.find { _1.type_champ == type } + dossier.send(:filled_champ, type_de_champ, nil) + end +end diff --git a/spec/models/columns/dossier_column_spec.rb b/spec/models/columns/dossier_column_spec.rb new file mode 100644 index 000000000..5b8651d2a --- /dev/null +++ b/spec/models/columns/dossier_column_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +describe Columns::DossierColumn do + describe 'value' do + let(:groupe_instructeur) { create(:groupe_instructeur, instructeurs: [create(:instructeur)]) } + + context 'when dossier columns' do + context 'when procedure for individual' do + let(:individual) { create(:individual, nom: "Sim", prenom: "Paul", gender: 'M.') } + let(:procedure) { create(:procedure, for_individual: true, groupe_instructeurs: [groupe_instructeur]) } + let(:dossier) { create(:dossier, individual:, mandataire_first_name: "Martin", mandataire_last_name: "Christophe", for_tiers: true) } + + it 'retrieve individual information' do + expect(procedure.find_column(label: "Prénom").value(dossier)).to eq("Paul") + expect(procedure.find_column(label: "Nom").value(dossier)).to eq("Sim") + expect(procedure.find_column(label: "Civilité").value(dossier)).to eq("M.") + end + end + + context 'when procedure for entreprise' do + let(:procedure) { create(:procedure, for_individual: false, groupe_instructeurs: [groupe_instructeur]) } + let(:dossier) { create(:dossier, :en_instruction, :with_entreprise, procedure:) } + + it 'retrieve entreprise information' do + expect(procedure.find_column(label: "Libellé NAF").value(dossier)).to eq('Transports par conduites') + end + end + + context 'when sva/svr enabled' do + let(:procedure) { create(:procedure, :sva, for_individual: true, groupe_instructeurs: [groupe_instructeur]) } + let(:dossier) { create(:dossier, :en_instruction, procedure:) } + + it 'does not fail' do + expect(procedure.find_column(label: "Date décision SVA").value(dossier)).to eq(nil) + end + end + end + end +end