commit
7c4e26d1b5
58 changed files with 2971 additions and 4369 deletions
|
@ -415,7 +415,7 @@ GEM
|
|||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
logstash-event (1.2.02)
|
||||
loofah (2.12.0)
|
||||
loofah (2.13.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -432,7 +432,7 @@ GEM
|
|||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.6.1)
|
||||
minitest (5.14.4)
|
||||
minitest (5.15.0)
|
||||
momentjs-rails (2.20.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.4.2)
|
||||
|
|
|
@ -12,4 +12,9 @@ $contact-padding: $default-space * 2;
|
|||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: $default-space;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
html {
|
||||
|
|
|
@ -565,3 +565,8 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: $dark-grey;
|
||||
}
|
||||
|
|
|
@ -14,14 +14,15 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
th {
|
||||
th,
|
||||
td.libelle {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
padding: (3 * $default-spacer) 2px;
|
||||
}
|
||||
|
||||
th.padded {
|
||||
padding-left: (2 * $default-spacer);
|
||||
&.padded {
|
||||
padding-left: (2 * $default-spacer);
|
||||
}
|
||||
}
|
||||
|
||||
&.hoverable {
|
||||
|
@ -38,7 +39,8 @@
|
|||
border-top: none;
|
||||
}
|
||||
|
||||
th {
|
||||
th,
|
||||
td.libelle {
|
||||
@include vertical-padding($default-spacer);
|
||||
|
||||
&.header-section {
|
||||
|
|
|
@ -125,7 +125,9 @@ module Administrateurs
|
|||
end
|
||||
|
||||
if procedure.routee?
|
||||
groupe_instructeur.instructeurs << instructeurs
|
||||
instructeurs.each do |instructeur|
|
||||
groupe_instructeur.add(instructeur)
|
||||
end
|
||||
|
||||
GroupeInstructeurMailer
|
||||
.add_instructeurs(groupe_instructeur, instructeurs, current_user.email)
|
||||
|
@ -139,7 +141,9 @@ module Administrateurs
|
|||
else
|
||||
|
||||
if instructeurs.present?
|
||||
procedure.defaut_groupe_instructeur.instructeurs << instructeurs
|
||||
instructeurs.each do |instructeur|
|
||||
procedure.defaut_groupe_instructeur.add(instructeur)
|
||||
end
|
||||
flash[:notice] = "Les instructeurs ont bien été affectés à la démarche"
|
||||
end
|
||||
end
|
||||
|
@ -158,7 +162,7 @@ module Administrateurs
|
|||
else
|
||||
instructeur = Instructeur.find(instructeur_id)
|
||||
if procedure.routee?
|
||||
if instructeur.remove_from_groupe_instructeur(groupe_instructeur)
|
||||
if groupe_instructeur.remove(instructeur)
|
||||
flash[:notice] = "L’instructeur « #{instructeur.email} » a été retiré du groupe."
|
||||
GroupeInstructeurMailer
|
||||
.remove_instructeur(groupe_instructeur, instructeur, current_user.email)
|
||||
|
@ -167,7 +171,7 @@ module Administrateurs
|
|||
flash[:alert] = "L’instructeur « #{instructeur.email} » n’est pas dans le groupe."
|
||||
end
|
||||
else
|
||||
if instructeur.remove_from_groupe_instructeur(procedure.defaut_groupe_instructeur)
|
||||
if procedure.defaut_groupe_instructeur.remove(instructeur)
|
||||
flash[:notice] = "L’instructeur a bien été désaffecté de la démarche"
|
||||
else
|
||||
flash[:alert] = "L’instructeur n’est pas affecté à la démarche"
|
||||
|
|
|
@ -20,7 +20,7 @@ module Instructeurs
|
|||
if groupe_instructeur.instructeurs.include?(instructeur)
|
||||
flash[:alert] = "L’instructeur « #{instructeur_email} » est déjà dans le groupe."
|
||||
else
|
||||
groupe_instructeur.instructeurs << instructeur
|
||||
groupe_instructeur.add(instructeur)
|
||||
flash[:notice] = "L’instructeur « #{instructeur_email} » a été affecté au groupe."
|
||||
GroupeInstructeurMailer
|
||||
.add_instructeurs(groupe_instructeur, [instructeur], current_user.email)
|
||||
|
@ -35,7 +35,7 @@ module Instructeurs
|
|||
flash[:alert] = "Suppression impossible : il doit y avoir au moins un instructeur dans le groupe"
|
||||
else
|
||||
instructeur = Instructeur.find(instructeur_id)
|
||||
if instructeur.remove_from_groupe_instructeur(groupe_instructeur)
|
||||
if groupe_instructeur.remove(instructeur)
|
||||
flash[:notice] = "L’instructeur « #{instructeur.email} » a été retiré du groupe."
|
||||
GroupeInstructeurMailer
|
||||
.remove_instructeur(groupe_instructeur, instructeur, current_user.email)
|
||||
|
|
|
@ -216,11 +216,13 @@ module Instructeurs
|
|||
|
||||
def email_notifications
|
||||
@procedure = procedure
|
||||
@assign_to = assign_to
|
||||
@assign_to = assign_tos.first
|
||||
end
|
||||
|
||||
def update_email_notifications
|
||||
assign_to.update!(assign_to_params)
|
||||
assign_tos.each do |assign_to|
|
||||
assign_to.update!(assign_to_params)
|
||||
end
|
||||
flash.notice = 'Vos notifications sont enregistrées.'
|
||||
redirect_to instructeur_procedure_path(procedure)
|
||||
end
|
||||
|
@ -290,10 +292,6 @@ module Instructeurs
|
|||
@exports = Export.find_for_groupe_instructeurs(groupe_instructeur_ids)
|
||||
end
|
||||
|
||||
def assign_to
|
||||
current_instructeur.assign_to.joins(:groupe_instructeur).find_by(groupe_instructeurs: { procedure: procedure })
|
||||
end
|
||||
|
||||
def assign_tos
|
||||
@assign_tos ||= current_instructeur
|
||||
.assign_to
|
||||
|
|
50
app/controllers/manager/zones_controller.rb
Normal file
50
app/controllers/manager/zones_controller.rb
Normal file
|
@ -0,0 +1,50 @@
|
|||
module Manager
|
||||
class ZonesController < Manager::ApplicationController
|
||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||
# For example, you may want to send an email after a foo is updated.
|
||||
#
|
||||
# def update
|
||||
# super
|
||||
# send_foo_updated_email(requested_resource)
|
||||
# end
|
||||
|
||||
# Override this method to specify custom lookup behavior.
|
||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||
# actions.
|
||||
#
|
||||
def find_resource(param)
|
||||
if param == "nil"
|
||||
NullZone.new
|
||||
else
|
||||
Zone.find(param)
|
||||
end
|
||||
end
|
||||
|
||||
# The result of this lookup will be available as `requested_resource`
|
||||
|
||||
# Override this if you have certain roles that require a subset
|
||||
# this will be used to set the records shown on the `index` action.
|
||||
#
|
||||
# def scoped_resource
|
||||
# if current_user.super_admin?
|
||||
# resource_class
|
||||
# else
|
||||
# resource_class.with_less_stuff
|
||||
# end
|
||||
# end
|
||||
|
||||
# Override `resource_params` if you want to transform the submitted
|
||||
# data before it's persisted. For example, the following would turn all
|
||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||
# and `dashboard`:
|
||||
#
|
||||
# def resource_params
|
||||
# params.require(resource_class.model_name.param_key).
|
||||
# permit(dashboard.permitted_attributes).
|
||||
# transform_values { |value| value == "" ? nil : value }
|
||||
# end
|
||||
|
||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
|
@ -14,9 +14,7 @@ module Users
|
|||
|
||||
def update_email
|
||||
requested_user = User.find_by(email: requested_email)
|
||||
|
||||
if requested_user.present?
|
||||
current_user.ask_for_merge(requested_user)
|
||||
if requested_user.present? && current_user.ask_for_merge(requested_user)
|
||||
current_user.update(unconfirmed_email: nil)
|
||||
|
||||
flash.notice = t('devise.registrations.update_needs_confirmation')
|
||||
|
|
|
@ -16,6 +16,7 @@ class ProcedureDashboard < Administrate::BaseDashboard
|
|||
id: Field::Number.with_options(searchable: true),
|
||||
libelle: Field::String,
|
||||
description: Field::String,
|
||||
zone: Field::BelongsTo,
|
||||
lien_site_web: Field::String, # TODO: use Field::Url when administrate-v0.12 will be released
|
||||
organisation: Field::String,
|
||||
direction: Field::String,
|
||||
|
@ -47,6 +48,7 @@ class ProcedureDashboard < Administrate::BaseDashboard
|
|||
:id,
|
||||
:created_at,
|
||||
:libelle,
|
||||
:zone,
|
||||
:service,
|
||||
:dossiers,
|
||||
:published_at,
|
||||
|
@ -64,6 +66,7 @@ class ProcedureDashboard < Administrate::BaseDashboard
|
|||
:lien_site_web,
|
||||
:organisation,
|
||||
:direction,
|
||||
:zone,
|
||||
:service,
|
||||
:created_at,
|
||||
:updated_at,
|
||||
|
|
53
app/dashboards/zone_dashboard.rb
Normal file
53
app/dashboards/zone_dashboard.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
require "administrate/base_dashboard"
|
||||
|
||||
class ZoneDashboard < Administrate::BaseDashboard
|
||||
# ATTRIBUTE_TYPES
|
||||
# a hash that describes the type of each of the model's fields.
|
||||
#
|
||||
# Each different type represents an Administrate::Field object,
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
procedures: Field::HasMany,
|
||||
id: Field::Number,
|
||||
acronym: Field::String,
|
||||
label: Field::String,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
# COLLECTION_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's index page.
|
||||
#
|
||||
# By default, it's limited to four items to reduce clutter on index pages.
|
||||
# Feel free to add, remove, or rearrange items.
|
||||
COLLECTION_ATTRIBUTES = [:procedures, :id, :acronym, :label].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = [:procedures, :id, :acronym, :label, :created_at, :updated_at].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = [:procedures, :acronym, :label].freeze
|
||||
|
||||
# COLLECTION_FILTERS
|
||||
# a hash that defines filters that can be used while searching via the search
|
||||
# field of the dashboard.
|
||||
#
|
||||
# For example to add an option to search for open resources by typing "open:"
|
||||
# in the search field:
|
||||
#
|
||||
# COLLECTION_FILTERS = {
|
||||
# open: ->(resources) { resources.where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how zones are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(zone)
|
||||
"Zone #{zone.label}"
|
||||
end
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
import React, { Suspense, lazy } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Loader = () => <div className="spinner left" />;
|
||||
|
||||
function LazyLoad({ component: Component, ...props }) {
|
||||
return (
|
||||
<Suspense fallback={<Loader />}>
|
||||
<Component {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
LazyLoad.propTypes = {
|
||||
component: PropTypes.object
|
||||
};
|
||||
|
||||
export default function Loadable(loader) {
|
||||
const LazyComponent = lazy(loader);
|
||||
|
||||
return function PureComponent(props) {
|
||||
return <LazyLoad component={LazyComponent} {...props} />;
|
||||
};
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/Chartkick'));
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/ComboAdresseSearch'));
|
|
@ -1,5 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() =>
|
||||
import('../components/ComboAnnuaireEducationSearch')
|
||||
);
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/ComboCommunesSearch'));
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/ComboDepartementsSearch'));
|
|
@ -1,5 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() =>
|
||||
import('../components/ComboMultipleDropdownList')
|
||||
);
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/ComboPaysSearch'));
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/ComboRegionsSearch'));
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/MapEditor'));
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/MapReader'));
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/Trix'));
|
|
@ -1,3 +0,0 @@
|
|||
import Loadable from '../components/Loadable';
|
||||
|
||||
export default Loadable(() => import('../components/TypesDeChampEditor'));
|
|
@ -2,7 +2,6 @@ import '../shared/polyfills';
|
|||
import Rails from '@rails/ujs';
|
||||
import * as ActiveStorage from '@rails/activestorage';
|
||||
import 'whatwg-fetch'; // window.fetch polyfill
|
||||
import ReactRailsUJS from 'react_ujs';
|
||||
|
||||
import '../shared/page-update-event';
|
||||
import '../shared/activestorage/ujs';
|
||||
|
@ -48,6 +47,38 @@ import {
|
|||
showNewAccountPasswordConfirmation
|
||||
} from '../new_design/fc-fusion';
|
||||
|
||||
import {
|
||||
registerReactComponents,
|
||||
Loadable
|
||||
} from '../shared/register-react-components';
|
||||
|
||||
registerReactComponents({
|
||||
Chartkick: Loadable(() => import('../components/Chartkick')),
|
||||
ComboAdresseSearch: Loadable(() =>
|
||||
import('../components/ComboAdresseSearch')
|
||||
),
|
||||
ComboAnnuaireEducationSearch: Loadable(() =>
|
||||
import('../components/ComboAnnuaireEducationSearch')
|
||||
),
|
||||
ComboCommunesSearch: Loadable(() =>
|
||||
import('../components/ComboCommunesSearch')
|
||||
),
|
||||
ComboDepartementsSearch: Loadable(() =>
|
||||
import('../components/ComboDepartementsSearch')
|
||||
),
|
||||
ComboMultipleDropdownList: Loadable(() =>
|
||||
import('../components/ComboMultipleDropdownList')
|
||||
),
|
||||
ComboPaysSearch: Loadable(() => import('../components/ComboPaysSearch')),
|
||||
ComboRegionsSearch: Loadable(() =>
|
||||
import('../components/ComboRegionsSearch')
|
||||
),
|
||||
MapEditor: Loadable(() => import('../components/MapEditor')),
|
||||
MapReader: Loadable(() => import('../components/MapReader')),
|
||||
Trix: Loadable(() => import('../components/Trix')),
|
||||
TypesDeChampEditor: Loadable(() => import('../components/TypesDeChampEditor'))
|
||||
});
|
||||
|
||||
// This is the global application namespace where we expose helpers used from rails views
|
||||
const DS = {
|
||||
fire: (eventName, data) => Rails.fire(document, eventName, data),
|
||||
|
@ -69,7 +100,3 @@ ActiveStorage.start();
|
|||
|
||||
// Expose globals
|
||||
window.DS = window.DS || DS;
|
||||
|
||||
// eslint-disable-next-line no-undef, react-hooks/rules-of-hooks
|
||||
ReactRailsUJS.useContext(require.context('loaders', true));
|
||||
addEventListener('ds:page:update', ReactRailsUJS.handleMount);
|
||||
|
|
108
app/javascript/shared/register-react-components.jsx
Normal file
108
app/javascript/shared/register-react-components.jsx
Normal file
|
@ -0,0 +1,108 @@
|
|||
import React, { Suspense, lazy, createElement } from 'react';
|
||||
import { render } from 'react-dom';
|
||||
|
||||
// This attribute holds the name of component which should be mounted
|
||||
// example: `data-react-class="MyApp.Items.EditForm"`
|
||||
const CLASS_NAME_ATTR = 'data-react-class';
|
||||
|
||||
// This attribute holds JSON stringified props for initializing the component
|
||||
// example: `data-react-props="{\"item\": { \"id\": 1, \"name\": \"My Item\"} }"`
|
||||
const PROPS_ATTR = 'data-react-props';
|
||||
|
||||
// A unique identifier to identify a node
|
||||
const CACHE_ID_ATTR = 'data-react-cache-id';
|
||||
|
||||
const CLASS_NAME_SELECTOR = `[${CLASS_NAME_ATTR}]`;
|
||||
|
||||
// helper method for the mount and unmount methods to find the
|
||||
// `data-react-class` DOM elements
|
||||
function findDOMNodes(searchSelector) {
|
||||
const [selector, parent] = getSelector(searchSelector);
|
||||
return parent.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
function getSelector(searchSelector) {
|
||||
switch (typeof searchSelector) {
|
||||
case 'undefined':
|
||||
return [CLASS_NAME_SELECTOR, document];
|
||||
case 'object':
|
||||
return [CLASS_NAME_SELECTOR, searchSelector];
|
||||
case 'string':
|
||||
return [
|
||||
['', ' ']
|
||||
.map(
|
||||
(separator) => `${searchSelector}${separator}${CLASS_NAME_SELECTOR}`
|
||||
)
|
||||
.join(', '),
|
||||
document
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class ReactComponentRegistry {
|
||||
#cache = {};
|
||||
#components;
|
||||
|
||||
constructor(components) {
|
||||
this.#components = components;
|
||||
}
|
||||
|
||||
getConstructor(className) {
|
||||
return this.#components[className];
|
||||
}
|
||||
|
||||
mountComponents(searchSelector) {
|
||||
const nodes = findDOMNodes(searchSelector);
|
||||
|
||||
for (const node of nodes) {
|
||||
const className = node.getAttribute(CLASS_NAME_ATTR);
|
||||
const ComponentClass = this.getConstructor(className);
|
||||
const propsJson = node.getAttribute(PROPS_ATTR);
|
||||
const props = propsJson && JSON.parse(propsJson);
|
||||
const cacheId = node.getAttribute(CACHE_ID_ATTR);
|
||||
|
||||
if (!ComponentClass) {
|
||||
const message = `Cannot find component: "${className}"`;
|
||||
console?.log(
|
||||
`%c[react-rails] %c${message} for element`,
|
||||
'font-weight: bold',
|
||||
'',
|
||||
node
|
||||
);
|
||||
throw new Error(
|
||||
`${message}. Make sure your component is available to render.`
|
||||
);
|
||||
} else {
|
||||
let component = this.#cache[cacheId];
|
||||
if (!component) {
|
||||
this.#cache[cacheId] = component = createElement(
|
||||
ComponentClass,
|
||||
props
|
||||
);
|
||||
}
|
||||
|
||||
render(component, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Loader = () => <div className="spinner left" />;
|
||||
|
||||
export function Loadable(loader) {
|
||||
const LazyComponent = lazy(loader);
|
||||
|
||||
return function PureComponent(props) {
|
||||
return (
|
||||
<Suspense fallback={<Loader />}>
|
||||
<LazyComponent {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function registerReactComponents(components) {
|
||||
const registry = new ReactComponentRegistry(components);
|
||||
|
||||
addEventListener('ds:page:update', () => registry.mountComponents());
|
||||
}
|
|
@ -7,12 +7,19 @@ if (enabled) {
|
|||
const trackerUrl = `${url}piwik.php`;
|
||||
const jsUrl = `${url}piwik.js`;
|
||||
|
||||
//
|
||||
// Configure Matomo analytics
|
||||
//
|
||||
|
||||
window._paq.push(['setCookieDomain', '*.www.demarches-simplifiees.fr']);
|
||||
window._paq.push(['setDomains', ['*.www.demarches-simplifiees.fr']]);
|
||||
// Don’t store any cookies or send any tracking request when the "Do Not Track" browser setting is enabled.
|
||||
window._paq.push(['setDoNotTrack', true]);
|
||||
// When enabling external link tracking, consider that it will also report links to attachments.
|
||||
// You’ll want to exclude links to attachments from being tracked – for instance using Matomo's
|
||||
// `setCustomRequestProcessing` callback.
|
||||
// window._paq.push(['enableLinkTracking']);
|
||||
window._paq.push(['trackPageView']);
|
||||
window._paq.push(['enableLinkTracking']);
|
||||
|
||||
// Load script from Matomo
|
||||
window._paq.push(['setTrackerUrl', trackerUrl]);
|
||||
|
|
|
@ -24,4 +24,21 @@ class GroupeInstructeur < ApplicationRecord
|
|||
|
||||
scope :without_group, -> (group) { where.not(id: group) }
|
||||
scope :for_api_v2, -> { includes(procedure: [:administrateurs]) }
|
||||
|
||||
def add(instructeur)
|
||||
return if in?(instructeur.groupe_instructeurs)
|
||||
|
||||
default_notification_settings = instructeur.notification_settings(procedure_id)
|
||||
instructeur.assign_to.create(groupe_instructeur: self, **default_notification_settings)
|
||||
end
|
||||
|
||||
def remove(instructeur)
|
||||
return if !in?(instructeur.groupe_instructeurs)
|
||||
|
||||
instructeur.groupe_instructeurs.destroy(self)
|
||||
instructeur.follows
|
||||
.joins(:dossier)
|
||||
.where(dossiers: { groupe_instructeur: self })
|
||||
.update_all(unfollowed_at: Time.zone.now)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -81,14 +81,13 @@ class Instructeur < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def remove_from_groupe_instructeur(groupe_instructeur)
|
||||
if groupe_instructeur.in?(groupe_instructeurs)
|
||||
groupe_instructeurs.destroy(groupe_instructeur)
|
||||
follows
|
||||
.joins(:dossier)
|
||||
.where(dossiers: { groupe_instructeur: groupe_instructeur })
|
||||
.update_all(unfollowed_at: Time.zone.now)
|
||||
end
|
||||
NOTIFICATION_SETTINGS = [:daily_email_notifications_enabled, :instant_email_dossier_notifications_enabled, :instant_email_message_notifications_enabled, :weekly_email_notifications_enabled]
|
||||
|
||||
def notification_settings(procedure_id)
|
||||
assign_to
|
||||
.joins(:groupe_instructeur)
|
||||
.find_by(groupe_instructeurs: { procedure_id: procedure_id })
|
||||
&.slice(*NOTIFICATION_SETTINGS) || {}
|
||||
end
|
||||
|
||||
def last_week_overview
|
||||
|
|
31
app/models/null_zone.rb
Normal file
31
app/models/null_zone.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
class NullZone
|
||||
include ActiveModel::Model
|
||||
|
||||
def procedures
|
||||
Procedure.where(zone: nil).where.not(published_at: nil).order(published_at: :desc)
|
||||
end
|
||||
|
||||
def self.reflect_on_association(association)
|
||||
OpenStruct.new(class_name: "Procedure") if association == :procedures
|
||||
end
|
||||
|
||||
def label
|
||||
"non renseignée"
|
||||
end
|
||||
|
||||
def id
|
||||
-1
|
||||
end
|
||||
|
||||
def acronym
|
||||
"NA"
|
||||
end
|
||||
|
||||
def created_at
|
||||
"NA"
|
||||
end
|
||||
|
||||
def updated_at
|
||||
"NA"
|
||||
end
|
||||
end
|
|
@ -63,6 +63,8 @@ class User < ApplicationRecord
|
|||
|
||||
before_validation -> { sanitize_email(:email) }
|
||||
|
||||
validate :does_not_merge_on_self, if: :requested_merge_into_id_changed?
|
||||
|
||||
def validate_password_complexity?
|
||||
administrateur?
|
||||
end
|
||||
|
@ -223,12 +225,21 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def ask_for_merge(requested_user)
|
||||
update(requested_merge_into: requested_user)
|
||||
UserMailer.ask_for_merge(self, requested_user.email).deliver_later
|
||||
if update(requested_merge_into: requested_user)
|
||||
UserMailer.ask_for_merge(self, requested_user.email).deliver_later
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def does_not_merge_on_self
|
||||
return if requested_merge_into_id != self.id
|
||||
errors.add(:requested_merge_into, :same)
|
||||
end
|
||||
|
||||
def link_invites!
|
||||
Invite.where(email: email).update_all(user_id: id)
|
||||
end
|
||||
|
|
|
@ -10,4 +10,5 @@
|
|||
#
|
||||
class Zone < ApplicationRecord
|
||||
validates :acronym, presence: true, uniqueness: true
|
||||
has_many :procedures, -> { order(published_at: :desc) }, inverse_of: :zone
|
||||
end
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.header-search{ role: 'search' }
|
||||
= form_tag "#{search_endpoint}", method: :get, class: "form" do
|
||||
= label_tag :q, "Numéro de dossier", class: 'hidden'
|
||||
= text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: "Rechercher un dossier"
|
||||
= text_field_tag "q", "#{@search_terms if @search_terms.present?}", placeholder: "Rechercher un dossier", title: "Rechercher un dossier"
|
||||
%button{ title: "Rechercher" }
|
||||
= image_tag "icons/search-blue.svg", alt: 'Rechercher', 'aria-hidden':'true', width: 24, height: 24, loading: 'lazy'
|
||||
|
|
70
app/views/manager/zones/index.html.erb
Normal file
70
app/views/manager/zones/index.html.erb
Normal file
|
@ -0,0 +1,70 @@
|
|||
<%#
|
||||
# Index
|
||||
|
||||
This view is the template for the index page.
|
||||
It is responsible for rendering the search bar, header and pagination.
|
||||
It renders the `_table` partial to display details about the resources.
|
||||
|
||||
## Local variables:
|
||||
|
||||
- `page`:
|
||||
An instance of [Administrate::Page::Collection][1].
|
||||
Contains helper methods to help display a table,
|
||||
and knows which attributes should be displayed in the resource's table.
|
||||
- `resources`:
|
||||
An instance of `ActiveRecord::Relation` containing the resources
|
||||
that match the user's search criteria.
|
||||
By default, these resources are passed to the table partial to be displayed.
|
||||
- `search_term`:
|
||||
A string containing the term the user has searched for, if any.
|
||||
- `show_search_bar`:
|
||||
A boolean that determines if the search bar should be shown.
|
||||
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection
|
||||
%>
|
||||
|
||||
<% content_for(:title) do %>
|
||||
<%= display_resource_name(page.resource_name) %>
|
||||
<% end %>
|
||||
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 class="main-content__page-title" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
|
||||
<div>
|
||||
<%= link_to "Procedures sans zone renseignée", manager_zone_path(id: 'nil') %>
|
||||
</div>
|
||||
|
||||
<% if show_search_bar %>
|
||||
<%= render(
|
||||
"search",
|
||||
search_term: search_term,
|
||||
resource_name: display_resource_name(page.resource_name)
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<div>
|
||||
<%= link_to(
|
||||
t(
|
||||
"administrate.actions.new_resource",
|
||||
name: display_resource_name(page.resource_name, singular: true).downcase
|
||||
),
|
||||
[:new, namespace, page.resource_path.to_sym],
|
||||
class: "button",
|
||||
) if valid_action?(:new) && show_action?(:new, new_resource) %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body main-content__body--flush">
|
||||
<%= render(
|
||||
"collection",
|
||||
collection_presenter: page,
|
||||
collection_field_name: resource_name,
|
||||
page: page,
|
||||
resources: resources,
|
||||
table_title: "page-title"
|
||||
) %>
|
||||
|
||||
<%= paginate resources, param_name: '_page' %>
|
||||
</section>
|
|
@ -1,19 +1,19 @@
|
|||
- champs.reject(&:exclude_from_view?).each do |c|
|
||||
- if c.type_champ == TypeDeChamp.type_champs.fetch(:repetition)
|
||||
%tr
|
||||
%th.libelle.repetition{ colspan: 3 }
|
||||
%td.libelle.repetition{ colspan: 3 }
|
||||
= "#{c.libelle} :"
|
||||
- c.rows.each do |champs|
|
||||
= render partial: "shared/dossiers/champ_row", locals: { champs: champs, demande_seen_at: demande_seen_at, profile: profile, repetition: true }
|
||||
%tr
|
||||
%th{ colspan: 4 }
|
||||
%td.libelle{ colspan: 4 }
|
||||
- else
|
||||
%tr
|
||||
- if c.type_champ == TypeDeChamp.type_champs.fetch(:header_section)
|
||||
%th.header-section{ colspan: 3 }
|
||||
= c.libelle
|
||||
- else
|
||||
%th.libelle{ class: repetition ? 'padded' : '' }
|
||||
%td.libelle{ class: repetition ? 'padded' : '' }
|
||||
= "#{c.libelle} :"
|
||||
%td.rich-text
|
||||
%div{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
%table.table.vertical.dossier-champs
|
||||
%tbody
|
||||
- if dossier.show_groupe_instructeur_details?
|
||||
%th= dossier.procedure.routing_criteria_name
|
||||
%td.libelle= dossier.procedure.routing_criteria_name
|
||||
%td{ class: highlight_if_unseen_class(demande_seen_at, dossier.groupe_instructeur_updated_at) }= dossier.groupe_instructeur.label
|
||||
%td.updated-at
|
||||
%span{ class: highlight_if_unseen_class(demande_seen_at, dossier.groupe_instructeur_updated_at) }
|
||||
|
|
|
@ -5,59 +5,59 @@
|
|||
%td= t('warning_for_private_info', scope: 'views.shared.dossiers.identite_entreprise', etablissement: raison_sociale_or_name(etablissement))
|
||||
- else
|
||||
%tr
|
||||
%th.libelle Dénomination :
|
||||
%td.libelle Dénomination :
|
||||
%td= raison_sociale_or_name(etablissement)
|
||||
%tr
|
||||
%th.libelle SIRET :
|
||||
%td.libelle SIRET :
|
||||
%td= etablissement.siret
|
||||
|
||||
- if etablissement.siret != etablissement.entreprise.siret_siege_social
|
||||
%tr
|
||||
%th.libelle SIRET du siège social:
|
||||
%td.libelle SIRET du siège social:
|
||||
%td= etablissement.entreprise.siret_siege_social
|
||||
%tr
|
||||
%th.libelle Forme juridique :
|
||||
%td.libelle Forme juridique :
|
||||
%td= sanitize(etablissement.entreprise.forme_juridique)
|
||||
%tr
|
||||
%th.libelle Libellé NAF :
|
||||
%td.libelle Libellé NAF :
|
||||
%td= etablissement.libelle_naf
|
||||
%tr
|
||||
%th.libelle Code NAF :
|
||||
%td.libelle Code NAF :
|
||||
%td= etablissement.naf
|
||||
%tr
|
||||
%th.libelle Date de création :
|
||||
%td.libelle Date de création :
|
||||
%td= try_format_date(etablissement.entreprise.date_creation)
|
||||
- if profile == 'instructeur'
|
||||
%tr
|
||||
%th.libelle
|
||||
%td.libelle
|
||||
Effectif mensuel
|
||||
= try_format_mois_effectif(etablissement)
|
||||
(URSSAF) :
|
||||
%td= etablissement.entreprise_effectif_mensuel
|
||||
%tr
|
||||
%th.libelle
|
||||
%td.libelle
|
||||
Effectif moyen annuel
|
||||
= etablissement.entreprise_effectif_annuel_annee
|
||||
(URSSAF) :
|
||||
%td= etablissement.entreprise_effectif_annuel
|
||||
%tr
|
||||
%th.libelle Effectif de l'organisation (INSEE) :
|
||||
%td.libelle Effectif de l'organisation (INSEE) :
|
||||
%td
|
||||
= effectif(etablissement)
|
||||
%tr
|
||||
%th.libelle Numéro de TVA intracommunautaire :
|
||||
%td.libelle Numéro de TVA intracommunautaire :
|
||||
%td= etablissement.entreprise.numero_tva_intracommunautaire
|
||||
%tr
|
||||
%th.libelle Adresse :
|
||||
%td.libelle Adresse :
|
||||
%td
|
||||
- etablissement.adresse.split("\n").each do |line|
|
||||
= line
|
||||
%br
|
||||
%tr
|
||||
%th.libelle Capital social :
|
||||
%td.libelle Capital social :
|
||||
%td= pretty_currency(etablissement.entreprise.capital_social)
|
||||
%tr
|
||||
%th.libelle Chiffre d’affaires :
|
||||
%td.libelle Chiffre d’affaires :
|
||||
%td
|
||||
- if profile == 'instructeur'
|
||||
%details
|
||||
|
@ -83,7 +83,7 @@
|
|||
= render partial: 'shared/dossiers/identite_entreprise_bilan_detail',
|
||||
locals: { libelle: 'Besoin en fonds de roulement', key: 'besoin_en_fonds_de_roulement', etablissement: etablissement }
|
||||
%tr
|
||||
%th.libelle
|
||||
%td.libelle
|
||||
Chiffres financiers clés (Banque de France)
|
||||
= "en #{pretty_currency_unit(etablissement.entreprise_bilans_bdf_monnaie)} :"
|
||||
|
||||
|
@ -105,12 +105,12 @@
|
|||
= link_to "au format ods", bilans_bdf_instructeur_dossier_path(procedure_id: @dossier.procedure.id, dossier_id: @dossier.id, format: 'ods')
|
||||
- else
|
||||
%tr
|
||||
%th.libelle
|
||||
%td.libelle
|
||||
Bilans Banque de France :
|
||||
%td Les 3 derniers bilans connus de votre entreprise par la Banque de France ont été joints à votre dossier.
|
||||
- if etablissement.entreprise_attestation_sociale.attached?
|
||||
%tr
|
||||
%th.libelle Attestation sociale :
|
||||
%td.libelle Attestation sociale :
|
||||
- if profile == 'instructeur'
|
||||
%td= link_to "Consulter l'attestation", url_for(etablissement.entreprise_attestation_sociale)
|
||||
- else
|
||||
|
@ -118,7 +118,7 @@
|
|||
|
||||
- if etablissement.entreprise_attestation_fiscale.attached?
|
||||
%tr
|
||||
%th.libelle Attestation fiscale :
|
||||
%td.libelle Attestation fiscale :
|
||||
- if profile == 'instructeur'
|
||||
%td= link_to "Consulter l'attestation", url_for(etablissement.entreprise_attestation_fiscale)
|
||||
- else
|
||||
|
@ -126,22 +126,22 @@
|
|||
|
||||
- if etablissement.association?
|
||||
%tr
|
||||
%th.libelle Numéro RNA :
|
||||
%td.libelle Numéro RNA :
|
||||
%td= etablissement.association_rna
|
||||
%tr
|
||||
%th.libelle Titre :
|
||||
%td.libelle Titre :
|
||||
%td= etablissement.association_titre
|
||||
%tr
|
||||
%th.libelle Objet :
|
||||
%td.libelle Objet :
|
||||
%td= etablissement.association_objet
|
||||
%tr
|
||||
%th.libelle Date de création :
|
||||
%td.libelle Date de création :
|
||||
%td= try_format_date(etablissement.association_date_creation)
|
||||
%tr
|
||||
%th.libelle Date de publication :
|
||||
%td.libelle Date de publication :
|
||||
%td= try_format_date(etablissement.association_date_publication)
|
||||
%tr
|
||||
%th.libelle Date de déclaration :
|
||||
%td.libelle Date de déclaration :
|
||||
%td= try_format_date(etablissement.association_date_declaration)
|
||||
|
||||
%p
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%tr
|
||||
%th.libelle
|
||||
%td.libelle
|
||||
= "#{libelle} :"
|
||||
%td
|
||||
%details
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
%table.table.vertical.dossier-champs
|
||||
%tbody
|
||||
%tr
|
||||
%th.libelle Civilité :
|
||||
%td.libelle Civilité :
|
||||
%td= individual.gender
|
||||
%tr
|
||||
%th.libelle Prénom :
|
||||
%td.libelle Prénom :
|
||||
%td= individual.prenom
|
||||
%tr
|
||||
%th.libelle Nom :
|
||||
%td.libelle Nom :
|
||||
%td= individual.nom
|
||||
- if individual.birthdate.present?
|
||||
%tr
|
||||
%th.libelle Date de naissance :
|
||||
%td.libelle Date de naissance :
|
||||
%td= try_format_date(individual.birthdate)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
%table.table.vertical.dossier-champs
|
||||
%tbody
|
||||
%tr
|
||||
%th.libelle Déposé le :
|
||||
%td.libelle Déposé le :
|
||||
%td= l(dossier.depose_at, format: '%d %B %Y')
|
||||
- if dossier.justificatif_motivation.attached?
|
||||
%tr
|
||||
%th.libelle Justificatif :
|
||||
%td.libelle Justificatif :
|
||||
%td
|
||||
.action
|
||||
= render partial: 'shared/attachment/show', locals: { attachment: dossier.justificatif_motivation.attachment }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
%table.table.vertical.dossier-champs
|
||||
%tbody
|
||||
%tr
|
||||
%th.libelle Email :
|
||||
%td.libelle Email :
|
||||
%td= user_deleted ? "#{email} (l’usager a supprimé son compte)" : email
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- placeholder = t('views.shared.dossiers.messages.form.write_message_to_administration_placeholder')
|
||||
- if instructeur_signed_in? || administrateur_signed_in?
|
||||
- placeholder = t('views.shared.dossiers.messages.form.write_message_placeholder')
|
||||
= f.text_area :body, rows: 5, placeholder: placeholder, required: true, class: 'message-textarea persisted-input'
|
||||
= f.text_area :body, rows: 5, placeholder: placeholder, title: placeholder, required: true, class: 'message-textarea persisted-input'
|
||||
.flex.justify-between.wrap
|
||||
- disable_piece_jointe = defined?(disable_piece_jointe) ? disable_piece_jointe : false
|
||||
%div
|
||||
|
|
|
@ -19,12 +19,11 @@
|
|||
= label_tag :email do
|
||||
Email
|
||||
%span.mandatory *
|
||||
= text_field_tag :email, params[:email], required: true
|
||||
= text_field_tag :email, params[:email], required: true, autocomplete: 'email'
|
||||
|
||||
.contact-champ
|
||||
= label_tag :type do
|
||||
= t('.your_question')
|
||||
%span.mandatory *
|
||||
= hidden_field_tag :type, params[:type]
|
||||
%dl
|
||||
- @options.each do |(question, question_type, link)|
|
||||
|
|
|
@ -244,9 +244,16 @@ en:
|
|||
one: User
|
||||
other: Users
|
||||
attributes:
|
||||
default_attributes: &default_attributes
|
||||
password: 'password'
|
||||
requested_merge_into: 'new email address'
|
||||
user:
|
||||
siret: 'SIRET number'
|
||||
password: 'password'
|
||||
<< : *default_attributes
|
||||
instructeur:
|
||||
<< : *default_attributes
|
||||
super_admin:
|
||||
<< : *default_attributes
|
||||
instructeur:
|
||||
password: 'password'
|
||||
errors:
|
||||
|
@ -268,6 +275,8 @@ en:
|
|||
too_short: 'is too short'
|
||||
password_confirmation:
|
||||
confirmation: ': The two passwords do not match'
|
||||
requested_merge_into:
|
||||
same: "can't be the same as the old one"
|
||||
invite:
|
||||
attributes:
|
||||
email:
|
||||
|
|
|
@ -244,6 +244,7 @@ fr:
|
|||
attributes:
|
||||
default_attributes: &default_attributes
|
||||
password: 'Le mot de passe'
|
||||
requested_merge_into: 'La nouvelle adresse email'
|
||||
user:
|
||||
siret: 'Numéro SIRET'
|
||||
<< : *default_attributes
|
||||
|
@ -273,6 +274,8 @@ fr:
|
|||
not_strong: 'n’est pas assez complexe'
|
||||
password_confirmation:
|
||||
confirmation: ': Les deux mots de passe ne correspondent pas'
|
||||
requested_merge_into:
|
||||
same: "ne peut être identique à l’ancienne"
|
||||
invite:
|
||||
attributes:
|
||||
email:
|
||||
|
|
|
@ -50,6 +50,6 @@ fr:
|
|||
mesri:
|
||||
show:
|
||||
not_filled: non renseigné
|
||||
fetching_data: "La récupération automatique des données pour l'INE %{ine} et en cours."
|
||||
fetching_data: "La récupération automatique des données pour l'INE %{ine} est en cours."
|
||||
data_fetched: "Des données concernant %{sources} liées à l'INE %{ine} ont été reçues depuis le MESRI."
|
||||
data_fetched_title: "Données obtenues du MESRI"
|
||||
|
|
|
@ -21,8 +21,8 @@ en:
|
|||
product:
|
||||
question: I have an idea to improve the website
|
||||
answer_html: "<p>Got an idea? Please check our <strong>enhancement dashboard</strong></p>
|
||||
<p><ul><li>Vote for your priority improvements</li>
|
||||
<li>Share your own ideas</li></ul></p>
|
||||
<ul><li>Vote for your priority improvements</li>
|
||||
<li>Share your own ideas</li></ul>
|
||||
<p><strong><a href=%{link_product}>➡ Access the enhancement dashboard</a></strong></p>"
|
||||
lost_user:
|
||||
question: I am having trouble finding the procedure I am looking for
|
||||
|
|
|
@ -21,8 +21,8 @@ fr:
|
|||
product:
|
||||
question: J’ai une idée d’amélioration pour votre site
|
||||
answer_html: "<p>Une idée ? Pensez à consulter notre <strong>tableau de bord des améliorations</strong></p>
|
||||
<p><ul><li>Votez pour vos améliorations prioritaires;</li>
|
||||
<li>Proposez votre propre idée.</li></ul></p>
|
||||
<ul><li>Votez pour vos améliorations prioritaires;</li>
|
||||
<li>Proposez votre propre idée.</li></ul>
|
||||
<p><strong><a href=%{link_product}>➡ Accéder au tableau des améliorations</a></strong></p>"
|
||||
lost_user:
|
||||
question: Je ne trouve pas la démarche que je veux faire
|
||||
|
|
|
@ -54,6 +54,8 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :super_admins, only: [:index, :show, :destroy]
|
||||
|
||||
resources :zones, only: [:index, :show]
|
||||
|
||||
post 'demandes/create_administrateur'
|
||||
post 'demandes/refuse_administrateur'
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
"react-popper": "^2.2.5",
|
||||
"react-query": "^3.9.7",
|
||||
"react-sortable-hoc": "^1.11.0",
|
||||
"react_ujs": "^2.6.1",
|
||||
"trix": "^1.2.3",
|
||||
"use-debounce": "^5.2.0",
|
||||
"webpack": "^4.46.0",
|
||||
|
@ -51,10 +50,10 @@
|
|||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.25.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"netlify-cli": "^2.61.2",
|
||||
"netlify-cli": "^8.3.0",
|
||||
"prettier": "^2.3.2",
|
||||
"webpack-bundle-analyzer": "^3.7.0",
|
||||
"webpack-dev-server": "^3"
|
||||
"webpack-dev-server": "^4.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:js": "eslint --ext .js,.jsx,.ts,.tsx ./app/javascript ./config/webpack",
|
||||
|
|
|
@ -48,6 +48,14 @@ describe Users::ProfilController, type: :controller do
|
|||
end
|
||||
|
||||
describe 'PATCH #update_email' do
|
||||
context 'when email is same as user' do
|
||||
it 'fails' do
|
||||
patch :update_email, params: { user: { email: user.email } }
|
||||
expect(response).to have_http_status(302)
|
||||
expect(flash[:alert]).to eq(["La nouvelle adresse email ne peut être identique à l’ancienne"])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when everything is fine' do
|
||||
let(:previous_request) { create(:user) }
|
||||
|
||||
|
@ -69,7 +77,7 @@ describe Users::ProfilController, type: :controller do
|
|||
before do
|
||||
user.update(unconfirmed_email: 'unconfirmed@mail.com')
|
||||
|
||||
expect_any_instance_of(User).to receive(:ask_for_merge).with(existing_user)
|
||||
expect(UserMailer).to receive(:ask_for_merge).with(user, existing_user.email).and_return(double(deliver_later: true))
|
||||
|
||||
perform_enqueued_jobs do
|
||||
patch :update_email, params: { user: { email: existing_user.email } }
|
||||
|
|
|
@ -3,6 +3,9 @@ describe 'populate_zones' do
|
|||
subject(:run_task) do
|
||||
rake_task.invoke
|
||||
end
|
||||
after(:each) do
|
||||
rake_task.reenable
|
||||
end
|
||||
|
||||
it 'populates zones' do
|
||||
run_task
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
describe GroupeInstructeur, type: :model do
|
||||
let(:procedure) { create(:procedure) }
|
||||
let(:admin) { create :administrateur }
|
||||
let(:procedure) { create :procedure, :published, administrateur: admin }
|
||||
let(:procedure_2) { create :procedure, :published, administrateur: admin }
|
||||
let(:procedure_3) { create :procedure, :published, administrateur: admin }
|
||||
let(:instructeur) { create :instructeur, administrateurs: [admin] }
|
||||
let(:procedure_assign) { assign(procedure) }
|
||||
|
||||
before do
|
||||
procedure_assign
|
||||
assign(procedure_2)
|
||||
procedure_3
|
||||
end
|
||||
|
||||
subject { GroupeInstructeur.new(label: label, procedure: procedure) }
|
||||
|
||||
context 'with no label provided' do
|
||||
|
@ -33,4 +45,62 @@ describe GroupeInstructeur, type: :model do
|
|||
|
||||
it { is_expected.to be_invalid }
|
||||
end
|
||||
|
||||
describe "#add" do
|
||||
let(:another_groupe_instructeur) { create(:groupe_instructeur, procedure: procedure) }
|
||||
|
||||
subject { another_groupe_instructeur.add(instructeur) }
|
||||
|
||||
it 'adds the instructeur to the groupe instructeur' do
|
||||
subject
|
||||
expect(another_groupe_instructeur.reload.instructeurs).to include(instructeur)
|
||||
end
|
||||
|
||||
context 'when joining another groupe instructeur on the same procedure' do
|
||||
before do
|
||||
procedure_assign.update(daily_email_notifications_enabled: true)
|
||||
subject
|
||||
end
|
||||
|
||||
it 'copies notifications settings from a previous group' do
|
||||
expect(instructeur.assign_to.last.daily_email_notifications_enabled).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#remove" do
|
||||
subject { procedure_to_remove.defaut_groupe_instructeur.remove(instructeur) }
|
||||
|
||||
context "with an assigned procedure" do
|
||||
let(:procedure_to_remove) { procedure }
|
||||
let!(:procedure_presentation) { procedure_assign.procedure_presentation }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
|
||||
describe "consequences" do
|
||||
before do
|
||||
procedure_assign.build_procedure_presentation
|
||||
procedure_assign.save
|
||||
subject
|
||||
end
|
||||
|
||||
it "removes the assign_to and procedure_presentation" do
|
||||
expect(AssignTo.where(id: procedure_assign).count).to eq(0)
|
||||
expect(ProcedurePresentation.where(assign_to_id: procedure_assign.id).count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with an already unassigned procedure" do
|
||||
let(:procedure_to_remove) { procedure_3 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign(procedure_to_assign, instructeur_assigne: instructeur)
|
||||
create :assign_to, instructeur: instructeur_assigne, procedure: procedure_to_assign, groupe_instructeur: procedure_to_assign.defaut_groupe_instructeur
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
describe Instructeur, type: :model do
|
||||
let(:admin) { create :administrateur }
|
||||
let!(:procedure) { create :procedure, :published, administrateur: admin }
|
||||
let!(:procedure_2) { create :procedure, :published, administrateur: admin }
|
||||
let!(:procedure_3) { create :procedure, :published, administrateur: admin }
|
||||
let(:procedure) { create :procedure, :published, administrateur: admin }
|
||||
let(:procedure_2) { create :procedure, :published, administrateur: admin }
|
||||
let(:procedure_3) { create :procedure, :published, administrateur: admin }
|
||||
let(:instructeur) { create :instructeur, administrateurs: [admin] }
|
||||
let!(:procedure_assign) { assign(procedure) }
|
||||
let(:procedure_assign) { assign(procedure) }
|
||||
|
||||
before do
|
||||
procedure_assign
|
||||
assign(procedure_2)
|
||||
procedure_3
|
||||
end
|
||||
|
||||
describe 'follow' do
|
||||
|
@ -84,36 +86,6 @@ describe Instructeur, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#remove_from_groupe_instructeur" do
|
||||
subject { instructeur.remove_from_groupe_instructeur(procedure_to_remove.defaut_groupe_instructeur) }
|
||||
|
||||
context "with an assigned procedure" do
|
||||
let(:procedure_to_remove) { procedure }
|
||||
let!(:procedure_presentation) { procedure_assign.procedure_presentation }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
|
||||
describe "consequences" do
|
||||
before do
|
||||
procedure_assign.build_procedure_presentation
|
||||
procedure_assign.save
|
||||
subject
|
||||
end
|
||||
|
||||
it "removes the assign_to and procedure_presentation" do
|
||||
expect(AssignTo.where(id: procedure_assign).count).to eq(0)
|
||||
expect(ProcedurePresentation.where(assign_to_id: procedure_assign.id).count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with an already unassigned procedure" do
|
||||
let(:procedure_to_remove) { procedure_3 }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'last_week_overview' do
|
||||
let!(:instructeur2) { create(:instructeur) }
|
||||
subject { instructeur2.last_week_overview }
|
||||
|
|
|
@ -8,10 +8,10 @@ describe ExpiredDossiersDeletionService do
|
|||
let(:reference_date) { Date.parse("March 8") }
|
||||
|
||||
describe '#process_expired_dossiers_brouillon' do
|
||||
let(:today) { Time.zone.now.at_midnight }
|
||||
let(:date_close_to_expiration) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 2.weeks }
|
||||
let(:date_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months - 6.days }
|
||||
let(:date_not_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 2.months }
|
||||
let(:today) { Time.zone.now.at_beginning_of_day }
|
||||
let(:date_close_to_expiration) { today - procedure.duree_conservation_dossiers_dans_ds.months + 13.days }
|
||||
let(:date_expired) { today - procedure.duree_conservation_dossiers_dans_ds.months - 6.days }
|
||||
let(:date_not_expired) { today - procedure.duree_conservation_dossiers_dans_ds.months + 2.months }
|
||||
|
||||
context 'send messages for dossiers expiring soon and delete expired' do
|
||||
let!(:expired_brouillon) { create(:dossier, procedure: procedure, created_at: date_expired, brouillon_close_to_expiration_notice_sent_at: today - (warning_period + 3.days)) }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
describe UpdateZoneToProceduresService do
|
||||
before(:all) do
|
||||
before(:each) do
|
||||
Rake::Task['zones:populate_zones'].invoke
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
Zone.destroy_all
|
||||
after(:each) do
|
||||
Rake::Task['zones:populate_zones'].reenable
|
||||
end
|
||||
|
||||
describe '#call' do
|
||||
|
|
Loading…
Reference in a new issue