commit
402622cb02
26 changed files with 493 additions and 127 deletions
|
@ -13,6 +13,10 @@ select {
|
|||
line-height: 1.42857143;
|
||||
}
|
||||
|
||||
dt {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
|
|
|
@ -97,12 +97,12 @@ module Instructeurs
|
|||
end
|
||||
|
||||
def archive
|
||||
dossier.update(archived: true)
|
||||
dossier.archiver!(current_instructeur)
|
||||
redirect_back(fallback_location: instructeur_procedures_url)
|
||||
end
|
||||
|
||||
def unarchive
|
||||
dossier.update(archived: false)
|
||||
dossier.desarchiver!(current_instructeur)
|
||||
redirect_back(fallback_location: instructeur_procedures_url)
|
||||
end
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ module NewAdministrateur
|
|||
end
|
||||
|
||||
def update
|
||||
type_de_champ = @procedure.draft_revision.find_or_clone_type_de_champ(TypeDeChamp.to_stable_id(params[:id]))
|
||||
type_de_champ = @procedure.draft_revision.find_or_clone_type_de_champ(params[:id])
|
||||
|
||||
if type_de_champ.update(type_de_champ_update_params)
|
||||
reset_procedure
|
||||
|
@ -26,13 +26,13 @@ module NewAdministrateur
|
|||
end
|
||||
|
||||
def move
|
||||
@procedure.draft_revision.move_type_de_champ(TypeDeChamp.to_stable_id(params[:id]), (params[:position] || params[:order_place]).to_i)
|
||||
@procedure.draft_revision.move_type_de_champ(params[:id], (params[:position] || params[:order_place]).to_i)
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def destroy
|
||||
@procedure.draft_revision.remove_type_de_champ(TypeDeChamp.to_stable_id(params[:id]))
|
||||
@procedure.draft_revision.remove_type_de_champ(params[:id])
|
||||
reset_procedure
|
||||
|
||||
head :no_content
|
||||
|
@ -67,7 +67,7 @@ module NewAdministrateur
|
|||
end
|
||||
|
||||
def type_de_champ_create_params
|
||||
type_de_champ_params = params.required(:type_de_champ).permit(:type_champ,
|
||||
params.required(:type_de_champ).permit(:type_champ,
|
||||
:libelle,
|
||||
:description,
|
||||
:mandatory,
|
||||
|
@ -77,12 +77,6 @@ module NewAdministrateur
|
|||
:piece_justificative_template,
|
||||
:cadastres,
|
||||
:mnhn)
|
||||
|
||||
if type_de_champ_params[:parent_id].present?
|
||||
type_de_champ_params[:parent_id] = TypeDeChamp.to_stable_id(type_de_champ_params[:parent_id])
|
||||
end
|
||||
|
||||
type_de_champ_params
|
||||
end
|
||||
|
||||
def type_de_champ_update_params
|
||||
|
|
25
app/graphql/mutations/dossier_archiver.rb
Normal file
25
app/graphql/mutations/dossier_archiver.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
module Mutations
|
||||
class DossierArchiver < Mutations::BaseMutation
|
||||
description "Archiver le dossier."
|
||||
|
||||
argument :dossier_id, ID, "Dossier ID", required: true, loads: Types::DossierType
|
||||
argument :instructeur_id, ID, "Instructeur qui prend la décision sur le dossier.", required: true, loads: Types::ProfileType
|
||||
|
||||
field :dossier, Types::DossierType, null: true
|
||||
field :errors, [Types::ValidationErrorType], null: true
|
||||
|
||||
def resolve(dossier:, instructeur:)
|
||||
if dossier.termine?
|
||||
dossier.archiver!(instructeur)
|
||||
|
||||
{ dossier: dossier }
|
||||
else
|
||||
{ errors: ["Un dossier ne peut être archivé qu’une fois le traitement terminé"] }
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?(dossier:, instructeur:)
|
||||
instructeur.is_a?(Instructeur) && instructeur.dossiers.exists?(id: dossier.id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -457,6 +457,38 @@ type DossierAccepterPayload {
|
|||
errors: [ValidationError!]
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DossierArchiver
|
||||
"""
|
||||
input DossierArchiverInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Dossier ID
|
||||
"""
|
||||
dossierId: ID!
|
||||
|
||||
"""
|
||||
Instructeur qui prend la décision sur le dossier.
|
||||
"""
|
||||
instructeurId: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of DossierArchiver
|
||||
"""
|
||||
type DossierArchiverPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
dossier: Dossier
|
||||
errors: [ValidationError!]
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DossierChangerGroupeInstructeur
|
||||
"""
|
||||
|
@ -713,8 +745,12 @@ type Effectif {
|
|||
type Entreprise {
|
||||
attestationFiscaleAttachment: File
|
||||
attestationSocialeAttachment: File
|
||||
|
||||
"""
|
||||
capital social de l’entreprise. -1 si inconnu.
|
||||
"""
|
||||
capitalSocial: BigInt!
|
||||
codeEffectifEntreprise: String!
|
||||
codeEffectifEntreprise: String
|
||||
dateCreation: ISO8601Date!
|
||||
|
||||
"""
|
||||
|
@ -921,6 +957,11 @@ type Mutation {
|
|||
"""
|
||||
dossierAccepter(input: DossierAccepterInput!): DossierAccepterPayload
|
||||
|
||||
"""
|
||||
Archiver le dossier.
|
||||
"""
|
||||
dossierArchiver(input: DossierArchiverInput!): DossierArchiverPayload
|
||||
|
||||
"""
|
||||
Changer le grope instructeur du dossier.
|
||||
"""
|
||||
|
@ -1011,10 +1052,10 @@ type PersonneMorale implements Demandeur {
|
|||
localite: String!
|
||||
naf: String!
|
||||
nomVoie: String!
|
||||
numeroVoie: String!
|
||||
numeroVoie: String
|
||||
siegeSocial: Boolean!
|
||||
siret: String!
|
||||
typeVoie: String!
|
||||
typeVoie: String
|
||||
}
|
||||
|
||||
type PersonnePhysique implements Demandeur {
|
||||
|
|
|
@ -7,6 +7,7 @@ module Types
|
|||
field :dossier_classer_sans_suite, mutation: Mutations::DossierClasserSansSuite
|
||||
field :dossier_refuser, mutation: Mutations::DossierRefuser
|
||||
field :dossier_accepter, mutation: Mutations::DossierAccepter
|
||||
field :dossier_archiver, mutation: Mutations::DossierArchiver
|
||||
field :dossier_changer_groupe_instructeur, mutation: Mutations::DossierChangerGroupeInstructeur
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,14 +7,14 @@ module Types
|
|||
end
|
||||
|
||||
field :siren, String, null: false
|
||||
field :capital_social, GraphQL::Types::BigInt, null: false
|
||||
field :capital_social, GraphQL::Types::BigInt, null: false, description: "capital social de l’entreprise. -1 si inconnu."
|
||||
field :numero_tva_intracommunautaire, String, null: false
|
||||
field :forme_juridique, String, null: false
|
||||
field :forme_juridique_code, String, null: false
|
||||
field :nom_commercial, String, null: false
|
||||
field :raison_sociale, String, null: false
|
||||
field :siret_siege_social, String, null: false
|
||||
field :code_effectif_entreprise, String, null: false
|
||||
field :code_effectif_entreprise, String, null: true
|
||||
field :effectif_mensuel, EffectifType, null: true, description: "effectif pour un mois donné"
|
||||
field :effectif_annuel, EffectifType, null: true, description: "effectif moyen d'une année"
|
||||
field :date_creation, GraphQL::Types::ISO8601Date, null: false
|
||||
|
@ -41,6 +41,17 @@ module Types
|
|||
end
|
||||
end
|
||||
|
||||
def capital_social
|
||||
# capital_social is defined as a BigInt, so we can't return an empty string when value is unknown
|
||||
# 0 could appear to be a legitimate value, so a negative value helps to ensure the value is not known
|
||||
object.capital_social || '-1'
|
||||
end
|
||||
|
||||
def code_effectif_entreprise
|
||||
# we need this in order to bypass Hashie::Dash deserialization issue on nil values
|
||||
object.code_effectif_entreprise
|
||||
end
|
||||
|
||||
def effectif_annuel
|
||||
if object.effectif_annuel.present?
|
||||
{
|
||||
|
@ -76,8 +87,8 @@ module Types
|
|||
field :naf, String, null: false
|
||||
field :libelle_naf, String, null: false
|
||||
field :adresse, String, null: false
|
||||
field :numero_voie, String, null: false
|
||||
field :type_voie, String, null: false
|
||||
field :numero_voie, String, null: true
|
||||
field :type_voie, String, null: true
|
||||
field :nom_voie, String, null: false
|
||||
field :complement_adresse, String, null: false
|
||||
field :code_postal, String, null: false
|
||||
|
|
|
@ -45,12 +45,20 @@ module ChampHelper
|
|||
"Culture : #{geo_area.culture} - Surface : #{geo_area.surface} ha"
|
||||
when GeoArea.sources.fetch(:selection_utilisateur)
|
||||
if geo_area.polygon?
|
||||
capture do
|
||||
concat "Une aire de surface #{geo_area.area} m"
|
||||
concat content_tag(:sup, "2")
|
||||
if geo_area.area.present?
|
||||
capture do
|
||||
concat "Une aire de surface #{geo_area.area} m"
|
||||
concat content_tag(:sup, "2")
|
||||
end
|
||||
else
|
||||
"Une aire de surface inconnue"
|
||||
end
|
||||
elsif geo_area.line?
|
||||
"Une ligne longue de #{geo_area.length} m"
|
||||
if geo_area.length.present?
|
||||
"Une ligne longue de #{geo_area.length} m"
|
||||
else
|
||||
"Une ligne de longueur inconnue"
|
||||
end
|
||||
elsif geo_area.point?
|
||||
"Un point situé à #{geo_area.location}"
|
||||
end
|
||||
|
|
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||
import mapboxgl from 'mapbox-gl';
|
||||
import ReactMapboxGl, { GeoJSONLayer, ZoomControl } from 'react-mapbox-gl';
|
||||
import DrawControl from 'react-mapbox-gl-draw';
|
||||
import { gpx, kml } from '@tmcw/togeojson/dist/togeojson.es.js';
|
||||
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
|
||||
|
||||
import { getJSON, ajax, fire } from '@utils';
|
||||
|
@ -11,7 +10,11 @@ import { getJSON, ajax, fire } from '@utils';
|
|||
import { getMapStyle, SwitchMapStyle } from '../MapStyles';
|
||||
|
||||
import SearchInput from './SearchInput';
|
||||
import { polygonCadastresFill, polygonCadastresLine } from './utils';
|
||||
import {
|
||||
polygonCadastresFill,
|
||||
polygonCadastresLine,
|
||||
readGeoFile
|
||||
} from './utils';
|
||||
import {
|
||||
noop,
|
||||
filterFeatureCollection,
|
||||
|
@ -149,19 +152,7 @@ function MapEditor({ featureCollection, url, preview, options }) {
|
|||
}
|
||||
|
||||
const onFileImport = (e, inputId) => {
|
||||
const isGpxFile = e.target.files[0].name.includes('.gpx');
|
||||
let reader = new FileReader();
|
||||
reader.readAsText(e.target.files[0], 'UTF-8');
|
||||
reader.onload = async (event) => {
|
||||
let featureCollection;
|
||||
isGpxFile
|
||||
? (featureCollection = gpx(
|
||||
new DOMParser().parseFromString(event.target.result, 'text/xml')
|
||||
))
|
||||
: (featureCollection = kml(
|
||||
new DOMParser().parseFromString(event.target.result, 'text/xml')
|
||||
));
|
||||
|
||||
readGeoFile(e.target.files[0]).then(async (featureCollection) => {
|
||||
const resultFeatureCollection = await getJSON(
|
||||
`${url}/import`,
|
||||
featureCollection,
|
||||
|
@ -196,7 +187,7 @@ function MapEditor({ featureCollection, url, preview, options }) {
|
|||
updateFeaturesList(resultFeatureCollection.features);
|
||||
setImportInputs(setInputs);
|
||||
setBbox(resultFeatureCollection.bbox);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const addInputFile = (e) => {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { gpx, kml } from '@tmcw/togeojson/dist/togeojson.es.js';
|
||||
|
||||
export const polygonCadastresFill = {
|
||||
'fill-color': '#EC3323',
|
||||
'fill-opacity': 0.3
|
||||
|
@ -8,3 +10,81 @@ export const polygonCadastresLine = {
|
|||
'line-width': 4,
|
||||
'line-dasharray': [1, 1]
|
||||
};
|
||||
|
||||
export function normalizeFeatureCollection(featureCollection) {
|
||||
const features = [];
|
||||
for (const feature of featureCollection.features) {
|
||||
switch (feature.geometry.type) {
|
||||
case 'MultiPoint':
|
||||
for (const coordinates of feature.geometry.coordinates) {
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates
|
||||
},
|
||||
properties: feature.properties
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'MultiLineString':
|
||||
for (const coordinates of feature.geometry.coordinates) {
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates
|
||||
},
|
||||
properties: feature.properties
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'MultiPolygon':
|
||||
for (const coordinates of feature.geometry.coordinates) {
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates
|
||||
},
|
||||
properties: feature.properties
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'GeometryCollection':
|
||||
for (const geometry of feature.geometry.geometries) {
|
||||
features.push({
|
||||
type: 'Feature',
|
||||
geometry,
|
||||
properties: feature.properties
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
features.push(feature);
|
||||
}
|
||||
}
|
||||
|
||||
featureCollection.features = features;
|
||||
return featureCollection;
|
||||
}
|
||||
|
||||
export function readGeoFile(file) {
|
||||
const isGpxFile = file.name.includes('.gpx');
|
||||
const reader = new FileReader();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
reader.onload = (event) => {
|
||||
const xml = new DOMParser().parseFromString(
|
||||
event.target.result,
|
||||
'text/xml'
|
||||
);
|
||||
const featureCollection = normalizeFeatureCollection(
|
||||
isGpxFile ? gpx(xml) : kml(xml)
|
||||
);
|
||||
|
||||
resolve(featureCollection);
|
||||
};
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,16 +1,121 @@
|
|||
import { show, hide, delegate } from '@utils';
|
||||
//
|
||||
// This content is inspired by w3c aria example
|
||||
// https://www.w3.org/TR/wai-aria-practices-1.1/examples/disclosure/disclosure-faq.html
|
||||
//
|
||||
|
||||
function updateContactElementsVisibility() {
|
||||
const contactSelect = document.querySelector('#contact-form #type');
|
||||
if (contactSelect) {
|
||||
const type = contactSelect.value;
|
||||
const visibleElements = `[data-contact-type-only="${type}"]`;
|
||||
const hiddenElements = `[data-contact-type-only]:not([data-contact-type-only="${type}"])`;
|
||||
class ButtonExpand {
|
||||
constructor(domNode) {
|
||||
this.domNode = domNode;
|
||||
|
||||
document.querySelectorAll(visibleElements).forEach(show);
|
||||
document.querySelectorAll(hiddenElements).forEach(hide);
|
||||
this.keyCode = Object.freeze({
|
||||
RETURN: 13
|
||||
});
|
||||
|
||||
this.allButtons = [];
|
||||
this.controlledNode = false;
|
||||
|
||||
var id = this.domNode.getAttribute('aria-controls');
|
||||
|
||||
if (id) {
|
||||
this.controlledNode = document.getElementById(id);
|
||||
}
|
||||
|
||||
this.domNode.setAttribute('aria-expanded', 'false');
|
||||
this.hideContent();
|
||||
|
||||
this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
|
||||
this.domNode.addEventListener('click', this.handleClick.bind(this));
|
||||
this.domNode.addEventListener('focus', this.handleFocus.bind(this));
|
||||
this.domNode.addEventListener('blur', this.handleBlur.bind(this));
|
||||
}
|
||||
|
||||
showContent() {
|
||||
this.domNode.setAttribute('aria-expanded', 'true');
|
||||
this.domNode.classList.add('primary');
|
||||
if (this.controlledNode) {
|
||||
this.controlledNode.classList.remove('hidden');
|
||||
}
|
||||
this.formInput.value = this.domNode.getAttribute('data-question-type');
|
||||
|
||||
this.allButtons.forEach((b) => {
|
||||
if (b != this) {
|
||||
b.hideContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hideContent() {
|
||||
this.domNode.setAttribute('aria-expanded', 'false');
|
||||
this.domNode.classList.remove('primary');
|
||||
if (this.controlledNode) {
|
||||
this.controlledNode.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
toggleExpand() {
|
||||
if (this.domNode.getAttribute('aria-expanded') === 'true') {
|
||||
this.hideContent();
|
||||
} else {
|
||||
this.showContent();
|
||||
}
|
||||
}
|
||||
|
||||
setAllButtons(buttons) {
|
||||
this.allButtons = buttons;
|
||||
}
|
||||
|
||||
setFormInput(formInput) {
|
||||
this.formInput = formInput;
|
||||
}
|
||||
|
||||
handleKeydown() {
|
||||
switch (event.keyCode) {
|
||||
case this.keyCode.RETURN:
|
||||
this.toggleExpand();
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.toggleExpand();
|
||||
}
|
||||
|
||||
handleFocus = function () {
|
||||
this.domNode.classList.add('focus');
|
||||
};
|
||||
|
||||
handleBlur() {
|
||||
this.domNode.classList.remove('focus');
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('ds:page:update', updateContactElementsVisibility);
|
||||
delegate('change', '#contact-form #type', updateContactElementsVisibility);
|
||||
/* Initialize Hide/Show Buttons */
|
||||
|
||||
if (document.querySelector('#contact-form')) {
|
||||
window.addEventListener(
|
||||
'ds:page:update',
|
||||
function () {
|
||||
var buttons = document.querySelectorAll(
|
||||
'button[aria-expanded][aria-controls], button.button-without-hint'
|
||||
);
|
||||
var expandButtons = [];
|
||||
var formInput = document.querySelector('form input#type');
|
||||
|
||||
buttons.forEach((button) => {
|
||||
var be = new ButtonExpand(button);
|
||||
expandButtons.push(be);
|
||||
});
|
||||
expandButtons.forEach((button) => button.setAllButtons(expandButtons));
|
||||
expandButtons.forEach((button) => button.setFormInput(formInput));
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ class Helpscout::FormAdapter
|
|||
|
||||
def self.options
|
||||
[
|
||||
[I18n.t(TYPE_INFO, scope: [:support, :question]), TYPE_INFO],
|
||||
[I18n.t(TYPE_PERDU, scope: [:support, :question]), TYPE_PERDU],
|
||||
[I18n.t(TYPE_INSTRUCTION, scope: [:support, :question]), TYPE_INSTRUCTION],
|
||||
[I18n.t(TYPE_AMELIORATION, scope: [:support, :question]), TYPE_AMELIORATION],
|
||||
[I18n.t(TYPE_INFO, scope: [:support, :question]), TYPE_INFO, FAQ_CONTACTER_SERVICE_EN_CHARGE_URL],
|
||||
[I18n.t(TYPE_PERDU, scope: [:support, :question]), TYPE_PERDU, LISTE_DES_DEMARCHES_URL],
|
||||
[I18n.t(TYPE_INSTRUCTION, scope: [:support, :question]), TYPE_INSTRUCTION, FAQ_OU_EN_EST_MON_DOSSIER_URL],
|
||||
[I18n.t(TYPE_AMELIORATION, scope: [:support, :question]), TYPE_AMELIORATION, FEATURE_UPVOTE_URL],
|
||||
[I18n.t(TYPE_AUTRE, scope: [:support, :question]), TYPE_AUTRE]
|
||||
]
|
||||
end
|
||||
|
|
|
@ -416,6 +416,16 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def archiver!(author)
|
||||
update!(archived: true)
|
||||
log_dossier_operation(author, :archiver)
|
||||
end
|
||||
|
||||
def desarchiver!(author)
|
||||
update!(archived: false)
|
||||
log_dossier_operation(author, :desarchiver)
|
||||
end
|
||||
|
||||
def text_summary
|
||||
if brouillon?
|
||||
parts = [
|
||||
|
@ -812,7 +822,7 @@ class Dossier < ApplicationRecord
|
|||
end
|
||||
|
||||
def send_web_hook
|
||||
if saved_change_to_state? && !brouillon? && procedure.web_hook_url
|
||||
if saved_change_to_state? && !brouillon? && procedure.web_hook_url.present?
|
||||
WebHookJob.perform_later(
|
||||
procedure,
|
||||
self
|
||||
|
|
|
@ -26,7 +26,9 @@ class DossierOperationLog < ApplicationRecord
|
|||
supprimer: 'supprimer',
|
||||
restaurer: 'restaurer',
|
||||
modifier_annotation: 'modifier_annotation',
|
||||
demander_un_avis: 'demander_un_avis'
|
||||
demander_un_avis: 'demander_un_avis',
|
||||
archiver: 'archiver',
|
||||
desarchiver: 'desarchiver'
|
||||
}
|
||||
|
||||
has_one_attached :serialized
|
||||
|
|
|
@ -79,13 +79,13 @@ class GeoArea < ApplicationRecord
|
|||
|
||||
def area
|
||||
if polygon? && RGeo::Geos.supported?
|
||||
rgeo_geometry.area.round(1)
|
||||
rgeo_geometry.area&.round(1)
|
||||
end
|
||||
end
|
||||
|
||||
def length
|
||||
if line? && RGeo::Geos.supported?
|
||||
rgeo_geometry.length.round(1)
|
||||
rgeo_geometry.length&.round(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ class Procedure < ApplicationRecord
|
|||
MAX_DUREE_CONSERVATION = 36
|
||||
MAX_DUREE_CONSERVATION_EXPORT = 3.hours
|
||||
|
||||
has_many :revisions, -> { order(:id) }, class_name: 'ProcedureRevision', inverse_of: :procedure, dependent: :destroy
|
||||
has_many :revisions, -> { order(:id) }, class_name: 'ProcedureRevision', inverse_of: :procedure
|
||||
belongs_to :draft_revision, class_name: 'ProcedureRevision', optional: false
|
||||
belongs_to :published_revision, class_name: 'ProcedureRevision', optional: true
|
||||
has_many :deleted_dossiers, dependent: :destroy
|
||||
|
@ -90,7 +90,10 @@ class Procedure < ApplicationRecord
|
|||
has_many :groupe_instructeurs, dependent: :destroy
|
||||
has_many :instructeurs, through: :groupe_instructeurs
|
||||
|
||||
has_many :dossiers, through: :groupe_instructeurs, dependent: :restrict_with_exception
|
||||
# This relationship is used in following dossiers through. We can not use revisions relationship
|
||||
# as order scope introduces invalid sql in some combinations.
|
||||
has_many :unordered_revisions, class_name: 'ProcedureRevision', inverse_of: :procedure, dependent: :destroy
|
||||
has_many :dossiers, through: :unordered_revisions, dependent: :restrict_with_exception
|
||||
|
||||
has_one :initiated_mail, class_name: "Mails::InitiatedMail", dependent: :destroy
|
||||
has_one :received_mail, class_name: "Mails::ReceivedMail", dependent: :destroy
|
||||
|
|
|
@ -160,7 +160,7 @@ class ProcedurePresentation < ApplicationRecord
|
|||
.filter_ilike(table, column, values)
|
||||
when 'groupe_instructeur'
|
||||
dossiers
|
||||
.includes(table)
|
||||
.joins(:groupe_instructeur)
|
||||
.filter_ilike(table, column, values)
|
||||
end.pluck(:id)
|
||||
end.reduce(:&)
|
||||
|
|
|
@ -11,6 +11,8 @@ class ProcedureRevision < ApplicationRecord
|
|||
self.implicit_order_column = :created_at
|
||||
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
|
||||
|
||||
has_many :dossiers, inverse_of: :revision, foreign_key: :revision_id
|
||||
|
||||
has_many :revision_types_de_champ, -> { public_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
|
||||
has_many :revision_types_de_champ_private, -> { private_only.ordered }, class_name: 'ProcedureRevisionTypeDeChamp', foreign_key: :revision_id, dependent: :destroy, inverse_of: :revision
|
||||
has_many :types_de_champ, through: :revision_types_de_champ, source: :type_de_champ
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
# stable_id :bigint
|
||||
#
|
||||
class TypeDeChamp < ApplicationRecord
|
||||
self.ignored_columns = ['procedure_id']
|
||||
|
||||
enum type_champs: {
|
||||
text: 'text',
|
||||
textarea: 'textarea',
|
||||
|
@ -309,21 +307,6 @@ class TypeDeChamp < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
# FIXME: We are changing how id is exposed to the editor.
|
||||
# We used to expose type_de_champ.id as primary key to the editor. With revisions
|
||||
# we need primary key to be type_de_champ.stable_id because any update can create
|
||||
# a new version but we do not want editor to know about this.
|
||||
# This is only needed for a clean migration without downtime. We want to ensure
|
||||
# that if editor send a simple id because it was loaded before deployment
|
||||
# we would still do the right thing.
|
||||
def self.to_stable_id(id_or_stable_id)
|
||||
if id_or_stable_id.to_s =~ /^stable:/
|
||||
id_or_stable_id.to_s.gsub(/^stable:/, '')
|
||||
else
|
||||
id_or_stable_id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_drop_down_list_value(value)
|
||||
|
|
|
@ -25,31 +25,21 @@
|
|||
= label_tag :type do
|
||||
= t('your_question', scope: [:support, :question])
|
||||
%span.mandatory *
|
||||
= select_tag :type, options_for_select(@options, params[:type]), include_blank: t('choose_question', scope: [:support, :question]), required: true
|
||||
|
||||
.support.card.featured.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_INFO } }
|
||||
.card-title
|
||||
= t('our_answer', scope: [:support, :response])
|
||||
.card-content
|
||||
= t('procedure_info_html', scope: [:support, :response], link_procedure_info: FAQ_CONTACTER_SERVICE_EN_CHARGE_URL)
|
||||
|
||||
.support.card.featured.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_PERDU } }
|
||||
.card-title
|
||||
= t('our_answer', scope: [:support, :response])
|
||||
.card-content
|
||||
= t('lost_user_html', scope: [:support, :response], base_url: APPLICATION_BASE_URL, link_lost_user: LISTE_DES_DEMARCHES_URL)
|
||||
|
||||
.support.card.featured.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_INSTRUCTION } }
|
||||
.card-title
|
||||
= t('our_answer', scope: [:support, :response])
|
||||
.card-content
|
||||
= t('instruction_info_html', scope: [:support, :response], link_instruction: FAQ_OU_EN_EST_MON_DOSSIER_URL)
|
||||
|
||||
.support.card.featured.hidden{ data: { 'contact-type-only': Helpscout::FormAdapter::TYPE_AMELIORATION } }
|
||||
.card-title
|
||||
= t('our_answer', scope: [:support, :response])
|
||||
.card-content
|
||||
= t('product_html', scope: [:support, :response], link_product: FEATURE_UPVOTE_URL)
|
||||
= hidden_field_tag :type, params[:type]
|
||||
%dl
|
||||
- @options.each do |(question, question_type, link)|
|
||||
%dt
|
||||
- if link.present?
|
||||
%button.button{ 'aria-expanded': 'false', 'aria-controls': question_type, data: { 'question-type': question_type } }= question
|
||||
- else
|
||||
%button.button.button-without-hint{ data: { 'question-type': question_type } }= question
|
||||
- if link.present?
|
||||
%dd
|
||||
.support.card.featured.hidden{ id: question_type }
|
||||
.card-title
|
||||
= t('our_answer', scope: [:support, :response])
|
||||
.card-content
|
||||
= t("#{question_type}_html", scope: [:support, :response], base_url: APPLICATION_BASE_URL, "link_#{question_type}": link)
|
||||
|
||||
.contact-champ
|
||||
= label_tag :dossier_id, t('file_number', scope: [:utils])
|
||||
|
|
|
@ -16,7 +16,7 @@ en:
|
|||
<p>If you have questions about the information requested, contact the service in charge of the procedure.</p>
|
||||
<p><a href=%{link_procedure_info}>Find more information</a></p>"
|
||||
instruction_info_html: "<p>If you have questions about the instruction of your application (response delay for example), contact directly the instructor via our mail system.</p>
|
||||
<p><a href=%{link_instruction}>Find more information</a></p>
|
||||
<p><a href=%{link_instruction_info}>Find more information</a></p>
|
||||
<br>
|
||||
<p>If you are facing technical issues on the website, use the form below. We will not be able to inform you about the instruction of your application.</p>"
|
||||
product_html: "<p>Got an idea? Please check our <strong>enhancement dashboard</strong></p>
|
||||
|
|
|
@ -16,7 +16,7 @@ fr:
|
|||
<p>Si vous avez des questions sur les informations à saisir, contactez les services en charge de la démarche.</p>
|
||||
<p><a href=%{link_procedure_info}>En savoir plus</a></p>"
|
||||
instruction_info_html: "<p>Si vous avez des questions sur l’instruction de votre dossier (par exemple sur les délais), nous vous invitons à contacter directement les services qui instruisent votre dossier par votre messagerie.</p>
|
||||
<p><a href=%{link_instruction}>En savoir plus</a></p>
|
||||
<p><a href=%{link_instruction_info}>En savoir plus</a></p>
|
||||
<br>
|
||||
<p>Si vous souhaitez poser une question pour un problème technique sur le site, utilisez le formulaire ci-dessous. Nous ne pourrons pas vous renseigner sur l'instruction de votre dossier.</p>"
|
||||
product_html: "<p>Une idée ? Pensez à consulter notre <strong>tableau de bord des améliorations</strong></p>
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace :after_party do
|
|||
|
||||
geometry_collections = GeoArea.where("geometry -> 'type' = '\"GeometryCollection\"'")
|
||||
multi_polygons = GeoArea.where("geometry -> 'type' = '\"MultiPolygon\"'")
|
||||
multi_line_strings = GeoArea.where("geometry -> 'type' = '\"MultiLineString\"'")
|
||||
|
||||
def valid_geometry?(geometry)
|
||||
RGeo::GeoJSON.decode(geometry.to_json, geo_factory: RGeo::Geographic.simple_mercator_factory)
|
||||
|
@ -26,6 +27,24 @@ namespace :after_party do
|
|||
end
|
||||
progress.finish
|
||||
|
||||
progress = ProgressReport.new(multi_line_strings.count)
|
||||
multi_line_strings.find_each do |multi_line_string|
|
||||
multi_line_string.geometry['coordinates'].each do |coordinates|
|
||||
geometry = {
|
||||
type: 'LineString',
|
||||
coordinates: coordinates
|
||||
}
|
||||
|
||||
if valid_geometry?(geometry)
|
||||
multi_line_string.champ.geo_areas.create!(geometry: geometry, source: 'selection_utilisateur')
|
||||
end
|
||||
end
|
||||
|
||||
multi_line_string.destroy
|
||||
progress.inc
|
||||
end
|
||||
progress.finish
|
||||
|
||||
progress = ProgressReport.new(multi_polygons.count)
|
||||
multi_polygons.find_each do |multi_polygon|
|
||||
multi_polygon.geometry['coordinates'].each do |coordinates|
|
||||
|
|
|
@ -396,37 +396,76 @@ describe API::V2::GraphqlController do
|
|||
... on PersonneMorale {
|
||||
siret
|
||||
siegeSocial
|
||||
numeroVoie
|
||||
typeVoie
|
||||
entreprise {
|
||||
siren
|
||||
dateCreation
|
||||
capitalSocial
|
||||
codeEffectifEntreprise
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "should be returned" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
number: dossier.id,
|
||||
usager: {
|
||||
id: dossier.user.to_typed_id,
|
||||
email: dossier.user.email
|
||||
},
|
||||
demandeur: {
|
||||
id: dossier.etablissement.to_typed_id,
|
||||
siret: dossier.etablissement.siret,
|
||||
siegeSocial: dossier.etablissement.siege_social,
|
||||
entreprise: {
|
||||
siren: dossier.etablissement.entreprise_siren,
|
||||
dateCreation: dossier.etablissement.entreprise_date_creation.iso8601,
|
||||
capitalSocial: dossier.etablissement.entreprise_capital_social.to_s
|
||||
context "in the nominal case" do
|
||||
it "should be returned" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
number: dossier.id,
|
||||
usager: {
|
||||
id: dossier.user.to_typed_id,
|
||||
email: dossier.user.email
|
||||
},
|
||||
demandeur: {
|
||||
id: dossier.etablissement.to_typed_id,
|
||||
siret: dossier.etablissement.siret,
|
||||
siegeSocial: dossier.etablissement.siege_social,
|
||||
numeroVoie: dossier.etablissement.numero_voie.to_s,
|
||||
typeVoie: dossier.etablissement.type_voie.to_s,
|
||||
entreprise: {
|
||||
siren: dossier.etablissement.entreprise_siren,
|
||||
dateCreation: dossier.etablissement.entreprise_date_creation.iso8601,
|
||||
capitalSocial: dossier.etablissement.entreprise_capital_social.to_s,
|
||||
codeEffectifEntreprise: dossier.etablissement.entreprise_code_effectif_entreprise.to_s
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context "when there are missing data" do
|
||||
before do
|
||||
dossier.etablissement.update!(entreprise_code_effectif_entreprise: nil, entreprise_capital_social: nil,
|
||||
numero_voie: nil, type_voie: nil)
|
||||
end
|
||||
|
||||
it "should be returned" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
expect(gql_data).to eq(dossier: {
|
||||
id: dossier.to_typed_id,
|
||||
number: dossier.id,
|
||||
usager: {
|
||||
id: dossier.user.to_typed_id,
|
||||
email: dossier.user.email
|
||||
},
|
||||
demandeur: {
|
||||
id: dossier.etablissement.to_typed_id,
|
||||
siret: dossier.etablissement.siret,
|
||||
siegeSocial: dossier.etablissement.siege_social,
|
||||
numeroVoie: nil,
|
||||
typeVoie: nil,
|
||||
entreprise: {
|
||||
siren: dossier.etablissement.entreprise_siren,
|
||||
dateCreation: dossier.etablissement.entreprise_date_creation.iso8601,
|
||||
capitalSocial: '-1',
|
||||
codeEffectifEntreprise: nil
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -846,6 +885,48 @@ describe API::V2::GraphqlController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'dossierArchiver' do
|
||||
let(:query) do
|
||||
"mutation {
|
||||
dossierArchiver(input: {
|
||||
dossierId: \"#{dossier.to_typed_id}\",
|
||||
instructeurId: \"#{instructeur.to_typed_id}\"
|
||||
}) {
|
||||
dossier {
|
||||
archived
|
||||
}
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}"
|
||||
end
|
||||
|
||||
it "validation error" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierArchiver: {
|
||||
dossier: nil,
|
||||
errors: [{ message: "Un dossier ne peut être archivé qu’une fois le traitement terminé" }]
|
||||
})
|
||||
end
|
||||
|
||||
context "should archive dossier" do
|
||||
let(:dossier) { create(:dossier, :sans_suite, :with_individual, procedure: procedure) }
|
||||
|
||||
it "change made" do
|
||||
expect(gql_errors).to eq(nil)
|
||||
|
||||
expect(gql_data).to eq(dossierArchiver: {
|
||||
dossier: {
|
||||
archived: true
|
||||
},
|
||||
errors: nil
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -813,6 +813,22 @@ describe Dossier do
|
|||
}.to_not have_enqueued_job(WebHookJob)
|
||||
end
|
||||
|
||||
it 'should not call webhook with empty value' do
|
||||
dossier.procedure.update_column(:web_hook_url, '')
|
||||
|
||||
expect {
|
||||
dossier.accepte!
|
||||
}.to_not have_enqueued_job(WebHookJob)
|
||||
end
|
||||
|
||||
it 'should not call webhook with blank value' do
|
||||
dossier.procedure.update_column(:web_hook_url, ' ')
|
||||
|
||||
expect {
|
||||
dossier.accepte!
|
||||
}.to_not have_enqueued_job(WebHookJob)
|
||||
end
|
||||
|
||||
it 'should call webhook' do
|
||||
dossier.procedure.update_column(:web_hook_url, '/webhook.json')
|
||||
|
||||
|
|
|
@ -734,7 +734,7 @@ describe ProcedurePresentation do
|
|||
let!(:gi_3) { procedure.groupe_instructeurs.create(label: '3') }
|
||||
|
||||
let!(:kept_dossier) { create(:dossier, procedure: procedure) }
|
||||
let!(:discarded_dossier) { create(:dossier, groupe_instructeur: gi_2) }
|
||||
let!(:discarded_dossier) { create(:dossier, procedure: procedure, groupe_instructeur: gi_2) }
|
||||
|
||||
it { is_expected.to contain_exactly(kept_dossier.id) }
|
||||
|
||||
|
@ -746,7 +746,7 @@ describe ProcedurePresentation do
|
|||
]
|
||||
end
|
||||
|
||||
let!(:other_kept_dossier) { create(:dossier, groupe_instructeur: gi_3) }
|
||||
let!(:other_kept_dossier) { create(:dossier, procedure: procedure, groupe_instructeur: gi_3) }
|
||||
|
||||
it 'returns every dossier that matches any of the search criteria for a given column' do
|
||||
is_expected.to contain_exactly(kept_dossier.id, other_kept_dossier.id)
|
||||
|
|
Loading…
Reference in a new issue