diff --git a/app/models/champ.rb b/app/models/champ.rb index 1f0741e3f..7771007c6 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -10,12 +10,13 @@ class Champ < ApplicationRecord has_many :geo_areas, dependent: :destroy belongs_to :etablissement, dependent: :destroy - delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, to: :type_de_champ + delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, to: :type_de_champ scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) } scope :public_only, -> { where(private: false) } scope :private_only, -> { where(private: true) } - scope :ordered, -> { includes(:type_de_champ).order('types_de_champ.order_place') } + scope :ordered, -> { includes(:type_de_champ).order(:row, 'types_de_champ.order_place') } + scope :root, -> { where(parent_id: nil) } def public? !private? diff --git a/app/models/champs/repetition_champ.rb b/app/models/champs/repetition_champ.rb new file mode 100644 index 000000000..3ad10bd7f --- /dev/null +++ b/app/models/champs/repetition_champ.rb @@ -0,0 +1,13 @@ +class Champs::RepetitionChamp < Champ + has_many :champs, -> { ordered }, foreign_key: :parent_id, dependent: :destroy + + accepts_nested_attributes_for :champs, allow_destroy: true + + def rows + champs.group_by(&:row).values + end + + def search_terms + # The user cannot enter any information here so it doesn’t make much sense to search + end +end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 436c8c707..f0849579c 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -18,8 +18,8 @@ class Dossier < ApplicationRecord has_one :attestation, dependent: :destroy has_many :pieces_justificatives, dependent: :destroy - has_many :champs, -> { public_only.ordered }, dependent: :destroy - has_many :champs_private, -> { private_only.ordered }, class_name: 'Champ', dependent: :destroy + has_many :champs, -> { root.public_only.ordered }, dependent: :destroy + has_many :champs_private, -> { root.private_only.ordered }, class_name: 'Champ', dependent: :destroy has_many :commentaires, dependent: :destroy has_many :invites, dependent: :destroy has_many :follows diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 61bf18695..10b0af8bc 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -28,11 +28,15 @@ class TypeDeChamp < ApplicationRecord dossier_link: 'dossier_link', piece_justificative: 'piece_justificative', siret: 'siret', - carte: 'carte' + carte: 'carte', + repetition: 'repetition' } belongs_to :procedure + belongs_to :parent, class_name: 'TypeDeChamp' + has_many :types_de_champ, foreign_key: :parent_id, class_name: 'TypeDeChamp', dependent: :destroy + store :options, accessors: [:cadastres, :quartiers_prioritaires, :parcelles_agricoles] after_initialize :set_dynamic_type @@ -105,7 +109,25 @@ class TypeDeChamp < ApplicationRecord end def non_fillable? - type_champ.in?([TypeDeChamp.type_champs.fetch(:header_section), TypeDeChamp.type_champs.fetch(:explication)]) + type_champ.in?([ + TypeDeChamp.type_champs.fetch(:header_section), + TypeDeChamp.type_champs.fetch(:explication) + ]) + end + + def exclude_from_export? + type_champ.in?([ + TypeDeChamp.type_champs.fetch(:header_section), + TypeDeChamp.type_champs.fetch(:explication), + TypeDeChamp.type_champs.fetch(:repetition) + ]) + end + + def exclude_from_view? + type_champ.in?([ + TypeDeChamp.type_champs.fetch(:explication), + TypeDeChamp.type_champs.fetch(:repetition) + ]) end def public? diff --git a/app/models/types_de_champ/repetition_type_de_champ.rb b/app/models/types_de_champ/repetition_type_de_champ.rb new file mode 100644 index 000000000..5abf8efd3 --- /dev/null +++ b/app/models/types_de_champ/repetition_type_de_champ.rb @@ -0,0 +1,2 @@ +class TypesDeChamp::RepetitionTypeDeChamp < TypesDeChamp::TypeDeChampBase +end diff --git a/app/services/procedure_export_service.rb b/app/services/procedure_export_service.rb index f550ad81d..b50c6c6f2 100644 --- a/app/services/procedure_export_service.rb +++ b/app/services/procedure_export_service.rb @@ -142,10 +142,10 @@ class ProcedureExportService headers = ATTRIBUTES.map do |key| label_for_export(key.to_s) end - headers += @procedure.types_de_champ.ordered.map do |champ| + headers += @procedure.types_de_champ.ordered.reject(&:exclude_from_export?).map do |champ| label_for_export(champ.libelle) end - headers += @procedure.types_de_champ_private.ordered.map do |champ| + headers += @procedure.types_de_champ_private.ordered.reject(&:exclude_from_export?).map do |champ| label_for_export(champ.libelle) end headers += ETABLISSEMENT_ATTRIBUTES.map do |key| @@ -184,10 +184,10 @@ class ProcedureExportService end end values = normalize_values(values) - values += dossier.champs.map do |champ| + values += dossier.champs.reject(&:exclude_from_export?).map do |champ| value_for_export(champ) end - values += dossier.champs_private.map do |champ| + values += dossier.champs_private.reject(&:exclude_from_export?).map do |champ| value_for_export(champ) end values += etablissement_data(dossier.etablissement) diff --git a/app/services/types_de_champ_service.rb b/app/services/types_de_champ_service.rb index 43a28b5ed..794a13afa 100644 --- a/app/services/types_de_champ_service.rb +++ b/app/services/types_de_champ_service.rb @@ -3,7 +3,8 @@ class TypesDeChampService TOGGLES = { TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?, - TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number? + TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?, + TypeDeChamp.type_champs.fetch(:repetition) => :champ_repetition? } def options diff --git a/app/views/shared/dossiers/_champs.html.haml b/app/views/shared/dossiers/_champs.html.haml index b7feb25a9..928efe27c 100644 --- a/app/views/shared/dossiers/_champs.html.haml +++ b/app/views/shared/dossiers/_champs.html.haml @@ -1,6 +1,6 @@ %table.table.vertical.dossier-champs %tbody - - champs.reject { |c| c.type_champ == TypeDeChamp.type_champs.fetch(:explication) }.each do |c| + - champs.reject(&:exclude_from_view?).each do |c| %tr - case c.type_champ - when TypeDeChamp.type_champs.fetch(:header_section) diff --git a/app/views/shared/dossiers/editable_champs/_repetition.html.haml b/app/views/shared/dossiers/editable_champs/_repetition.html.haml new file mode 100644 index 000000000..b9fcfa711 --- /dev/null +++ b/app/views/shared/dossiers/editable_champs/_repetition.html.haml @@ -0,0 +1 @@ +%h2.repetition-libelle= champ.libelle diff --git a/config/features.rb b/config/features.rb index 48cc54e1d..d998c2ee0 100644 --- a/config/features.rb +++ b/config/features.rb @@ -11,6 +11,8 @@ Flipflop.configure do title: "Champ SIRET" feature :champ_integer_number, title: "Champ nombre entier" + feature :champ_repetition, + title: "Bloc répétable (NE MARCHE PAS – NE PAS ACTIVER)" end feature :web_hook diff --git a/config/locales/models/type_de_champ/fr.yml b/config/locales/models/type_de_champ/fr.yml index 642e43579..a76f1ad6b 100644 --- a/config/locales/models/type_de_champ/fr.yml +++ b/config/locales/models/type_de_champ/fr.yml @@ -31,3 +31,4 @@ fr: piece_justificative: 'Pièce justificative' siret: 'SIRET' carte: 'Carte' + repetition: 'Bloc répétable' diff --git a/db/migrate/20181217125100_create_champ_groups.rb b/db/migrate/20181217125100_create_champ_groups.rb new file mode 100644 index 000000000..ac55a12e8 --- /dev/null +++ b/db/migrate/20181217125100_create_champ_groups.rb @@ -0,0 +1,15 @@ +class CreateChampGroups < ActiveRecord::Migration[5.2] + def change + add_column :types_de_champ, :parent_id, :bigint + add_index :types_de_champ, :parent_id + + add_column :champs, :parent_id, :bigint + add_index :champs, :parent_id + + add_column :champs, :row, :integer + add_index :champs, :row + + add_foreign_key :types_de_champ, :types_de_champ, column: :parent_id + add_foreign_key :champs, :champs, column: :parent_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 626f70db6..2a8ec1ccc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -153,8 +153,12 @@ ActiveRecord::Schema.define(version: 2018_12_18_204707) do t.datetime "updated_at" t.boolean "private", default: false, null: false t.integer "etablissement_id" + t.bigint "parent_id" + t.integer "row" t.index ["dossier_id"], name: "index_champs_on_dossier_id" + t.index ["parent_id"], name: "index_champs_on_parent_id" t.index ["private"], name: "index_champs_on_private" + t.index ["row"], name: "index_champs_on_row" t.index ["type_de_champ_id"], name: "index_champs_on_type_de_champ_id" end @@ -539,6 +543,8 @@ ActiveRecord::Schema.define(version: 2018_12_18_204707) do t.datetime "updated_at" t.jsonb "options" t.bigint "stable_id" + t.bigint "parent_id" + t.index ["parent_id"], name: "index_types_de_champ_on_parent_id" t.index ["private"], name: "index_types_de_champ_on_private" t.index ["stable_id"], name: "index_types_de_champ_on_stable_id" end @@ -600,6 +606,7 @@ ActiveRecord::Schema.define(version: 2018_12_18_204707) do add_foreign_key "attestation_templates", "procedures" add_foreign_key "attestations", "dossiers" add_foreign_key "avis", "gestionnaires", column: "claimant_id" + add_foreign_key "champs", "champs", column: "parent_id" add_foreign_key "closed_mails", "procedures" add_foreign_key "commentaires", "dossiers" add_foreign_key "dossier_operation_logs", "dossiers" @@ -613,5 +620,6 @@ ActiveRecord::Schema.define(version: 2018_12_18_204707) do add_foreign_key "received_mails", "procedures" add_foreign_key "refused_mails", "procedures" add_foreign_key "services", "administrateurs" + add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id" add_foreign_key "without_continuation_mails", "procedures" end diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index f9c345837..20d04628d 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -165,4 +165,8 @@ FactoryBot.define do champ.etablissement.signature = champ.etablissement.sign end end + + factory :champ_repetition, class: 'Champs::RepetitionChamp' do + type_de_champ { create(:type_de_champ_repetition) } + end end diff --git a/spec/factories/type_de_champ.rb b/spec/factories/type_de_champ.rb index caf15f043..26df602b2 100644 --- a/spec/factories/type_de_champ.rb +++ b/spec/factories/type_de_champ.rb @@ -93,6 +93,9 @@ FactoryBot.define do factory :type_de_champ_carte do type_champ { TypeDeChamp.type_champs.fetch(:carte) } end + factory :type_de_champ_repetition do + type_champ { TypeDeChamp.type_champs.fetch(:repetition) } + end trait :private do private { true } diff --git a/spec/models/champ_spec.rb b/spec/models/champ_spec.rb index 9056d7d40..a47d82206 100644 --- a/spec/models/champ_spec.rb +++ b/spec/models/champ_spec.rb @@ -399,4 +399,30 @@ describe Champ do it { expect{ champ.save }.to_not change(VirusScan, :count) } end end + + describe "repetition" do + let(:champ) { create(:champ_repetition) } + let(:champ_text) { create(:champ_text, row: 0) } + let(:champ_integer_number) { create(:champ_integer_number, row: 0) } + let(:champ_text2) { create(:champ_text, row: 1) } + + it { + expect(champ.rows.size).to eq(0) + + champ.champs << champ_text2 + expect(champ.rows.size).to eq(1) + + champ.champs << champ_integer_number + row = champ.reload.rows.first + expect(row.size).to eq(1) + expect(row.first).to eq(champ_integer_number) + + champ.champs << champ_text + row = champ.reload.rows.first + expect(row.size).to eq(2) + expect(row.second).to eq(champ_text) + + expect(champ.rows.size).to eq(2) + } + end end diff --git a/spec/models/type_de_champ_shared_example.rb b/spec/models/type_de_champ_shared_example.rb index 5fbceb3bd..a1cde8dfd 100644 --- a/spec/models/type_de_champ_shared_example.rb +++ b/spec/models/type_de_champ_shared_example.rb @@ -110,4 +110,20 @@ shared_examples 'type_de_champ_spec' do end end end + + describe "repetition" do + let(:type_de_champ) { create(:type_de_champ_repetition) } + let(:type_de_champ_text) { create(:type_de_champ_text) } + let(:type_de_champ_integer_number) { create(:type_de_champ_integer_number) } + + it { + expect(type_de_champ.types_de_champ.size).to eq(0) + type_de_champ.types_de_champ << type_de_champ_integer_number + expect(type_de_champ.types_de_champ.size).to eq(1) + type_de_champ.types_de_champ << type_de_champ_text + expect(type_de_champ.types_de_champ.size).to eq(2) + expect(type_de_champ_integer_number.parent).to eq(type_de_champ) + expect(type_de_champ_text.parent).to eq(type_de_champ) + } + end end diff --git a/spec/services/procedure_export_service_spec.rb b/spec/services/procedure_export_service_spec.rb index 67e50a4b7..5dcad08be 100644 --- a/spec/services/procedure_export_service_spec.rb +++ b/spec/services/procedure_export_service_spec.rb @@ -55,8 +55,6 @@ describe ProcedureExportService do :regions, :departements, :engagement, - :header_section, - :explication, :dossier_link, :piece_justificative, :siret, @@ -123,7 +121,7 @@ describe ProcedureExportService do } let(:champs_data) { - dossier.champs.ordered.map(&:for_export) + dossier.reload.champs.reject(&:exclude_from_export?).map(&:for_export) } let(:etablissement_data) { @@ -132,8 +130,8 @@ describe ProcedureExportService do it 'should have values' do expect(data.first[0..14]).to eq(dossier_data) - expect(data.first[15..40]).to eq(champs_data) - expect(data.first[41..64]).to eq(etablissement_data) + expect(data.first[15..38]).to eq(champs_data) + expect(data.first[39..62]).to eq(etablissement_data) expect(data).to eq([ dossier_data + champs_data + etablissement_data @@ -178,8 +176,8 @@ describe ProcedureExportService do it 'should have values' do expect(data.first[0..14]).to eq(dossier_data) - expect(data.first[15..40]).to eq(champs_data) - expect(data.first[41..64]).to eq(etablissement_data) + expect(data.first[15..38]).to eq(champs_data) + expect(data.first[39..62]).to eq(etablissement_data) expect(data).to eq([ dossier_data + champs_data + etablissement_data