commit
337f934fad
11 changed files with 101 additions and 103 deletions
|
@ -1,16 +1,15 @@
|
||||||
class Champs::CarteController < ApplicationController
|
class Champs::CarteController < ApplicationController
|
||||||
before_action :authenticate_logged_user!
|
before_action :authenticate_logged_user!
|
||||||
|
|
||||||
EMPTY_GEO_JSON = '[]'
|
|
||||||
ERROR_GEO_JSON = ''
|
ERROR_GEO_JSON = ''
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@selector = ".carte-#{params[:position]}"
|
@selector = ".carte-#{params[:position]}"
|
||||||
|
|
||||||
if params[:dossier].key?(:champs_attributes)
|
feature_collection = if params[:dossier].key?(:champs_attributes)
|
||||||
coordinates = params[:dossier][:champs_attributes][params[:position]][:value]
|
params[:dossier][:champs_attributes][params[:position]][:value]
|
||||||
else
|
else
|
||||||
coordinates = params[:dossier][:champs_private_attributes][params[:position]][:value]
|
params[:dossier][:champs_private_attributes][params[:position]][:value]
|
||||||
end
|
end
|
||||||
|
|
||||||
@champ = if params[:champ_id].present?
|
@champ = if params[:champ_id].present?
|
||||||
|
@ -21,40 +20,21 @@ class Champs::CarteController < ApplicationController
|
||||||
|
|
||||||
geo_areas = []
|
geo_areas = []
|
||||||
|
|
||||||
if coordinates == EMPTY_GEO_JSON
|
if feature_collection == ERROR_GEO_JSON
|
||||||
@champ.value = nil
|
|
||||||
@champ.geo_areas = []
|
|
||||||
elsif coordinates == ERROR_GEO_JSON
|
|
||||||
@error = true
|
@error = true
|
||||||
@champ.value = nil
|
|
||||||
@champ.geo_areas = []
|
|
||||||
else
|
else
|
||||||
coordinates = JSON.parse(coordinates)
|
feature_collection = JSON.parse(feature_collection, symbolize_names: true)
|
||||||
|
|
||||||
if @champ.cadastres?
|
if @champ.cadastres?
|
||||||
cadastres = ApiCartoService.generate_cadastre(coordinates)
|
populate_cadastres(feature_collection)
|
||||||
geo_areas += cadastres.map do |cadastre|
|
|
||||||
cadastre[:source] = GeoArea.sources.fetch(:cadastre)
|
|
||||||
cadastre
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
selections_utilisateur = legacy_selections_utilisateur_to_polygons(coordinates)
|
geo_areas = GeoArea.from_feature_collection(feature_collection)
|
||||||
geo_areas += selections_utilisateur.map do |selection_utilisateur|
|
|
||||||
selection_utilisateur.merge(source: GeoArea.sources.fetch(:selection_utilisateur))
|
|
||||||
end
|
|
||||||
|
|
||||||
@champ.geo_areas = geo_areas.map do |geo_area|
|
|
||||||
GeoArea.new(geo_area)
|
|
||||||
end
|
|
||||||
|
|
||||||
@champ.value = coordinates.to_json
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if @champ.persisted?
|
if @champ.persisted?
|
||||||
@champ.save
|
@champ.update(value: nil, geo_areas: geo_areas)
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue ApiCarto::API::ResourceNotFound
|
rescue ApiCarto::API::ResourceNotFound
|
||||||
flash.alert = 'Les données cartographiques sont temporairement indisponibles. Réessayez dans un instant.'
|
flash.alert = 'Les données cartographiques sont temporairement indisponibles. Réessayez dans un instant.'
|
||||||
response.status = 503
|
response.status = 503
|
||||||
|
@ -62,14 +42,23 @@ class Champs::CarteController < ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def legacy_selections_utilisateur_to_polygons(coordinates)
|
def populate_cadastres(feature_collection)
|
||||||
coordinates.map do |lat_longs|
|
coordinates = feature_collection[:features].filter do |feature|
|
||||||
{
|
feature[:geometry][:type] == 'Polygon'
|
||||||
geometry: {
|
end.map do |feature|
|
||||||
type: 'Polygon',
|
feature[:geometry][:coordinates][0].map { |(lng, lat)| { 'lng' => lng, 'lat' => lat } }
|
||||||
coordinates: [lat_longs.map { |lat_long| [lat_long['lng'], lat_long['lat']] }]
|
end
|
||||||
|
|
||||||
|
if coordinates.present?
|
||||||
|
cadastres = ApiCartoService.generate_cadastre(coordinates)
|
||||||
|
|
||||||
|
feature_collection[:features] += cadastres.map do |cadastre|
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: cadastre.delete(:geometry),
|
||||||
|
properties: cadastre.merge(source: GeoArea.sources.fetch(:cadastre))
|
||||||
}
|
}
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
import FreeDraw from 'leaflet-freedraw';
|
import FreeDraw from 'leaflet-freedraw';
|
||||||
|
import area from '@turf/area';
|
||||||
import { fire, delegate } from '@utils';
|
import { fire, delegate } from '@utils';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
import polygonArea from './polygon_area';
|
import createFeatureCollection from './create-feature-collection';
|
||||||
|
|
||||||
const MAPS = new WeakMap();
|
const MAPS = new WeakMap();
|
||||||
|
|
||||||
|
@ -81,13 +82,18 @@ function drawUserSelectionEditor(map, { selection }) {
|
||||||
|
|
||||||
export function addFreeDrawEvents(map, selector) {
|
export function addFreeDrawEvents(map, selector) {
|
||||||
const input = findInput(selector);
|
const input = findInput(selector);
|
||||||
|
|
||||||
map.freeDraw.on('markers', ({ latLngs }) => {
|
map.freeDraw.on('markers', ({ latLngs }) => {
|
||||||
if (latLngs.length === 0) {
|
if (latLngs.length === 0) {
|
||||||
input.value = EMPTY_GEO_JSON;
|
input.value = EMPTY_GEO_JSON;
|
||||||
} else if (polygonArea(latLngs) < 300000) {
|
|
||||||
input.value = JSON.stringify(latLngs);
|
|
||||||
} else {
|
} else {
|
||||||
input.value = ERROR_GEO_JSON;
|
const featureCollection = createFeatureCollection(latLngs);
|
||||||
|
|
||||||
|
if (area(featureCollection) < 300000) {
|
||||||
|
input.value = JSON.stringify(featureCollection);
|
||||||
|
} else {
|
||||||
|
input.value = ERROR_GEO_JSON;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fire(input, 'change');
|
fire(input, 'change');
|
||||||
|
@ -121,7 +127,7 @@ function getCurrentMap(element) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMPTY_GEO_JSON = '[]';
|
const EMPTY_GEO_JSON = '{ "type": "FeatureCollection", "features": [] }';
|
||||||
const ERROR_GEO_JSON = '';
|
const ERROR_GEO_JSON = '';
|
||||||
|
|
||||||
function findInput(selector) {
|
function findInput(selector) {
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import area from '@turf/area';
|
export default function createFeatureCollection(latLngs) {
|
||||||
|
return {
|
||||||
export default function polygonArea(latLngs) {
|
|
||||||
return area({
|
|
||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
features: latLngs.map(featurePolygonLatLngs)
|
features: latLngs.map(featurePolygonLatLngs)
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function featurePolygonLatLngs(latLngs) {
|
function featurePolygonLatLngs(latLngs) {
|
||||||
return {
|
return {
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
properties: {},
|
properties: {
|
||||||
|
source: 'selection_utilisateur'
|
||||||
|
},
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Polygon',
|
type: 'Polygon',
|
||||||
coordinates: [latLngs.map(({ lng, lat }) => [lng, lat])]
|
coordinates: [latLngs.map(({ lng, lat }) => [lng, lat])]
|
|
@ -74,6 +74,7 @@ class Champs::CarteChamp < Champ
|
||||||
def to_feature_collection
|
def to_feature_collection
|
||||||
{
|
{
|
||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
|
id: type_de_champ.stable_id,
|
||||||
bbox: bounding_box,
|
bbox: bounding_box,
|
||||||
features: (legacy_selections_utilisateur + except_selections_utilisateur).map(&:to_feature)
|
features: (legacy_selections_utilisateur + except_selections_utilisateur).map(&:to_feature)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,4 +43,14 @@ class GeoArea < ApplicationRecord
|
||||||
def rgeo_geometry
|
def rgeo_geometry
|
||||||
RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.from_feature_collection(feature_collection)
|
||||||
|
feature_collection[:features].map do |feature|
|
||||||
|
GeoArea.new(
|
||||||
|
source: feature[:properties].delete(:source),
|
||||||
|
properties: feature[:properties],
|
||||||
|
geometry: feature[:geometry]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -85,11 +85,8 @@ class Procedure < ApplicationRecord
|
||||||
validate :validate_for_publication, on: :publication
|
validate :validate_for_publication, on: :publication
|
||||||
validate :check_juridique
|
validate :check_juridique
|
||||||
validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false }
|
validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,50}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false }
|
||||||
# FIXME: remove duree_conservation_required flag once all procedures are converted to the new style
|
validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }
|
||||||
validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }, if: :durees_conservation_required
|
validates :duree_conservation_dossiers_hors_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||||
validates :duree_conservation_dossiers_hors_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :durees_conservation_required
|
|
||||||
validates :duree_conservation_dossiers_dans_ds, allow_nil: true, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }, unless: :durees_conservation_required
|
|
||||||
validates :duree_conservation_dossiers_hors_ds, allow_nil: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, unless: :durees_conservation_required
|
|
||||||
validates_with MonAvisEmbedValidator
|
validates_with MonAvisEmbedValidator
|
||||||
validates :notice, content_type: [
|
validates :notice, content_type: [
|
||||||
"application/msword",
|
"application/msword",
|
||||||
|
@ -112,7 +109,6 @@ class Procedure < ApplicationRecord
|
||||||
|
|
||||||
validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes }
|
validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes }
|
||||||
before_save :update_juridique_required
|
before_save :update_juridique_required
|
||||||
before_save :update_durees_conservation_required
|
|
||||||
after_initialize :ensure_path_exists
|
after_initialize :ensure_path_exists
|
||||||
before_save :ensure_path_exists
|
before_save :ensure_path_exists
|
||||||
after_create :ensure_default_groupe_instructeur
|
after_create :ensure_default_groupe_instructeur
|
||||||
|
@ -597,11 +593,6 @@ class Procedure < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_durees_conservation_required
|
|
||||||
self.durees_conservation_required ||= duree_conservation_dossiers_hors_ds.present? && duree_conservation_dossiers_dans_ds.present?
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def percentile_time(start_attribute, end_attribute, p)
|
def percentile_time(start_attribute, end_attribute, p)
|
||||||
times = dossiers
|
times = dossiers
|
||||||
.where.not(start_attribute => nil, end_attribute => nil)
|
.where.not(start_attribute => nil, end_attribute => nil)
|
||||||
|
|
|
@ -38,7 +38,7 @@ class DossierSerializer < ActiveModel::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
if champ_carte.present?
|
if champ_carte.present?
|
||||||
champs_geo_areas = geo_areas.filter do |geo_area|
|
champs_geo_areas = champ_carte.geo_areas.filter do |geo_area|
|
||||||
geo_area.source != GeoArea.sources.fetch(:selection_utilisateur)
|
geo_area.source != GeoArea.sources.fetch(:selection_utilisateur)
|
||||||
end
|
end
|
||||||
champs_geo_areas << champ_carte.selection_utilisateur_legacy_geo_area
|
champs_geo_areas << champ_carte.selection_utilisateur_legacy_geo_area
|
||||||
|
|
|
@ -5,3 +5,4 @@
|
||||||
locals: { champ: @champ, error: @error }) %>
|
locals: { champ: @champ, error: @error }) %>
|
||||||
|
|
||||||
<%= fire_event('carte:update', { selector: @selector, data: @champ.to_render_data }.to_json) %>
|
<%= fire_event('carte:update', { selector: @selector, data: @champ.to_render_data }.to_json) %>
|
||||||
|
<%= fire_event('map:update', { featureCollection: @champ.to_feature_collection }.to_json) %>
|
||||||
|
|
|
@ -36,7 +36,12 @@ describe Champs::CarteController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when coordinates are empty' do
|
context 'when coordinates are empty' do
|
||||||
let(:value) { '[]' }
|
let(:value) do
|
||||||
|
{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: []
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
it {
|
it {
|
||||||
expect(assigns(:error)).to eq(nil)
|
expect(assigns(:error)).to eq(nil)
|
||||||
|
@ -47,11 +52,26 @@ describe Champs::CarteController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when coordinates are informed' do
|
context 'when coordinates are informed' do
|
||||||
let(:value) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]].to_json }
|
let(:value) do
|
||||||
|
{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
source: 'selection_utilisateur'
|
||||||
|
},
|
||||||
|
geometry: { type: 'Polygon', coordinates: [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.377510070800781, 48.8712640169951], [2.3859214782714844, 48.87442541960633]]] }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
it { expect(response.body).not_to be_nil }
|
it {
|
||||||
it { expect(response.body).to include('MultiPolygon') }
|
expect(response.body).not_to be_nil
|
||||||
it { expect(response.body).to include('[2.38715792094576,48.8723062632126]') }
|
expect(response.body).to include('MultiPolygon')
|
||||||
|
expect(response.body).to include('[2.38715792094576,48.8723062632126]')
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when error' do
|
context 'when error' do
|
||||||
|
@ -76,10 +96,25 @@ describe Champs::CarteController, type: :controller do
|
||||||
post :show, params: params, format: 'js'
|
post :show, params: params, format: 'js'
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:value) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]].to_json }
|
let(:value) do
|
||||||
|
{
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
type: 'Feature',
|
||||||
|
properties: {
|
||||||
|
source: 'selection_utilisateur'
|
||||||
|
},
|
||||||
|
geometry: { type: 'Polygon', coordinates: [[[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.377510070800781, 48.8712640169951], [2.3859214782714844, 48.87442541960633]]] }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
it { expect(response.status).to eq 503 }
|
it {
|
||||||
it { expect(response.body).to include('Les données cartographiques sont temporairement indisponibles') }
|
expect(response.status).to eq 503
|
||||||
|
expect(response.body).to include('Les données cartographiques sont temporairement indisponibles')
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
describe Champs::CarteChamp do
|
describe Champs::CarteChamp do
|
||||||
let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas) }
|
let(:champ) { Champs::CarteChamp.new(geo_areas: geo_areas, type_de_champ: create(:type_de_champ_carte)) }
|
||||||
let(:value) { '' }
|
let(:value) { '' }
|
||||||
let(:coordinates) { [[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]] }
|
let(:coordinates) { [[2.3859214782714844, 48.87442541960633], [2.3850631713867183, 48.87273183590832], [2.3809432983398438, 48.87081237174292], [2.3859214782714844, 48.87442541960633]] }
|
||||||
let(:geo_json) do
|
let(:geo_json) do
|
||||||
|
@ -49,6 +49,7 @@ describe Champs::CarteChamp do
|
||||||
let(:feature_collection) {
|
let(:feature_collection) {
|
||||||
{
|
{
|
||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
|
id: champ.type_de_champ.stable_id,
|
||||||
bbox: champ.bounding_box,
|
bbox: champ.bounding_box,
|
||||||
features: features
|
features: features
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,22 +252,11 @@ describe Procedure do
|
||||||
|
|
||||||
shared_examples 'duree de conservation' do
|
shared_examples 'duree de conservation' do
|
||||||
context 'duree_conservation_required it true, the field gets validated' do
|
context 'duree_conservation_required it true, the field gets validated' do
|
||||||
before { subject.durees_conservation_required = true }
|
|
||||||
|
|
||||||
it { is_expected.not_to allow_value(nil).for(field_name) }
|
it { is_expected.not_to allow_value(nil).for(field_name) }
|
||||||
it { is_expected.not_to allow_value('').for(field_name) }
|
it { is_expected.not_to allow_value('').for(field_name) }
|
||||||
it { is_expected.not_to allow_value('trois').for(field_name) }
|
it { is_expected.not_to allow_value('trois').for(field_name) }
|
||||||
it { is_expected.to allow_value(3).for(field_name) }
|
it { is_expected.to allow_value(3).for(field_name) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'duree_conservation_required is false, the field doesn’t get validated' do
|
|
||||||
before { subject.durees_conservation_required = false }
|
|
||||||
|
|
||||||
it { is_expected.to allow_value(nil).for(field_name) }
|
|
||||||
it { is_expected.to allow_value('').for(field_name) }
|
|
||||||
it { is_expected.not_to allow_value('trois').for(field_name) }
|
|
||||||
it { is_expected.to allow_value(3).for(field_name) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'duree de conservation dans ds' do
|
describe 'duree de conservation dans ds' do
|
||||||
|
@ -283,31 +272,6 @@ describe Procedure do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#duree_de_conservation_required' do
|
|
||||||
it 'automatically jumps to true once both durees de conservation have been set' do
|
|
||||||
p = build(
|
|
||||||
:procedure,
|
|
||||||
durees_conservation_required: false,
|
|
||||||
duree_conservation_dossiers_dans_ds: nil,
|
|
||||||
duree_conservation_dossiers_hors_ds: nil
|
|
||||||
)
|
|
||||||
p.save
|
|
||||||
expect(p.durees_conservation_required).to be_falsey
|
|
||||||
|
|
||||||
p.duree_conservation_dossiers_hors_ds = 3
|
|
||||||
p.save
|
|
||||||
expect(p.durees_conservation_required).to be_falsey
|
|
||||||
|
|
||||||
p.duree_conservation_dossiers_dans_ds = 6
|
|
||||||
p.save
|
|
||||||
expect(p.durees_conservation_required).to be_truthy
|
|
||||||
|
|
||||||
p.duree_conservation_dossiers_dans_ds = nil
|
|
||||||
p.save
|
|
||||||
expect(p.durees_conservation_required).to be_truthy
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#types_de_champ (ordered)' do
|
describe '#types_de_champ (ordered)' do
|
||||||
let(:procedure) { create(:procedure) }
|
let(:procedure) { create(:procedure) }
|
||||||
let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 1) }
|
let!(:type_de_champ_0) { create(:type_de_champ, procedure: procedure, order_place: 1) }
|
||||||
|
|
Loading…
Reference in a new issue