fix(revisions): fix repetitions export with revisions
This commit is contained in:
parent
39c94992a7
commit
f6b8689a97
10 changed files with 78 additions and 57 deletions
|
@ -21,6 +21,7 @@
|
||||||
#
|
#
|
||||||
class Champs::RepetitionChamp < Champ
|
class Champs::RepetitionChamp < Champ
|
||||||
accepts_nested_attributes_for :champs, allow_destroy: true
|
accepts_nested_attributes_for :champs, allow_destroy: true
|
||||||
|
delegate :libelle_for_export, to: :type_de_champ
|
||||||
|
|
||||||
def rows
|
def rows
|
||||||
champs.group_by(&:row).values
|
champs.group_by(&:row).values
|
||||||
|
@ -57,13 +58,6 @@ class Champs::RepetitionChamp < Champ
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# We have to truncate the label here as spreadsheets have a (30 char) limit on length.
|
|
||||||
def libelle_for_export
|
|
||||||
str = "(#{stable_id}) #{libelle}"
|
|
||||||
# /\*?[] are invalid Excel worksheet characters
|
|
||||||
ActiveStorage::Filename.new(str.delete('[]*?')).sanitized
|
|
||||||
end
|
|
||||||
|
|
||||||
class Row < Hashie::Dash
|
class Row < Hashie::Dash
|
||||||
property :index
|
property :index
|
||||||
property :dossier_id
|
property :dossier_id
|
||||||
|
@ -73,19 +67,11 @@ class Champs::RepetitionChamp < Champ
|
||||||
self[attribute]
|
self[attribute]
|
||||||
end
|
end
|
||||||
|
|
||||||
def spreadsheet_columns
|
def spreadsheet_columns(types_de_champ)
|
||||||
[
|
[
|
||||||
['Dossier ID', :dossier_id],
|
['Dossier ID', :dossier_id],
|
||||||
['Ligne', :index]
|
['Ligne', :index]
|
||||||
] + exported_champs
|
] + Dossier.champs_for_export(champs, types_de_champ)
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def exported_champs
|
|
||||||
champs.reject(&:exclude_from_export?).map do |champ|
|
|
||||||
[champ.libelle, champ.for_export]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -918,12 +918,12 @@ class Dossier < ApplicationRecord
|
||||||
columns << ['Groupe instructeur', groupe_instructeur.label]
|
columns << ['Groupe instructeur', groupe_instructeur.label]
|
||||||
end
|
end
|
||||||
|
|
||||||
columns + champs_for_export(types_de_champ)
|
columns + self.class.champs_for_export(champs + champs_private, types_de_champ)
|
||||||
end
|
end
|
||||||
|
|
||||||
def champs_for_export(types_de_champ)
|
def self.champs_for_export(champs, types_de_champ)
|
||||||
# Index values by stable_id
|
# Index values by stable_id
|
||||||
values = (champs + champs_private).reject(&:exclude_from_export?)
|
values = champs.reject(&:exclude_from_export?)
|
||||||
.index_by(&:stable_id)
|
.index_by(&:stable_id)
|
||||||
.transform_values(&:for_export)
|
.transform_values(&:for_export)
|
||||||
|
|
||||||
|
|
|
@ -99,20 +99,16 @@ class Procedure < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def types_de_champ_for_procedure_presentation
|
def types_de_champ_for_procedure_presentation
|
||||||
explanatory_types_de_champ = [:header_section, :explication, :repetition].map { |k| TypeDeChamp.type_champs.fetch(k) }
|
|
||||||
|
|
||||||
if brouillon?
|
if brouillon?
|
||||||
TypeDeChamp
|
TypeDeChamp.fillable
|
||||||
.joins(:revisions)
|
.joins(:revision_types_de_champ)
|
||||||
.where.not(type_champ: explanatory_types_de_champ)
|
.where(revision_types_de_champ: { revision: draft_revision })
|
||||||
.where(procedure_revisions: { id: draft_revision_id })
|
|
||||||
.order(:private, :position)
|
.order(:private, :position)
|
||||||
else
|
else
|
||||||
# fetch all type_de_champ.stable_id for all the revisions expect draft
|
# 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
|
# and for each stable_id take the bigger (more recent) type_de_champ.id
|
||||||
recent_ids = TypeDeChamp
|
recent_ids = TypeDeChamp.fillable
|
||||||
.joins(:revisions)
|
.joins(:revisions)
|
||||||
.where.not(type_champ: explanatory_types_de_champ)
|
|
||||||
.where(procedure_revisions: { procedure_id: id })
|
.where(procedure_revisions: { procedure_id: id })
|
||||||
.where.not(procedure_revisions: { id: draft_revision_id })
|
.where.not(procedure_revisions: { id: draft_revision_id })
|
||||||
.group(:stable_id)
|
.group(:stable_id)
|
||||||
|
@ -139,6 +135,7 @@ class Procedure < ApplicationRecord
|
||||||
else
|
else
|
||||||
TypeDeChamp.root
|
TypeDeChamp.root
|
||||||
.public_only
|
.public_only
|
||||||
|
.fillable
|
||||||
.where(revision: revisions - [draft_revision])
|
.where(revision: revisions - [draft_revision])
|
||||||
.order(:created_at)
|
.order(:created_at)
|
||||||
.uniq
|
.uniq
|
||||||
|
@ -151,6 +148,7 @@ class Procedure < ApplicationRecord
|
||||||
else
|
else
|
||||||
TypeDeChamp.root
|
TypeDeChamp.root
|
||||||
.private_only
|
.private_only
|
||||||
|
.fillable
|
||||||
.where(revision: revisions - [draft_revision])
|
.where(revision: revisions - [draft_revision])
|
||||||
.order(:created_at)
|
.order(:created_at)
|
||||||
.uniq
|
.uniq
|
||||||
|
|
|
@ -86,6 +86,8 @@ class TypeDeChamp < ApplicationRecord
|
||||||
scope :ordered, -> { order(order_place: :asc) }
|
scope :ordered, -> { order(order_place: :asc) }
|
||||||
scope :root, -> { where(parent_id: nil) }
|
scope :root, -> { where(parent_id: nil) }
|
||||||
scope :repetition, -> { where(type_champ: type_champs.fetch(:repetition)) }
|
scope :repetition, -> { where(type_champ: type_champs.fetch(:repetition)) }
|
||||||
|
scope :not_repetition, -> { where.not(type_champ: type_champs.fetch(:repetition)) }
|
||||||
|
scope :fillable, -> { where.not(type_champ: [type_champs.fetch(:header_section), type_champs.fetch(:explication)]) }
|
||||||
|
|
||||||
has_many :champ, inverse_of: :type_de_champ, dependent: :destroy do
|
has_many :champ, inverse_of: :type_de_champ, dependent: :destroy do
|
||||||
def build(params = {})
|
def build(params = {})
|
||||||
|
@ -287,6 +289,24 @@ class TypeDeChamp < ApplicationRecord
|
||||||
options.slice(*TypesDeChamp::CarteTypeDeChamp::LAYERS)
|
options.slice(*TypesDeChamp::CarteTypeDeChamp::LAYERS)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def types_de_champ_for_revision(revision)
|
||||||
|
if revision.draft?
|
||||||
|
# if we are asking for children on a draft revision, just use current child types_de_champ
|
||||||
|
types_de_champ.fillable
|
||||||
|
else
|
||||||
|
# otherwise return all types_de_champ in their latest state
|
||||||
|
types_de_champ = TypeDeChamp
|
||||||
|
.fillable
|
||||||
|
.joins(:parent)
|
||||||
|
.where(parent: { stable_id: stable_id })
|
||||||
|
|
||||||
|
TypeDeChamp
|
||||||
|
.where(id: types_de_champ.group(:stable_id).select('MAX(types_de_champ.id)'))
|
||||||
|
.joins(parent: :revision_types_de_champ)
|
||||||
|
.order(:order_place, 'procedure_revision_types_de_champ.revision_id': :desc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
FEATURE_FLAGS = {}
|
FEATURE_FLAGS = {}
|
||||||
|
|
||||||
def self.type_de_champ_types_for(procedure, user)
|
def self.type_de_champ_types_for(procedure, user)
|
||||||
|
|
|
@ -4,4 +4,11 @@ class TypesDeChamp::RepetitionTypeDeChamp < TypesDeChamp::TypeDeChampBase
|
||||||
champ.add_row
|
champ.add_row
|
||||||
champ
|
champ
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We have to truncate the label here as spreadsheets have a (30 char) limit on length.
|
||||||
|
def libelle_for_export(index = 0)
|
||||||
|
str = "(#{stable_id}) #{libelle}"
|
||||||
|
# /\*?[] are invalid Excel worksheet characters
|
||||||
|
ActiveStorage::Filename.new(str.delete('[]*?')).sanitized
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
class TypesDeChamp::TypeDeChampBase
|
class TypesDeChamp::TypeDeChampBase
|
||||||
include ActiveModel::Validations
|
include ActiveModel::Validations
|
||||||
|
|
||||||
delegate :description, :libelle, to: :@type_de_champ
|
delegate :description, :libelle, :stable_id, to: :@type_de_champ
|
||||||
|
|
||||||
def initialize(type_de_champ)
|
def initialize(type_de_champ)
|
||||||
@type_de_champ = type_de_champ
|
@type_de_champ = type_de_champ
|
||||||
end
|
end
|
||||||
|
|
||||||
def tags_for_template
|
def tags_for_template
|
||||||
stable_id = @type_de_champ.stable_id
|
stable_id = self.stable_id
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
libelle: libelle,
|
libelle: libelle,
|
||||||
|
@ -21,7 +21,7 @@ class TypesDeChamp::TypeDeChampBase
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def libelle_for_export(index)
|
def libelle_for_export(index = 0)
|
||||||
libelle
|
libelle
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -39,21 +39,22 @@ class ProcedureExportService
|
||||||
@avis ||= dossiers.flat_map(&:avis)
|
@avis ||= dossiers.flat_map(&:avis)
|
||||||
end
|
end
|
||||||
|
|
||||||
def champs_repetables
|
|
||||||
@champs_repetables ||= dossiers.flat_map do |dossier|
|
|
||||||
[dossier.champs, dossier.champs_private]
|
|
||||||
.flatten
|
|
||||||
.filter { |champ| champ.is_a?(Champs::RepetitionChamp) }
|
|
||||||
end.group_by(&:libelle_for_export)
|
|
||||||
end
|
|
||||||
|
|
||||||
def champs_repetables_options
|
def champs_repetables_options
|
||||||
champs_repetables.map do |libelle, champs|
|
revision = @procedure.active_revision
|
||||||
[
|
champs_by_stable_id = dossiers
|
||||||
libelle,
|
.flat_map { |dossier| (dossier.champs + dossier.champs_private).filter(&:repetition?) }
|
||||||
champs.flat_map(&:rows_for_export)
|
.group_by(&:stable_id)
|
||||||
]
|
|
||||||
end
|
@procedure.types_de_champ_for_procedure_presentation.repetition
|
||||||
|
.map { |type_de_champ_repetition| [type_de_champ_repetition, type_de_champ_repetition.types_de_champ_for_revision(revision).to_a] }
|
||||||
|
.filter { |(_, types_de_champ)| types_de_champ.present? }
|
||||||
|
.map do |(type_de_champ_repetition, types_de_champ)|
|
||||||
|
{
|
||||||
|
sheet_name: type_de_champ_repetition.libelle_for_export,
|
||||||
|
instances: champs_by_stable_id.fetch(type_de_champ_repetition.stable_id, []).flat_map(&:rows_for_export),
|
||||||
|
spreadsheet_columns: Proc.new { |instance| instance.spreadsheet_columns(types_de_champ) }
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
DEFAULT_STYLES = {
|
DEFAULT_STYLES = {
|
||||||
|
@ -69,8 +70,8 @@ class ProcedureExportService
|
||||||
{ instances: etablissements.to_a, sheet_name: 'Etablissements' }
|
{ instances: etablissements.to_a, sheet_name: 'Etablissements' }
|
||||||
when :avis
|
when :avis
|
||||||
{ instances: avis.to_a, sheet_name: 'Avis' }
|
{ instances: avis.to_a, sheet_name: 'Avis' }
|
||||||
when Array
|
when Hash
|
||||||
{ instances: table.last, sheet_name: table.first }
|
table
|
||||||
end.merge(DEFAULT_STYLES)
|
end.merge(DEFAULT_STYLES)
|
||||||
|
|
||||||
# transliterate: convert to ASCII characters
|
# transliterate: convert to ASCII characters
|
||||||
|
@ -84,7 +85,7 @@ class ProcedureExportService
|
||||||
end
|
end
|
||||||
|
|
||||||
def spreadsheet_columns(format)
|
def spreadsheet_columns(format)
|
||||||
types_de_champ = @procedure.types_de_champ_for_procedure_presentation.to_a
|
types_de_champ = @procedure.types_de_champ_for_procedure_presentation.not_repetition.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)
|
||||||
|
|
|
@ -1341,14 +1341,20 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "champs_for_export" do
|
describe "champs_for_export" do
|
||||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_datetime, :with_yes_no, :with_explication, :with_commune) }
|
let(:procedure) { create(:procedure, :with_type_de_champ, :with_datetime, :with_yes_no, :with_explication, :with_commune, :with_repetition) }
|
||||||
let(:text_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:text) } }
|
let(:text_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:text) } }
|
||||||
let(:yes_no_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } }
|
let(:yes_no_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:yes_no) } }
|
||||||
let(:datetime_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
|
let(:datetime_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:datetime) } }
|
||||||
let(:explication_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:explication) } }
|
let(:explication_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:explication) } }
|
||||||
let(:commune_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:communes) } }
|
let(:commune_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:communes) } }
|
||||||
|
let(:repetition_type_de_champ) { procedure.types_de_champ.find { |type_de_champ| type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:repetition) } }
|
||||||
|
let(:repetition_champ) { dossier.champs.find(&:repetition?) }
|
||||||
|
let(:repetition_second_revision_champ) { dossier_second_revision.champs.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(dossier.champs, procedure.types_de_champ_for_procedure_presentation.not_repetition) }
|
||||||
|
let(:dossier_second_revision_champs_for_export) { Dossier.champs_for_export(dossier_second_revision.champs, procedure.types_de_champ_for_procedure_presentation.not_repetition) }
|
||||||
|
let(:repetition_second_revision_champs_for_export) { Dossier.champs_for_export(repetition_second_revision_champ.champs, procedure.types_de_champ_for_procedure_presentation.repetition) }
|
||||||
|
|
||||||
context "when procedure published" do
|
context "when procedure published" do
|
||||||
before do
|
before do
|
||||||
|
@ -1358,16 +1364,19 @@ describe Dossier do
|
||||||
procedure.draft_revision.add_type_de_champ(type_champ: TypeDeChamp.type_champs.fetch(:text), libelle: 'New text field')
|
procedure.draft_revision.add_type_de_champ(type_champ: TypeDeChamp.type_champs.fetch(:text), libelle: 'New text field')
|
||||||
procedure.draft_revision.find_or_clone_type_de_champ(yes_no_type_de_champ.stable_id).update(libelle: 'Updated yes/no')
|
procedure.draft_revision.find_or_clone_type_de_champ(yes_no_type_de_champ.stable_id).update(libelle: 'Updated yes/no')
|
||||||
procedure.draft_revision.find_or_clone_type_de_champ(commune_type_de_champ.stable_id).update(libelle: 'Commune de naissance')
|
procedure.draft_revision.find_or_clone_type_de_champ(commune_type_de_champ.stable_id).update(libelle: 'Commune de naissance')
|
||||||
|
procedure.draft_revision.find_or_clone_type_de_champ(repetition_type_de_champ.stable_id).update(libelle: 'Repetition')
|
||||||
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)
|
||||||
dossier.reload
|
dossier.reload
|
||||||
procedure.reload
|
procedure.reload
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have champs from all revisions" do
|
it "should have champs from all revisions" do
|
||||||
expect(dossier.types_de_champ.map(&:libelle)).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Yes/no", explication_type_de_champ.libelle, commune_type_de_champ.libelle])
|
expect(dossier.types_de_champ.map(&:libelle)).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Yes/no", explication_type_de_champ.libelle, commune_type_de_champ.libelle, repetition_type_de_champ.libelle])
|
||||||
expect(dossier_second_revision.types_de_champ.map(&:libelle)).to eq([datetime_type_de_champ.libelle, "Updated yes/no", explication_type_de_champ.libelle, 'Commune de naissance', "New text field"])
|
expect(dossier_second_revision.types_de_champ.map(&:libelle)).to eq([datetime_type_de_champ.libelle, "Updated yes/no", explication_type_de_champ.libelle, 'Commune de naissance', "Repetition", "New text field"])
|
||||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation).map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Updated yes/no", "Commune de naissance", "Commune de naissance (Code insee)", "New text field"])
|
expect(dossier_champs_for_export.map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle, datetime_type_de_champ.libelle, "Updated yes/no", "Commune de naissance", "Commune de naissance (Code insee)", "New text field"])
|
||||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation)).to eq(dossier_second_revision.champs_for_export(dossier_second_revision.procedure.types_de_champ_for_procedure_presentation))
|
expect(dossier_champs_for_export).to eq(dossier_second_revision_champs_for_export)
|
||||||
|
expect(repetition_second_revision_champs_for_export.map { |(libelle)| libelle }).to eq(procedure.types_de_champ_for_procedure_presentation.repetition.map(&:libelle_for_export))
|
||||||
|
expect(repetition_second_revision_champs_for_export.first.size).to eq(2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1375,7 +1384,7 @@ describe Dossier do
|
||||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_explication) }
|
let(:procedure) { create(:procedure, :with_type_de_champ, :with_explication) }
|
||||||
|
|
||||||
it "should not contain non-exportable types de champ" do
|
it "should not contain non-exportable types de champ" do
|
||||||
expect(dossier.champs_for_export(dossier.procedure.types_de_champ_for_procedure_presentation).map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle])
|
expect(dossier_champs_for_export.map { |(libelle)| libelle }).to eq([text_type_de_champ.libelle])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
describe ProcedurePresentation do
|
describe ProcedurePresentation do
|
||||||
describe "#types_de_champ_for_procedure_presentation" do
|
describe "#types_de_champ_for_procedure_presentation" do
|
||||||
subject { procedure.types_de_champ_for_procedure_presentation.pluck(:libelle) }
|
subject { procedure.types_de_champ_for_procedure_presentation.not_repetition.pluck(:libelle) }
|
||||||
|
|
||||||
context 'for a draft procedure' do
|
context 'for a draft procedure' do
|
||||||
let(:procedure) { create(:procedure) }
|
let(:procedure) { create(:procedure) }
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe ProcedureRevision do
|
||||||
revision.reload
|
revision.reload
|
||||||
expect(revision.types_de_champ.index(type_de_champ)).to eq(2)
|
expect(revision.types_de_champ.index(type_de_champ)).to eq(2)
|
||||||
expect(revision.procedure.types_de_champ.index(type_de_champ)).to eq(2)
|
expect(revision.procedure.types_de_champ.index(type_de_champ)).to eq(2)
|
||||||
expect(revision.procedure.types_de_champ_for_procedure_presentation.index(type_de_champ)).to eq(2)
|
expect(revision.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(type_de_champ)).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'move up' do
|
it 'move up' do
|
||||||
|
@ -66,7 +66,7 @@ describe ProcedureRevision do
|
||||||
revision.reload
|
revision.reload
|
||||||
expect(revision.types_de_champ.index(last_type_de_champ)).to eq(0)
|
expect(revision.types_de_champ.index(last_type_de_champ)).to eq(0)
|
||||||
expect(revision.procedure.types_de_champ.index(last_type_de_champ)).to eq(0)
|
expect(revision.procedure.types_de_champ.index(last_type_de_champ)).to eq(0)
|
||||||
expect(revision.procedure.types_de_champ_for_procedure_presentation.index(last_type_de_champ)).to eq(0)
|
expect(revision.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(last_type_de_champ)).to eq(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'repetition' do
|
context 'repetition' do
|
||||||
|
|
Loading…
Add table
Reference in a new issue