commit
1aa4936b94
75 changed files with 854 additions and 185 deletions
11
Gemfile.lock
11
Gemfile.lock
|
@ -78,7 +78,7 @@ GEM
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
administrate (0.12.0)
|
administrate (0.13.0)
|
||||||
actionpack (>= 4.2)
|
actionpack (>= 4.2)
|
||||||
actionview (>= 4.2)
|
actionview (>= 4.2)
|
||||||
activerecord (>= 4.2)
|
activerecord (>= 4.2)
|
||||||
|
@ -212,7 +212,7 @@ GEM
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
faraday (0.15.4)
|
faraday (0.15.4)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
ffi (1.9.25)
|
ffi (1.12.2)
|
||||||
flipper (0.17.2)
|
flipper (0.17.2)
|
||||||
flipper-active_record (0.17.2)
|
flipper-active_record (0.17.2)
|
||||||
activerecord (>= 4.2, < 7)
|
activerecord (>= 4.2, < 7)
|
||||||
|
@ -588,10 +588,9 @@ GEM
|
||||||
sass-listen (4.0.0)
|
sass-listen (4.0.0)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
sassc (2.0.0)
|
sassc (2.2.1)
|
||||||
ffi (~> 1.9.6)
|
ffi (~> 1.9)
|
||||||
rake
|
sassc-rails (2.1.2)
|
||||||
sassc-rails (2.1.0)
|
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
sassc (>= 2.0)
|
sassc (>= 2.0)
|
||||||
sprockets (> 3.0)
|
sprockets (> 3.0)
|
||||||
|
|
|
@ -68,17 +68,19 @@ En local, un utilisateur de test est créé automatiquement, avec les identifian
|
||||||
### Programmation des jobs
|
### Programmation des jobs
|
||||||
|
|
||||||
AutoArchiveProcedureJob.set(cron: "* * * * *").perform_later
|
AutoArchiveProcedureJob.set(cron: "* * * * *").perform_later
|
||||||
WeeklyOverviewJob.set(cron: "0 7 * * 1").perform_later
|
WeeklyOverviewJob.set(cron: "0 7 * * MON").perform_later
|
||||||
DeclarativeProceduresJob.set(cron: "* * * * *").perform_later
|
DeclarativeProceduresJob.set(cron: "* * * * *").perform_later
|
||||||
UpdateAdministrateurUsageStatisticsJob.set(cron: "0 10 * * *").perform_later
|
UpdateAdministrateurUsageStatisticsJob.set(cron: "0 10 * * *").perform_later
|
||||||
FindDubiousProceduresJob.set(cron: "0 0 * * *").perform_later
|
FindDubiousProceduresJob.set(cron: "0 0 * * *").perform_later
|
||||||
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
||||||
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
|
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
|
||||||
InstructeurEmailNotificationJob.set(cron: "0 10 * * 1,2,3,4,5,6").perform_later
|
InstructeurEmailNotificationJob.set(cron: "0 10 * * MON-FRI").perform_later
|
||||||
PurgeUnattachedBlobsJob.set(cron: "0 0 * * *").perform_later
|
PurgeUnattachedBlobsJob.set(cron: "0 0 * * *").perform_later
|
||||||
OperationsSignatureJob.set(cron: "0 6 * * *").perform_later
|
OperationsSignatureJob.set(cron: "0 6 * * *").perform_later
|
||||||
SeekAndDestroyExpiredDossiersJob.set(cron: "0 7 * * *").perform_later
|
ExpiredDossiersDeletionJob.set(cron: "0 7 * * *").perform_later
|
||||||
PurgeStaleExportsJob.set(cron: "*/5 * * * *").perform_later
|
PurgeStaleExportsJob.set(cron: "*/5 * * * *").perform_later
|
||||||
|
NotifyDraftNotSubmittedJob.set(cron: "0 7 * * *").perform_later
|
||||||
|
DiscardedDossiersDeletionJob.set(cron: "0 7 * * *").perform_later
|
||||||
|
|
||||||
### Voir les emails envoyés en local
|
### Voir les emails envoyés en local
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,10 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deleted-cell {
|
||||||
|
padding: (2 * $default-spacer) $default-spacer;
|
||||||
|
}
|
||||||
|
|
||||||
.icon.folder {
|
.icon.folder {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: $default-padding;
|
||||||
|
}
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
@include notice-text-style;
|
@include notice-text-style;
|
||||||
margin-top: - $default-spacer;
|
margin-top: - $default-spacer;
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
input[type=email] {
|
input[type=email] {
|
||||||
width: auto;
|
width: auto;
|
||||||
margin-bottom: 0;
|
margin-bottom: $default-spacer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|
|
@ -17,21 +17,41 @@
|
||||||
border: 1px solid $blue;
|
border: 1px solid $blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.en-instruction {
|
||||||
|
@extend .instruction;
|
||||||
|
}
|
||||||
|
|
||||||
&.construction {
|
&.construction {
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
color: $black;
|
color: $black;
|
||||||
border: 1px solid $black;
|
border: 1px solid $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.en-construction {
|
||||||
|
@extend .construction;
|
||||||
|
}
|
||||||
|
|
||||||
&.accepted {
|
&.accepted {
|
||||||
background-color: $green;
|
background-color: $green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.accepte {
|
||||||
|
@extend .accepted;
|
||||||
|
}
|
||||||
|
|
||||||
&.refused {
|
&.refused {
|
||||||
background-color: $dark-red;
|
background-color: $dark-red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.refuse {
|
||||||
|
@extend .refused;
|
||||||
|
}
|
||||||
|
|
||||||
&.without-continuation {
|
&.without-continuation {
|
||||||
background-color: $black;
|
background-color: $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.sans-suite {
|
||||||
|
@extend .without-continuation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
@import "common";
|
@import "common";
|
||||||
@import "constants";
|
@import "constants";
|
||||||
@import "mixins";
|
@import "mixins";
|
||||||
|
@import "utils";
|
||||||
|
|
||||||
$header-landing-breakpoint: 1040px;
|
$header-landing-breakpoint: 1040px;
|
||||||
$header-mobile-breakpoint: 550px;
|
$header-mobile-breakpoint: 550px;
|
||||||
|
@ -148,6 +149,10 @@ $header-mobile-breakpoint: 550px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.hidden {
|
||||||
|
@extend .hidden;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@extend %outline;
|
@extend %outline;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
margin-bottom: 1 * $default-padding;
|
margin-bottom: 1 * $default-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.titre-dossiers {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.dossiers-table {
|
.dossiers-table {
|
||||||
margin-top: $default-spacer;
|
margin-top: $default-spacer;
|
||||||
margin-bottom: 3 * $default-spacer;
|
margin-bottom: 3 * $default-spacer;
|
||||||
|
@ -30,6 +34,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.afficher-dossiers-supprimes {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
@ -48,7 +57,7 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.explication-onglet {
|
.explication-onglet {
|
||||||
margin-bottom: 3 * $default-spacer;
|
margin-bottom: 3 * $default-spacer;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,14 @@ module Instructeurs
|
||||||
assign_exports
|
assign_exports
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deleted_dossiers
|
||||||
|
@procedure = procedure
|
||||||
|
@deleted_dossiers = @procedure
|
||||||
|
.deleted_dossiers.where.not(state: :brouillon)
|
||||||
|
.order(:dossier_id)
|
||||||
|
.page params[:page]
|
||||||
|
end
|
||||||
|
|
||||||
def update_displayed_fields
|
def update_displayed_fields
|
||||||
values = params[:values]
|
values = params[:values]
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Manager
|
||||||
|
|
||||||
def hide
|
def hide
|
||||||
dossier = Dossier.find(params[:id])
|
dossier = Dossier.find(params[:id])
|
||||||
dossier.delete_and_keep_track(current_administration)
|
dossier.delete_and_keep_track!(current_administration, :manager_request)
|
||||||
|
|
||||||
logger.info("Le dossier #{dossier.id} est supprimé par #{current_administration.email}")
|
logger.info("Le dossier #{dossier.id} est supprimé par #{current_administration.email}")
|
||||||
flash[:notice] = "Le dossier #{dossier.id} a été supprimé."
|
flash[:notice] = "Le dossier #{dossier.id} a été supprimé."
|
||||||
|
|
|
@ -166,6 +166,12 @@ module Users
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def extend_conservation
|
||||||
|
dossier.update(en_construction_conservation_extension: dossier.en_construction_conservation_extension + 1.month)
|
||||||
|
flash[:notice] = 'Votre dossier sera conservé un mois supplémentaire'
|
||||||
|
redirect_to dossier_path(@dossier)
|
||||||
|
end
|
||||||
|
|
||||||
def modifier
|
def modifier
|
||||||
@dossier = dossier_with_champs
|
@dossier = dossier_with_champs
|
||||||
end
|
end
|
||||||
|
@ -203,7 +209,7 @@ module Users
|
||||||
dossier = current_user.dossiers.includes(:user, procedure: :administrateurs).find(params[:id])
|
dossier = current_user.dossiers.includes(:user, procedure: :administrateurs).find(params[:id])
|
||||||
|
|
||||||
if dossier.can_be_deleted_by_user?
|
if dossier.can_be_deleted_by_user?
|
||||||
dossier.delete_and_keep_track(current_user)
|
dossier.delete_and_keep_track!(current_user, :user_request)
|
||||||
flash.notice = 'Votre dossier a bien été supprimé.'
|
flash.notice = 'Votre dossier a bien été supprimé.'
|
||||||
redirect_to dossiers_path
|
redirect_to dossiers_path
|
||||||
else
|
else
|
||||||
|
|
|
@ -12,7 +12,6 @@ class ProcedureDashboard < Administrate::BaseDashboard
|
||||||
types_de_champ_private: TypesDeChampCollectionField,
|
types_de_champ_private: TypesDeChampCollectionField,
|
||||||
path: ProcedureLinkField,
|
path: ProcedureLinkField,
|
||||||
dossiers: Field::HasMany,
|
dossiers: Field::HasMany,
|
||||||
instructeurs: Field::HasMany,
|
|
||||||
administrateurs: Field::HasMany,
|
administrateurs: Field::HasMany,
|
||||||
id: Field::Number.with_options(searchable: true),
|
id: Field::Number.with_options(searchable: true),
|
||||||
libelle: Field::String,
|
libelle: Field::String,
|
||||||
|
@ -75,7 +74,6 @@ class ProcedureDashboard < Administrate::BaseDashboard
|
||||||
:types_de_champ_private,
|
:types_de_champ_private,
|
||||||
:for_individual,
|
:for_individual,
|
||||||
:auto_archive_on,
|
:auto_archive_on,
|
||||||
:instructeurs,
|
|
||||||
:initiated_mail_template,
|
:initiated_mail_template,
|
||||||
:received_mail_template,
|
:received_mail_template,
|
||||||
:closed_mail_template,
|
:closed_mail_template,
|
||||||
|
|
|
@ -83,6 +83,24 @@ module DossierHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def status_badge(state)
|
||||||
|
status_text = dossier_display_state(state, lower: true)
|
||||||
|
status_class = state.tr('_', '-')
|
||||||
|
content_tag(:span, status_text, class: "label #{status_class} ")
|
||||||
|
end
|
||||||
|
|
||||||
|
def deletion_reason_badge(reason)
|
||||||
|
if reason.present?
|
||||||
|
status_text = I18n.t(reason, scope: [:activerecord, :attributes, :deleted_dossier, :reason])
|
||||||
|
status_class = reason.tr('_', '-')
|
||||||
|
else
|
||||||
|
status_text = I18n.t(:unknown, scope: [:activerecord, :attributes, :deleted_dossier, :reason])
|
||||||
|
status_class = 'unknown'
|
||||||
|
end
|
||||||
|
|
||||||
|
content_tag(:span, status_text, class: "label #{status_class} ")
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def dinum_instance?
|
def dinum_instance?
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { fire, timeoutable } from '@utils';
|
||||||
|
|
||||||
// Manages a queue of Autosave operations,
|
// Manages a queue of Autosave operations,
|
||||||
// and sends `autosave:*` events to indicate the state of the requests.
|
// and sends `autosave:*` events to indicate the state of the requests.
|
||||||
export default class AutosaveController {
|
export default class AutoSaveController {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.timeoutDelay = 60000; // 1mn
|
this.timeoutDelay = 60000; // 1mn
|
||||||
this.latestPromise = Promise.resolve();
|
this.latestPromise = Promise.resolve();
|
|
@ -1,4 +1,4 @@
|
||||||
import AutosaveController from './autosave-controller.js';
|
import AutoSaveController from './auto-save-controller.js';
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
delegate,
|
delegate,
|
||||||
|
@ -14,7 +14,7 @@ const AUTOSAVE_DEBOUNCE_DELAY = gon.autosave.debounce_delay;
|
||||||
const AUTOSAVE_STATUS_VISIBLE_DURATION = gon.autosave.status_visible_duration;
|
const AUTOSAVE_STATUS_VISIBLE_DURATION = gon.autosave.status_visible_duration;
|
||||||
|
|
||||||
// Create a controller responsible for queuing autosave operations.
|
// Create a controller responsible for queuing autosave operations.
|
||||||
const autosaveController = new AutosaveController();
|
const autoSaveController = new AutoSaveController();
|
||||||
|
|
||||||
// Whenever a 'change' event is triggered on one of the form inputs, try to autosave.
|
// Whenever a 'change' event is triggered on one of the form inputs, try to autosave.
|
||||||
|
|
||||||
|
@ -26,13 +26,13 @@ delegate(
|
||||||
formInputsSelector,
|
formInputsSelector,
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
const form = document.querySelector(formSelector);
|
const form = document.querySelector(formSelector);
|
||||||
autosaveController.enqueueAutosaveRequest(form);
|
autoSaveController.enqueueAutosaveRequest(form);
|
||||||
}, AUTOSAVE_DEBOUNCE_DELAY)
|
}, AUTOSAVE_DEBOUNCE_DELAY)
|
||||||
);
|
);
|
||||||
|
|
||||||
delegate('click', '.autosave-retry', () => {
|
delegate('click', '.autosave-retry', () => {
|
||||||
const form = document.querySelector(formSelector);
|
const form = document.querySelector(formSelector);
|
||||||
autosaveController.enqueueAutosaveRequest(form);
|
autoSaveController.enqueueAutosaveRequest(form);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Display some UI during the autosave
|
// Display some UI during the autosave
|
|
@ -16,13 +16,13 @@ import '../shared/franceconnect';
|
||||||
import '../shared/toggle-target';
|
import '../shared/toggle-target';
|
||||||
|
|
||||||
import '../new_design/dropdown';
|
import '../new_design/dropdown';
|
||||||
import '../new_design/autosave';
|
|
||||||
import '../new_design/form-validation';
|
import '../new_design/form-validation';
|
||||||
import '../new_design/procedure-context';
|
import '../new_design/procedure-context';
|
||||||
import '../new_design/procedure-form';
|
import '../new_design/procedure-form';
|
||||||
import '../new_design/select2';
|
import '../new_design/select2';
|
||||||
import '../new_design/spinner';
|
import '../new_design/spinner';
|
||||||
import '../new_design/support';
|
import '../new_design/support';
|
||||||
|
import '../new_design/dossiers/auto-save';
|
||||||
|
|
||||||
import '../new_design/champs/carte';
|
import '../new_design/champs/carte';
|
||||||
import '../new_design/champs/linked-drop-down-list';
|
import '../new_design/champs/linked-drop-down-list';
|
||||||
|
|
8
app/jobs/discarded_dossiers_deletion_job.rb
Normal file
8
app/jobs/discarded_dossiers_deletion_job.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class DiscardedDossiersDeletionJob < ApplicationJob
|
||||||
|
queue_as :cron
|
||||||
|
|
||||||
|
def perform(*args)
|
||||||
|
Dossier.discarded_brouillon_expired.destroy_all
|
||||||
|
Dossier.discarded_en_construction_expired.destroy_all
|
||||||
|
end
|
||||||
|
end
|
7
app/jobs/notify_draft_not_submitted_job.rb
Normal file
7
app/jobs/notify_draft_not_submitted_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
class NotifyDraftNotSubmittedJob < ApplicationJob
|
||||||
|
queue_as :cron
|
||||||
|
|
||||||
|
def perform(*args)
|
||||||
|
Dossier.notify_draft_not_submitted
|
||||||
|
end
|
||||||
|
end
|
|
@ -104,4 +104,11 @@ class DossierMailer < ApplicationMailer
|
||||||
|
|
||||||
mail(from: NO_REPLY_EMAIL, to: instructeur.email, subject: @subject)
|
mail(from: NO_REPLY_EMAIL, to: instructeur.email, subject: @subject)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notify_brouillon_not_submitted(dossier)
|
||||||
|
@subject = "Attention : votre dossier n'est pas déposé."
|
||||||
|
@dossier = dossier
|
||||||
|
|
||||||
|
mail(to: dossier.user.email, subject: @subject)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Administrateur < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def email
|
def email
|
||||||
user.email
|
user&.email
|
||||||
end
|
end
|
||||||
|
|
||||||
# validate :password_complexity, if: Proc.new { |a| Devise.password_length.include?(a.password.try(:size)) }
|
# validate :password_complexity, if: Proc.new { |a| Devise.password_length.include?(a.password.try(:size)) }
|
||||||
|
|
|
@ -80,6 +80,10 @@ class Champ < ApplicationRecord
|
||||||
type_de_champ.to_typed_id
|
type_de_champ.to_typed_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def html_label?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def needs_dossier_id?
|
def needs_dossier_id?
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
class Champs::CiviliteChamp < Champ
|
class Champs::CiviliteChamp < Champ
|
||||||
|
def html_label?
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,6 +13,10 @@ class Champs::DatetimeChamp < Champ
|
||||||
value.present? ? I18n.l(Time.zone.parse(value)) : ""
|
value.present? ? I18n.l(Time.zone.parse(value)) : ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def html_label?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def format_before_save
|
def format_before_save
|
||||||
|
|
|
@ -2,8 +2,8 @@ class Champs::DecimalNumberChamp < Champ
|
||||||
validates :value, numericality: {
|
validates :value, numericality: {
|
||||||
allow_nil: true,
|
allow_nil: true,
|
||||||
allow_blank: true,
|
allow_blank: true,
|
||||||
message: -> (object, data) {
|
message: -> (object, _data) {
|
||||||
"« #{object.libelle} » " + object.errors.generate_message(data[:attribute].downcase, :not_a_number)
|
"« #{object.libelle} » " + object.errors.generate_message(:value, :not_a_number)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ class Champs::IntegerNumberChamp < Champ
|
||||||
only_integer: true,
|
only_integer: true,
|
||||||
allow_nil: true,
|
allow_nil: true,
|
||||||
allow_blank: true,
|
allow_blank: true,
|
||||||
message: -> (object, data) {
|
message: -> (object, _data) {
|
||||||
"« #{object.libelle} » " + object.errors.generate_message(data[:attribute].downcase, :not_an_integer)
|
"« #{object.libelle} » " + object.errors.generate_message(:value, :not_an_integer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,10 @@ class Champs::PieceJustificativeChamp < Champ
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def main_value_name
|
||||||
|
:piece_justificative_file
|
||||||
|
end
|
||||||
|
|
||||||
def search_terms
|
def search_terms
|
||||||
# We don’t know how to search inside documents yet
|
# We don’t know how to search inside documents yet
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,20 @@
|
||||||
class DeletedDossier < ApplicationRecord
|
class DeletedDossier < ApplicationRecord
|
||||||
belongs_to :procedure
|
belongs_to :procedure
|
||||||
|
|
||||||
def self.create_from_dossier(dossier)
|
enum reason: {
|
||||||
DeletedDossier.create!(dossier_id: dossier.id, procedure: dossier.procedure, state: dossier.state, deleted_at: Time.zone.now)
|
user_request: 'user_request',
|
||||||
|
manager_request: 'manager_request',
|
||||||
|
user_removed: 'user_removed',
|
||||||
|
expired: 'expired'
|
||||||
|
}
|
||||||
|
|
||||||
|
def self.create_from_dossier(dossier, reason)
|
||||||
|
create!(
|
||||||
|
reason: reasons.fetch(reason),
|
||||||
|
dossier_id: dossier.id,
|
||||||
|
procedure: dossier.procedure,
|
||||||
|
state: dossier.state,
|
||||||
|
deleted_at: Time.zone.now
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,11 @@ class Dossier < ApplicationRecord
|
||||||
|
|
||||||
TAILLE_MAX_ZIP = 50.megabytes
|
TAILLE_MAX_ZIP = 50.megabytes
|
||||||
|
|
||||||
|
REMAINING_DAYS_BEFORE_CLOSING = 2
|
||||||
|
INTERVAL_BEFORE_CLOSING = "#{REMAINING_DAYS_BEFORE_CLOSING} days"
|
||||||
|
INTERVAL_BEFORE_EXPIRATION = '1 month'
|
||||||
|
INTERVAL_EXPIRATION = '1 month 5 days'
|
||||||
|
|
||||||
has_one :etablissement, dependent: :destroy
|
has_one :etablissement, dependent: :destroy
|
||||||
has_one :individual, validate: false, dependent: :destroy
|
has_one :individual, validate: false, dependent: :destroy
|
||||||
has_one :attestation, dependent: :destroy
|
has_one :attestation, dependent: :destroy
|
||||||
|
@ -38,7 +43,7 @@ class Dossier < ApplicationRecord
|
||||||
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
|
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
|
||||||
has_many :avis, inverse_of: :dossier, dependent: :destroy
|
has_many :avis, inverse_of: :dossier, dependent: :destroy
|
||||||
|
|
||||||
has_many :dossier_operation_logs, dependent: :destroy
|
has_many :dossier_operation_logs, dependent: :nullify
|
||||||
|
|
||||||
belongs_to :groupe_instructeur
|
belongs_to :groupe_instructeur
|
||||||
has_one :procedure, through: :groupe_instructeur
|
has_one :procedure, through: :groupe_instructeur
|
||||||
|
@ -164,28 +169,67 @@ class Dossier < ApplicationRecord
|
||||||
user: [])
|
user: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
scope :brouillon_close_to_expiration, -> do
|
scope :with_notifiable_procedure, -> do
|
||||||
brouillon
|
joins(:procedure)
|
||||||
.joins(:procedure)
|
.where.not(procedures: { aasm_state: :brouillon })
|
||||||
.where("dossiers.created_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - INTERVAL '1 month' <= now()")
|
|
||||||
end
|
|
||||||
scope :en_construction_close_to_expiration, -> do
|
|
||||||
en_construction
|
|
||||||
.joins(:procedure)
|
|
||||||
.where("dossiers.en_construction_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - INTERVAL '1 month' <= now()")
|
|
||||||
end
|
|
||||||
scope :en_instruction_close_to_expiration, -> do
|
|
||||||
en_instruction
|
|
||||||
.joins(:procedure)
|
|
||||||
.where("dossiers.en_instruction_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - INTERVAL '1 month' <= now()")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :brouillon_expired, -> { brouillon.where("brouillon_close_to_expiration_notice_sent_at < (now() - INTERVAL '1 month 5 days')") }
|
scope :brouillon_close_to_expiration, -> do
|
||||||
scope :en_construction_expired, -> { en_construction.where("en_construction_close_to_expiration_notice_sent_at < (now() - INTERVAL '1 month 5 days')") }
|
state_brouillon
|
||||||
|
.joins(:procedure)
|
||||||
|
.where("dossiers.created_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
|
||||||
|
end
|
||||||
|
scope :en_construction_close_to_expiration, -> do
|
||||||
|
state_en_construction
|
||||||
|
.joins(:procedure)
|
||||||
|
.where("dossiers.en_construction_at + dossiers.en_construction_conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
|
||||||
|
end
|
||||||
|
scope :en_instruction_close_to_expiration, -> do
|
||||||
|
state_en_instruction
|
||||||
|
.joins(:procedure)
|
||||||
|
.where("dossiers.en_instruction_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
|
||||||
|
end
|
||||||
|
|
||||||
|
scope :brouillon_expired, -> do
|
||||||
|
state_brouillon
|
||||||
|
.where("brouillon_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
|
||||||
|
end
|
||||||
|
scope :en_construction_expired, -> do
|
||||||
|
state_en_construction
|
||||||
|
.where("en_construction_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
|
||||||
|
end
|
||||||
|
|
||||||
scope :without_brouillon_expiration_notice_sent, -> { where(brouillon_close_to_expiration_notice_sent_at: nil) }
|
scope :without_brouillon_expiration_notice_sent, -> { where(brouillon_close_to_expiration_notice_sent_at: nil) }
|
||||||
scope :without_en_construction_expiration_notice_sent, -> { where(en_construction_close_to_expiration_notice_sent_at: nil) }
|
scope :without_en_construction_expiration_notice_sent, -> { where(en_construction_close_to_expiration_notice_sent_at: nil) }
|
||||||
|
|
||||||
|
scope :discarded_brouillon_expired, -> do
|
||||||
|
with_discarded
|
||||||
|
.discarded
|
||||||
|
.state_brouillon
|
||||||
|
.where('hidden_at < ?', 1.month.ago)
|
||||||
|
end
|
||||||
|
scope :discarded_en_construction_expired, -> do
|
||||||
|
with_discarded
|
||||||
|
.discarded
|
||||||
|
.state_en_construction
|
||||||
|
.joins(:procedure)
|
||||||
|
.where('dossiers.hidden_at < ?', 1.month.ago)
|
||||||
|
.where(procedures: { hidden_at: nil })
|
||||||
|
end
|
||||||
|
|
||||||
|
scope :brouillon_near_procedure_closing_date, -> do
|
||||||
|
# select users who have submitted dossier for the given 'procedures.id'
|
||||||
|
users_who_submitted =
|
||||||
|
state_not_brouillon
|
||||||
|
.joins(:groupe_instructeur)
|
||||||
|
.where("groupe_instructeurs.procedure_id = procedures.id")
|
||||||
|
.select(:user_id)
|
||||||
|
# select dossier in brouillon where procedure closes in two days and for which the user has not submitted a Dossier
|
||||||
|
brouillon.joins(:procedure)
|
||||||
|
.where("procedures.auto_archive_on - INTERVAL :before_closing = :now", { now: Time.zone.today, before_closing: INTERVAL_BEFORE_CLOSING })
|
||||||
|
.where.not(user: users_who_submitted)
|
||||||
|
end
|
||||||
|
|
||||||
scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) }
|
scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) }
|
||||||
scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: []) }
|
scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: []) }
|
||||||
|
|
||||||
|
@ -310,6 +354,10 @@ class Dossier < ApplicationRecord
|
||||||
instruction_commencee? && retention_end_date <= Time.zone.now
|
instruction_commencee? && retention_end_date <= Time.zone.now
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def en_construction_close_to_expiration?
|
||||||
|
Dossier.en_construction_close_to_expiration.where(id: self).present?
|
||||||
|
end
|
||||||
|
|
||||||
def assign_to_groupe_instructeur(groupe_instructeur, author = nil)
|
def assign_to_groupe_instructeur(groupe_instructeur, author = nil)
|
||||||
if groupe_instructeur.procedure == procedure && groupe_instructeur != self.groupe_instructeur
|
if groupe_instructeur.procedure == procedure && groupe_instructeur != self.groupe_instructeur
|
||||||
if update(groupe_instructeur: groupe_instructeur, groupe_instructeur_updated_at: Time.zone.now)
|
if update(groupe_instructeur: groupe_instructeur, groupe_instructeur_updated_at: Time.zone.now)
|
||||||
|
@ -368,6 +416,14 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def log_operations?
|
||||||
|
!procedure.brouillon?
|
||||||
|
end
|
||||||
|
|
||||||
|
def keep_track_on_deletion?
|
||||||
|
!procedure.brouillon?
|
||||||
|
end
|
||||||
|
|
||||||
def expose_legacy_carto_api?
|
def expose_legacy_carto_api?
|
||||||
procedure.expose_legacy_carto_api?
|
procedure.expose_legacy_carto_api?
|
||||||
end
|
end
|
||||||
|
@ -404,21 +460,29 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_and_keep_track(author)
|
def expired_keep_track!
|
||||||
deleted_dossier = DeletedDossier.create_from_dossier(self)
|
if keep_track_on_deletion?
|
||||||
discard!
|
DeletedDossier.create_from_dossier(self, :expired)
|
||||||
|
log_automatic_dossier_operation(:supprimer, self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_and_keep_track!(author, reason)
|
||||||
|
if keep_track_on_deletion? && en_construction?
|
||||||
|
deleted_dossier = DeletedDossier.create_from_dossier(self, reason)
|
||||||
|
|
||||||
if en_construction?
|
|
||||||
administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
|
administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
|
||||||
administration_emails.each do |email|
|
administration_emails.each do |email|
|
||||||
DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later
|
DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later
|
||||||
end
|
end
|
||||||
end
|
|
||||||
DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later
|
DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later
|
||||||
|
|
||||||
log_dossier_operation(author, :supprimer, self)
|
log_dossier_operation(author, :supprimer, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
discard!
|
||||||
|
end
|
||||||
|
|
||||||
def after_passer_en_instruction(instructeur)
|
def after_passer_en_instruction(instructeur)
|
||||||
instructeur.follow(self)
|
instructeur.follow(self)
|
||||||
|
|
||||||
|
@ -624,6 +688,7 @@ class Dossier < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def log_dossier_operation(author, operation, subject = nil)
|
def log_dossier_operation(author, operation, subject = nil)
|
||||||
|
if log_operations?
|
||||||
DossierOperationLog.create_and_serialize(
|
DossierOperationLog.create_and_serialize(
|
||||||
dossier: self,
|
dossier: self,
|
||||||
operation: DossierOperationLog.operations.fetch(operation),
|
operation: DossierOperationLog.operations.fetch(operation),
|
||||||
|
@ -631,8 +696,10 @@ class Dossier < ApplicationRecord
|
||||||
subject: subject
|
subject: subject
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def log_automatic_dossier_operation(operation, subject = nil)
|
def log_automatic_dossier_operation(operation, subject = nil)
|
||||||
|
if log_operations?
|
||||||
DossierOperationLog.create_and_serialize(
|
DossierOperationLog.create_and_serialize(
|
||||||
dossier: self,
|
dossier: self,
|
||||||
operation: DossierOperationLog.operations.fetch(operation),
|
operation: DossierOperationLog.operations.fetch(operation),
|
||||||
|
@ -640,6 +707,7 @@ class Dossier < ApplicationRecord
|
||||||
subject: subject
|
subject: subject
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update_state_dates
|
def update_state_dates
|
||||||
if en_construction? && !self.en_construction_at
|
if en_construction? && !self.en_construction_at
|
||||||
|
@ -680,4 +748,12 @@ class Dossier < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.notify_draft_not_submitted
|
||||||
|
brouillon_near_procedure_closing_date
|
||||||
|
.includes(:user)
|
||||||
|
.find_each do |dossier|
|
||||||
|
DossierMailer.notify_brouillon_not_submitted(dossier).deliver_later
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,8 +12,9 @@ class DossierOperationLog < ApplicationRecord
|
||||||
demander_un_avis: 'demander_un_avis'
|
demander_un_avis: 'demander_un_avis'
|
||||||
}
|
}
|
||||||
|
|
||||||
belongs_to :dossier
|
|
||||||
has_one_attached :serialized
|
has_one_attached :serialized
|
||||||
|
|
||||||
|
belongs_to :dossier, optional: true
|
||||||
belongs_to :bill_signature, optional: true
|
belongs_to :bill_signature, optional: true
|
||||||
|
|
||||||
def self.create_and_serialize(params)
|
def self.create_and_serialize(params)
|
||||||
|
|
|
@ -111,7 +111,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_be_deleted?
|
def can_be_deleted?
|
||||||
administrateur.nil? && instructeur.nil? && dossiers.state_instruction_commencee.empty?
|
administrateur.nil? && instructeur.nil? && dossiers.with_discarded.state_instruction_commencee.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_and_keep_track_dossiers(administration)
|
def delete_and_keep_track_dossiers(administration)
|
||||||
|
@ -120,7 +120,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
dossiers.each do |dossier|
|
dossiers.each do |dossier|
|
||||||
dossier.delete_and_keep_track(administration)
|
dossier.delete_and_keep_track!(administration, :user_removed)
|
||||||
end
|
end
|
||||||
dossiers.with_discarded.destroy_all
|
dossiers.with_discarded.destroy_all
|
||||||
destroy!
|
destroy!
|
||||||
|
|
|
@ -15,6 +15,7 @@ class ExpiredDossiersDeletionService
|
||||||
.without_brouillon_expiration_notice_sent
|
.without_brouillon_expiration_notice_sent
|
||||||
|
|
||||||
dossiers_close_to_expiration
|
dossiers_close_to_expiration
|
||||||
|
.with_notifiable_procedure
|
||||||
.includes(:user, :procedure)
|
.includes(:user, :procedure)
|
||||||
.group_by(&:user)
|
.group_by(&:user)
|
||||||
.each do |(user, dossiers)|
|
.each do |(user, dossiers)|
|
||||||
|
@ -33,6 +34,7 @@ class ExpiredDossiersDeletionService
|
||||||
.without_en_construction_expiration_notice_sent
|
.without_en_construction_expiration_notice_sent
|
||||||
|
|
||||||
dossiers_close_to_expiration
|
dossiers_close_to_expiration
|
||||||
|
.with_notifiable_procedure
|
||||||
.includes(:user)
|
.includes(:user)
|
||||||
.group_by(&:user)
|
.group_by(&:user)
|
||||||
.each do |(user, dossiers)|
|
.each do |(user, dossiers)|
|
||||||
|
@ -56,6 +58,7 @@ class ExpiredDossiersDeletionService
|
||||||
dossiers_to_remove = Dossier.brouillon_expired
|
dossiers_to_remove = Dossier.brouillon_expired
|
||||||
|
|
||||||
dossiers_to_remove
|
dossiers_to_remove
|
||||||
|
.with_notifiable_procedure
|
||||||
.includes(:user, :procedure)
|
.includes(:user, :procedure)
|
||||||
.group_by(&:user)
|
.group_by(&:user)
|
||||||
.each do |(user, dossiers)|
|
.each do |(user, dossiers)|
|
||||||
|
@ -65,20 +68,15 @@ class ExpiredDossiersDeletionService
|
||||||
).deliver_later
|
).deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
dossiers_to_remove.each do |dossier|
|
dossiers_to_remove.destroy_all
|
||||||
DeletedDossier.create_from_dossier(dossier)
|
|
||||||
dossier.destroy
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.delete_expired_en_construction_and_notify
|
def self.delete_expired_en_construction_and_notify
|
||||||
dossiers_to_remove = Dossier.en_construction_expired
|
dossiers_to_remove = Dossier.en_construction_expired
|
||||||
|
dossiers_to_remove.each(&:expired_keep_track!)
|
||||||
dossiers_to_remove.each do |dossier|
|
|
||||||
DeletedDossier.create_from_dossier(dossier)
|
|
||||||
end
|
|
||||||
|
|
||||||
dossiers_to_remove
|
dossiers_to_remove
|
||||||
|
.with_notifiable_procedure
|
||||||
.includes(:user)
|
.includes(:user)
|
||||||
.group_by(&:user)
|
.group_by(&:user)
|
||||||
.each do |(user, dossiers)|
|
.each do |(user, dossiers)|
|
||||||
|
@ -102,6 +100,7 @@ class ExpiredDossiersDeletionService
|
||||||
|
|
||||||
def self.group_by_fonctionnaire_email(dossiers)
|
def self.group_by_fonctionnaire_email(dossiers)
|
||||||
dossiers
|
dossiers
|
||||||
|
.with_notifiable_procedure
|
||||||
.includes(:followers_instructeurs, procedure: [:administrateurs])
|
.includes(:followers_instructeurs, procedure: [:administrateurs])
|
||||||
.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |dossier, h|
|
.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |dossier, h|
|
||||||
(dossier.followers_instructeurs + dossier.procedure.administrateurs).each { |destinataire| h[destinataire.email] << dossier }
|
(dossier.followers_instructeurs + dossier.procedure.administrateurs).each { |destinataire| h[destinataire.email] << dossier }
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<%= render_flash(timeout: 5000, sticky: true) %>
|
<%= render_flash(timeout: 5000, sticky: true) %>
|
||||||
<%= remove_element("#attachment_#{@attachment_id}") %>
|
<%= remove_element(".attachment-actions-#{@attachment_id}") %>
|
||||||
<%= show_element("#attachment_file_#{@attachment_id}") %>
|
<%= show_element(".attachment-input-#{@attachment_id}") %>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
- content_for(:title, "#{@subject}")
|
||||||
|
|
||||||
|
%p
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
%p
|
||||||
|
Le dossier n°#{@dossier.id} pour la démarche «
|
||||||
|
%strong
|
||||||
|
#{@dossier.procedure.libelle}
|
||||||
|
» est commencé mais n'est pas encore déposé.
|
||||||
|
%p
|
||||||
|
Si vous souhaitez que ce dossier soit pris en compte, il vous faut le déposer avant le
|
||||||
|
#{l(@dossier.procedure.auto_archive_on - 1.day, format: '%-d %B %Y')} à 23h59, date de cloture de la démarche.
|
||||||
|
%p
|
||||||
|
Pour cela, affichez le dossier avec le bouton ci-dessous, vérifiez votre dossier puis
|
||||||
|
cliquez sur le bouton
|
||||||
|
%strong
|
||||||
|
'Déposer le dossier'
|
||||||
|
%p
|
||||||
|
Si vous ne souhaitez plus déposer le dossier, vous n'avez rien à faire.
|
||||||
|
|
||||||
|
= round_button('Afficher votre dossier', dossier_url(@dossier), :primary)
|
||||||
|
|
||||||
|
= render partial: "layouts/mailers/signature"
|
74
app/views/instructeurs/procedures/deleted_dossiers.html.haml
Normal file
74
app/views/instructeurs/procedures/deleted_dossiers.html.haml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
- content_for(:title, "#{@procedure.libelle}")
|
||||||
|
|
||||||
|
#procedure-show
|
||||||
|
.sub-header
|
||||||
|
.container.flex
|
||||||
|
|
||||||
|
.procedure-logo{ style: "background-image: url(#{@procedure.logo_url})",
|
||||||
|
role: 'img', 'aria-label': "logo de la démarche #{@procedure.libelle}" }
|
||||||
|
|
||||||
|
.procedure-header
|
||||||
|
%h1= procedure_libelle @procedure
|
||||||
|
= link_to 'gestion des notifications', email_notifications_instructeur_procedure_path(@procedure), class: 'header-link'
|
||||||
|
|
|
||||||
|
= link_to 'statistiques', stats_instructeur_procedure_path(@procedure), class: 'header-link', data: { turbolinks: false } # Turbolinks disabled for Chartkick. See Issue #350
|
||||||
|
|
||||||
|
- if @procedure.routee?
|
||||||
|
|
|
||||||
|
- if current_administrateur.present? && current_administrateur.owns?(@procedure)
|
||||||
|
= link_to 'instructeurs', procedure_groupe_instructeurs_path(@procedure), class: 'header-link'
|
||||||
|
- else
|
||||||
|
= link_to 'instructeurs', instructeur_groupes_path(@procedure), class: 'header-link'
|
||||||
|
|
||||||
|
%ul.tabs
|
||||||
|
= tab_item('à suivre',
|
||||||
|
instructeur_procedure_path(@procedure, statut: 'a-suivre'))
|
||||||
|
= tab_item(t('pluralize.followed', count: 1),
|
||||||
|
instructeur_procedure_path(@procedure, statut: 'suivis'),
|
||||||
|
active: @statut == 'suivis')
|
||||||
|
|
||||||
|
= tab_item(t('pluralize.processed', count: 1),
|
||||||
|
instructeur_procedure_path(@procedure, statut: 'traites'))
|
||||||
|
|
||||||
|
= tab_item('tous les dossiers',
|
||||||
|
instructeur_procedure_path(@procedure, statut: 'tous'))
|
||||||
|
|
||||||
|
= tab_item(t('pluralize.archived', count: 1),
|
||||||
|
instructeur_procedure_path(@procedure, statut: 'archives'),
|
||||||
|
active: true)
|
||||||
|
|
||||||
|
.container
|
||||||
|
%h1.titre-dossiers Dossiers supprimés
|
||||||
|
%details.explication-onglet
|
||||||
|
%summary Les dossiers ont été supprimés. Vous ne pouvez plus les récupérer depuis Démarches Simplifiées.
|
||||||
|
Ceci s'explique pour les raisons suivantes :
|
||||||
|
%ul
|
||||||
|
%li L'utilisateur a intentionnellement supprimé son dossier.
|
||||||
|
%li Le délai de conservation maximal de #{@procedure.duree_conservation_dossiers_dans_ds} mois a expiré. Conformément au règlement RGPD, DS ne peut continuer à les héberger.
|
||||||
|
- if @deleted_dossiers.any?
|
||||||
|
= paginate @deleted_dossiers
|
||||||
|
%table.table.dossiers-table.hoverable
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th.notification-col
|
||||||
|
%th.number-col N° dossier
|
||||||
|
%th.status-col Etat
|
||||||
|
%th.status-col Raison de suppression
|
||||||
|
%th.status-col Date de suppression
|
||||||
|
%tbody
|
||||||
|
- @deleted_dossiers.each do |deleted_dossier|
|
||||||
|
%tr
|
||||||
|
%td.folder-col
|
||||||
|
%span.icon.folder
|
||||||
|
%td.number-col
|
||||||
|
= deleted_dossier.dossier_id
|
||||||
|
%td.status-col
|
||||||
|
= status_badge(deleted_dossier.state)
|
||||||
|
%td.reason-col
|
||||||
|
= deletion_reason_badge(deleted_dossier.reason)
|
||||||
|
%td.date-col.deleted-cell
|
||||||
|
= l(deleted_dossier.deleted_at, format: '%d/%m/%y')
|
||||||
|
= paginate @deleted_dossiers
|
||||||
|
- else
|
||||||
|
Aucun dossier supprimé
|
||||||
|
|
|
@ -63,6 +63,10 @@
|
||||||
%p.explication-onglet Tous les dossiers qui ont été déposés sur cette démarche, sans aucun filtre.
|
%p.explication-onglet Tous les dossiers qui ont été déposés sur cette démarche, sans aucun filtre.
|
||||||
- if @statut == 'archives'
|
- if @statut == 'archives'
|
||||||
%p.explication-onglet Les dossiers de cet onglet sont archivés : vous ne pouvez plus y répondre, et les demandeurs ne peuvent plus les modifier.
|
%p.explication-onglet Les dossiers de cet onglet sont archivés : vous ne pouvez plus y répondre, et les demandeurs ne peuvent plus les modifier.
|
||||||
|
.afficher-dossiers-supprimes
|
||||||
|
= link_to deleted_dossiers_instructeur_procedure_path(@procedure) do
|
||||||
|
%span.icon.delete
|
||||||
|
Afficher les dossiers supprimés
|
||||||
|
|
||||||
- if @dossiers.present? || @current_filters.count > 0
|
- if @dossiers.present? || @current_filters.count > 0
|
||||||
= paginate @dossiers
|
= paginate @dossiers
|
||||||
|
@ -139,7 +143,7 @@
|
||||||
|
|
||||||
%td.status-col
|
%td.status-col
|
||||||
= link_to(instructeur_dossier_path(@procedure, dossier), class: 'cell-link') do
|
= link_to(instructeur_dossier_path(@procedure, dossier), class: 'cell-link') do
|
||||||
= render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier }
|
= status_badge(dossier.state)
|
||||||
%td.action-col.follow-col= render partial: 'dossier_actions', locals: { procedure: @procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
|
%td.action-col.follow-col= render partial: 'dossier_actions', locals: { procedure: @procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
|
||||||
= paginate @dossiers
|
= paginate @dossiers
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
%td= link_to(dossier.user.email, dossier_linked_path(current_instructeur, dossier), class: 'cell-link')
|
%td= link_to(dossier.user.email, dossier_linked_path(current_instructeur, dossier), class: 'cell-link')
|
||||||
%td.status-col
|
%td.status-col
|
||||||
= link_to(dossier_linked_path(current_instructeur, dossier), class: 'cell-link') do
|
= link_to(dossier_linked_path(current_instructeur, dossier), class: 'cell-link') do
|
||||||
= render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier }
|
= status_badge(dossier.state)
|
||||||
%td.action-col.follow-col= render partial: 'instructeurs/procedures/dossier_actions', locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
|
%td.action-col.follow-col= render partial: 'instructeurs/procedures/dossier_actions', locals: { procedure: dossier.procedure, dossier: dossier, dossier_is_followed: @followed_dossiers_id.include?(dossier.id) }
|
||||||
- else
|
- else
|
||||||
%h2 Aucun dossier correspondant à votre recherche n'a été trouvé
|
%h2 Aucun dossier correspondant à votre recherche n'a été trouvé
|
||||||
|
|
|
@ -15,8 +15,12 @@
|
||||||
= form_tag dossier_invites_path(dossier), remote: true, method: :post, class: 'form' do
|
= form_tag dossier_invites_path(dossier), remote: true, method: :post, class: 'form' do
|
||||||
.row
|
.row
|
||||||
.col
|
.col
|
||||||
= email_field_tag :invite_email, '', class: 'small', placeholder: 'adresse email', required: true
|
%span
|
||||||
|
= label_tag :invite_email, "Adresse email"
|
||||||
|
= email_field_tag :invite_email, '', class: 'small', placeholder: 'Adresse email', required: true
|
||||||
.col
|
.col
|
||||||
|
%span
|
||||||
|
= label_tag :invite_message, "Ajouter un message à la personne invitée (optionnel)"
|
||||||
= text_area_tag :invite_message, '', class: 'small', placeholder: 'Ajouter un message à la personne invitée (optionnel)'
|
= text_area_tag :invite_message, '', class: 'small', placeholder: 'Ajouter un message à la personne invitée (optionnel)'
|
||||||
.col
|
.col
|
||||||
= submit_tag 'Envoyer une invitation', class: 'button accepted'
|
= submit_tag 'Envoyer une invitation', class: 'button accepted'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.dropdown.header-menu-opener
|
.dropdown.header-menu-opener
|
||||||
%button.button.dropdown-button.header-menu-button{ title: "Mon compte" }
|
%button.button.dropdown-button.header-menu-button{ title: "Mon compte" }
|
||||||
|
.hidden Mon compte
|
||||||
= image_tag "icons/account-circle.svg", alt: ''
|
= image_tag "icons/account-circle.svg", alt: ''
|
||||||
%ul.header-menu.dropdown-content
|
%ul.header-menu.dropdown-content
|
||||||
%li
|
%li
|
||||||
|
|
|
@ -47,9 +47,10 @@
|
||||||
%li
|
%li
|
||||||
.header-search{ role: 'search' }
|
.header-search{ role: 'search' }
|
||||||
= form_tag recherche_dossiers_path, method: :post, class: "form" do
|
= form_tag recherche_dossiers_path, method: :post, class: "form" do
|
||||||
|
= label_tag :dossier_id, "Numéro de dossier", class: 'hidden'
|
||||||
= text_field_tag :dossier_id, "", placeholder: "Numéro de dossier"
|
= text_field_tag :dossier_id, "", placeholder: "Numéro de dossier"
|
||||||
%button{ title: "Rechercher" }
|
%button{ title: "Rechercher" }
|
||||||
= image_tag "icons/search-blue.svg", alt: ''
|
= image_tag "icons/search-blue.svg", alt: 'Rechercher', 'aria-hidden':'true'
|
||||||
|
|
||||||
- if instructeur_signed_in? || user_signed_in?
|
- if instructeur_signed_in? || user_signed_in?
|
||||||
%li
|
%li
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
- content_for(:title, 'Accessibilité')
|
- content_for(:title, 'Accessibilité')
|
||||||
|
- content_for :footer do
|
||||||
|
= render partial: "root/footer"
|
||||||
|
|
||||||
.accessibilite
|
.accessibilite
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
- content_for(:title, 'Suivi')
|
- content_for(:title, 'Suivi')
|
||||||
|
- content_for :footer do
|
||||||
|
= render partial: "root/footer"
|
||||||
|
|
||||||
.suivi
|
.suivi
|
||||||
%h1.new-h1 Cookies déposés et configuration du suivi
|
%h1.new-h1 Cookies déposés et configuration du suivi
|
||||||
|
|
|
@ -13,17 +13,16 @@
|
||||||
= link_to('le modèle suivant', url_for(template), target: '_blank', rel: 'noopener')
|
= link_to('le modèle suivant', url_for(template), target: '_blank', rel: 'noopener')
|
||||||
|
|
||||||
- if persisted
|
- if persisted
|
||||||
.attachment-actions{ id: "attachment_#{attachment_id}" }
|
.attachment-actions{ class: "attachment-actions-#{attachment_id}" }
|
||||||
.attachment-action
|
.attachment-action
|
||||||
= render partial: "shared/attachment/show", locals: { attachment: attachment, user_can_upload: true }
|
= render partial: "shared/attachment/show", locals: { attachment: attachment, user_can_upload: true }
|
||||||
- if user_can_destroy
|
- if user_can_destroy
|
||||||
.attachment-action
|
.attachment-action
|
||||||
= link_to 'Supprimer', attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }), remote: true, method: :delete, class: 'button small danger'
|
= link_to 'Supprimer', attachment_url(attachment.id, { signed_id: attachment.blob.signed_id }), remote: true, method: :delete, class: 'button small danger'
|
||||||
.attachment-action
|
.attachment-action
|
||||||
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': "#attachment_file_#{attachment_id}" }
|
= button_tag 'Remplacer', type: 'button', class: 'button small', data: { 'toggle-target': ".attachment-input-#{attachment_id}" }
|
||||||
|
|
||||||
= form.file_field attached_file.name,
|
= form.file_field attached_file.name,
|
||||||
id: "attachment_file_#{attachment_id}",
|
class: "attachment-input attachment-input-#{attachment_id} #{'hidden' if persisted}",
|
||||||
class: "attachment-input #{'hidden' if persisted}",
|
|
||||||
accept: accept,
|
accept: accept,
|
||||||
direct_upload: true
|
direct_upload: true
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
- if dossier.brouillon?
|
|
||||||
%span.label.brouillon brouillon
|
|
||||||
- elsif dossier.en_construction?
|
|
||||||
%span.label.construction en construction
|
|
||||||
- elsif dossier.en_instruction?
|
|
||||||
%span.label.instruction en instruction
|
|
||||||
- elsif dossier.accepte?
|
|
||||||
%span.label.accepted accepté
|
|
||||||
- elsif dossier.refuse?
|
|
||||||
%span.label.refused refusé
|
|
||||||
- elsif dossier.sans_suite?
|
|
||||||
%span.label.without-continuation sans suite
|
|
|
@ -1,11 +1,10 @@
|
||||||
|
= # we do this trick because some html elements should use 'label' and some should be plain paragraphs
|
||||||
|
- if champ.html_label?
|
||||||
= form.label champ.main_value_name do
|
= form.label champ.main_value_name do
|
||||||
#{champ.libelle}
|
= render partial: 'shared/dossiers/editable_champs/champ_label_content', locals: { champ: champ, seen_at: seen_at }
|
||||||
- if champ.mandatory?
|
- else
|
||||||
%span.mandatory *
|
%h4.form-label
|
||||||
|
= render partial: 'shared/dossiers/editable_champs/champ_label_content', locals: { champ: champ, seen_at: seen_at }
|
||||||
- if champ.updated_at.present? && seen_at.present?
|
|
||||||
%span.updated-at{ class: highlight_if_unseen_class(seen_at, champ.updated_at) }
|
|
||||||
= "modifié le #{try_format_datetime(champ.updated_at)}"
|
|
||||||
|
|
||||||
- if champ.description.present?
|
- if champ.description.present?
|
||||||
.notice{ id: describedby_id(champ) }= string_to_html(champ.description)
|
.notice{ id: describedby_id(champ) }= string_to_html(champ.description)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#{champ.libelle}
|
||||||
|
- if champ.mandatory?
|
||||||
|
%span.mandatory *
|
||||||
|
|
||||||
|
- if champ.updated_at.present? && seen_at.present?
|
||||||
|
%span.updated-at{ class: highlight_if_unseen_class(seen_at, champ.updated_at) }
|
||||||
|
= "modifié le #{try_format_datetime(champ.updated_at)}"
|
|
@ -1,4 +1,6 @@
|
||||||
.radios
|
%fieldset.radios
|
||||||
|
%legend.mandatory-explanation
|
||||||
|
Sélectionnez une des valeurs
|
||||||
%label
|
%label
|
||||||
= form.radio_button :value, Individual::GENDER_MALE
|
= form.radio_button :value, Individual::GENDER_MALE
|
||||||
Monsieur
|
Monsieur
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
champ.primary_options,
|
champ.primary_options,
|
||||||
{ required: champ.mandatory? },
|
{ required: champ.mandatory? },
|
||||||
{ data: { secondary_options: champ.secondary_options } }
|
{ data: { secondary_options: champ.secondary_options } }
|
||||||
|
%span
|
||||||
|
= form.label :secondary_value, "Valeur secondaire dépendant de la première", class: 'hidden'
|
||||||
= form.select :secondary_value,
|
= form.select :secondary_value,
|
||||||
champ.secondary_options[champ.primary_value],
|
champ.secondary_options[champ.primary_value],
|
||||||
{ required: champ.mandatory? },
|
{ required: champ.mandatory? },
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
.radios
|
%fieldset.radios
|
||||||
|
%legend.mandatory-explanation
|
||||||
|
Sélectionnez une des deux valeurs
|
||||||
%label
|
%label
|
||||||
= form.radio_button :value, true
|
= form.radio_button :value, true
|
||||||
Oui
|
Oui
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
- content_for(:title, 'Statistiques')
|
- content_for(:title, 'Statistiques')
|
||||||
|
- content_for :footer do
|
||||||
|
= render partial: "root/footer"
|
||||||
|
|
||||||
.statistiques
|
.statistiques
|
||||||
-# Load Chartkick lazily, by using our React lazy-loader.
|
-# Load Chartkick lazily, by using our React lazy-loader.
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
- content_for(:title, 'Contact')
|
- content_for(:title, 'Contact')
|
||||||
|
- content_for :footer do
|
||||||
|
= render partial: "root/footer"
|
||||||
|
|
||||||
#contact-form
|
#contact-form
|
||||||
.container
|
.container
|
||||||
|
|
|
@ -8,12 +8,20 @@
|
||||||
|
|
||||||
%p.mb-1 Merci de remplir vos informations personnelles pour accéder à la démarche.
|
%p.mb-1 Merci de remplir vos informations personnelles pour accéder à la démarche.
|
||||||
|
|
||||||
%label
|
%span.form-label
|
||||||
%span.mandatory *
|
%span.mandatory *
|
||||||
champs requis
|
champs requis
|
||||||
|
|
||||||
|
%fieldset
|
||||||
|
%legend
|
||||||
= f.label :gender, class: "required"
|
= f.label :gender, class: "required"
|
||||||
= f.select :gender, [Individual::GENDER_MALE, Individual::GENDER_FEMALE], {}, class: "small"
|
.radios
|
||||||
|
%label
|
||||||
|
= f.radio_button :gender, Individual::GENDER_MALE
|
||||||
|
= Individual::GENDER_MALE
|
||||||
|
%label
|
||||||
|
= f.radio_button :gender, Individual::GENDER_FEMALE
|
||||||
|
= Individual::GENDER_FEMALE
|
||||||
|
|
||||||
.flex
|
.flex
|
||||||
.inline-champ
|
.inline-champ
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
= procedure_libelle(dossier.procedure)
|
= procedure_libelle(dossier.procedure)
|
||||||
%td.status-col
|
%td.status-col
|
||||||
= link_to(url_for_dossier(dossier), class: 'cell-link') do
|
= link_to(url_for_dossier(dossier), class: 'cell-link') do
|
||||||
= render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier }
|
= status_badge(dossier.state)
|
||||||
%td.updated-at-col
|
%td.updated-at-col
|
||||||
= link_to(url_for_dossier(dossier), class: 'cell-link') do
|
= link_to(url_for_dossier(dossier), class: 'cell-link') do
|
||||||
= try_format_date(dossier.updated_at)
|
= try_format_date(dossier.updated_at)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.sub-header
|
.sub-header
|
||||||
.container
|
.container
|
||||||
= render partial: 'shared/dossiers/status_badge', locals: { dossier: dossier }
|
= status_badge(dossier.state)
|
||||||
|
|
||||||
.title-container
|
.title-container
|
||||||
%span.icon.folder
|
%span.icon.folder
|
||||||
|
@ -22,6 +22,16 @@
|
||||||
%li
|
%li
|
||||||
= link_to "Tout le dossier", dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link"
|
= link_to "Tout le dossier", dossier_path(dossier, format: :pdf), target: "_blank", rel: "noopener", class: "menu-item menu-link"
|
||||||
|
|
||||||
|
- if dossier.en_construction_close_to_expiration?
|
||||||
|
.card.warning
|
||||||
|
.card-title Votre dossier va expirer
|
||||||
|
%p
|
||||||
|
Votre dossier a été déposé, mais va bientôt expirer. Cela signifie qu'il va bientôt être supprimé sans avoir été traité par l’administration.
|
||||||
|
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.
|
||||||
|
%br
|
||||||
|
= button_to 'Repousser sa suppression', users_dossier_repousser_expiration_path(dossier), class: 'button secondary'
|
||||||
|
|
||||||
%ul.tabs
|
%ul.tabs
|
||||||
= dynamic_tab_item('Résumé', dossier_path(dossier))
|
= dynamic_tab_item('Résumé', dossier_path(dossier))
|
||||||
= dynamic_tab_item('Demande', [demande_dossier_path(dossier), modifier_dossier_path(dossier)])
|
= dynamic_tab_item('Demande', [demande_dossier_path(dossier), modifier_dossier_path(dossier)])
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
Non
|
Non
|
||||||
|
|
||||||
= f.label :password, "Mot de passe"
|
= f.label :password, "Mot de passe"
|
||||||
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum"
|
= f.password_field :password, value: @user.password, placeholder: "8 caractères minimum", 'aria-describedby':'8 caractères minimum'
|
||||||
|
|
||||||
= f.submit "Créer un compte", class: "button large primary expand"
|
= f.submit "Créer un compte", class: "button large primary expand"
|
||||||
|
|
||||||
|
|
49
config/initializers/date_select.rb
Normal file
49
config/initializers/date_select.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# We monkey patch the DateTimeSelector in order to add accessibility labels
|
||||||
|
# https://stackoverflow.com/a/47836699
|
||||||
|
module ActionView
|
||||||
|
module Helpers
|
||||||
|
class DateTimeSelector
|
||||||
|
# Given an ordering of datetime components, create the selection HTML
|
||||||
|
# and join them with their appropriate separators.
|
||||||
|
def build_selects_from_types(order)
|
||||||
|
select = ""
|
||||||
|
order.reverse_each do |type|
|
||||||
|
separator = separator(type)
|
||||||
|
select.insert(0, separator.to_s + send("select_#{type}").to_s)
|
||||||
|
end
|
||||||
|
# rubocop:disable Rails/OutputSafety
|
||||||
|
select.html_safe
|
||||||
|
# rubocop:enable Rails/OutputSafety
|
||||||
|
end
|
||||||
|
|
||||||
|
def datetime_accessibility_label(n, label)
|
||||||
|
prefix_re = @options[:prefix].match('(.*)\[(.*)\]\[(\d+)\]')
|
||||||
|
if prefix_re.nil? || prefix_re.size < 2
|
||||||
|
prefix = []
|
||||||
|
else
|
||||||
|
prefix = prefix_re.to_a.drop(1)
|
||||||
|
end
|
||||||
|
field_for = "#{prefix.join('_')}_#{@options[:field_name]}"
|
||||||
|
|
||||||
|
"<span class='hidden'><label for='#{field_for}_#{n}i'>#{label}</label></span>"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the separator for a given datetime component.
|
||||||
|
def separator(type)
|
||||||
|
return "" if @options[:use_hidden]
|
||||||
|
case type
|
||||||
|
when :year
|
||||||
|
datetime_accessibility_label(1, 'Année')
|
||||||
|
when :month
|
||||||
|
datetime_accessibility_label(2, 'Mois')
|
||||||
|
when :day
|
||||||
|
datetime_accessibility_label(3, 'Jour')
|
||||||
|
when :hour
|
||||||
|
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] + datetime_accessibility_label(4, 'Heure')
|
||||||
|
when :minute, :second
|
||||||
|
@options[:"discard_#{type}"] ? "" : datetime_accessibility_label(5, 'Minute')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
94
config/initializers/pg_interval_5_2.rb
Normal file
94
config/initializers/pg_interval_5_2.rb
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# from https://gist.github.com/Envek/7077bfc36b17233f60ad
|
||||||
|
|
||||||
|
# PostgreSQL interval data type support from https://github.com/rails/rails/pull/16919
|
||||||
|
# Works with both Rails 5.2 and 6.0
|
||||||
|
# Place this file to config/initializers/
|
||||||
|
|
||||||
|
require "active_support/duration"
|
||||||
|
|
||||||
|
# activerecord/lib/active_record/connection_adapters/postgresql/oid/interval.rb
|
||||||
|
module ActiveRecord
|
||||||
|
module ConnectionAdapters
|
||||||
|
module PostgreSQL
|
||||||
|
module OID # :nodoc:
|
||||||
|
class Interval < Type::Value # :nodoc:
|
||||||
|
def type
|
||||||
|
:interval
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_value(value)
|
||||||
|
case value
|
||||||
|
when ::ActiveSupport::Duration
|
||||||
|
value
|
||||||
|
when ::String
|
||||||
|
begin
|
||||||
|
::ActiveSupport::Duration.parse(value)
|
||||||
|
rescue ::ActiveSupport::Duration::ISO8601Parser::ParsingError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize(value)
|
||||||
|
case value
|
||||||
|
when ::ActiveSupport::Duration
|
||||||
|
value.iso8601(precision: self.precision)
|
||||||
|
when ::Numeric
|
||||||
|
# Sometimes operations on Times returns just float number of seconds so we need to handle that.
|
||||||
|
# Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
|
||||||
|
value.seconds.iso8601(precision: self.precision)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def type_cast_for_schema(value)
|
||||||
|
serialize(value).inspect
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
|
||||||
|
require 'active_record/connection_adapters/postgresql_adapter'
|
||||||
|
PostgreSQLAdapterWithInterval = Module.new do
|
||||||
|
def initialize_type_map(m = type_map)
|
||||||
|
super
|
||||||
|
m.register_type "interval" do |*_args, sql_type|
|
||||||
|
precision = extract_precision(sql_type)
|
||||||
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Interval.new(precision: precision)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure_connection
|
||||||
|
super
|
||||||
|
execute('SET intervalstyle = iso_8601', 'SCHEMA')
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Type.register(:interval, ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::Interval, adapter: :postgresql)
|
||||||
|
end
|
||||||
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(PostgreSQLAdapterWithInterval)
|
||||||
|
|
||||||
|
# activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
|
||||||
|
require 'active_record/connection_adapters/postgresql/schema_statements'
|
||||||
|
module SchemaStatementsWithInterval
|
||||||
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **)
|
||||||
|
case type.to_s
|
||||||
|
when 'interval'
|
||||||
|
case precision
|
||||||
|
when nil; "interval"
|
||||||
|
when 0..6; "interval(#{precision})"
|
||||||
|
else raise(ActiveRecordError, "No interval type has precision of #{precision}. The allowed range of precision is from 0 to 6")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements.prepend(SchemaStatementsWithInterval)
|
|
@ -18,7 +18,7 @@ CADRE_JURIDIQUE_URL = [DOC_URL, "tutoriels/video-le-cadre-juridique"].join("/")
|
||||||
WEBINAIRE_URL = "https://app.livestorm.co/demarches-simplifiees"
|
WEBINAIRE_URL = "https://app.livestorm.co/demarches-simplifiees"
|
||||||
LISTE_DES_DEMARCHES_URL = [DOC_URL, "listes-des-demarches"].join("/")
|
LISTE_DES_DEMARCHES_URL = [DOC_URL, "listes-des-demarches"].join("/")
|
||||||
CGU_URL = [DOC_URL, "cgu"].join("/")
|
CGU_URL = [DOC_URL, "cgu"].join("/")
|
||||||
MENTIONS_LEGALES_URL = [CGU_URL, "4-mentions-legales"].join("#")
|
MENTIONS_LEGALES_URL = [DOC_URL, "mentions-legales"].join("/")
|
||||||
API_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "api"].join("/")
|
API_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "api"].join("/")
|
||||||
WEBHOOK_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "webhook"].join("/")
|
WEBHOOK_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "webhook"].join("/")
|
||||||
FAQ_URL = "https://faq.demarches-simplifiees.fr"
|
FAQ_URL = "https://faq.demarches-simplifiees.fr"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
fr:
|
fr:
|
||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
champs:
|
champ:
|
||||||
value: La valeur du champ
|
value: La valeur du champ
|
||||||
|
|
11
config/locales/models/deleted_dossier/fr.yml
Normal file
11
config/locales/models/deleted_dossier/fr.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
fr:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
deleted_dossier:
|
||||||
|
reason:
|
||||||
|
user_request: Demande d’usager
|
||||||
|
manager_request: Demande d’administration
|
||||||
|
user_removed: Suppression d'un compte usager
|
||||||
|
expired: Expiration
|
||||||
|
unknown: Inconnue
|
||||||
|
|
|
@ -146,6 +146,7 @@ Rails.application.routes.draw do
|
||||||
post '/carte/zones' => 'carte#zones'
|
post '/carte/zones' => 'carte#zones'
|
||||||
get '/carte' => 'carte#show'
|
get '/carte' => 'carte#show'
|
||||||
post '/carte' => 'carte#save'
|
post '/carte' => 'carte#save'
|
||||||
|
post '/repousser-expiration' => 'dossiers#extend_conservation'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Redirection of legacy "/users/dossiers" route to "/dossiers"
|
# Redirection of legacy "/users/dossiers" route to "/dossiers"
|
||||||
|
@ -306,6 +307,7 @@ Rails.application.routes.draw do
|
||||||
get 'stats'
|
get 'stats'
|
||||||
get 'email_notifications'
|
get 'email_notifications'
|
||||||
patch 'update_email_notifications'
|
patch 'update_email_notifications'
|
||||||
|
get 'deleted_dossiers'
|
||||||
|
|
||||||
resources :dossiers, only: [:show], param: :dossier_id do
|
resources :dossiers, only: [:show], param: :dossier_id do
|
||||||
member do
|
member do
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddEnConstructionConservationExtensionToDossiers < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :dossiers, :en_construction_conservation_extension, :interval, :default => 0.days
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddReasonToDeletedDossiers < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :deleted_dossiers, :reason, :string
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2020_03_04_155418) do
|
ActiveRecord::Schema.define(version: 2020_03_19_103836) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -213,6 +213,7 @@ ActiveRecord::Schema.define(version: 2020_03_04_155418) do
|
||||||
t.string "state"
|
t.string "state"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.string "reason"
|
||||||
t.index ["procedure_id"], name: "index_deleted_dossiers_on_procedure_id"
|
t.index ["procedure_id"], name: "index_deleted_dossiers_on_procedure_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -254,6 +255,7 @@ ActiveRecord::Schema.define(version: 2020_03_04_155418) do
|
||||||
t.datetime "en_construction_close_to_expiration_notice_sent_at"
|
t.datetime "en_construction_close_to_expiration_notice_sent_at"
|
||||||
t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin
|
t.index "to_tsvector('french'::regconfig, (search_terms || private_search_terms))", name: "index_dossiers_on_search_terms_private_search_terms", using: :gin
|
||||||
t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
|
t.index "to_tsvector('french'::regconfig, search_terms)", name: "index_dossiers_on_search_terms", using: :gin
|
||||||
|
t.interval "en_construction_conservation_extension", default: "00:00:00"
|
||||||
t.index ["archived"], name: "index_dossiers_on_archived"
|
t.index ["archived"], name: "index_dossiers_on_archived"
|
||||||
t.index ["groupe_instructeur_id"], name: "index_dossiers_on_groupe_instructeur_id"
|
t.index ["groupe_instructeur_id"], name: "index_dossiers_on_groupe_instructeur_id"
|
||||||
t.index ["hidden_at"], name: "index_dossiers_on_hidden_at"
|
t.index ["hidden_at"], name: "index_dossiers_on_hidden_at"
|
||||||
|
|
|
@ -411,6 +411,20 @@ describe Instructeurs::ProceduresController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#deleted_dossiers' do
|
||||||
|
let(:instructeur) { create(:instructeur) }
|
||||||
|
let(:procedure) { create(:procedure, instructeurs: [instructeur]) }
|
||||||
|
let(:deleted_dossier) { create(:deleted_dossier, procedure: procedure, state: :en_construction) }
|
||||||
|
let!(:deleted_dossier_brouillon) { create(:deleted_dossier, procedure: procedure, state: :brouillon) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(instructeur.user)
|
||||||
|
get :deleted_dossiers, params: { procedure_id: procedure.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(assigns(:deleted_dossiers)).to match_array([deleted_dossier]) }
|
||||||
|
end
|
||||||
|
|
||||||
describe '#update_email_notifications' do
|
describe '#update_email_notifications' do
|
||||||
let(:instructeur) { create(:instructeur) }
|
let(:instructeur) { create(:instructeur) }
|
||||||
let!(:procedure) { create(:procedure, instructeurs: [instructeur]) }
|
let!(:procedure) { create(:procedure, instructeurs: [instructeur]) }
|
||||||
|
|
|
@ -189,6 +189,7 @@ feature 'The routing', js: true do
|
||||||
visit commencer_path(path: procedure.reload.path)
|
visit commencer_path(path: procedure.reload.path)
|
||||||
click_on 'Commencer la démarche'
|
click_on 'Commencer la démarche'
|
||||||
|
|
||||||
|
choose 'M.'
|
||||||
fill_in 'individual_nom', with: 'Nom'
|
fill_in 'individual_nom', with: 'Nom'
|
||||||
fill_in 'individual_prenom', with: 'Prenom'
|
fill_in 'individual_prenom', with: 'Prenom'
|
||||||
click_button('Continuer')
|
click_button('Continuer')
|
||||||
|
|
|
@ -19,7 +19,7 @@ feature 'The user' do
|
||||||
fill_in('text', with: 'super texte')
|
fill_in('text', with: 'super texte')
|
||||||
fill_in('textarea', with: 'super textarea')
|
fill_in('textarea', with: 'super textarea')
|
||||||
fill_in('date', with: '12-12-2012')
|
fill_in('date', with: '12-12-2012')
|
||||||
select_date_and_time(Time.zone.parse('06/01/1985 7h05'), form_id_for('datetime'))
|
select_date_and_time(Time.zone.parse('06/01/1985 7h05'), form_id_for_datetime('datetime'))
|
||||||
fill_in('number', with: '42')
|
fill_in('number', with: '42')
|
||||||
check('checkbox')
|
check('checkbox')
|
||||||
choose('Madame')
|
choose('Madame')
|
||||||
|
@ -74,7 +74,7 @@ feature 'The user' do
|
||||||
expect(page).to have_field('text', with: 'super texte')
|
expect(page).to have_field('text', with: 'super texte')
|
||||||
expect(page).to have_field('textarea', with: 'super textarea')
|
expect(page).to have_field('textarea', with: 'super textarea')
|
||||||
expect(page).to have_field('date', with: '2012-12-12')
|
expect(page).to have_field('date', with: '2012-12-12')
|
||||||
check_date_and_time(Time.zone.parse('06/01/1985 7:05'), form_id_for('datetime'))
|
check_date_and_time(Time.zone.parse('06/01/1985 7:05'), form_id_for_datetime('datetime'))
|
||||||
expect(page).to have_field('number', with: '42')
|
expect(page).to have_field('number', with: '42')
|
||||||
expect(page).to have_checked_field('checkbox')
|
expect(page).to have_checked_field('checkbox')
|
||||||
expect(page).to have_checked_field('Madame')
|
expect(page).to have_checked_field('Madame')
|
||||||
|
@ -167,7 +167,7 @@ feature 'The user' do
|
||||||
fill_individual
|
fill_individual
|
||||||
|
|
||||||
# Add an attachment
|
# Add an attachment
|
||||||
find('.editable-champ-piece_justificative input[type=file]').attach_file(Rails.root + 'spec/fixtures/files/file.pdf')
|
find_field('Pièce justificative').attach_file(Rails.root + 'spec/fixtures/files/file.pdf')
|
||||||
click_on 'Enregistrer le brouillon'
|
click_on 'Enregistrer le brouillon'
|
||||||
expect(page).to have_content('Votre brouillon a bien été sauvegardé')
|
expect(page).to have_content('Votre brouillon a bien été sauvegardé')
|
||||||
expect(page).to have_text('file.pdf')
|
expect(page).to have_text('file.pdf')
|
||||||
|
@ -182,7 +182,7 @@ feature 'The user' do
|
||||||
|
|
||||||
# Replace the attachment
|
# Replace the attachment
|
||||||
within('.attachment') { click_on 'Remplacer' }
|
within('.attachment') { click_on 'Remplacer' }
|
||||||
find('.editable-champ-piece_justificative input[type=file]').attach_file(Rails.root + 'spec/fixtures/files/RIB.pdf')
|
find_field('Pièce justificative').attach_file(Rails.root + 'spec/fixtures/files/RIB.pdf')
|
||||||
click_on 'Enregistrer le brouillon'
|
click_on 'Enregistrer le brouillon'
|
||||||
expect(page).to have_no_text('file.pdf')
|
expect(page).to have_no_text('file.pdf')
|
||||||
expect(page).to have_text('RIB.pdf')
|
expect(page).to have_text('RIB.pdf')
|
||||||
|
@ -250,12 +250,39 @@ feature 'The user' do
|
||||||
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
|
find(:xpath, ".//label[contains(text()[normalize-space()], '#{libelle}')]")[:for]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def form_id_for_datetime(libelle)
|
||||||
|
# The HTML for datetime is a bit specific since it has 5 selects, below here is a sample HTML
|
||||||
|
# So, we want to find the partial id of a datetime (partial because there are 5 ids:
|
||||||
|
# dossier_champs_attributes_3_value_1i, 2i, ... 5i) ; we are interested in the 'dossier_champs_attributes_3_value' part
|
||||||
|
# which is then completed in select_date_and_time and check_date_and_time
|
||||||
|
#
|
||||||
|
# We find the H2, find the first select in the next .datetime div, then strip the last 3 characters
|
||||||
|
#
|
||||||
|
# <h4 class="form-label">
|
||||||
|
# libelle
|
||||||
|
# </h4>
|
||||||
|
# <div class="datetime">
|
||||||
|
# <span class="hidden">
|
||||||
|
# <label for="dossier_champs_attributes_3_value_3i">Jour</label></span>
|
||||||
|
# <select id="dossier_champs_attributes_3_value_3i" name="dossier[champs_attributes][3][value(3i)]">
|
||||||
|
# <option value=""></option>
|
||||||
|
# <option value="1">1</option>
|
||||||
|
# <option value="2">2</option>
|
||||||
|
# <!-- … -->
|
||||||
|
# </select>
|
||||||
|
# <!-- … 4 other selects for month, year, minute and seconds -->
|
||||||
|
# </div>
|
||||||
|
e = find(:xpath, ".//h4[contains(text()[normalize-space()], '#{libelle}')]")
|
||||||
|
e.sibling('.datetime').first('select')[:id][0..-4]
|
||||||
|
end
|
||||||
|
|
||||||
def champ_value_for(libelle)
|
def champ_value_for(libelle)
|
||||||
champs = user_dossier.champs
|
champs = user_dossier.champs
|
||||||
champs.find { |c| c.libelle == libelle }.value
|
champs.find { |c| c.libelle == libelle }.value
|
||||||
end
|
end
|
||||||
|
|
||||||
def fill_individual
|
def fill_individual
|
||||||
|
choose 'M.'
|
||||||
fill_in('individual_prenom', with: 'prenom')
|
fill_in('individual_prenom', with: 'prenom')
|
||||||
fill_in('individual_nom', with: 'nom')
|
fill_in('individual_nom', with: 'nom')
|
||||||
click_on 'Continuer'
|
click_on 'Continuer'
|
||||||
|
|
|
@ -22,6 +22,7 @@ feature 'Creating a new dossier:' do
|
||||||
expect(page).to have_current_path identite_dossier_path(user.reload.dossiers.last)
|
expect(page).to have_current_path identite_dossier_path(user.reload.dossiers.last)
|
||||||
expect(page).to have_procedure_description(procedure)
|
expect(page).to have_procedure_description(procedure)
|
||||||
|
|
||||||
|
choose 'M.'
|
||||||
fill_in 'individual_nom', with: 'Nom'
|
fill_in 'individual_nom', with: 'Nom'
|
||||||
fill_in 'individual_prenom', with: 'Prenom'
|
fill_in 'individual_prenom', with: 'Prenom'
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,6 +62,7 @@ feature 'linked dropdown lists' do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fill_individual
|
def fill_individual
|
||||||
|
choose 'M.'
|
||||||
fill_in('individual_prenom', with: 'prenom')
|
fill_in('individual_prenom', with: 'prenom')
|
||||||
fill_in('individual_nom', with: 'nom')
|
fill_in('individual_nom', with: 'nom')
|
||||||
click_on 'Continuer'
|
click_on 'Continuer'
|
||||||
|
|
|
@ -5,7 +5,7 @@ RSpec.describe AutoReceiveDossiersForProcedureJob, type: :job do
|
||||||
let(:date) { Time.utc(2017, 9, 1, 10, 5, 0) }
|
let(:date) { Time.utc(2017, 9, 1, 10, 5, 0) }
|
||||||
let(:instruction_date) { date + 120 }
|
let(:instruction_date) { date + 120 }
|
||||||
|
|
||||||
let(:procedure) { create(:procedure, :with_instructeur) }
|
let(:procedure) { create(:procedure, :published, :with_instructeur) }
|
||||||
let(:nouveau_dossier1) { create(:dossier, :en_construction, procedure: procedure) }
|
let(:nouveau_dossier1) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: procedure) }
|
let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
let(:dossier_recu) { create(:dossier, :en_instruction, procedure: procedure) }
|
let(:dossier_recu) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||||
|
|
|
@ -6,7 +6,7 @@ RSpec.describe DeclarativeProceduresJob, type: :job do
|
||||||
let(:instruction_date) { date + 120 }
|
let(:instruction_date) { date + 120 }
|
||||||
|
|
||||||
let(:state) { nil }
|
let(:state) { nil }
|
||||||
let(:procedure) { create(:procedure, :with_instructeur, declarative_with_state: state) }
|
let(:procedure) { create(:procedure, :published, :with_instructeur, declarative_with_state: state) }
|
||||||
let(:nouveau_dossier1) { create(:dossier, :en_construction, procedure: procedure) }
|
let(:nouveau_dossier1) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: procedure) }
|
let(:nouveau_dossier2) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
let(:dossier_recu) { create(:dossier, :en_instruction, procedure: procedure) }
|
let(:dossier_recu) { create(:dossier, :en_instruction, procedure: procedure) }
|
||||||
|
|
|
@ -98,7 +98,7 @@ RSpec.describe DossierMailer, type: :mailer do
|
||||||
|
|
||||||
describe '.notify_automatic_deletion_to_user' do
|
describe '.notify_automatic_deletion_to_user' do
|
||||||
let(:dossier) { create(:dossier) }
|
let(:dossier) { create(:dossier) }
|
||||||
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier) }
|
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier, :expired) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
duree = dossier.procedure.duree_conservation_dossiers_dans_ds
|
duree = dossier.procedure.duree_conservation_dossiers_dans_ds
|
||||||
|
@ -116,7 +116,7 @@ RSpec.describe DossierMailer, type: :mailer do
|
||||||
|
|
||||||
describe '.notify_automatic_deletion_to_administration' do
|
describe '.notify_automatic_deletion_to_administration' do
|
||||||
let(:dossier) { create(:dossier) }
|
let(:dossier) { create(:dossier) }
|
||||||
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier) }
|
let(:deleted_dossier) { DeletedDossier.create_from_dossier(dossier, :expired) }
|
||||||
|
|
||||||
subject { described_class.notify_automatic_deletion_to_administration([deleted_dossier], dossier.user.email) }
|
subject { described_class.notify_automatic_deletion_to_administration([deleted_dossier], dossier.user.email) }
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,10 @@ class DossierMailerPreview < ActionMailer::Preview
|
||||||
DossierMailer.notify_automatic_deletion_to_administration([deleted_dossier, deleted_dossier], administration_email)
|
DossierMailer.notify_automatic_deletion_to_administration([deleted_dossier, deleted_dossier], administration_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def notify_brouillon_not_submitted
|
||||||
|
DossierMailer.notify_brouillon_not_submitted(draft)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def usager_email
|
def usager_email
|
||||||
|
@ -76,7 +80,7 @@ class DossierMailerPreview < ActionMailer::Preview
|
||||||
end
|
end
|
||||||
|
|
||||||
def procedure
|
def procedure
|
||||||
Procedure.new(id: 1234, libelle: 'Dotation d’Équipement des Territoires Ruraux - Exercice 2019', service: service, logo: Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png'))
|
Procedure.new(id: 1234, libelle: 'Dotation d’Équipement des Territoires Ruraux - Exercice 2019', service: service, logo: Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png'), auto_archive_on: Time.zone.today + Dossier::REMAINING_DAYS_BEFORE_CLOSING.days)
|
||||||
end
|
end
|
||||||
|
|
||||||
def service
|
def service
|
||||||
|
|
|
@ -46,7 +46,7 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'brouillon_close_to_expiration' do
|
describe 'brouillon_close_to_expiration' do
|
||||||
let(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) }
|
let(:procedure) { create(:procedure, :published, duree_conservation_dossiers_dans_ds: 6) }
|
||||||
let!(:young_dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
let!(:young_dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
let!(:expiring_dossier) { create(:dossier, created_at: 170.days.ago, procedure: procedure) }
|
let!(:expiring_dossier) { create(:dossier, created_at: 170.days.ago, procedure: procedure) }
|
||||||
let!(:just_expired_dossier) { create(:dossier, created_at: (6.months + 1.hour + 10.seconds).ago, procedure: procedure) }
|
let!(:just_expired_dossier) { create(:dossier, created_at: (6.months + 1.hour + 10.seconds).ago, procedure: procedure) }
|
||||||
|
@ -63,7 +63,7 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'en_construction_close_to_expiration' do
|
describe 'en_construction_close_to_expiration' do
|
||||||
let(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) }
|
let(:procedure) { create(:procedure, :published, duree_conservation_dossiers_dans_ds: 6) }
|
||||||
let!(:young_dossier) { create(:dossier, procedure: procedure) }
|
let!(:young_dossier) { create(:dossier, procedure: procedure) }
|
||||||
let!(:expiring_dossier) { create(:dossier, :en_construction, en_construction_at: 170.days.ago, procedure: procedure) }
|
let!(:expiring_dossier) { create(:dossier, :en_construction, en_construction_at: 170.days.ago, procedure: procedure) }
|
||||||
let!(:just_expired_dossier) { create(:dossier, :en_construction, en_construction_at: (6.months + 1.hour + 10.seconds).ago, procedure: procedure) }
|
let!(:just_expired_dossier) { create(:dossier, :en_construction, en_construction_at: (6.months + 1.hour + 10.seconds).ago, procedure: procedure) }
|
||||||
|
@ -77,10 +77,19 @@ describe Dossier do
|
||||||
is_expected.to include(just_expired_dossier)
|
is_expected.to include(just_expired_dossier)
|
||||||
is_expected.to include(long_expired_dossier)
|
is_expected.to include(long_expired_dossier)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'does not include an expiring dossier that has been postponed' do
|
||||||
|
before do
|
||||||
|
expiring_dossier.update(en_construction_conservation_extension: 1.month)
|
||||||
|
expiring_dossier.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.not_to include(expiring_dossier) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'en_instruction_close_to_expiration' do
|
describe 'en_instruction_close_to_expiration' do
|
||||||
let(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) }
|
let(:procedure) { create(:procedure, :published, duree_conservation_dossiers_dans_ds: 6) }
|
||||||
let!(:young_dossier) { create(:dossier, procedure: procedure) }
|
let!(:young_dossier) { create(:dossier, procedure: procedure) }
|
||||||
let!(:expiring_dossier) { create(:dossier, :en_instruction, en_instruction_at: 170.days.ago, procedure: procedure) }
|
let!(:expiring_dossier) { create(:dossier, :en_instruction, en_instruction_at: 170.days.ago, procedure: procedure) }
|
||||||
let!(:just_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: (6.months + 1.hour + 10.seconds).ago, procedure: procedure) }
|
let!(:just_expired_dossier) { create(:dossier, :en_instruction, en_instruction_at: (6.months + 1.hour + 10.seconds).ago, procedure: procedure) }
|
||||||
|
@ -441,7 +450,7 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#unfollow_stale_instructeurs" do
|
describe "#unfollow_stale_instructeurs" do
|
||||||
let(:procedure) { create(:procedure) }
|
let(:procedure) { create(:procedure, :published) }
|
||||||
let(:instructeur) { create(:instructeur) }
|
let(:instructeur) { create(:instructeur) }
|
||||||
let(:new_groupe_instructeur) { create(:groupe_instructeur, procedure: procedure) }
|
let(:new_groupe_instructeur) { create(:groupe_instructeur, procedure: procedure) }
|
||||||
let(:instructeur2) { create(:instructeur, groupe_instructeurs: [procedure.defaut_groupe_instructeur, new_groupe_instructeur]) }
|
let(:instructeur2) { create(:instructeur, groupe_instructeurs: [procedure.defaut_groupe_instructeur, new_groupe_instructeur]) }
|
||||||
|
@ -661,23 +670,42 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#delete_and_keep_track" do
|
describe "#delete_and_keep_track!" do
|
||||||
let(:dossier) { create(:dossier) }
|
let(:dossier) { create(:dossier, :en_construction) }
|
||||||
let(:deleted_dossier) { DeletedDossier.find_by!(dossier_id: dossier.id) }
|
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
|
||||||
let(:last_operation) { dossier.dossier_operation_logs.last }
|
let(:last_operation) { dossier.dossier_operation_logs.last }
|
||||||
|
let(:reason) { :user_request }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(DossierMailer).to receive(:notify_deletion_to_user).and_return(double(deliver_later: nil))
|
allow(DossierMailer).to receive(:notify_deletion_to_user).and_return(double(deliver_later: nil))
|
||||||
allow(DossierMailer).to receive(:notify_deletion_to_administration).and_return(double(deliver_later: nil))
|
allow(DossierMailer).to receive(:notify_deletion_to_administration).and_return(double(deliver_later: nil))
|
||||||
end
|
end
|
||||||
|
|
||||||
subject! { dossier.delete_and_keep_track(dossier.user) }
|
subject! { dossier.delete_and_keep_track!(dossier.user, reason) }
|
||||||
|
|
||||||
|
context 'brouillon' do
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
|
||||||
|
it 'hides the dossier' do
|
||||||
|
expect(dossier.discarded?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'do not creates a DeletedDossier record' do
|
||||||
|
expect(deleted_dossier).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'do not records the operation in the log' do
|
||||||
|
expect(last_operation).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'en_construction' do
|
||||||
it 'hides the dossier' do
|
it 'hides the dossier' do
|
||||||
expect(dossier.hidden_at).to be_present
|
expect(dossier.hidden_at).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates a DeletedDossier record' do
|
it 'creates a DeletedDossier record' do
|
||||||
|
expect(deleted_dossier.reason).to eq DeletedDossier.reasons.fetch(reason)
|
||||||
expect(deleted_dossier.dossier_id).to eq dossier.id
|
expect(deleted_dossier.dossier_id).to eq dossier.id
|
||||||
expect(deleted_dossier.procedure).to eq dossier.procedure
|
expect(deleted_dossier.procedure).to eq dossier.procedure
|
||||||
expect(deleted_dossier.state).to eq dossier.state
|
expect(deleted_dossier.state).to eq dossier.state
|
||||||
|
@ -721,6 +749,33 @@ describe Dossier do
|
||||||
expect(DossierMailer).not_to have_received(:notify_deletion_to_administration)
|
expect(DossierMailer).not_to have_received(:notify_deletion_to_administration)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with reason: manager_request' do
|
||||||
|
let(:reason) { :manager_request }
|
||||||
|
|
||||||
|
it 'hides the dossier' do
|
||||||
|
expect(dossier.discarded?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'records the operation in the log' do
|
||||||
|
expect(last_operation.operation).to eq("supprimer")
|
||||||
|
expect(last_operation.automatic_operation?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with reason: user_removed' do
|
||||||
|
let(:reason) { :user_removed }
|
||||||
|
|
||||||
|
it 'hides the dossier' do
|
||||||
|
expect(dossier.discarded?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'records the operation in the log' do
|
||||||
|
expect(last_operation.operation).to eq("supprimer")
|
||||||
|
expect(last_operation.automatic_operation?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'webhook' do
|
describe 'webhook' do
|
||||||
|
@ -1097,6 +1152,33 @@ describe Dossier do
|
||||||
it { expect(Dossier.for_procedure(procedure_2)).to contain_exactly(dossier_2_1) }
|
it { expect(Dossier.for_procedure(procedure_2)).to contain_exactly(dossier_2_1) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#notify_draft_not_submitted' do
|
||||||
|
let!(:user1) { create(:user) }
|
||||||
|
let!(:user2) { create(:user) }
|
||||||
|
let!(:procedure_near_closing) { create(:procedure, auto_archive_on: Time.zone.today + Dossier::REMAINING_DAYS_BEFORE_CLOSING.days) }
|
||||||
|
let!(:procedure_closed_later) { create(:procedure, auto_archive_on: Time.zone.today + Dossier::REMAINING_DAYS_BEFORE_CLOSING.days + 1.day) }
|
||||||
|
let!(:procedure_closed_before) { create(:procedure, auto_archive_on: Time.zone.today + Dossier::REMAINING_DAYS_BEFORE_CLOSING.days - 1.day) }
|
||||||
|
|
||||||
|
# user 1 has three draft dossiers where one is for procedure that closes in two days ==> should trigger one mail
|
||||||
|
let!(:draft_near_closing) { create(:dossier, user: user1, procedure: procedure_near_closing) }
|
||||||
|
let!(:draft_before) { create(:dossier, user: user1, procedure: procedure_closed_before) }
|
||||||
|
let!(:draft_later) { create(:dossier, user: user1, procedure: procedure_closed_later) }
|
||||||
|
|
||||||
|
# user 2 submitted a draft and en_construction dossier for the same procedure ==> should not trigger the mail
|
||||||
|
let!(:draft_near_closing_2) { create(:dossier, :en_construction, user: user2, procedure: procedure_near_closing) }
|
||||||
|
let!(:submitted_near_closing_2) { create(:dossier, user: user2, procedure: procedure_near_closing) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(DossierMailer).to receive(:notify_brouillon_not_submitted).and_return(double(deliver_later: nil))
|
||||||
|
Dossier.notify_draft_not_submitted
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'notifies draft is not submitted' do
|
||||||
|
expect(DossierMailer).to have_received(:notify_brouillon_not_submitted).once
|
||||||
|
expect(DossierMailer).to have_received(:notify_brouillon_not_submitted).with(draft_near_closing)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#geo_position' do
|
describe '#geo_position' do
|
||||||
let(:lat) { "46.538192" }
|
let(:lat) { "46.538192" }
|
||||||
let(:lon) { "2.428462" }
|
let(:lon) { "2.428462" }
|
||||||
|
@ -1131,4 +1213,41 @@ describe Dossier do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'dossier_operation_log after dossier deletion' do
|
||||||
|
let(:dossier) { create(:dossier) }
|
||||||
|
let(:dossier_operation_log) { create(:dossier_operation_log, dossier: dossier) }
|
||||||
|
|
||||||
|
it 'should nullify dossier link' do
|
||||||
|
expect(dossier_operation_log.dossier).to eq(dossier)
|
||||||
|
expect(DossierOperationLog.count).to eq(1)
|
||||||
|
dossier.destroy
|
||||||
|
expect(dossier_operation_log.reload.dossier).to be_nil
|
||||||
|
expect(DossierOperationLog.count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'discarded_brouillon_expired and discarded_en_construction_expired' do
|
||||||
|
before do
|
||||||
|
create(:dossier)
|
||||||
|
create(:dossier, :en_construction)
|
||||||
|
create(:dossier).discard!
|
||||||
|
create(:dossier, :en_construction).discard!
|
||||||
|
|
||||||
|
Timecop.travel(2.months.ago) do
|
||||||
|
create(:dossier).discard!
|
||||||
|
create(:dossier, :en_construction).discard!
|
||||||
|
|
||||||
|
create(:dossier).procedure.hide!
|
||||||
|
create(:dossier, :en_construction).procedure.hide!
|
||||||
|
end
|
||||||
|
Timecop.travel(1.week.ago) do
|
||||||
|
create(:dossier).discard!
|
||||||
|
create(:dossier, :en_construction).discard!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(Dossier.discarded_brouillon_expired.count).to eq(2) }
|
||||||
|
it { expect(Dossier.discarded_en_construction_expired.count).to eq(1) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -264,7 +264,7 @@ describe User, type: :model do
|
||||||
user.delete_and_keep_track_dossiers(administration)
|
user.delete_and_keep_track_dossiers(administration)
|
||||||
|
|
||||||
expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
|
expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
|
||||||
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_present
|
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
|
||||||
expect(User.find_by(id: user.id)).to be_nil
|
expect(User.find_by(id: user.id)).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -278,16 +278,16 @@ describe User, type: :model do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "keep track of dossiers and delete user" do
|
it "keep track of dossiers and delete user" do
|
||||||
dossier_cache.delete_and_keep_track(administration)
|
dossier_cache.delete_and_keep_track!(administration, :user_request)
|
||||||
user.delete_and_keep_track_dossiers(administration)
|
user.delete_and_keep_track_dossiers(administration)
|
||||||
|
|
||||||
expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
|
expect(DeletedDossier.find_by(dossier_id: dossier_en_construction)).to be_present
|
||||||
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_present
|
expect(DeletedDossier.find_by(dossier_id: dossier_brouillon)).to be_nil
|
||||||
expect(User.find_by(id: user.id)).to be_nil
|
expect(User.find_by(id: user.id)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't destroy dossiers of another user" do
|
it "doesn't destroy dossiers of another user" do
|
||||||
dossier_cache.delete_and_keep_track(administration)
|
dossier_cache.delete_and_keep_track!(administration, :user_request)
|
||||||
user.delete_and_keep_track_dossiers(administration)
|
user.delete_and_keep_track_dossiers(administration)
|
||||||
|
|
||||||
expect(Dossier.find_by(id: dossier_from_another_user.id)).to be_present
|
expect(Dossier.find_by(id: dossier_from_another_user.id)).to be_present
|
||||||
|
|
|
@ -4,7 +4,7 @@ describe ExpiredDossiersDeletionService do
|
||||||
describe '#process_expired_dossiers_brouillon' do
|
describe '#process_expired_dossiers_brouillon' do
|
||||||
let(:draft_expiration) { 1.month + 5.days }
|
let(:draft_expiration) { 1.month + 5.days }
|
||||||
let!(:today) { Time.zone.now.at_midnight }
|
let!(:today) { Time.zone.now.at_midnight }
|
||||||
let!(:procedure) { create(:procedure, duree_conservation_dossiers_dans_ds: 6) }
|
let!(:procedure) { create(:procedure, :published, duree_conservation_dossiers_dans_ds: 6) }
|
||||||
let!(:date_close_to_expiration) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 1.month }
|
let!(:date_close_to_expiration) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 1.month }
|
||||||
let!(:date_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months - 6.days }
|
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!(:date_not_expired) { Date.today - procedure.duree_conservation_dossiers_dans_ds.months + 2.months }
|
||||||
|
@ -34,7 +34,7 @@ describe ExpiredDossiersDeletionService do
|
||||||
it 'deletes and notify expired brouillon' do
|
it 'deletes and notify expired brouillon' do
|
||||||
expect(DossierMailer).to have_received(:notify_brouillon_deletion).once
|
expect(DossierMailer).to have_received(:notify_brouillon_deletion).once
|
||||||
expect(DossierMailer).to have_received(:notify_brouillon_deletion).with([expired_brouillon.hash_for_deletion_mail], expired_brouillon.user.email)
|
expect(DossierMailer).to have_received(:notify_brouillon_deletion).with([expired_brouillon.hash_for_deletion_mail], expired_brouillon.user.email)
|
||||||
expect(DeletedDossier.find_by(dossier_id: expired_brouillon.id)).to be_present
|
expect(DeletedDossier.find_by(dossier_id: expired_brouillon.id)).not_to be_present
|
||||||
expect { expired_brouillon.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
expect { expired_brouillon.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -192,7 +192,7 @@ describe ExpiredDossiersDeletionService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when an instructeur is also administrateur' do
|
context 'when an instructeur is also administrateur' do
|
||||||
let!(:procedure) { create(:procedure) }
|
let!(:procedure) { create(:procedure, :published) }
|
||||||
let!(:administrateur) { procedure.administrateurs.first }
|
let!(:administrateur) { procedure.administrateurs.first }
|
||||||
let!(:dossier) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: (conservation_par_defaut - 1.month + 1.day).ago) }
|
let!(:dossier) { create(:dossier, :en_construction, procedure: procedure, en_construction_at: (conservation_par_defaut - 1.month + 1.day).ago) }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue