commit
f5d2fc7bdd
28 changed files with 327 additions and 733 deletions
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
70
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '28 22 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript', 'ruby' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
|
@ -159,7 +159,6 @@ class ApplicationController < ActionController::Base
|
|||
gon.sentry = sentry_config
|
||||
|
||||
if administrateur_signed_in?
|
||||
gon.sendinblue = sendinblue_config
|
||||
gon.crisp = crisp_config
|
||||
end
|
||||
end
|
||||
|
@ -285,26 +284,6 @@ class ApplicationController < ActionController::Base
|
|||
}
|
||||
end
|
||||
|
||||
def sendinblue_config
|
||||
sendinblue = Rails.application.secrets.sendinblue
|
||||
|
||||
{
|
||||
key: sendinblue[:client_key],
|
||||
enabled: sendinblue[:enabled],
|
||||
administrateur: {
|
||||
email: current_user&.email,
|
||||
payload: {
|
||||
DS_SIGN_IN_COUNT: current_user&.sign_in_count,
|
||||
DS_CREATED_AT: current_administrateur&.created_at,
|
||||
DS_ACTIVE: current_user&.active?,
|
||||
DS_ID: current_administrateur&.id,
|
||||
DS_GESTIONNAIRE_ID: current_instructeur&.id,
|
||||
DS_ROLES: current_user_roles
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def crisp_config
|
||||
crisp = Rails.application.secrets.crisp
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ module Users
|
|||
procedure = Procedure.find_by(path: params[:path])
|
||||
|
||||
if procedure&.close?
|
||||
flash.alert = t('errors.messages.procedure_archived')
|
||||
flash.alert = t('errors.messages.procedure_archived', service_name: procedure.service.nom, service_phone_number: procedure.service.telephone, service_email: procedure.service.email)
|
||||
else
|
||||
flash.alert = t('errors.messages.procedure_not_found')
|
||||
end
|
||||
|
|
|
@ -8,7 +8,6 @@ import * as Turbo from '@hotwired/turbo';
|
|||
import '../shared/activestorage/ujs';
|
||||
import '../shared/remote-poller';
|
||||
import '../shared/safari-11-file-xhr-workaround';
|
||||
import '../shared/franceconnect';
|
||||
import '../shared/toggle-target';
|
||||
import '../shared/ujs-error-handling';
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
import '../shared/track/sendinblue';
|
||||
import '../shared/track/crisp';
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
const fconnect = {
|
||||
tracesUrl: '/traces',
|
||||
aboutUrl: ''
|
||||
};
|
||||
const document = window.document;
|
||||
|
||||
function init() {
|
||||
fconnect.currentHost = 'fcp.integ01.dev-franceconnect.fr';
|
||||
|
||||
if (window.location.hostname == 'www.demarches-simplifiees.fr')
|
||||
fconnect.currentHost = 'app.franceconnect.gouv.fr';
|
||||
|
||||
var fconnectProfile = document.getElementById('fconnect-profile');
|
||||
if (fconnectProfile) {
|
||||
var linkAccess = document.querySelector('#fconnect-profile > a');
|
||||
var fcLogoutUrl = fconnectProfile.getAttribute('data-fc-logout-url');
|
||||
var access = createFCAccessElement(fcLogoutUrl);
|
||||
fconnectProfile.appendChild(access);
|
||||
linkAccess.onclick = toggleElement.bind(access);
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('DOMContentLoaded', init);
|
||||
|
||||
function toggleElement(event) {
|
||||
event.preventDefault();
|
||||
if (this.style.display === 'block') {
|
||||
this.style.display = 'none';
|
||||
} else {
|
||||
this.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function closeFCPopin(event) {
|
||||
event.preventDefault();
|
||||
fconnect.popin.className = 'fade-out';
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(fconnect.popin);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function openFCPopin() {
|
||||
fconnect.popin = document.createElement('div');
|
||||
fconnect.popin.id = 'fc-background';
|
||||
|
||||
var iframe = createFCIframe();
|
||||
|
||||
document.body.appendChild(fconnect.popin);
|
||||
|
||||
fconnect.popin.appendChild(iframe);
|
||||
|
||||
setTimeout(function () {
|
||||
fconnect.popin.className = 'fade-in';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function createFCIframe() {
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('id', 'fconnect-iframe');
|
||||
iframe.frameBorder = 0;
|
||||
iframe.name = 'fconnect-iframe';
|
||||
return iframe;
|
||||
}
|
||||
|
||||
function createFCAccessElement(logoutUrl) {
|
||||
var access = document.createElement('div');
|
||||
access.id = 'fconnect-access';
|
||||
access.innerHTML = '<h5>Vous êtes identifié grâce à FranceConnect</h5>';
|
||||
access.appendChild(createAboutLink());
|
||||
access.appendChild(document.createElement('hr'));
|
||||
access.appendChild(createHistoryLink());
|
||||
access.appendChild(createLogoutElement(logoutUrl));
|
||||
return access;
|
||||
}
|
||||
|
||||
function createHistoryLink() {
|
||||
var historyLink = document.createElement('a');
|
||||
historyLink.target = 'fconnect-iframe';
|
||||
historyLink.href = '//' + fconnect.currentHost + fconnect.tracesUrl;
|
||||
historyLink.onclick = openFCPopin;
|
||||
historyLink.innerHTML = 'Historique des connexions/échanges de données';
|
||||
|
||||
return historyLink;
|
||||
}
|
||||
|
||||
function createAboutLink() {
|
||||
var aboutLink = document.createElement('a');
|
||||
aboutLink.href = fconnect.aboutUrl
|
||||
? '//' + fconnect.currentHost + fconnect.aboutUrl
|
||||
: '#';
|
||||
if (fconnect.aboutUrl) {
|
||||
aboutLink.target = 'fconnect-iframe';
|
||||
aboutLink.onclick = openFCPopin;
|
||||
}
|
||||
aboutLink.innerHTML = "Qu'est-ce-que FranceConnect ?";
|
||||
|
||||
return aboutLink;
|
||||
}
|
||||
|
||||
function createLogoutElement(logoutUrl) {
|
||||
var elm = document.createElement('div');
|
||||
elm.className = 'logout';
|
||||
elm.innerHTML =
|
||||
'<a class="btn btn-default" href="' + logoutUrl + '">Se déconnecter</a>';
|
||||
return elm;
|
||||
}
|
||||
|
||||
var eventMethod = window.addEventListener ? 'addEventListener' : 'attachEvent';
|
||||
var eventer = window[eventMethod];
|
||||
var messageEvent = eventMethod == 'attachEvent' ? 'onmessage' : 'message';
|
||||
|
||||
// Listen to message from child window
|
||||
eventer(
|
||||
messageEvent,
|
||||
function (e) {
|
||||
var key = e.message ? 'message' : 'data';
|
||||
var data = e[key];
|
||||
if (data === 'close_popup') {
|
||||
closeFCPopin(e);
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
|
@ -1,22 +0,0 @@
|
|||
const { key, enabled, administrateur } = gon.sendinblue || {};
|
||||
|
||||
if (enabled) {
|
||||
window.sib = {
|
||||
equeue: [],
|
||||
client_key: key,
|
||||
email_id: administrateur.email
|
||||
};
|
||||
|
||||
const script = document.createElement('script');
|
||||
const firstScript = document.getElementsByTagName('script')[0];
|
||||
script.type = 'text/javascript';
|
||||
script.id = 'sendinblue-js';
|
||||
script.async = true;
|
||||
script.src = `https://sibautomation.com/sa.js?key=${window.sib.client_key}`;
|
||||
firstScript.parentNode.insertBefore(script, firstScript);
|
||||
|
||||
window.sib.equeue.push({ page: [] });
|
||||
window.sib.equeue.push({
|
||||
identify: [administrateur.email, administrateur.payload]
|
||||
});
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
class Cron::UpdateAdministrateurUsageStatisticsJob < Cron::CronJob
|
||||
self.schedule_expression = "every day at 10 am"
|
||||
|
||||
def perform
|
||||
AdministrateurUsageStatisticsService.new.update_administrateurs
|
||||
end
|
||||
end
|
|
@ -1109,20 +1109,17 @@ class Dossier < ApplicationRecord
|
|||
if procedure.routee?
|
||||
columns << ['Groupe instructeur', groupe_instructeur.label]
|
||||
end
|
||||
|
||||
columns + self.class.champs_for_export(champs + champs_private, types_de_champ)
|
||||
end
|
||||
|
||||
# Get all the champs values for the types de champ in the final list.
|
||||
# Dossier might not have corresponding champ – display nil.
|
||||
# To do so, we build a virtual champ when there is no value so we can call for_export with all indexes
|
||||
def self.champs_for_export(champs, types_de_champ)
|
||||
# Index values by stable_id
|
||||
values = champs.reject(&:exclude_from_export?)
|
||||
.index_by(&:stable_id)
|
||||
.transform_values(&:for_export)
|
||||
|
||||
# Get all the champs values for the types de champ in the final list.
|
||||
# Dossier might not have corresponding champ – display nil.
|
||||
types_de_champ.flat_map do |type_de_champ|
|
||||
Array.wrap(values[type_de_champ.stable_id] || [nil]).map.with_index do |champ_value, index|
|
||||
champ_or_new = champs.find { |champ| champ.stable_id == type_de_champ.stable_id }
|
||||
champ_or_new ||= type_de_champ.champ.build
|
||||
Array.wrap(champ_or_new.for_export || [nil]).map.with_index do |champ_value, index|
|
||||
[type_de_champ.libelle_for_export(index), champ_value]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -713,9 +713,20 @@ class Procedure < ApplicationRecord
|
|||
end
|
||||
|
||||
def create_new_revision
|
||||
draft_revision
|
||||
new_draft = draft_revision
|
||||
.deep_clone(include: [:revision_types_de_champ])
|
||||
.tap(&:save!)
|
||||
|
||||
children = new_draft.revision_types_de_champ.where.not(parent_id: nil)
|
||||
children.each do |child|
|
||||
old_parent = draft_revision.revision_types_de_champ.find(child.parent_id)
|
||||
new_parent = new_draft.revision_types_de_champ.find_by(type_de_champ_id: old_parent.type_de_champ_id)
|
||||
child.update!(parent_id: new_parent.id)
|
||||
end
|
||||
|
||||
new_draft.revision_types_de_champ.reload
|
||||
|
||||
new_draft
|
||||
end
|
||||
|
||||
def average_dossier_weight
|
||||
|
|
|
@ -53,23 +53,23 @@ class ProcedureRevision < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def find_or_clone_type_de_champ(id)
|
||||
type_de_champ = find_type_de_champ_by_id(id)
|
||||
def find_or_clone_type_de_champ(stable_id)
|
||||
type_de_champ = find_type_de_champ_by_stable_id(stable_id)
|
||||
|
||||
if type_de_champ.revision == self
|
||||
if type_de_champ.only_present_on_draft?
|
||||
type_de_champ
|
||||
elsif type_de_champ.parent.present?
|
||||
find_or_clone_type_de_champ(type_de_champ.parent.stable_id).types_de_champ.find_by!(stable_id: id)
|
||||
find_or_clone_type_de_champ(type_de_champ.parent.stable_id).types_de_champ.find_by!(stable_id: stable_id)
|
||||
else
|
||||
revise_type_de_champ(type_de_champ)
|
||||
end
|
||||
end
|
||||
|
||||
def move_type_de_champ(id, position)
|
||||
type_de_champ = find_type_de_champ_by_id(id)
|
||||
def move_type_de_champ(stable_id, position)
|
||||
type_de_champ = find_type_de_champ_by_stable_id(stable_id)
|
||||
|
||||
if type_de_champ.parent.present?
|
||||
repetition_type_de_champ = find_or_clone_type_de_champ(id).parent
|
||||
repetition_type_de_champ = find_or_clone_type_de_champ(stable_id).parent
|
||||
|
||||
move_type_de_champ_hash(repetition_type_de_champ.types_de_champ.to_a, type_de_champ, position).each do |(id, position)|
|
||||
type_de_champ = repetition_type_de_champ.types_de_champ.find(id)
|
||||
|
@ -85,13 +85,13 @@ class ProcedureRevision < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def remove_type_de_champ(id)
|
||||
type_de_champ = find_type_de_champ_by_id(id)
|
||||
def remove_type_de_champ(stable_id)
|
||||
type_de_champ = find_type_de_champ_by_stable_id(stable_id)
|
||||
|
||||
if type_de_champ.revision == self
|
||||
if type_de_champ.only_present_on_draft?
|
||||
type_de_champ.destroy
|
||||
elsif type_de_champ.parent.present?
|
||||
find_or_clone_type_de_champ(id).destroy
|
||||
find_or_clone_type_de_champ(stable_id).destroy
|
||||
else
|
||||
types_de_champ.delete(type_de_champ)
|
||||
end
|
||||
|
@ -384,9 +384,8 @@ class ProcedureRevision < ApplicationRecord
|
|||
cloned_type_de_champ
|
||||
end
|
||||
|
||||
def find_type_de_champ_by_id(id)
|
||||
types_de_champ.find_by(stable_id: id) ||
|
||||
types_de_champ_in_repetition.find_by!(stable_id: id)
|
||||
def find_type_de_champ_by_stable_id(stable_id)
|
||||
types_de_champ.find_by(stable_id: stable_id)
|
||||
end
|
||||
|
||||
def types_de_champ_in_repetition
|
||||
|
|
|
@ -164,6 +164,10 @@ class TypeDeChamp < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def only_present_on_draft?
|
||||
revisions.size == 1
|
||||
end
|
||||
|
||||
def non_fillable?
|
||||
type_champ.in?([
|
||||
TypeDeChamp.type_champs.fetch(:header_section),
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
# Note: this class uses a `synthetic_state` for Dossier, that diverges from the standard state:
|
||||
# - 'termine' is the synthetic_state for all dossiers
|
||||
# whose state is 'accepte', 'refuse' or 'sans_suite',
|
||||
# even when `archive` is true
|
||||
# - 'archive' is the synthetic_state for all dossiers
|
||||
# where archive is true,
|
||||
# except those whose synthetic_state is already 'termine'
|
||||
# - For all other dossiers, the synthetic_state and the state are the same
|
||||
class AdministrateurUsageStatisticsService
|
||||
def update_administrateurs
|
||||
Administrateur.includes(:user).find_each do |administrateur|
|
||||
stats = administrateur_stats(administrateur)
|
||||
api.update_contact(administrateur.email, stats)
|
||||
end
|
||||
api.run
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def api
|
||||
@api ||= Sendinblue::API.new_properly_configured!
|
||||
end
|
||||
|
||||
def administrateur_stats(administrateur)
|
||||
nb_dossiers_by_procedure_id = nb_dossiers_by_procedure_id(administrateur.id)
|
||||
nb_dossiers_by_synthetic_state = nb_dossiers_by_synthetic_state(administrateur.id)
|
||||
nb_dossiers_roi = nb_dossiers_by_procedure_id.reject { |procedure_id, _count| is_brouillon(procedure_id) }.map { |_procedure_id, count| count }.sum
|
||||
|
||||
result = {
|
||||
ds_sign_in_count: administrateur&.user&.sign_in_count,
|
||||
ds_created_at: administrateur.created_at,
|
||||
ds_active: administrateur.user.active?,
|
||||
ds_id: administrateur.id,
|
||||
nb_services: nb_services_by_administrateur_id[administrateur.id],
|
||||
nb_instructeurs: nb_instructeurs_by_administrateur_id[administrateur.id],
|
||||
|
||||
ds_nb_demarches_actives: nb_demarches_by_administrateur_id_and_state[[administrateur.id, "publiee"]],
|
||||
ds_nb_demarches_archives: nb_demarches_by_administrateur_id_and_state[[administrateur.id, "close"]],
|
||||
ds_nb_demarches_brouillons: nb_demarches_by_administrateur_id_and_state[[administrateur.id, "brouillon"]],
|
||||
|
||||
nb_demarches_test: nb_dossiers_by_procedure_id
|
||||
.filter { |procedure_id, count| count > 0 && is_brouillon(procedure_id) }
|
||||
.count,
|
||||
nb_demarches_prod: nb_dossiers_by_procedure_id
|
||||
.reject { |procedure_id, count| count == 0 || is_brouillon(procedure_id) }
|
||||
.count,
|
||||
nb_demarches_prod_20: nb_dossiers_by_procedure_id
|
||||
.reject { |procedure_id, count| count < 20 || is_brouillon(procedure_id) }
|
||||
.count,
|
||||
|
||||
nb_dossiers: nb_dossiers_by_procedure_id
|
||||
.reject { |procedure_id, _count| is_brouillon(procedure_id) }
|
||||
.map { |_procedure_id, count| count }
|
||||
.sum,
|
||||
nb_dossiers_max: nb_dossiers_by_procedure_id
|
||||
.reject { |procedure_id, _count| is_brouillon(procedure_id) }
|
||||
.map { |_procedure_id, count| count }
|
||||
.max || 0,
|
||||
nb_dossiers_traite: nb_dossiers_by_synthetic_state['termine'],
|
||||
nb_dossiers_dossier_en_instruction: nb_dossiers_by_synthetic_state['en_instruction'],
|
||||
admin_roi_low: nb_dossiers_roi * 7,
|
||||
admin_roi_high: nb_dossiers_roi * 17
|
||||
}
|
||||
|
||||
if administrateur&.user&.current_sign_in_at.present?
|
||||
result[:ds_current_sign_in_at] = administrateur.user.current_sign_in_at
|
||||
end
|
||||
|
||||
if administrateur&.user&.last_sign_in_at.present?
|
||||
result[:ds_last_sign_in_at] = administrateur.user.last_sign_in_at
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Returns a hash { procedure_id => dossier_count }:
|
||||
# - The keys are the ids of procedures owned by administrateur_id
|
||||
# - The values are the number of dossiers for that procedure.
|
||||
# Brouillons, and dossiers that are 'archive' but not 'termine', are not counted.
|
||||
def nb_dossiers_by_procedure_id(administrateur_id)
|
||||
with_default(
|
||||
0,
|
||||
nb_dossiers_by_administrateur_id_and_procedure_id_and_synthetic_state[administrateur_id]
|
||||
.transform_values do |nb_dossiers_by_synthetic_state|
|
||||
nb_dossiers_by_synthetic_state
|
||||
.reject { |synthetic_state, _count| ['brouillon', 'archive'].include?(synthetic_state) }
|
||||
.map { |_synthetic_state, count| count }
|
||||
.sum
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
# Returns a hash { synthetic_state => dossier_count }
|
||||
# - The keys are dossier synthetic_states (see class comment)
|
||||
# - The values are the number of dossiers in that synthetic state, for procedures owned by `administrateur_id`
|
||||
# Dossier on procedures en test are not counted
|
||||
def nb_dossiers_by_synthetic_state(administrateur_id)
|
||||
with_default(
|
||||
0,
|
||||
nb_dossiers_by_administrateur_id_and_procedure_id_and_synthetic_state[administrateur_id]
|
||||
.reject { |procedure_id, _nb_dossiers_by_synthetic_state| is_brouillon(procedure_id) }
|
||||
.flat_map { |_procedure_id, nb_dossiers_by_synthetic_state| nb_dossiers_by_synthetic_state.to_a }
|
||||
.group_by { |synthetic_state, _count| synthetic_state }
|
||||
.transform_values { |synthetic_states_and_counts| synthetic_states_and_counts.map { |_synthetic_state, count| count }.sum }
|
||||
)
|
||||
end
|
||||
|
||||
def nb_demarches_by_administrateur_id_and_state
|
||||
@nb_demarches_by_administrateur_id_and_state ||= with_default(0, Procedure.joins(:administrateurs).group('administrateurs.id', :aasm_state).count)
|
||||
end
|
||||
|
||||
def nb_services_by_administrateur_id
|
||||
@nb_services_by_administrateur_id ||= with_default(0, Service.group(:administrateur_id).count)
|
||||
end
|
||||
|
||||
def nb_instructeurs_by_administrateur_id
|
||||
@nb_instructeurs_by_administrateur_id ||= with_default(0, Administrateur.joins(:instructeurs).group('administrateurs.id').count)
|
||||
end
|
||||
|
||||
def nb_dossiers_by_administrateur_id_and_procedure_id_and_synthetic_state
|
||||
if @nb_dossiers_by_administrateur_id_and_procedure_id_and_synthetic_state.present?
|
||||
return @nb_dossiers_by_administrateur_id_and_procedure_id_and_synthetic_state
|
||||
end
|
||||
|
||||
result = {}
|
||||
|
||||
Dossier
|
||||
.joins(revision: { procedure: [:administrateurs] })
|
||||
.group(
|
||||
'administrateurs.id',
|
||||
'procedure_revisions.procedure_id',
|
||||
<<~EOSQL
|
||||
CASE
|
||||
WHEN state IN('accepte', 'refuse', 'sans_suite') THEN 'termine'
|
||||
WHEN archived THEN 'archive'
|
||||
ELSE state
|
||||
END
|
||||
EOSQL
|
||||
)
|
||||
.count
|
||||
.each do |(administrateur_id, procedure_id, synthetic_state), count|
|
||||
result.deep_merge!(
|
||||
{ administrateur_id => { procedure_id => { synthetic_state => count } } }
|
||||
)
|
||||
end
|
||||
|
||||
@nb_dossiers_by_administrateur_id_and_procedure_id_and_synthetic_state =
|
||||
with_default({}, result)
|
||||
end
|
||||
|
||||
def is_brouillon(procedure_id)
|
||||
procedure_states[procedure_id] == 'brouillon'
|
||||
end
|
||||
|
||||
def procedure_states
|
||||
@procedure_states ||= Procedure.pluck(:id, :aasm_state).to_h
|
||||
end
|
||||
|
||||
def with_default(default, hash)
|
||||
hash.default = default
|
||||
hash
|
||||
end
|
||||
end
|
|
@ -12,6 +12,8 @@
|
|||
- else
|
||||
%p
|
||||
= t('users.dossiers.header.banner.procedure_deleted_dossier_en_cours_content')
|
||||
%p
|
||||
= t('users.dossiers.header.banner.contact_service', service_name: dossier.procedure.service.nom, service_phone_number: dossier.procedure.service.telephone, service_email: dossier.procedure.service.email)
|
||||
|
||||
- else
|
||||
.flex.justify-between
|
||||
|
@ -19,4 +21,6 @@
|
|||
= render(partial: 'users/dossiers/show/print_dossier', locals: { dossier: dossier }) if !dossier.brouillon?
|
||||
%p
|
||||
= t('users.dossiers.header.banner.procedure_close_content')
|
||||
%p
|
||||
= t('users.dossiers.header.banner.contact_service', service_name: dossier.procedure.service.nom, service_phone_number: dossier.procedure.service.telephone, service_email: dossier.procedure.service.email)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ Rails.application.config.content_security_policy do |policy|
|
|||
|
||||
# Javascript: allow us, SendInBlue and Matomo.
|
||||
# We need unsafe_inline because miniprofiler and us have some inline buttons :(
|
||||
scripts_whitelist = ["*.sendinblue.com", "*.crisp.chat", "crisp.chat", "*.sibautomation.com", "sibautomation.com", "cdn.jsdelivr.net", "maxcdn.bootstrapcdn.com", "code.jquery.com"]
|
||||
scripts_whitelist = ["*.crisp.chat", "crisp.chat", "cdn.jsdelivr.net", "maxcdn.bootstrapcdn.com", "code.jquery.com"]
|
||||
scripts_whitelist << URI(MATOMO_IFRAME_URL).host if MATOMO_IFRAME_URL.present?
|
||||
policy.script_src(:self, :unsafe_eval, :unsafe_inline, :blob, *scripts_whitelist)
|
||||
|
||||
|
@ -20,7 +20,7 @@ Rails.application.config.content_security_policy do |policy|
|
|||
# It's too complicated to be fixed right now (and it wouldn't add value: this is hardcoded in views, so not subject to injections)
|
||||
policy.style_src(:self, :unsafe_inline, "*.crisp.chat", "crisp.chat", 'cdn.jsdelivr.net', 'maxcdn.bootstrapcdn.com')
|
||||
|
||||
connect_whitelist = ["wss://*.crisp.chat", "*.crisp.chat", "in-automate.sendinblue.com", "app.franceconnect.gouv.fr", "sentry.io", "openmaptiles.geo.data.gouv.fr", "openmaptiles.github.io", "tiles.geo.api.gouv.fr", "wxs.ign.fr"]
|
||||
connect_whitelist = ["wss://*.crisp.chat", "*.crisp.chat", "app.franceconnect.gouv.fr", "sentry.io", "openmaptiles.geo.data.gouv.fr", "openmaptiles.github.io", "tiles.geo.api.gouv.fr", "wxs.ign.fr"]
|
||||
connect_whitelist << ENV.fetch('APP_HOST')
|
||||
connect_whitelist << URI(DS_PROXY_URL).host if DS_PROXY_URL.present?
|
||||
connect_whitelist << URI(API_ADRESSE_URL).host if API_ADRESSE_URL.present?
|
||||
|
|
|
@ -359,7 +359,7 @@ en:
|
|||
# # etablissement_fail: 'Désolé, nous n’avons pas réussi à enregistrer l’établissement correspondant à ce numéro SIRET'
|
||||
france_connect:
|
||||
connexion: "Error trying to connect to France Connect."
|
||||
procedure_archived: "This procedure has been closed, it is no longer possible to submit a file."
|
||||
procedure_archived: This procedure has been closed, it is no longer possible to submit a file. For more information, please contact the service %{service_name}, available at %{service_phone_number} or by email %{service_email}
|
||||
# # procedure_not_draft: "This procedure is not a draft anymore."
|
||||
# cadastres_empty:
|
||||
# one: "Aucune parcelle cadastrale sur la zone sélectionnée"
|
||||
|
|
|
@ -364,7 +364,7 @@ fr:
|
|||
france_connect:
|
||||
connexion: "Erreur lors de la connexion à France Connect."
|
||||
forbidden_html: "Seul-e-s les usagers peuvent se connecter via France Connect. En tant qu’instructeur ou administrateur, nous vous invitons à <a href='%{reset_link}'>réininitialiser votre mot de passe</a>."
|
||||
procedure_archived: "Cette démarche en ligne a été close, il n’est plus possible de déposer de dossier."
|
||||
procedure_archived: Cette démarche en ligne a été close, il n’est plus possible de déposer de dossier. Pour plus d'informations veuillez contacter le service %{service_name} au %{service_phone_number} ou par email à %{service_email}
|
||||
empty_repetition: '« %{value} » doit comporter au moins un champ répétable'
|
||||
empty_drop_down: '« %{value} » doit comporter au moins un choix sélectionnable'
|
||||
# procedure_not_draft: "Cette démarche n’est maintenant plus en brouillon."
|
||||
|
|
|
@ -4,6 +4,12 @@ en:
|
|||
header:
|
||||
banner:
|
||||
title: Your file will expire
|
||||
procedure_deleted_title: The procedure linked to your file is deleted
|
||||
procedure_close_title: The procedure linked to your file is closed
|
||||
procedure_deleted_dossier_en_cours_content: You can still consult your file, but it is no longer possible to modify it
|
||||
procedure_deleted_dossier_termine_content: Your file has been processed by the administration, no action is possible
|
||||
procedure_close_content: You can still consult your file, but it will not be processed by the administration
|
||||
contact_service: For more information, please contact the service %{service_name}, available at %{service_phone_number} or by email %{service_email}
|
||||
states:
|
||||
brouillon: Your file is still in draft and will soon expire. So it will be deleted soon without being instructed. If you want to pursue your procedure you can submit it now. Otherwise you are able to delay its expiration by clicking on the underneath button.
|
||||
en_construction: Your file is pending for instruction. The maximum delay is 6 months, but you can extend the duration by a month by clicking on the underneath button.
|
||||
|
|
|
@ -8,6 +8,7 @@ fr:
|
|||
procedure_deleted_dossier_en_cours_content: "Vous pouvez toujours consulter votre dossier, mais il n’est plus possible de le modifier."
|
||||
procedure_deleted_dossier_termine_content: "Votre dossier a été traité par l'administration, aucune action n'est possible"
|
||||
procedure_close_content: "Vous pouvez toujours consulter votre dossier, mais il ne sera pas traité par l'administration"
|
||||
contact_service: Pour plus d'informations, veuillez vous rapprocher du service %{service_name}, disponible au %{service_phone_number} ou par email %{service_email}
|
||||
title: Votre dossier va expirer
|
||||
states:
|
||||
brouillon: Votre dossier est en brouillon, mais va bientôt expirer. Cela signifie qu’il va bientôt être supprimé sans avoir été déposé. Si vous souhaitez le conserver afin de poursuivre la démarche, vous pouvez le conserver un mois de plus en cliquant sur le bouton ci-dessous.
|
||||
|
|
|
@ -642,12 +642,15 @@ describe Instructeurs::DossiersController, type: :controller do
|
|||
|
||||
describe "#update_annotations" do
|
||||
let(:procedure) do
|
||||
create(:procedure, :published, types_de_champ_private: [
|
||||
procedure = create(:procedure, :published, types_de_champ_private: [
|
||||
build(:type_de_champ_multiple_drop_down_list, position: 0),
|
||||
build(:type_de_champ_linked_drop_down_list, position: 1),
|
||||
build(:type_de_champ_datetime, position: 2),
|
||||
build(:type_de_champ_repetition, :with_types_de_champ, position: 3)
|
||||
build(:type_de_champ_datetime, position: 2)
|
||||
], instructeurs: instructeurs)
|
||||
|
||||
create(:type_de_champ_repetition, :with_types_de_champ, procedure: procedure, position: 3, private: true)
|
||||
|
||||
procedure
|
||||
end
|
||||
let(:dossier) { create(:dossier, :en_construction, :with_populated_annotations, procedure: procedure) }
|
||||
let(:another_instructeur) { create(:instructeur) }
|
||||
|
|
|
@ -190,6 +190,12 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :with_repetition_commune do
|
||||
after(:build) do |procedure, _evaluator|
|
||||
build(:type_de_champ_repetition, types_de_champ: [build(:type_de_champ_communes)], procedure: procedure)
|
||||
end
|
||||
end
|
||||
|
||||
trait :with_number do
|
||||
after(:build) do |procedure, _evaluator|
|
||||
build(:type_de_champ_number, procedure: procedure)
|
||||
|
|
|
@ -188,8 +188,17 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
trait :with_types_de_champ do
|
||||
after(:build) do |type_de_champ, _evaluator|
|
||||
build(:type_de_champ, libelle: 'sub type de champ', parent: type_de_champ)
|
||||
after(:build) do |type_de_champ, evaluator|
|
||||
tdc = build(:type_de_champ, libelle: 'sub type de champ', parent: type_de_champ)
|
||||
|
||||
evaluator.procedure.save
|
||||
|
||||
ProcedureRevisionTypeDeChamp.create!(
|
||||
revision_id: evaluator.procedure.active_revision.id,
|
||||
type_de_champ_id: tdc.id,
|
||||
parent_id: tdc.parent.revision_type_de_champ.id,
|
||||
position: 0
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1458,6 +1458,17 @@ describe Dossier do
|
|||
expect(repetition_second_revision_champs_for_export.map { |(libelle)| libelle }).to eq(procedure.types_de_champ_for_procedure_presentation.repetition.map(&:libelle_for_export))
|
||||
expect(repetition_second_revision_champs_for_export.first.size).to eq(2)
|
||||
end
|
||||
|
||||
context 'within a repetition having a type de champs commune (multiple values for export)' do
|
||||
it 'works' do
|
||||
proc_test = create(:procedure, :with_repetition_commune)
|
||||
dossier_test = create(:dossier, procedure: proc_test)
|
||||
repetition = proc_test.types_de_champ_for_procedure_presentation.repetition.first
|
||||
type_champs = repetition.types_de_champ_for_revision(proc_test.active_revision).to_a
|
||||
expect(type_champs.size).to eq(1)
|
||||
expect(Dossier.champs_for_export(dossier.champs, type_champs).size).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when procedure brouillon" do
|
||||
|
|
|
@ -1,42 +1,43 @@
|
|||
describe ProcedureRevision do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private, :with_repetition) }
|
||||
let(:revision) { procedure.active_revision }
|
||||
let(:type_de_champ) { revision.types_de_champ_public.first }
|
||||
let(:type_de_champ_private) { revision.types_de_champ_private.first }
|
||||
let(:draft) { procedure.draft_revision }
|
||||
let(:type_de_champ_public) { draft.types_de_champ_public.first }
|
||||
let(:type_de_champ_private) { draft.types_de_champ_private.first }
|
||||
let(:type_de_champ_repetition) do
|
||||
type_de_champ = revision.types_de_champ_public.repetition.first
|
||||
type_de_champ.update(stable_id: 3333)
|
||||
type_de_champ
|
||||
repetition = draft.types_de_champ_public.repetition.first
|
||||
repetition.update(stable_id: 3333)
|
||||
repetition
|
||||
end
|
||||
|
||||
describe '#add_type_de_champ' do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private, :with_repetition) }
|
||||
|
||||
it 'type_de_champ' do
|
||||
expect(revision.types_de_champ_public.size).to eq(2)
|
||||
new_type_de_champ = revision.add_type_de_champ({
|
||||
expect(draft.types_de_champ_public.size).to eq(2)
|
||||
new_type_de_champ = draft.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un champ text"
|
||||
})
|
||||
revision.reload
|
||||
expect(revision.types_de_champ_public.size).to eq(3)
|
||||
expect(revision.types_de_champ_public.last).to eq(new_type_de_champ)
|
||||
expect(revision.revision_types_de_champ_public.last.position).to eq(2)
|
||||
expect(revision.revision_types_de_champ_public.last.type_de_champ).to eq(new_type_de_champ)
|
||||
draft.reload
|
||||
expect(draft.types_de_champ_public.size).to eq(3)
|
||||
expect(draft.types_de_champ_public.last).to eq(new_type_de_champ)
|
||||
expect(draft.revision_types_de_champ_public.last.position).to eq(2)
|
||||
expect(draft.revision_types_de_champ_public.last.type_de_champ).to eq(new_type_de_champ)
|
||||
end
|
||||
|
||||
it 'type_de_champ_private' do
|
||||
expect(revision.types_de_champ_private.size).to eq(1)
|
||||
revision.add_type_de_champ({
|
||||
expect(draft.types_de_champ_private.size).to eq(1)
|
||||
draft.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un champ text",
|
||||
private: true
|
||||
})
|
||||
revision.reload
|
||||
expect(revision.types_de_champ_private.size).to eq(2)
|
||||
draft.reload
|
||||
expect(draft.types_de_champ_private.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'type_de_champ_repetition' do
|
||||
expect(type_de_champ_repetition.types_de_champ.size).to eq(1)
|
||||
revision.add_type_de_champ({
|
||||
draft.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un champ text",
|
||||
parent_id: type_de_champ_repetition.stable_id
|
||||
|
@ -47,131 +48,159 @@ describe ProcedureRevision do
|
|||
|
||||
describe '#move_type_de_champ' do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, types_de_champ_count: 4) }
|
||||
let(:last_type_de_champ) { revision.types_de_champ_public.last }
|
||||
|
||||
it 'move down' do
|
||||
expect(revision.types_de_champ_public.index(type_de_champ)).to eq(0)
|
||||
type_de_champ.update(order_place: nil)
|
||||
revision.move_type_de_champ(type_de_champ.stable_id, 2)
|
||||
revision.reload
|
||||
expect(revision.types_de_champ_public.index(type_de_champ)).to eq(2)
|
||||
expect(revision.procedure.types_de_champ.index(type_de_champ)).to eq(2)
|
||||
expect(revision.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(type_de_champ)).to eq(2)
|
||||
end
|
||||
|
||||
it 'move up' do
|
||||
expect(revision.types_de_champ_public.index(last_type_de_champ)).to eq(3)
|
||||
last_type_de_champ.update(order_place: nil)
|
||||
revision.move_type_de_champ(last_type_de_champ.stable_id, 0)
|
||||
revision.reload
|
||||
expect(revision.types_de_champ_public.index(last_type_de_champ)).to eq(0)
|
||||
expect(revision.procedure.types_de_champ.index(last_type_de_champ)).to eq(0)
|
||||
expect(revision.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(last_type_de_champ)).to eq(0)
|
||||
end
|
||||
|
||||
context 'repetition' do
|
||||
let(:procedure) { create(:procedure, :with_repetition) }
|
||||
let(:type_de_champ) { type_de_champ_repetition.types_de_champ.first }
|
||||
let(:last_type_de_champ) { type_de_champ_repetition.types_de_champ.last }
|
||||
|
||||
before do
|
||||
revision.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un champ text",
|
||||
parent_id: type_de_champ_repetition.stable_id
|
||||
})
|
||||
revision.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un champ text",
|
||||
parent_id: type_de_champ_repetition.stable_id
|
||||
})
|
||||
type_de_champ_repetition.reload
|
||||
end
|
||||
let(:last_type_de_champ) { draft.types_de_champ_public.last }
|
||||
|
||||
context 'with 4 types de champ publiques' do
|
||||
it 'move down' do
|
||||
expect(type_de_champ_repetition.types_de_champ.index(type_de_champ)).to eq(0)
|
||||
revision.move_type_de_champ(type_de_champ.stable_id, 2)
|
||||
type_de_champ_repetition.reload
|
||||
expect(type_de_champ_repetition.types_de_champ.index(type_de_champ)).to eq(2)
|
||||
expect(draft.types_de_champ_public.index(type_de_champ_public)).to eq(0)
|
||||
type_de_champ_public.update(order_place: nil)
|
||||
draft.move_type_de_champ(type_de_champ_public.stable_id, 2)
|
||||
draft.reload
|
||||
expect(draft.types_de_champ_public.index(type_de_champ_public)).to eq(2)
|
||||
expect(draft.procedure.types_de_champ.index(type_de_champ_public)).to eq(2)
|
||||
expect(draft.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(type_de_champ_public)).to eq(2)
|
||||
end
|
||||
|
||||
it 'move up' do
|
||||
expect(type_de_champ_repetition.types_de_champ.index(last_type_de_champ)).to eq(2)
|
||||
revision.move_type_de_champ(last_type_de_champ.stable_id, 0)
|
||||
expect(draft.types_de_champ_public.index(last_type_de_champ)).to eq(3)
|
||||
last_type_de_champ.update(order_place: nil)
|
||||
draft.move_type_de_champ(last_type_de_champ.stable_id, 0)
|
||||
draft.reload
|
||||
expect(draft.types_de_champ_public.index(last_type_de_champ)).to eq(0)
|
||||
expect(draft.procedure.types_de_champ.index(last_type_de_champ)).to eq(0)
|
||||
expect(draft.procedure.types_de_champ_for_procedure_presentation.not_repetition.index(last_type_de_champ)).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a champ repetition repetition' do
|
||||
let(:procedure) { create(:procedure, :with_repetition) }
|
||||
|
||||
let!(:second_child) do
|
||||
draft.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "second child",
|
||||
parent_id: type_de_champ_repetition.stable_id
|
||||
})
|
||||
end
|
||||
|
||||
let!(:last_child) do
|
||||
draft.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "last child",
|
||||
parent_id: type_de_champ_repetition.stable_id
|
||||
})
|
||||
end
|
||||
|
||||
it 'move down' do
|
||||
expect(type_de_champ_repetition.types_de_champ.index(second_child)).to eq(1)
|
||||
draft.move_type_de_champ(second_child.stable_id, 2)
|
||||
type_de_champ_repetition.reload
|
||||
expect(type_de_champ_repetition.types_de_champ.index(last_type_de_champ)).to eq(0)
|
||||
expect(type_de_champ_repetition.types_de_champ.index(second_child)).to eq(2)
|
||||
end
|
||||
|
||||
it 'move up' do
|
||||
expect(type_de_champ_repetition.types_de_champ.index(last_child)).to eq(2)
|
||||
draft.move_type_de_champ(last_child.stable_id, 0)
|
||||
type_de_champ_repetition.reload
|
||||
expect(type_de_champ_repetition.types_de_champ.index(last_child)).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove_type_de_champ' do
|
||||
it 'type_de_champ' do
|
||||
expect(revision.types_de_champ_public.size).to eq(2)
|
||||
revision.remove_type_de_champ(type_de_champ.stable_id)
|
||||
procedure.reload
|
||||
expect(revision.types_de_champ_public.size).to eq(1)
|
||||
context 'for a classic tdc' do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) }
|
||||
|
||||
it 'type_de_champ' do
|
||||
draft.remove_type_de_champ(type_de_champ_public.stable_id)
|
||||
|
||||
expect(draft.types_de_champ_public).to be_empty
|
||||
end
|
||||
|
||||
it 'type_de_champ_private' do
|
||||
draft.remove_type_de_champ(type_de_champ_private.stable_id)
|
||||
|
||||
expect(draft.types_de_champ_private).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it 'type_de_champ_private' do
|
||||
expect(revision.types_de_champ_private.size).to eq(1)
|
||||
revision.remove_type_de_champ(type_de_champ_private.stable_id)
|
||||
expect(revision.types_de_champ_private.size).to eq(0)
|
||||
end
|
||||
context 'for a type_de_champ_repetition' do
|
||||
let(:procedure) { create(:procedure, :with_repetition) }
|
||||
|
||||
it 'type_de_champ_repetition' do
|
||||
expect(type_de_champ_repetition.types_de_champ.size).to eq(1)
|
||||
expect(revision.types_de_champ_public.size).to eq(2)
|
||||
revision.remove_type_de_champ(type_de_champ_repetition.types_de_champ.first.stable_id)
|
||||
type_de_champ_repetition.reload
|
||||
expect(type_de_champ_repetition.types_de_champ.size).to eq(0)
|
||||
expect(revision.types_de_champ_public.size).to eq(2)
|
||||
it 'can remove its children' do
|
||||
draft.remove_type_de_champ(type_de_champ_repetition.types_de_champ.first.stable_id)
|
||||
|
||||
expect(type_de_champ_repetition.types_de_champ).to be_empty
|
||||
expect(draft.types_de_champ_public.size).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_new_revision' do
|
||||
let(:new_revision) { procedure.create_new_revision }
|
||||
let(:new_draft) { procedure.create_new_revision }
|
||||
|
||||
before { new_revision.save }
|
||||
context 'from a simple procedure' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
|
||||
it 'should be part of procedure' do
|
||||
expect(new_revision.procedure).to eq(revision.procedure)
|
||||
expect(procedure.revisions.count).to eq(2)
|
||||
expect(procedure.revisions).to eq([revision, new_revision])
|
||||
it 'should be part of procedure' do
|
||||
expect(new_draft.procedure).to eq(draft.procedure)
|
||||
expect(procedure.revisions.count).to eq(2)
|
||||
expect(procedure.revisions).to eq([draft, new_draft])
|
||||
end
|
||||
end
|
||||
|
||||
it 'should have types_de_champ' do
|
||||
expect(new_revision.types_de_champ_public.count).to eq(2)
|
||||
expect(new_revision.types_de_champ_private.count).to eq(1)
|
||||
expect(new_revision.types_de_champ_public).to eq(revision.types_de_champ_public)
|
||||
expect(new_revision.types_de_champ_private).to eq(revision.types_de_champ_private)
|
||||
context 'with simple tdc' do
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) }
|
||||
|
||||
expect(new_revision.revision_types_de_champ_public.count).to eq(2)
|
||||
expect(new_revision.revision_types_de_champ_private.count).to eq(1)
|
||||
expect(new_revision.revision_types_de_champ_public.count).to eq(revision.revision_types_de_champ_public.count)
|
||||
expect(new_revision.revision_types_de_champ_private.count).to eq(revision.revision_types_de_champ_private.count)
|
||||
expect(new_revision.revision_types_de_champ_public).not_to eq(revision.revision_types_de_champ_public)
|
||||
expect(new_revision.revision_types_de_champ_private).not_to eq(revision.revision_types_de_champ_private)
|
||||
it 'should have the same tdcs with different links' do
|
||||
expect(new_draft.types_de_champ_public.count).to eq(1)
|
||||
expect(new_draft.types_de_champ_private.count).to eq(1)
|
||||
expect(new_draft.types_de_champ_public).to eq(draft.types_de_champ_public)
|
||||
expect(new_draft.types_de_champ_private).to eq(draft.types_de_champ_private)
|
||||
|
||||
expect(new_draft.revision_types_de_champ_public.count).to eq(1)
|
||||
expect(new_draft.revision_types_de_champ_private.count).to eq(1)
|
||||
expect(new_draft.revision_types_de_champ_public).not_to eq(draft.revision_types_de_champ_public)
|
||||
expect(new_draft.revision_types_de_champ_private).not_to eq(draft.revision_types_de_champ_private)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with repetition_type_de_champ' do
|
||||
let(:procedure) { create(:procedure, :with_repetition) }
|
||||
|
||||
it 'should have the same tdcs with different links' do
|
||||
expect(new_draft.types_de_champ.count).to eq(2)
|
||||
expect(new_draft.types_de_champ).to eq(draft.types_de_champ)
|
||||
|
||||
new_repetition, new_child = new_draft.types_de_champ.partition(&:repetition?).map(&:first)
|
||||
|
||||
parent = new_draft.revision_types_de_champ.find_by(type_de_champ: new_repetition)
|
||||
child = new_draft.revision_types_de_champ.find_by(type_de_champ: new_child)
|
||||
|
||||
expect(child.parent_id).to eq(parent.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#compare' do
|
||||
let(:type_de_champ_first) { revision.types_de_champ_public.first }
|
||||
let(:type_de_champ_second) { revision.types_de_champ_public.second }
|
||||
let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private, :with_repetition) }
|
||||
let(:type_de_champ_first) { draft.types_de_champ_public.first }
|
||||
let(:type_de_champ_second) { draft.types_de_champ_public.second }
|
||||
|
||||
it 'type_de_champ' do
|
||||
expect(new_revision.types_de_champ_public.size).to eq(2)
|
||||
new_type_de_champ = new_revision.add_type_de_champ({
|
||||
expect(new_draft.types_de_champ_public.size).to eq(2)
|
||||
new_type_de_champ = new_draft.add_type_de_champ({
|
||||
type_champ: TypeDeChamp.type_champs.fetch(:text),
|
||||
libelle: "Un champ text"
|
||||
})
|
||||
revision.reload
|
||||
new_revision.reload
|
||||
expect(new_revision.types_de_champ_public.size).to eq(3)
|
||||
expect(new_revision.types_de_champ_public.last).to eq(new_type_de_champ)
|
||||
expect(new_revision.revision_types_de_champ_public.last.position).to eq(2)
|
||||
expect(new_revision.revision_types_de_champ_public.last.type_de_champ).to eq(new_type_de_champ)
|
||||
expect(new_revision.revision_types_de_champ_public.last.type_de_champ.revision).to eq(new_revision)
|
||||
expect(procedure.active_revision.different_from?(new_revision)).to be_truthy
|
||||
expect(procedure.active_revision.compare(new_revision)).to eq([
|
||||
draft.reload
|
||||
new_draft.reload
|
||||
expect(new_draft.types_de_champ_public.size).to eq(3)
|
||||
expect(new_draft.types_de_champ_public.last).to eq(new_type_de_champ)
|
||||
expect(new_draft.revision_types_de_champ_public.last.position).to eq(2)
|
||||
expect(new_draft.revision_types_de_champ_public.last.type_de_champ).to eq(new_type_de_champ)
|
||||
expect(new_draft.revision_types_de_champ_public.last.type_de_champ.revision).to eq(new_draft)
|
||||
expect(procedure.active_revision.different_from?(new_draft)).to be_truthy
|
||||
expect(procedure.active_revision.compare(new_draft)).to eq([
|
||||
{
|
||||
model: :type_de_champ,
|
||||
op: :add,
|
||||
|
@ -181,8 +210,8 @@ describe ProcedureRevision do
|
|||
}
|
||||
])
|
||||
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ_public.first.stable_id).update(libelle: 'modifier le libelle')
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
new_draft.find_or_clone_type_de_champ(new_draft.types_de_champ_public.first.stable_id).update(libelle: 'modifier le libelle')
|
||||
expect(procedure.active_revision.compare(new_draft.reload)).to eq([
|
||||
{
|
||||
model: :type_de_champ,
|
||||
op: :update,
|
||||
|
@ -201,10 +230,10 @@ describe ProcedureRevision do
|
|||
stable_id: new_type_de_champ.stable_id
|
||||
}
|
||||
])
|
||||
expect(new_revision.types_de_champ_public.first.revision).to eq(new_revision)
|
||||
expect(new_draft.types_de_champ_public.first.revision).to eq(new_draft)
|
||||
|
||||
new_revision.move_type_de_champ(new_revision.types_de_champ_public.second.stable_id, 2)
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
new_draft.move_type_de_champ(new_draft.types_de_champ_public.second.stable_id, 2)
|
||||
expect(procedure.active_revision.compare(new_draft.reload)).to eq([
|
||||
{
|
||||
model: :type_de_champ,
|
||||
op: :update,
|
||||
|
@ -232,10 +261,10 @@ describe ProcedureRevision do
|
|||
stable_id: type_de_champ_second.stable_id
|
||||
}
|
||||
])
|
||||
expect(new_revision.types_de_champ_public.last.revision).to eq(revision)
|
||||
expect(new_draft.types_de_champ_public.last.revision).to eq(draft)
|
||||
|
||||
new_revision.remove_type_de_champ(new_revision.types_de_champ_public.first.stable_id)
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
new_draft.remove_type_de_champ(new_draft.types_de_champ_public.first.stable_id)
|
||||
expect(procedure.active_revision.compare(new_draft.reload)).to eq([
|
||||
{
|
||||
model: :type_de_champ,
|
||||
op: :remove,
|
||||
|
@ -252,9 +281,9 @@ describe ProcedureRevision do
|
|||
}
|
||||
])
|
||||
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ_public.last.stable_id).update(description: 'une description')
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ_public.last.stable_id).update(mandatory: true)
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
new_draft.find_or_clone_type_de_champ(new_draft.types_de_champ_public.last.stable_id).update(description: 'une description')
|
||||
new_draft.find_or_clone_type_de_champ(new_draft.types_de_champ_public.last.stable_id).update(mandatory: true)
|
||||
expect(procedure.active_revision.compare(new_draft.reload)).to eq([
|
||||
{
|
||||
model: :type_de_champ,
|
||||
op: :remove,
|
||||
|
@ -291,9 +320,9 @@ describe ProcedureRevision do
|
|||
}
|
||||
])
|
||||
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ_public.last.types_de_champ.first.stable_id).update(type_champ: :drop_down_list)
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ_public.last.types_de_champ.first.stable_id).update(drop_down_options: ['one', 'two'])
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
new_draft.find_or_clone_type_de_champ(new_draft.types_de_champ_public.last.types_de_champ.first.stable_id).update(type_champ: :drop_down_list)
|
||||
new_draft.find_or_clone_type_de_champ(new_draft.types_de_champ_public.last.types_de_champ.first.stable_id).update(drop_down_options: ['one', 'two'])
|
||||
expect(procedure.active_revision.compare(new_draft.reload)).to eq([
|
||||
{
|
||||
model: :type_de_champ,
|
||||
op: :remove,
|
||||
|
@ -336,7 +365,7 @@ describe ProcedureRevision do
|
|||
private: false,
|
||||
from: "text",
|
||||
to: "drop_down_list",
|
||||
stable_id: new_revision.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
stable_id: new_draft.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
},
|
||||
{
|
||||
model: :type_de_champ,
|
||||
|
@ -346,13 +375,13 @@ describe ProcedureRevision do
|
|||
private: false,
|
||||
from: [],
|
||||
to: ["one", "two"],
|
||||
stable_id: new_revision.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
stable_id: new_draft.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
}
|
||||
])
|
||||
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ_public.last.types_de_champ.first.stable_id).update(type_champ: :carte)
|
||||
new_revision.find_or_clone_type_de_champ(new_revision.types_de_champ_public.last.types_de_champ.first.stable_id).update(options: { cadastres: true, znieff: true })
|
||||
expect(procedure.active_revision.compare(new_revision.reload)).to eq([
|
||||
new_draft.find_or_clone_type_de_champ(new_draft.types_de_champ_public.last.types_de_champ.first.stable_id).update(type_champ: :carte)
|
||||
new_draft.find_or_clone_type_de_champ(new_draft.types_de_champ_public.last.types_de_champ.first.stable_id).update(options: { cadastres: true, znieff: true })
|
||||
expect(procedure.active_revision.compare(new_draft.reload)).to eq([
|
||||
{
|
||||
model: :type_de_champ,
|
||||
op: :remove,
|
||||
|
@ -395,7 +424,7 @@ describe ProcedureRevision do
|
|||
private: false,
|
||||
from: "text",
|
||||
to: "carte",
|
||||
stable_id: new_revision.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
stable_id: new_draft.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
},
|
||||
{
|
||||
model: :type_de_champ,
|
||||
|
@ -405,7 +434,7 @@ describe ProcedureRevision do
|
|||
private: false,
|
||||
from: [],
|
||||
to: [:cadastres, :znieff],
|
||||
stable_id: new_revision.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
stable_id: new_draft.types_de_champ_public.last.types_de_champ.first.stable_id
|
||||
}
|
||||
])
|
||||
end
|
||||
|
|
|
@ -90,7 +90,8 @@ shared_examples 'type_de_champ_spec' do
|
|||
end
|
||||
|
||||
describe 'changing the type_champ from a repetition' do
|
||||
let(:tdc) { create(:type_de_champ_repetition, :with_types_de_champ) }
|
||||
let!(:procedure) { create(:procedure) }
|
||||
let(:tdc) { create(:type_de_champ_repetition, :with_types_de_champ, procedure: procedure) }
|
||||
|
||||
before do
|
||||
tdc.update_attribute('type_champ', target_type_champ)
|
||||
|
|
|
@ -1,224 +0,0 @@
|
|||
describe AdministrateurUsageStatisticsService do
|
||||
describe '#administrateur_stats' do
|
||||
let(:service) { AdministrateurUsageStatisticsService.new }
|
||||
subject { service.send(:administrateur_stats, administrateur) }
|
||||
|
||||
before { Timecop.freeze(Time.zone.now) }
|
||||
after { Timecop.return }
|
||||
|
||||
context 'for an administrateur that has nothing' do
|
||||
let(:administrateur) { create(:administrateur) }
|
||||
|
||||
it do
|
||||
is_expected.to eq(
|
||||
ds_sign_in_count: 0,
|
||||
ds_created_at: Time.zone.now,
|
||||
ds_active: false,
|
||||
ds_id: administrateur.id,
|
||||
nb_services: 0,
|
||||
nb_instructeurs: 0,
|
||||
ds_nb_demarches_actives: 0,
|
||||
ds_nb_demarches_archives: 0,
|
||||
ds_nb_demarches_brouillons: 0,
|
||||
nb_demarches_test: 0,
|
||||
nb_demarches_prod: 0,
|
||||
nb_demarches_prod_20: 0,
|
||||
nb_dossiers: 0,
|
||||
nb_dossiers_max: 0,
|
||||
nb_dossiers_traite: 0,
|
||||
nb_dossiers_dossier_en_instruction: 0,
|
||||
admin_roi_low: 0,
|
||||
admin_roi_high: 0
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for an administrateur that has plenty of things' do
|
||||
let(:administrateur) do
|
||||
create(:administrateur,
|
||||
user: create(:user, :with_strong_password, sign_in_count: 17, current_sign_in_at: Time.zone.local(2019, 3, 7), last_sign_in_at: Time.zone.local(2019, 2, 27)),
|
||||
services: [create(:service)],
|
||||
instructeurs: [create(:instructeur)])
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
ds_sign_in_count: 17,
|
||||
ds_current_sign_in_at: Time.zone.local(2019, 3, 7),
|
||||
ds_last_sign_in_at: Time.zone.local(2019, 2, 27),
|
||||
ds_created_at: Time.zone.now,
|
||||
ds_active: true,
|
||||
ds_id: administrateur.id,
|
||||
nb_services: 1,
|
||||
nb_instructeurs: 1
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'counting procedures and dossiers' do
|
||||
let(:administrateur) do
|
||||
create(:administrateur, procedures: [procedure])
|
||||
end
|
||||
|
||||
context 'with a freshly active procedure' do
|
||||
let(:procedure) { create(:procedure, :published) }
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
ds_nb_demarches_actives: 1,
|
||||
ds_nb_demarches_archives: 0,
|
||||
ds_nb_demarches_brouillons: 0,
|
||||
nb_demarches_test: 0,
|
||||
nb_demarches_prod: 0,
|
||||
nb_demarches_prod_20: 0,
|
||||
nb_dossiers: 0,
|
||||
nb_dossiers_max: 0,
|
||||
nb_dossiers_traite: 0,
|
||||
nb_dossiers_dossier_en_instruction: 0,
|
||||
admin_roi_low: 0,
|
||||
admin_roi_high: 0
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a procedure close' do
|
||||
let(:procedure) { create(:procedure, :closed) }
|
||||
let!(:dossiers) do
|
||||
(1..7).flat_map do
|
||||
[
|
||||
create(:dossier, :en_construction, procedure: procedure),
|
||||
create(:dossier, :en_instruction, procedure: procedure),
|
||||
create(:dossier, :accepte, procedure: procedure)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
ds_nb_demarches_actives: 0,
|
||||
ds_nb_demarches_archives: 1,
|
||||
ds_nb_demarches_brouillons: 0,
|
||||
nb_demarches_test: 0,
|
||||
nb_demarches_prod: 1,
|
||||
nb_demarches_prod_20: 1,
|
||||
nb_dossiers: 21,
|
||||
nb_dossiers_max: 21,
|
||||
nb_dossiers_traite: 7,
|
||||
nb_dossiers_dossier_en_instruction: 7,
|
||||
admin_roi_low: 147,
|
||||
admin_roi_high: 357
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a procedure brouillon' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
ds_nb_demarches_actives: 0,
|
||||
ds_nb_demarches_archives: 0,
|
||||
ds_nb_demarches_brouillons: 1,
|
||||
nb_demarches_test: 0,
|
||||
nb_demarches_prod: 0,
|
||||
nb_demarches_prod_20: 0,
|
||||
nb_dossiers: 0,
|
||||
nb_dossiers_max: 0,
|
||||
nb_dossiers_traite: 0,
|
||||
nb_dossiers_dossier_en_instruction: 0,
|
||||
admin_roi_low: 0,
|
||||
admin_roi_high: 0
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a procedure en test' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
let!(:dossiers) do
|
||||
(1..7).flat_map do
|
||||
[
|
||||
create(:dossier, :en_construction, procedure: procedure),
|
||||
create(:dossier, :en_instruction, procedure: procedure),
|
||||
create(:dossier, :accepte, procedure: procedure)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
ds_nb_demarches_actives: 0,
|
||||
ds_nb_demarches_archives: 0,
|
||||
ds_nb_demarches_brouillons: 1,
|
||||
nb_demarches_test: 1,
|
||||
nb_demarches_prod: 0,
|
||||
nb_demarches_prod_20: 0,
|
||||
nb_dossiers: 0,
|
||||
nb_dossiers_max: 0,
|
||||
nb_dossiers_traite: 0,
|
||||
nb_dossiers_dossier_en_instruction: 0,
|
||||
admin_roi_low: 0,
|
||||
admin_roi_high: 0
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a procedure en prod' do
|
||||
let(:procedure) { create(:procedure, :published) }
|
||||
let!(:dossiers) do
|
||||
[
|
||||
create(:dossier, :en_construction, procedure: procedure),
|
||||
create(:dossier, :en_instruction, procedure: procedure),
|
||||
create(:dossier, :accepte, procedure: procedure)
|
||||
]
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
ds_nb_demarches_actives: 1,
|
||||
ds_nb_demarches_archives: 0,
|
||||
ds_nb_demarches_brouillons: 0,
|
||||
nb_demarches_test: 0,
|
||||
nb_demarches_prod: 1,
|
||||
nb_demarches_prod_20: 0,
|
||||
nb_dossiers: 3,
|
||||
nb_dossiers_max: 3,
|
||||
nb_dossiers_traite: 1,
|
||||
nb_dossiers_dossier_en_instruction: 1,
|
||||
admin_roi_low: 21,
|
||||
admin_roi_high: 51
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a procedure en prod and more than 20 dossiers' do
|
||||
let(:procedure) { create(:procedure, :published) }
|
||||
let!(:dossiers) do
|
||||
(1..7).flat_map do
|
||||
[
|
||||
create(:dossier, :en_construction, procedure: procedure),
|
||||
create(:dossier, :en_instruction, procedure: procedure),
|
||||
create(:dossier, :accepte, procedure: procedure)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
ds_nb_demarches_actives: 1,
|
||||
ds_nb_demarches_archives: 0,
|
||||
ds_nb_demarches_brouillons: 0,
|
||||
nb_demarches_test: 0,
|
||||
nb_demarches_prod: 1,
|
||||
nb_demarches_prod_20: 1,
|
||||
nb_dossiers: 21,
|
||||
nb_dossiers_max: 21,
|
||||
nb_dossiers_traite: 7,
|
||||
nb_dossiers_dossier_en_instruction: 7,
|
||||
admin_roi_low: 147,
|
||||
admin_roi_high: 357
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,11 @@ describe DownloadableFileService do
|
|||
let(:procedure) { create(:procedure, :published) }
|
||||
let(:service) { ProcedureArchiveService.new(procedure) }
|
||||
|
||||
before do
|
||||
FileUtils.mkdir_p('/tmp/test_archive_creation')
|
||||
stub_const("DownloadableFileService::ARCHIVE_CREATION_DIR", '/tmp/test_archive_creation')
|
||||
end
|
||||
|
||||
describe '#download_and_zip' do
|
||||
let(:archive) { build(:archive, id: '3') }
|
||||
let(:filename) { service.send(:zip_root_folder, archive) }
|
||||
|
|
|
@ -32,7 +32,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
|
|||
end
|
||||
|
||||
context "when the procedure is discarded with a dossier en construction" do
|
||||
let(:procedure) { create(:procedure, :discarded) }
|
||||
let(:procedure) { create(:procedure, :with_service, :discarded) }
|
||||
let(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
|
||||
it 'affiche que la démarche est supprimée' do
|
||||
|
@ -46,7 +46,7 @@ describe 'users/dossiers/show/header.html.haml', type: :view do
|
|||
end
|
||||
|
||||
context "when the procedure is discarded with a dossier terminé" do
|
||||
let(:procedure) { create(:procedure, :discarded) }
|
||||
let(:procedure) { create(:procedure, :with_service, :discarded) }
|
||||
let(:dossier) { create(:dossier, :accepte, procedure: procedure) }
|
||||
|
||||
it 'affiche que la démarche est supprimée' do
|
||||
|
|
Loading…
Reference in a new issue