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