diff --git a/.eslintrc.js b/.eslintrc.js
index 8a6120396..1722d6614 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -10,6 +10,7 @@ module.exports = {
plugins: ['prettier'],
extends: ['eslint:recommended', 'prettier'],
env: {
+ es6: true,
browser: true
},
rules: {
diff --git a/app/assets/javascripts/old_design/admin.js b/app/assets/javascripts/old_design/admin.js
index 9b45a08c2..12fe3f8cc 100644
--- a/app/assets/javascripts/old_design/admin.js
+++ b/app/assets/javascripts/old_design/admin.js
@@ -27,6 +27,7 @@ $(document).on('change', 'select.form-control.type-champ', function() {
parent.removeClass('header-section');
parent.children('.drop-down-list').removeClass('show-inline');
parent.children('.pj-template').removeClass('show-inline');
+ parent.children('.carte-options').removeClass('show-inline');
$('.mandatory', parent).show();
@@ -42,6 +43,9 @@ $(document).on('change', 'select.form-control.type-champ', function() {
case 'piece_justificative':
parent.children('.pj-template').addClass('show-inline');
break;
+ case 'carte':
+ parent.children('.carte-options').addClass('show-inline');
+ break;
case 'explication':
$('.mandatory', parent).hide();
break;
diff --git a/app/assets/stylesheets/admin_type_de_champ.scss b/app/assets/stylesheets/admin_type_de_champ.scss
index 2d31eb5bd..b444a553b 100644
--- a/app/assets/stylesheets/admin_type_de_champ.scss
+++ b/app/assets/stylesheets/admin_type_de_champ.scss
@@ -32,7 +32,8 @@
}
.form-group.drop-down-list,
- .form-group.pj-template {
+ .form-group.pj-template,
+ .form-group.carte-options {
display: none;
}
diff --git a/app/assets/stylesheets/new_design/forms.scss b/app/assets/stylesheets/new_design/forms.scss
index 25af18eb4..d8b4af32f 100644
--- a/app/assets/stylesheets/new_design/forms.scss
+++ b/app/assets/stylesheets/new_design/forms.scss
@@ -228,8 +228,14 @@
// scss-lint:enable
}
- .algolia-autocomplete {
- margin-bottom: 2 * $default-padding;
+ .editable-champ {
+ &:not(.editable-champ-carte) .algolia-autocomplete {
+ margin-bottom: 2 * $default-padding;
+ }
+
+ .geo-areas {
+ margin-bottom: 2 * $default-padding;
+ }
}
input.aa-input,
diff --git a/app/assets/stylesheets/new_design/map.scss b/app/assets/stylesheets/new_design/map.scss
index dde4e1494..c7cb4c564 100644
--- a/app/assets/stylesheets/new_design/map.scss
+++ b/app/assets/stylesheets/new_design/map.scss
@@ -1,4 +1,77 @@
-#map {
+#map,
+.carte {
height: 400px;
margin-bottom: 16px;
+ z-index: 0;
+}
+
+.leaflet-container path {
+ cursor: url("/assets/edit.png"), default !important;
+}
+
+.carte {
+ &.edit {
+ g path.leaflet-polygon {
+ transition: all 0.25s;
+ stroke-width: 4px;
+ stroke-opacity: 1;
+ stroke: #D7217E;
+ position: absolute;
+ z-index: 100;
+ fill: #D7217E;
+ fill-opacity: 0.75;
+ }
+
+ div.leaflet-edge {
+ box-shadow: 0 0 0 2px #FFFFFF, 0 0 10px rgba(0, 0, 0, 0.35);
+ border: 5px solid #D7217E;
+ border-radius: 10px;
+ transition: opacity 0.25s;
+ cursor: move;
+ opacity: 0;
+ pointer-events: none;
+ box-sizing: border-box;
+ width: 0 !important;
+ height: 0 !important;
+ }
+ }
+
+ &.mode-create {
+ cursor: url("/assets/pencil.png"), crosshair !important;
+ }
+
+ &.mode-edit div.leaflet-edge {
+ opacity: 1;
+ pointer-events: all;
+ }
+
+ &.mode-delete path.leaflet-polygon {
+ cursor: no-drop !important;
+
+ &:hover {
+ fill: #4D4D4D !important;
+ }
+ }
+}
+
+.editable-champ-carte {
+ .toolbar {
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+
+ .button {
+ width: 200px;
+ margin-right: 10px;
+ }
+ }
+}
+
+.areas-title {
+ font-weight: bold;
+ margin-bottom: 5px;
+}
+
+.areas {
+ margin-bottom: 10px;
}
diff --git a/app/controllers/admin/procedures_controller.rb b/app/controllers/admin/procedures_controller.rb
index d9633078b..6a37e24f4 100644
--- a/app/controllers/admin/procedures_controller.rb
+++ b/app/controllers/admin/procedures_controller.rb
@@ -276,7 +276,7 @@ class Admin::ProceduresController < AdminController
if @procedure&.locked?
params.require(:procedure).permit(*editable_params)
else
- params.require(:procedure).permit(*editable_params, :duree_conservation_dossiers_dans_ds, :duree_conservation_dossiers_hors_ds, :lien_demarche, :for_individual, :individual_with_siret, :ask_birthday, module_api_carto_attributes: [:id, :use_api_carto, :quartiers_prioritaires, :cadastre]).merge(administrateur_id: current_administrateur.id)
+ params.require(:procedure).permit(*editable_params, :duree_conservation_dossiers_dans_ds, :duree_conservation_dossiers_hors_ds, :for_individual, :individual_with_siret, :ask_birthday, module_api_carto_attributes: [:id, :use_api_carto, :quartiers_prioritaires, :cadastre]).merge(administrateur_id: current_administrateur.id)
end
end
diff --git a/app/controllers/api/v1/procedures_controller.rb b/app/controllers/api/v1/procedures_controller.rb
index 4bca4d565..8e2d8a1a5 100644
--- a/app/controllers/api/v1/procedures_controller.rb
+++ b/app/controllers/api/v1/procedures_controller.rb
@@ -2,7 +2,7 @@ class API::V1::ProceduresController < APIController
before_action :fetch_procedure_and_check_token
def show
- render json: { procedure: ProcedureSerializer.new(@procedure.decorate).as_json }
+ render json: { procedure: ProcedureSerializer.new(@procedure).as_json }
end
private
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a97facd7a..21c98a15b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -37,6 +37,10 @@ class ApplicationController < ActionController::Base
logged_user.present?
end
+ def logged_user_ids
+ logged_users.map(&:id)
+ end
+
helper_method :logged_in?
protected
diff --git a/app/controllers/champs/carte_controller.rb b/app/controllers/champs/carte_controller.rb
new file mode 100644
index 000000000..4dc16440b
--- /dev/null
+++ b/app/controllers/champs/carte_controller.rb
@@ -0,0 +1,61 @@
+class Champs::CarteController < ApplicationController
+ before_action :authenticate_logged_user!
+
+ def show
+ @selector = ".carte-#{params[:position]}"
+
+ if params[:dossier].key?(:champs_attributes)
+ geo_json = params[:dossier][:champs_attributes][params[:position]][:value]
+ else
+ geo_json = params[:dossier][:champs_private_attributes][params[:position]][:value]
+ end
+
+ if params[:champ_id].present?
+ @champ = Champ
+ .joins(:dossier)
+ .where(dossiers: { user_id: logged_user_ids })
+ .find_by(id: params[:champ_id])
+ else
+ @champ = Champs::CarteChamp.new(type_de_champ: TypeDeChamp.new(
+ type_champ: TypeDeChamp.type_champs.fetch(:carte),
+ options: {
+ quartiers_prioritaires: true,
+ cadastres: true
+ }
+ ))
+ end
+
+ geo_areas = []
+ geo_json = geo_json.blank? ? [] : JSON.parse(geo_json)
+
+ if geo_json.first == ["error", "TooManyPolygons"]
+ @error = true
+ elsif geo_json.present?
+ if @champ.cadastres?
+ cadastres = ModuleApiCartoService.generate_cadastre(geo_json)
+ geo_areas += cadastres.map do |cadastre|
+ cadastre[:source] = GeoArea.sources.fetch(:cadastre)
+ cadastre
+ end
+ end
+
+ if @champ.quartiers_prioritaires?
+ quartiers_prioritaires = ModuleApiCartoService.generate_qp(geo_json)
+ geo_areas += quartiers_prioritaires.map do |qp|
+ qp[:source] = GeoArea.sources.fetch(:quartier_prioritaire)
+ qp
+ end
+ end
+ end
+
+ @champ.geo_areas = geo_areas.map do |geo_area|
+ GeoArea.new(geo_area)
+ end
+
+ @champ.value = geo_json.to_json
+
+ if @champ.persisted?
+ @champ.save
+ end
+ end
+end
diff --git a/app/controllers/users/carte_controller.rb b/app/controllers/users/carte_controller.rb
index 0fb5b0f34..855a7dd52 100644
--- a/app/controllers/users/carte_controller.rb
+++ b/app/controllers/users/carte_controller.rb
@@ -65,9 +65,13 @@ class Users::CarteController < UsersController
# https://tools.ietf.org/html/rfc7946#section-3.1.6
if json_latlngs.present?
multipolygone = JSON.parse(json_latlngs)
- multipolygone.reject! { |polygone| polygone.count < 4 }
- if multipolygone.present?
- multipolygone.to_json
+ if multipolygone.first == ["error", "TooManyPolygons"]
+ [].to_json
+ else
+ multipolygone.reject! { |polygone| polygone.count < 4 }
+ if multipolygone.present?
+ multipolygone.to_json
+ end
end
end
end
diff --git a/app/decorators/procedure_decorator.rb b/app/decorators/procedure_decorator.rb
index c9b310fc0..6b1fc43c3 100644
--- a/app/decorators/procedure_decorator.rb
+++ b/app/decorators/procedure_decorator.rb
@@ -22,8 +22,4 @@ class ProcedureDecorator < Draper::Decorator
end
end
end
-
- def geographic_information
- module_api_carto
- end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 2889bb9c5..e6d7cf6d9 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -91,7 +91,7 @@ module ApplicationHelper
end
def ensure_safe_json(json)
- JSON.parse(json).to_json
+ json.present? ? JSON.parse(json).to_json : '[]'
rescue Exception => e
Raven.capture_exception(e)
{}
diff --git a/app/helpers/champ_helper.rb b/app/helpers/champ_helper.rb
index 94d5fd40e..8f97ed229 100644
--- a/app/helpers/champ_helper.rb
+++ b/app/helpers/champ_helper.rb
@@ -3,4 +3,15 @@ module ChampHelper
types_without_label = [TypeDeChamp.type_champs.fetch(:header_section), TypeDeChamp.type_champs.fetch(:explication)]
!types_without_label.include?(champ.type_champ)
end
+
+ def geo_data(champ)
+ # rubocop:disable Rails/OutputSafety
+ raw({
+ position: champ.position,
+ selection: champ.value.present? ? JSON.parse(champ.value) : [],
+ quartiersPrioritaires: champ.quartiers_prioritaires? ? champ.quartiers_prioritaires : [],
+ cadastres: champ.cadastres? ? champ.cadastres : []
+ }.to_json)
+ # rubocop:enable Rails/OutputSafety
+ end
end
diff --git a/app/javascript/new_design/carto.js b/app/javascript/new_design/carto.js
index 3cf8a57a5..310c8429b 100644
--- a/app/javascript/new_design/carto.js
+++ b/app/javascript/new_design/carto.js
@@ -1,16 +1,17 @@
import { getData } from '../shared/data';
-import { initMap } from '../shared/carto';
import {
+ initMap,
drawCadastre,
drawQuartiersPrioritaires,
drawUserSelection
-} from './carto/draw';
+} from '../shared/carte';
function initialize() {
- if (document.getElementById('map')) {
- const position = getData('carto').position;
- const map = initMap(position);
+ const element = document.getElementById('map');
+
+ if (element) {
const data = getData('carto');
+ const map = initMap(element, data.position);
// draw external polygons
drawCadastre(map, data);
diff --git a/app/javascript/new_design/carto/draw.js b/app/javascript/new_design/carto/draw.js
deleted file mode 100644
index 1e84c0a71..000000000
--- a/app/javascript/new_design/carto/draw.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import L from 'leaflet';
-import {
- drawLayer,
- noEditStyle,
- CADASTRE_POLYGON_STYLE,
- QP_POLYGON_STYLE
-} from '../../shared/carto';
-
-export function drawCadastre(map, data) {
- drawLayer(
- map,
- data.cadastres,
- noEditStyle(CADASTRE_POLYGON_STYLE),
- 'cadastres'
- );
-}
-
-export function drawQuartiersPrioritaires(map, data) {
- drawLayer(
- map,
- data.quartiersPrioritaires,
- noEditStyle(QP_POLYGON_STYLE),
- 'quartiersPrioritaires'
- );
-}
-
-export function drawUserSelection(map, data) {
- if (data.selection.length > 0) {
- const polygon = L.polygon(data.selection, {
- color: 'red',
- zIndex: 3
- }).addTo(map);
- map.fitBounds(polygon.getBounds());
- }
-}
diff --git a/app/javascript/new_design/champs/carte.js b/app/javascript/new_design/champs/carte.js
new file mode 100644
index 000000000..97a46ae9c
--- /dev/null
+++ b/app/javascript/new_design/champs/carte.js
@@ -0,0 +1,62 @@
+import { CREATE } from 'leaflet-freedraw';
+import { delegate } from '@utils';
+import {
+ initMap,
+ getCurrentMap,
+ geocodeAddress,
+ drawCadastre,
+ drawQuartiersPrioritaires,
+ drawUserSelection,
+ addFreeDrawEvents
+} from '../../shared/carte';
+
+function initialize() {
+ for (let element of document.querySelectorAll('.carte')) {
+ diplayMap(element, null, true);
+ }
+
+ window.DS.drawMapData = (selector, data) => {
+ let element = document.querySelector(selector);
+ diplayMap(element, data);
+ };
+}
+
+function diplayMap(element, data, initial = false) {
+ data = data || JSON.parse(element.dataset.geo);
+ let editable = element.classList.contains('edit');
+
+ let map = initMap(element, data.position, editable);
+
+ // draw external polygons
+ drawCadastre(map, data, editable);
+ drawQuartiersPrioritaires(map, data, editable);
+
+ // draw user polygon
+ if (initial) {
+ drawUserSelection(map, data, editable);
+
+ if (editable) {
+ let input = element.parentElement.querySelector('input[data-remote]');
+ addFreeDrawEvents(map, input);
+ }
+ }
+}
+
+addEventListener('turbolinks:load', initialize);
+
+delegate('click', '.toolbar .new-area', event => {
+ event.preventDefault();
+ let map = getCurrentMap(event.target);
+
+ if (map) {
+ map.freeDraw.mode(CREATE);
+ }
+});
+
+delegate('autocomplete:select', '.toolbar [data-address]', event => {
+ let map = getCurrentMap(event.target);
+
+ if (map) {
+ geocodeAddress(map, event.detail.label);
+ }
+});
diff --git a/app/javascript/old_design/carto.js b/app/javascript/old_design/carto.js
index e3a41de63..0db35839a 100644
--- a/app/javascript/old_design/carto.js
+++ b/app/javascript/old_design/carto.js
@@ -1,81 +1,52 @@
-import L from 'leaflet';
-
-import FreeDraw, { NONE, CREATE } from 'leaflet-freedraw';
-import { fire, on, getJSON } from '@utils';
-
+import { CREATE } from 'leaflet-freedraw';
+import { on } from '@utils';
import { getData } from '../shared/data';
-import { initMap } from '../shared/carto';
-
-import polygonArea from './carto/polygon_area';
-import drawFactory from './carto/draw';
+import {
+ initMap,
+ geocodeAddress,
+ drawUserSelection,
+ drawCadastre,
+ drawQuartiersPrioritaires,
+ addFreeDrawEvents
+} from '../shared/carte';
function initialize() {
- if (document.getElementById('map')) {
- const data = getData('carto');
- const position = data.position;
+ const element = document.getElementById('map');
- const map = initMap(position);
- const freeDraw = new FreeDraw({
- mode: NONE,
- smoothFactor: 4,
- mergePolygons: false
+ if (element) {
+ const data = getData('carto');
+ const map = initMap(element, data.position, true);
+
+ addAddressSelectEvent(map);
+
+ on('#new', 'click', () => {
+ map.freeDraw.mode(CREATE);
});
- map.addLayer(freeDraw);
+ const cartoDrawZones = data => {
+ drawCadastre(map, data, true);
+ drawQuartiersPrioritaires(map, data, true);
+ };
- addEventFreeDraw(freeDraw);
- addEventSearchAddress(map);
-
- const cartoDrawZones = drawFactory(map, freeDraw);
window.DS = { cartoDrawZones };
+ // draw external polygons
cartoDrawZones(data);
- if (freeDraw.polygons[0]) {
- map.setZoom(18);
- map.fitBounds(freeDraw.polygons[0].getBounds());
- }
+ // draw user polygon
+ drawUserSelection(map, data, true);
+ addFreeDrawEvents(map, 'input[name=selection]');
}
}
addEventListener('turbolinks:load', initialize);
-function addEventFreeDraw(freeDraw) {
- freeDraw.on('markers', ({ latLngs }) => {
- const input = document.querySelector('input[name=selection]');
-
- if (polygonArea(latLngs) < 300000) {
- input.value = JSON.stringify(latLngs);
- } else {
- input.value = '{ "error": "TooManyPolygons" }';
- }
-
- fire(input, 'change');
- });
-
- on('#map', 'click', () => {
- freeDraw.mode(NONE);
- });
-
- on('#new', 'click', () => {
- freeDraw.mode(CREATE);
- });
-}
-
-function getAddressPoint(map, request) {
- getJSON('/address/geocode', { request }).then(data => {
- if (data.lat !== null) {
- map.setView(new L.LatLng(data.lat, data.lon), data.zoom);
- }
- });
-}
-
-function addEventSearchAddress(map) {
+function addAddressSelectEvent(map) {
on(
'#search-by-address input[type=address]',
'autocomplete:select',
- (_, seggestion) => {
- getAddressPoint(map, seggestion['label']);
+ (_, { label }) => {
+ geocodeAddress(map, label);
}
);
}
diff --git a/app/javascript/old_design/carto/draw.js b/app/javascript/old_design/carto/draw.js
deleted file mode 100644
index 46dc47575..000000000
--- a/app/javascript/old_design/carto/draw.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { EDIT, DELETE } from 'leaflet-freedraw';
-import { on } from '@utils';
-
-import {
- drawLayer,
- CADASTRE_POLYGON_STYLE,
- QP_POLYGON_STYLE
-} from '../../shared/carto';
-
-const SOURCES = {
- cadastres: CADASTRE_POLYGON_STYLE,
- quartiersPrioritaires: QP_POLYGON_STYLE
-};
-
-export default function draw(map, freeDraw) {
- return data => {
- if (data.selection) {
- drawSelection(freeDraw, data.selection);
- }
- for (let source of Object.keys(SOURCES)) {
- if (data[source]) {
- drawLayer(map, data[source], SOURCES[source], source);
- }
- }
- addEventEdit(freeDraw);
- };
-}
-
-function drawSelection(selection, freeDraw) {
- for (let polygon of selection) {
- freeDraw.createPolygon(polygon);
- }
-}
-
-function addEventEdit(freeDraw) {
- document
- .querySelector('.leaflet-container svg')
- .removeAttribute('pointer-events');
-
- on('.leaflet-container g path', 'click', () => {
- setTimeout(() => {
- freeDraw.mode(EDIT | DELETE);
- }, 50);
- });
-}
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 530b94bdf..a8ec784e8 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -18,6 +18,7 @@ import '../new_design/form-validation';
import '../new_design/carto';
import '../new_design/select2';
+import '../new_design/champs/carte';
import '../new_design/champs/linked-drop-down-list';
import { toggleCondidentielExplanation } from '../new_design/avis';
diff --git a/app/javascript/shared/carte.js b/app/javascript/shared/carte.js
new file mode 100644
index 000000000..f56c859dc
--- /dev/null
+++ b/app/javascript/shared/carte.js
@@ -0,0 +1,188 @@
+import L from 'leaflet';
+import FreeDraw, { NONE, EDIT, DELETE } from 'leaflet-freedraw';
+import { fire, getJSON, delegate } from '@utils';
+
+import polygonArea from './polygon_area';
+
+const LAYERS = {};
+const MAPS = new WeakMap();
+
+export function initMap(element, position, editable = false) {
+ if (MAPS.has(element)) {
+ return MAPS.get(element);
+ } else {
+ const map = L.map(element, {
+ scrollWheelZoom: false
+ }).setView([position.lat, position.lon], editable ? 18 : position.zoom);
+
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution:
+ '© OpenStreetMap contributors'
+ }).addTo(map);
+
+ if (editable) {
+ const freeDraw = new FreeDraw({
+ mode: NONE,
+ smoothFactor: 4,
+ mergePolygons: false
+ });
+ map.addLayer(freeDraw);
+ map.freeDraw = freeDraw;
+ }
+
+ MAPS.set(element, map);
+ return map;
+ }
+}
+
+export function drawCadastre(map, { cadastres }, editable = false) {
+ drawLayer(
+ map,
+ cadastres,
+ editable ? CADASTRE_POLYGON_STYLE : noEditStyle(CADASTRE_POLYGON_STYLE),
+ 'cadastres'
+ );
+}
+
+export function drawQuartiersPrioritaires(
+ map,
+ { quartiersPrioritaires },
+ editable = false
+) {
+ drawLayer(
+ map,
+ quartiersPrioritaires,
+ editable ? QP_POLYGON_STYLE : noEditStyle(QP_POLYGON_STYLE),
+ 'quartiersPrioritaires'
+ );
+}
+
+export function drawUserSelection(map, { selection }, editable = false) {
+ let hasSelection = selection && selection.length > 0;
+
+ if (editable) {
+ if (hasSelection) {
+ selection.forEach(polygon => map.freeDraw.create(polygon));
+ let polygon = map.freeDraw.all()[0];
+ if (polygon) {
+ map.fitBounds(polygon.getBounds());
+ }
+ }
+ } else if (hasSelection) {
+ const polygon = L.polygon(selection, {
+ color: 'red',
+ zIndex: 3
+ }).addTo(map);
+
+ map.fitBounds(polygon.getBounds());
+ }
+}
+
+export function geocodeAddress(map, query) {
+ getJSON('/address/geocode', { request: query }).then(data => {
+ if (data.lat !== null) {
+ map.setView(new L.LatLng(data.lat, data.lon), data.zoom);
+ }
+ });
+}
+
+export function getCurrentMap(input) {
+ let element = input.closest('.toolbar').parentElement.querySelector('.carte');
+
+ if (MAPS.has(element)) {
+ return MAPS.get(element);
+ }
+}
+
+export function addFreeDrawEvents(map, selector) {
+ const input = findInput(selector);
+ map.freeDraw.on('markers', ({ latLngs }) => {
+ if (latLngs.length === 0) {
+ input.value = '';
+ } else if (polygonArea(latLngs) < 300000) {
+ input.value = JSON.stringify(latLngs);
+ } else {
+ input.value = '{ "error": "TooManyPolygons" }';
+ }
+
+ fire(input, 'change');
+ });
+}
+
+function findInput(selector) {
+ return typeof selector === 'string'
+ ? document.querySelector(selector)
+ : selector;
+}
+
+function createLayer(map, layerName) {
+ const layer = (LAYERS[layerName] = new L.GeoJSON(undefined, {
+ interactive: false
+ }));
+ layer.addTo(map);
+ return layer;
+}
+
+function removeLayer(map, layerName) {
+ const layer = LAYERS[layerName];
+
+ if (layer) {
+ delete LAYERS[layerName];
+ map.removeLayer(layer);
+ }
+}
+
+function drawLayer(map, data, style, layerName = 'default') {
+ removeLayer(map, layerName);
+
+ if (Array.isArray(data) && data.length > 0) {
+ const layer = createLayer(map, layerName);
+
+ data.forEach(function(item) {
+ layer.addData(item.geometry);
+ });
+
+ layer.setStyle(style).addTo(map);
+ }
+}
+
+function noEditStyle(style) {
+ return Object.assign({}, style, {
+ opacity: 0.7,
+ fillOpacity: 0.5,
+ color: style.fillColor
+ });
+}
+
+const POLYGON_STYLE = {
+ weight: 2,
+ opacity: 0.3,
+ color: 'white',
+ dashArray: '3',
+ fillOpacity: 0.7
+};
+
+const CADASTRE_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
+ fillColor: '#8a6d3b'
+});
+
+const QP_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
+ fillColor: '#31708f'
+});
+
+delegate('click', '.carte.edit', event => {
+ let element = event.target;
+ let isPath = element.matches('.leaflet-container g path');
+ let map = element.matches('.carte') ? element : element.closest('.carte');
+ let freeDraw = MAPS.has(map) ? MAPS.get(map).freeDraw : null;
+
+ if (freeDraw) {
+ if (isPath) {
+ setTimeout(() => {
+ freeDraw.mode(EDIT | DELETE);
+ }, 50);
+ } else {
+ freeDraw.mode(NONE);
+ }
+ }
+});
diff --git a/app/javascript/shared/carto.js b/app/javascript/shared/carto.js
deleted file mode 100644
index 1b96ef7af..000000000
--- a/app/javascript/shared/carto.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import L from 'leaflet';
-
-const LAYERS = {};
-
-export function initMap(position) {
- const map = L.map('map', {
- scrollWheelZoom: false
- }).setView([position.lat, position.lon], position.zoom);
-
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
- attribution:
- '© OpenStreetMap contributors'
- }).addTo(map);
-
- return map;
-}
-
-export function drawLayer(map, data, style, layerName = 'default') {
- removeLayer(map, layerName);
-
- if (Array.isArray(data) && data.length > 0) {
- const layer = createLayer(map, layerName);
-
- data.forEach(function(item) {
- layer.addData(item.geometry);
- });
-
- layer.setStyle(style).addTo(map);
- }
-}
-
-export function noEditStyle(style) {
- return Object.assign({}, style, {
- opacity: 0.7,
- fillOpacity: 0.5,
- color: style.fillColor
- });
-}
-
-const POLYGON_STYLE = {
- weight: 2,
- opacity: 0.3,
- color: 'white',
- dashArray: '3',
- fillOpacity: 0.7
-};
-
-export const CADASTRE_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
- fillColor: '#8a6d3b'
-});
-
-export const QP_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
- fillColor: '#31708f'
-});
-
-function createLayer(map, layerName) {
- const layer = (LAYERS[layerName] = new L.GeoJSON(undefined, {
- interactive: false
- }));
- layer.addTo(map);
- return layer;
-}
-
-function removeLayer(map, layerName) {
- const layer = LAYERS[layerName];
-
- if (layer) {
- delete LAYERS[layerName];
- map.removeLayer(layer);
- }
-}
diff --git a/app/javascript/old_design/carto/polygon_area.js b/app/javascript/shared/polygon_area.js
similarity index 100%
rename from app/javascript/old_design/carto/polygon_area.js
rename to app/javascript/shared/polygon_area.js
diff --git a/app/models/champ.rb b/app/models/champ.rb
index 99bd1e2c0..94123a53e 100644
--- a/app/models/champ.rb
+++ b/app/models/champ.rb
@@ -17,7 +17,16 @@ class Champ < ApplicationRecord
end
def mandatory_and_blank?
- mandatory? && value.blank?
+ if mandatory?
+ case type_de_champ.type_champ
+ when TypeDeChamp.type_champs.fetch(:carte)
+ value.blank? || value == '[]'
+ else
+ value.blank?
+ end
+ else
+ false
+ end
end
def search_terms
diff --git a/app/models/champs/carte_champ.rb b/app/models/champs/carte_champ.rb
index 8928501e6..9a752722d 100644
--- a/app/models/champs/carte_champ.rb
+++ b/app/models/champs/carte_champ.rb
@@ -1,5 +1,5 @@
class Champs::CarteChamp < Champ
- has_many :geo_areas, dependent: :destroy
+ has_many :geo_areas, foreign_key: :champ_id, dependent: :destroy
# We are not using scopes here as we want to access
# the following collections on unsaved records.
@@ -15,6 +15,14 @@ class Champs::CarteChamp < Champ
end
end
+ def cadastres?
+ type_de_champ&.cadastres && type_de_champ.cadastres != '0'
+ end
+
+ def quartiers_prioritaires?
+ type_de_champ&.quartiers_prioritaires && type_de_champ.quartiers_prioritaires != '0'
+ end
+
def position
if dossier.present?
dossier.geo_position
@@ -26,4 +34,8 @@ class Champs::CarteChamp < Champ
{ lon: lon, lat: lat, zoom: zoom }
end
end
+
+ def zones
+ value.blank? ? [] : JSON.parse(value)
+ end
end
diff --git a/app/serializers/champs/carte_champ_serializer.rb b/app/serializers/champs/carte_champ_serializer.rb
new file mode 100644
index 000000000..da41b34f9
--- /dev/null
+++ b/app/serializers/champs/carte_champ_serializer.rb
@@ -0,0 +1,11 @@
+class Champs::CarteChampSerializer < ChampSerializer
+ has_many :geo_areas
+
+ def value
+ if object.value.present?
+ JSON.parse(object.value)
+ else
+ nil
+ end
+ end
+end
diff --git a/app/serializers/champs/siret_champ_serializer.rb b/app/serializers/champs/siret_champ_serializer.rb
new file mode 100644
index 000000000..9bb54ce4d
--- /dev/null
+++ b/app/serializers/champs/siret_champ_serializer.rb
@@ -0,0 +1,12 @@
+class Champs::SiretChampSerializer < ChampSerializer
+ has_one :etablissement
+ has_one :entreprise
+
+ def etablissement
+ object.etablissement
+ end
+
+ def entreprise
+ object.etablissement&.entreprise
+ end
+end
diff --git a/app/serializers/geo_area_serializer.rb b/app/serializers/geo_area_serializer.rb
new file mode 100644
index 000000000..7d501ff4c
--- /dev/null
+++ b/app/serializers/geo_area_serializer.rb
@@ -0,0 +1,16 @@
+class GeoAreaSerializer < ActiveModel::Serializer
+ attributes :geometry,
+ :source,
+ :surface_intersection,
+ :surface_parcelle,
+ :numero,
+ :feuille,
+ :section,
+ :code_dep,
+ :nom_com,
+ :code_com,
+ :code_arr,
+ :code,
+ :nom,
+ :commune
+end
diff --git a/app/serializers/procedure_serializer.rb b/app/serializers/procedure_serializer.rb
index 25c347f91..6982cfd84 100644
--- a/app/serializers/procedure_serializer.rb
+++ b/app/serializers/procedure_serializer.rb
@@ -1,6 +1,7 @@
class ProcedureSerializer < ActiveModel::Serializer
+ include Rails.application.routes.url_helpers
+
attribute :libelle, key: :label
- attribute :lien_demarche, key: :link
attributes :id,
:description,
@@ -8,10 +9,30 @@ class ProcedureSerializer < ActiveModel::Serializer
:direction,
:archived_at,
:geographic_information,
- :total_dossier
+ :total_dossier,
+ :link,
+ :state
has_one :geographic_information, serializer: ModuleApiCartoSerializer
has_many :types_de_champ, serializer: TypeDeChampSerializer
has_many :types_de_champ_private, serializer: TypeDeChampSerializer
has_many :types_de_piece_justificative, serializer: TypeDePieceJustificativeSerializer
+
+ def link
+ if object.path.present?
+ if object.brouillon_avec_lien?
+ commencer_test_url(procedure_path: object.path)
+ else
+ commencer_url(procedure_path: object.path)
+ end
+ end
+ end
+
+ def state
+ object.aasm_state
+ end
+
+ def geographic_information
+ object.module_api_carto
+ end
end
diff --git a/app/services/types_de_champ_service.rb b/app/services/types_de_champ_service.rb
index fa4b3b126..191d5e626 100644
--- a/app/services/types_de_champ_service.rb
+++ b/app/services/types_de_champ_service.rb
@@ -14,6 +14,8 @@ class TypesDeChampService
:id,
:mandatory,
:piece_justificative_template,
+ :quartiers_prioritaires,
+ :cadastres,
drop_down_list_attributes: [:value, :id]
])
diff --git a/app/views/admin/types_de_champ/_fields.html.haml b/app/views/admin/types_de_champ/_fields.html.haml
index b157e895c..5a3d74ec7 100644
--- a/app/views/admin/types_de_champ/_fields.html.haml
+++ b/app/views/admin/types_de_champ/_fields.html.haml
@@ -38,6 +38,16 @@
= ff.file_field :piece_justificative_template,
direct_upload: true
+ .form-group.carte-options{ class: (type_champ == TypeDeChamp.type_champs.fetch(:carte)) ? 'show-inline' : nil }
+ %h4 Utilisation de la cartographie
+ %label
+ = ff.check_box :quartiers_prioritaires
+ Quartiers prioritaires
+ %br
+ %label
+ = ff.check_box :cadastres
+ Cadastre
+
- hide_mandatory = (ff.object.object.private? || type_champ == TypeDeChamp.type_champs.fetch(:explication))
.form-group.mandatory{ style: hide_mandatory ? 'visibility: hidden;' : nil }
%h4 Obligatoire ?
diff --git a/app/views/administration_mailer/invite_admin.html.haml b/app/views/administration_mailer/invite_admin.html.haml
index 425d5f5de..a3e52ba41 100644
--- a/app/views/administration_mailer/invite_admin.html.haml
+++ b/app/views/administration_mailer/invite_admin.html.haml
@@ -7,19 +7,29 @@
Je vous remercie de l’intérêt que vous portez à notre outil de dématérialisation de démarches.
%p
- Votre compte administrateur a été créé pour l'adresse email #{@admin.email}. Pour l’activer, je vous invite à cliquer sur le lien suivant :
- = link_to(admin_activate_url(token: @reset_password_token), admin_activate_url(token: @reset_password_token))
+ Votre compte administrateur a été créé pour l'adresse email #{@admin.email}.
+
+%p
+ %b
+ Pour l’activer, cliquez sur le lien suivant :
+ = link_to(admin_activate_url(token: @reset_password_token), admin_activate_url(token: @reset_password_token))
%p
Afin de vous accompagner dans la découverte de demarches-simplifiees.fr, je vous propose de m’appeler pour faire un point sur vos besoins de dématérialisation.
%br
Vous pouvez me joindre au numéro suivant :
- = CONTACT_PHONE
+ = link_to(CONTACT_PHONE, "tel:#{CONTACT_PHONE}")
\.
%p
- Je vous invite également à consulter notre site de documentation qui regroupe l'ensemble des informations relatives à demarches-simplifiees.fr ainsi que des tutoriels d’utilisation :
- = link_to(DOC_URL, DOC_URL)
+ Je vous invite également à prendre quelques minutes pour consulter notre tutoriel à destination des nouveaux administrateurs :
+ = link_to(ADMINISTRATEUR_TUTORIAL_URL, ADMINISTRATEUR_TUTORIAL_URL)
+ \.
+
+%p
+ Enfin, vous pouvez vous inscrire à notre prochain webinaire en ligne pour découvrir les fonctionnalités de notre outil :
+ = link_to(WEBINAIRE_URL, WEBINAIRE_URL)
+ \.
%p
= render partial: "layouts/mailers/bizdev_signature", locals: { author_name: @author_name }
diff --git a/app/views/administration_mailer/refuse_admin.html.haml b/app/views/administration_mailer/refuse_admin.html.haml
index 7e3fc8475..654afd798 100644
--- a/app/views/administration_mailer/refuse_admin.html.haml
+++ b/app/views/administration_mailer/refuse_admin.html.haml
@@ -10,7 +10,9 @@
Pour les usagers qui souhaitent remplir une démarche, l’entrée dans demarches-simplifiees.fr se fait via un lien fourni par l’administration responsable, sur son propre site web. Ce lien vous permettra de créer un compte et de remplir le formulaire dans la foulée.
%p
- Si par contre vous rencontrez des problèmes lors de l'utilisation de demarches-simplifiees.fr en tant qu'usager, merci d'expliciter le problème rencontré.
+ Si par contre vous rencontrez des problèmes lors de l'utilisation de demarches-simplifiees.fr en tant qu'usager, merci d'expliciter le problème rencontré sur notre
+ = link_to("formulaire de contact", contact_url)
+ \.
%p
Cordialement,
diff --git a/app/views/champs/carte/show.js.erb b/app/views/champs/carte/show.js.erb
new file mode 100644
index 000000000..45ae01b28
--- /dev/null
+++ b/app/views/champs/carte/show.js.erb
@@ -0,0 +1,5 @@
+<%= render_to_element("#{@selector} + .geo-areas",
+ partial: 'shared/champs/carte/geo_areas',
+ locals: { champ: @champ, error: @error }) %>
+
+DS.drawMapData("<%= @selector %>", <%= geo_data(@champ) %>);
diff --git a/app/views/gestionnaire_mailer/invite_gestionnaire.html.haml b/app/views/gestionnaire_mailer/invite_gestionnaire.html.haml
index a7076b704..65bcd8bfb 100644
--- a/app/views/gestionnaire_mailer/invite_gestionnaire.html.haml
+++ b/app/views/gestionnaire_mailer/invite_gestionnaire.html.haml
@@ -11,8 +11,8 @@
= link_to(gestionnaire_activate_url(token: @reset_password_token), gestionnaire_activate_url(token: @reset_password_token))
%p
- Par ailleurs, notre site de documentation qui regroupe l'ensemble des informations relatives à demarches-simplifiees.fr ainsi que des tutoriels d’utilisation est à votre disposition :
- = link_to(DOC_URL, DOC_URL)
+ Par ailleurs, nous vous invitons à prendre quelques minutes pour consulter notre tutoriel à destination des nouveaux instructeurs :
+ = link_to(INSTRUCTEUR_TUTORIAL_URL, INSTRUCTEUR_TUTORIAL_URL)
%p
Bonne journée,
diff --git a/app/views/layouts/_new_header.haml b/app/views/layouts/_new_header.haml
index 0a951df0f..4b2da6288 100644
--- a/app/views/layouts/_new_header.haml
+++ b/app/views/layouts/_new_header.haml
@@ -23,15 +23,10 @@
%span.badge.warning= avis_counter
%li
.tab-link.contact-link
- Contact
+ Aide
.contact-details
- Vous avez besoin d’aide ? Contactez-nous :
- %br
- – par téléphone :
- = link_to CONTACT_PHONE, "tel:#{CONTACT_PHONE}"
- %br
- – par email :
- = contact_link CONTACT_EMAIL
+ Besoin d’aide technique ? Contactez-nous
+ = contact_link("par email")
- if nav_bar_profile == :user
%ul.header-tabs
diff --git a/app/views/shared/champs/carte/_geo_areas.html.haml b/app/views/shared/champs/carte/_geo_areas.html.haml
new file mode 100644
index 000000000..863adc1d3
--- /dev/null
+++ b/app/views/shared/champs/carte/_geo_areas.html.haml
@@ -0,0 +1,27 @@
+- if champ.quartiers_prioritaires?
+ .areas-title Quartiers prioritaires
+ .areas
+ - if error.present?
+ .error Merci de dessiner une surface plus petite afin de récupérer les quartiers prioritaires.
+ - elsif champ.value.blank?
+ Aucune zone tracée
+ - elsif champ.quartiers_prioritaires.blank?
+ = t('errors.messages.quartiers_prioritaires_empty', count: champ.zones.size)
+ - else
+ %ul
+ - champ.quartiers_prioritaires.each do |qp|
+ %li #{qp.commune} : #{qp.nom}
+
+- if champ.cadastres?
+ .areas-title Parcelles cadastrales
+ .areas
+ - if error.present?
+ .error Merci de dessiner une surface plus petite afin de récupérer les parcelles cadastrales.
+ - elsif champ.value.blank?
+ Aucune zone tracée
+ - elsif champ.cadastres.blank?
+ = t('errors.messages.cadastres_empty', count: champ.zones.size)
+ - else
+ %ul
+ - champ.cadastres.each do |pc|
+ %li Parcelle n° #{pc.numero} - Feuille #{pc.code_arr} #{pc.section} #{pc.feuille}
diff --git a/app/views/shared/champs/carto/_init.html.haml b/app/views/shared/champs/carte/_init.html.haml
similarity index 100%
rename from app/views/shared/champs/carto/_init.html.haml
rename to app/views/shared/champs/carte/_init.html.haml
diff --git a/app/views/shared/champs/carte/_show.html.haml b/app/views/shared/champs/carte/_show.html.haml
new file mode 100644
index 000000000..decb10dd0
--- /dev/null
+++ b/app/views/shared/champs/carte/_show.html.haml
@@ -0,0 +1,3 @@
+.carte{ data: { geo: geo_data(champ) } }
+.geo-areas
+ = render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false }
diff --git a/app/views/shared/dossiers/_champs.html.haml b/app/views/shared/dossiers/_champs.html.haml
index eee34dde3..cf69342c6 100644
--- a/app/views/shared/dossiers/_champs.html.haml
+++ b/app/views/shared/dossiers/_champs.html.haml
@@ -56,6 +56,13 @@
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
- if c.etablissement.present?
= render partial: "shared/dossiers/identite_entreprise", locals: { etablissement: c.etablissement, profile: profile }
+ - when TypeDeChamp.type_champs.fetch(:carte)
+ %th.libelle
+ = "#{c.libelle} :"
+ %td.rich-text
+ %span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
+ - if c.value.present?
+ = render partial: "shared/champs/carte/show", locals: { champ: c }
- else
%th.libelle
= "#{c.libelle} :"
diff --git a/app/views/shared/dossiers/_map.html.haml b/app/views/shared/dossiers/_map.html.haml
index e110b0268..a972cc400 100644
--- a/app/views/shared/dossiers/_map.html.haml
+++ b/app/views/shared/dossiers/_map.html.haml
@@ -16,4 +16,4 @@
%li
= "Parcelle n° #{p.numero} - Feuille #{p.code_arr} #{p.section} #{p.feuille}"
- = render partial: 'shared/champs/carto/init', locals: { dossier: dossier }
+ = render partial: 'shared/champs/carte/init', locals: { dossier: dossier }
diff --git a/app/views/shared/dossiers/editable_champs/_carte.html.haml b/app/views/shared/dossiers/editable_champs/_carte.html.haml
new file mode 100644
index 000000000..eb218fa97
--- /dev/null
+++ b/app/views/shared/dossiers/editable_champs/_carte.html.haml
@@ -0,0 +1,11 @@
+.toolbar
+ %button.button.primary.new-area Ajouter une zone
+ %input.address{ data: { address: true, autocomplete: 'address' }, placeholder: 'Saissisez une adresse ou positionner la carte' }
+
+.carte.edit{ data: { geo: geo_data(champ) }, class: "carte-#{form.index}" }
+
+.geo-areas
+ = render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false }
+
+= form.hidden_field :value,
+ data: { remote: true, url: champs_carte_path(form.index), params: { champ_id: champ&.id }.to_query, method: 'post' }
diff --git a/app/views/users/carte/_map.html.haml b/app/views/users/carte/_map.html.haml
index 7fc00020e..b4e8b93e1 100644
--- a/app/views/users/carte/_map.html.haml
+++ b/app/views/users/carte/_map.html.haml
@@ -1,8 +1,8 @@
#carte-page.row
.col-md-12.col-lg-12
- #map.edit
+ #map.carte.edit
%span.zones
= render partial: 'zones', locals: { dossier: dossier, error: @error }
-= render partial: 'shared/champs/carto/init', locals: { dossier: dossier }
+= render partial: 'shared/champs/carte/init', locals: { dossier: dossier }
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index cb8344d0e..50bbaa0bc 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -1,5 +1,24 @@
{
"ignored_warnings": [
+ {
+ "warning_type": "Cross-Site Scripting",
+ "warning_code": 2,
+ "fingerprint": "0d61a1267d264f1e61cc2398a2683703ac60878129dc9515542f246a80ad575b",
+ "check_name": "CrossSiteScripting",
+ "message": "Unescaped model attribute",
+ "file": "app/views/champs/carto/show.js.erb",
+ "line": 5,
+ "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
+ "code": "geo_data((Champ.joins(:dossier).where(:dossiers => ({ :user_id => logged_user_ids })).find_by(:id => params.permit(:champ_id)) or CartoChamp.new))",
+ "render_path": [{"type":"controller","class":"Champs::CartoController","method":"show","line":48,"file":"app/controllers/champs/carto_controller.rb"}],
+ "location": {
+ "type": "template",
+ "template": "champs/carto/show"
+ },
+ "user_input": "Champ.joins(:dossier).where(:dossiers => ({ :user_id => logged_user_ids }))",
+ "confidence": "Weak",
+ "note": "Not an injection because logged_user_ids have no user input"
+ },
{
"warning_type": "SQL Injection",
"warning_code": 0,
@@ -61,6 +80,6 @@
"note": "Not an injection because of `sanitized_column`"
}
],
- "updated": "2018-10-11 12:09:03 +0200",
+ "updated": "2018-10-16 11:28:34 +0300",
"brakeman_version": "4.3.1"
}
diff --git a/config/initializers/urls.rb b/config/initializers/urls.rb
index c5d3c2703..c1ca9b978 100644
--- a/config/initializers/urls.rb
+++ b/config/initializers/urls.rb
@@ -11,6 +11,9 @@ FOG_BASE_URL = "https://storage.apientreprise.fr"
# External services URLs
DOC_URL = "https://doc.demarches-simplifiees.fr"
+ADMINISTRATEUR_TUTORIAL_URL = "https://doc.demarches-simplifiees.fr/tutoriels/tutoriel-administrateur"
+INSTRUCTEUR_TUTORIAL_URL = "https://doc.demarches-simplifiees.fr/tutoriels/tutoriel-accompagnateur"
+WEBINAIRE_URL = "https://doc.demarches-simplifiees.fr/pour-aller-plus-loin/webinaires"
LISTE_DES_DEMARCHES_URL = [DOC_URL, "listes-des-demarches"].join("/")
CGU_URL = [DOC_URL, "cgu"].join("/")
MENTIONS_LEGALES_URL = [CGU_URL, "4-mentions-legales"].join("#")
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 1079a5629..fbd48b6db 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -177,6 +177,12 @@ fr:
connexion: "Erreur lors de la connexion à France Connect."
extension_white_list_error: "Le format de fichier de la pièce jointe n'est pas valide."
procedure_archived: "Cette démarche en ligne a été fermée, il n'est plus possible de déposer de dossier."
+ cadastres_empty:
+ one: "Aucune parcelle cadastrale sur la zone séléctionnée"
+ other: "Aucune parcelle cadastrale sur les zones séléctionnées"
+ quartiers_prioritaires_empty:
+ one: "Aucun quartier prioritaire sur la zone séléctionnée"
+ other: "Aucun quartier prioritaire sur les zones séléctionnées"
date:
abbr_day_names:
diff --git a/config/locales/models/type_de_champ/fr.yml b/config/locales/models/type_de_champ/fr.yml
index 655429494..2aed2a270 100644
--- a/config/locales/models/type_de_champ/fr.yml
+++ b/config/locales/models/type_de_champ/fr.yml
@@ -28,3 +28,4 @@ fr:
dossier_link: 'Lien vers un autre dossier'
piece_justificative: 'Pièce justificative'
siret: 'SIRET'
+ carte: 'Carte'
diff --git a/config/routes.rb b/config/routes.rb
index 8e7faec7f..8479addd8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -125,6 +125,7 @@ Rails.application.routes.draw do
namespace :champs do
get ':position/siret', to: 'siret#show', as: :siret
get ':position/dossier_link', to: 'dossier_link#show', as: :dossier_link
+ post ':position/carte', to: 'carte#show', as: :carte
end
namespace :commencer do
diff --git a/spec/controllers/admin/procedures_controller_spec.rb b/spec/controllers/admin/procedures_controller_spec.rb
index b0215e956..18585d239 100644
--- a/spec/controllers/admin/procedures_controller_spec.rb
+++ b/spec/controllers/admin/procedures_controller_spec.rb
@@ -11,7 +11,6 @@ describe Admin::ProceduresController, type: :controller do
let(:description) { 'Description de test' }
let(:organisation) { 'Organisation de test' }
let(:direction) { 'Direction de test' }
- let(:lien_demarche) { 'http://localhost.com' }
let(:cadre_juridique) { 'cadre juridique' }
let(:use_api_carto) { '0' }
let(:quartiers_prioritaires) { '0' }
@@ -26,7 +25,6 @@ describe Admin::ProceduresController, type: :controller do
description: description,
organisation: organisation,
direction: direction,
- lien_demarche: lien_demarche,
cadre_juridique: cadre_juridique,
duree_conservation_dossiers_dans_ds: duree_conservation_dossiers_dans_ds,
duree_conservation_dossiers_hors_ds: duree_conservation_dossiers_hors_ds,
@@ -198,7 +196,6 @@ describe Admin::ProceduresController, type: :controller do
it { expect(subject.description).to eq(description) }
it { expect(subject.organisation).to eq(organisation) }
it { expect(subject.direction).to eq(direction) }
- it { expect(subject.lien_demarche).to eq(lien_demarche) }
it { expect(subject.administrateur_id).to eq(admin.id) }
it { expect(subject.duree_conservation_dossiers_dans_ds).to eq(duree_conservation_dossiers_dans_ds) }
it { expect(subject.duree_conservation_dossiers_hors_ds).to eq(duree_conservation_dossiers_hors_ds) }
@@ -270,7 +267,6 @@ describe Admin::ProceduresController, type: :controller do
let(:description) { 'blabla' }
let(:organisation) { 'plop' }
let(:direction) { 'plap' }
- let(:lien_demarche) { 'http://plip.com' }
let(:use_api_carto) { '1' }
let(:cadastre) { '1' }
let(:duree_conservation_dossiers_dans_ds) { 7 }
@@ -285,7 +281,6 @@ describe Admin::ProceduresController, type: :controller do
it { expect(subject.description).to eq(description) }
it { expect(subject.organisation).to eq(organisation) }
it { expect(subject.direction).to eq(direction) }
- it { expect(subject.lien_demarche).to eq(lien_demarche) }
it { expect(subject.duree_conservation_dossiers_dans_ds).to eq(duree_conservation_dossiers_dans_ds) }
it { expect(subject.duree_conservation_dossiers_hors_ds).to eq(duree_conservation_dossiers_hors_ds) }
end
@@ -349,7 +344,6 @@ describe Admin::ProceduresController, type: :controller do
it { expect(subject.organisation).to eq procedure_params[:organisation] }
it { expect(subject.direction).to eq procedure_params[:direction] }
- it { expect(subject.lien_demarche).not_to eq procedure_params[:lien_demarche] }
it { expect(subject.for_individual).not_to eq procedure_params[:for_individual] }
it { expect(subject.individual_with_siret).not_to eq procedure_params[:individual_with_siret] }
it { expect(subject.use_api_carto).not_to eq procedure_params[:module_api_carto_attributes][:use_api_carto] }
diff --git a/spec/controllers/api/v1/procedures_controller_spec.rb b/spec/controllers/api/v1/procedures_controller_spec.rb
index 6eee811ab..24eba07ea 100644
--- a/spec/controllers/api/v1/procedures_controller_spec.rb
+++ b/spec/controllers/api/v1/procedures_controller_spec.rb
@@ -36,7 +36,6 @@ describe API::V1::ProceduresController, type: :controller do
it { expect(subject[:description]).to eq(procedure.description) }
it { expect(subject[:organisation]).to eq(procedure.organisation) }
it { expect(subject[:direction]).to eq(procedure.direction) }
- it { expect(subject[:link]).to eq(procedure.lien_demarche) }
it { expect(subject[:archived_at]).to eq(procedure.archived_at) }
it { expect(subject[:total_dossier]).to eq(procedure.total_dossier) }
it { is_expected.to have_key(:types_de_champ) }
diff --git a/spec/controllers/champs/carte_controller_spec.rb b/spec/controllers/champs/carte_controller_spec.rb
new file mode 100644
index 000000000..fadf1fa1a
--- /dev/null
+++ b/spec/controllers/champs/carte_controller_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Champs::CarteController, type: :controller do
+ let(:user) { create(:user) }
+ let(:procedure) { create(:procedure, :published) }
+ let(:dossier) { create(:dossier, user: user, procedure: procedure) }
+ let(:params) do
+ {
+ dossier: {
+ champs_attributes: {
+ '1' => { value: selection.to_json }
+ }
+ },
+ position: '1',
+ champ_id: champ.id
+ }
+ end
+ let(:champ) do
+ create(:type_de_champ_carte, options: {
+ quartiers_prioritaires: true
+ }).champ.create(dossier: dossier)
+ end
+
+ describe 'POST #show' do
+ render_views
+
+ before { sign_in user }
+ before do
+ allow_any_instance_of(ApiCarto::QuartiersPrioritairesAdapter)
+ .to receive(:results)
+ .and_return([{ code: "QPCODE1234", geometry: { type: "MultiPolygon", coordinates: [[[[2.38715792094576, 48.8723062632126], [2.38724851642619, 48.8721392348061]]]] } }])
+
+ post :show, params: params, format: 'js'
+ end
+
+ context 'when coordinates are empty' do
+ let(:selection) { [] }
+
+ it { expect(response.body).to include("DS.drawMapData(\".carte-1\", {\"position\":{\"lon\":\"2.428462\",\"lat\":\"46.538192\",\"zoom\":\"13\"},\"selection\":[],\"quartiersPrioritaires\":[],\"cadastres\":[]});") }
+ end
+
+ context 'when coordinates are informed' do
+ let(:selection) { [[{ "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 }]] }
+
+ it { expect(response.body).not_to be_nil }
+ it { expect(response.body).to include('QPCODE1234') }
+ it { expect(response.body).to include('MultiPolygon') }
+ it { expect(response.body).to include('[2.38715792094576,48.8723062632126]') }
+ end
+ end
+end
diff --git a/spec/decorators/procedure_decorator_spec.rb b/spec/decorators/procedure_decorator_spec.rb
index c6bef231d..8fba48bba 100644
--- a/spec/decorators/procedure_decorator_spec.rb
+++ b/spec/decorators/procedure_decorator_spec.rb
@@ -26,11 +26,4 @@ describe ProcedureDecorator do
subject { super().logo_img }
it { is_expected.to match(/http.*#{ActionController::Base.helpers.image_url("marianne.svg")}/) }
end
-
- describe 'geographic_information' do
- subject { super().geographic_information }
- it { expect(subject.use_api_carto).to be_falsey }
- it { expect(subject.quartiers_prioritaires).to be_falsey }
- it { expect(subject.cadastre).to be_falsey }
- end
end
diff --git a/spec/factories/geo_area.rb b/spec/factories/geo_area.rb
new file mode 100644
index 000000000..3050dc075
--- /dev/null
+++ b/spec/factories/geo_area.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :geo_area do
+ source { GeoArea.sources.fetch(:cadastre) }
+ numero { '42' }
+ feuille { 'A11' }
+ end
+end
diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb
index 2f548163d..a99134ccf 100644
--- a/spec/factories/procedure.rb
+++ b/spec/factories/procedure.rb
@@ -1,7 +1,6 @@
FactoryBot.define do
sequence(:published_path) { |n| "fake_path#{n}" }
factory :procedure do
- lien_demarche { 'http://localhost' }
sequence(:libelle) { |n| "Procedure #{n}" }
description { "Demande de subvention à l'intention des associations" }
organisation { "Orga DINSIC" }
diff --git a/spec/models/champ_shared_example.rb b/spec/models/champ_shared_example.rb
index 91eeb2438..31ae546fa 100644
--- a/spec/models/champ_shared_example.rb
+++ b/spec/models/champ_shared_example.rb
@@ -9,6 +9,12 @@ shared_examples 'champ_spec' do
it { expect(champ.mandatory_and_blank?).to be(true) }
end
+ context 'when carte mandatory and blank' do
+ let(:type_de_champ) { build(:type_de_champ_carte, mandatory: mandatory) }
+ let(:value) { '[]' }
+ it { expect(champ.mandatory_and_blank?).to be(true) }
+ end
+
context 'when not blank' do
let(:value) { 'yop' }
it { expect(champ.mandatory_and_blank?).to be(false) }
diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb
index cffd74f0d..a6b34fe02 100644
--- a/spec/models/procedure_spec.rb
+++ b/spec/models/procedure_spec.rb
@@ -164,12 +164,6 @@ describe Procedure do
it { is_expected.to allow_value('Description Demande de subvention').for(:description) }
end
- context 'lien_demarche' do
- it { is_expected.to allow_value(nil).for(:lien_demarche) }
- it { is_expected.to allow_value('').for(:lien_demarche) }
- it { is_expected.to allow_value('http://localhost').for(:lien_demarche) }
- end
-
context 'organisation' do
it { is_expected.to allow_value('URRSAF').for(:organisation) }
end
diff --git a/spec/serializers/champs/carte_champ_serializer_spec.rb b/spec/serializers/champs/carte_champ_serializer_spec.rb
new file mode 100644
index 000000000..66419ac57
--- /dev/null
+++ b/spec/serializers/champs/carte_champ_serializer_spec.rb
@@ -0,0 +1,18 @@
+describe Champs::CarteChampSerializer do
+ describe '#attributes' do
+ subject { Champs::CarteChampSerializer.new(champ).serializable_hash }
+
+ context 'when type champ is carte' do
+ let(:geo_area) { create(:geo_area) }
+ let(:champ) { create(:type_de_champ_carte).champ.create(geo_areas: [geo_area]) }
+
+ it {
+ expect(subject[:geo_areas].first).to include(
+ source: GeoArea.sources.fetch(:cadastre),
+ numero: '42',
+ feuille: 'A11'
+ )
+ }
+ end
+ end
+end
diff --git a/spec/serializers/champs/siret_champ_serializer_spec.rb b/spec/serializers/champs/siret_champ_serializer_spec.rb
new file mode 100644
index 000000000..1614dc7bd
--- /dev/null
+++ b/spec/serializers/champs/siret_champ_serializer_spec.rb
@@ -0,0 +1,16 @@
+describe Champs::SiretChampSerializer do
+ describe '#attributes' do
+ subject { Champs::SiretChampSerializer.new(champ).serializable_hash }
+
+ context 'when type champ is siret' do
+ let(:etablissement) { create(:etablissement) }
+ let(:champ) { create(:type_de_champ_siret).champ.create(etablissement: etablissement, value: etablissement.siret) }
+
+ it {
+ is_expected.to include(value: etablissement.siret)
+ expect(subject[:etablissement]).to include(siret: etablissement.siret)
+ expect(subject[:entreprise]).to include(capital_social: etablissement.entreprise_capital_social)
+ }
+ end
+ end
+end
diff --git a/spec/serializers/procedure_serializer_spec.rb b/spec/serializers/procedure_serializer_spec.rb
new file mode 100644
index 000000000..487866665
--- /dev/null
+++ b/spec/serializers/procedure_serializer_spec.rb
@@ -0,0 +1,11 @@
+describe ProcedureSerializer do
+ describe '#attributes' do
+ subject { ProcedureSerializer.new(procedure).serializable_hash }
+ let(:procedure) { create(:procedure, :published) }
+
+ it {
+ is_expected.to include(link: "http://localhost:3000/commencer/#{procedure.path}")
+ is_expected.to include(state: "publiee")
+ }
+ end
+end
diff --git a/spec/views/layouts/_new_header_spec.rb b/spec/views/layouts/_new_header_spec.rb
index 5e7b5ee9e..9c81e7118 100644
--- a/spec/views/layouts/_new_header_spec.rb
+++ b/spec/views/layouts/_new_header_spec.rb
@@ -27,7 +27,7 @@ describe 'layouts/_new_header.html.haml', type: :view do
it "displays the contact infos" do
expect(rendered).to have_text("Contact")
- expect(rendered).to have_link(CONTACT_EMAIL, href: contact_url)
+ expect(rendered).to have_link("par email", href: contact_url)
end
end
end