Merge branch 'develop'

This commit is contained in:
Mathieu Magnin 2017-06-12 15:25:54 +02:00
commit 4a04c3f0d4
150 changed files with 2416 additions and 733 deletions

View file

@ -64,7 +64,7 @@ jobs:
- "0a:67:42:7d:7e:b7:e1:3c:48:8f:bf:68:10:51:a8:44" - "0a:67:42:7d:7e:b7:e1:3c:48:8f:bf:68:10:51:a8:44"
- deploy: - deploy:
command: | command: |
if [ "${CIRCLE_BRANCH}" == "staging" ]; then if [ "${CIRCLE_BRANCH}" == "develop" ]; then
bundle exec rake deploy_ha bundle exec rake deploy_ha
fi fi

View file

@ -1,4 +1,4 @@
exclude: 'app/assets/stylesheets/reset.scss' exclude: 'app/assets/stylesheets/new_design/reset.scss'
linters: linters:
BangFormat: BangFormat:

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -3,5 +3,3 @@ $light-blue: #F2F6FA;
// Bootstrap constants // Bootstrap constants
$font-size-base: 16px; $font-size-base: 16px;
$page-width: 1040px;

View file

@ -1,14 +0,0 @@
%horizontal-list {
list-style-type: none;
margin: 0;
padding: 0;
font-size: 0px;
}
%horizontal-list-item {
display: inline-block;
&:last-of-type {
margin-right: 0;
}
}

View file

@ -13,13 +13,8 @@
// file per style scope. // file per style scope.
// //
// = require _card // = require _card
// = require _colors
// = require _constants
// = require _helpers // = require _helpers
// = require _mixins
// = require _placeholders
// = require _turbolinks // = require _turbolinks
// = require _typography
// = require admin_procedures_modal // = require admin_procedures_modal
// = require admin_type_de_champ // = require admin_type_de_champ
// = require backoffice // = require backoffice
@ -31,9 +26,7 @@
// = require dossier_show // = require dossier_show
// = require dossiers // = require dossiers
// = require etapes // = require etapes
// = require fonts
// = require france_connect_particulier // = require france_connect_particulier
// = require landing
// = require left_panel // = require left_panel
// = require login // = require login
// = require main_container // = require main_container
@ -47,7 +40,6 @@
// = require recapitulatif // = require recapitulatif
// = require search // = require search
// = require siret // = require siret
// = require stats
// = require support_navigator_banner // = require support_navigator_banner
// = require switch_menu // = require switch_menu
// = require typeahead // = require typeahead

View file

@ -1,10 +0,0 @@
// = require reset
// = require custom_reset
// = require common
// = require utils
// = require fonts
// = require new_alert
// = require new_header
// = require new_footer
// = require landing
// = require navbar

View file

@ -0,0 +1,3 @@
$page-width: 1040px;
$default-padding: 15px;

View file

@ -0,0 +1,21 @@
@import "constants";
@import "mixins";
%horizontal-list {
list-style-type: none;
margin: 0;
padding: 0;
font-size: 0px;
display: flex;
flex-wrap: wrap;
}
%horizontal-list-item {
display: inline-block;
}
%page-width-container {
@include horizontal-padding($default-padding);
max-width: $page-width + 2 * $default-padding;
margin: 0 auto;
}

View file

@ -0,0 +1,91 @@
@import "typography";
@import "colors";
.avis-sign-up {
display: flex;
.left,
.right {
width: 50%;
padding: 60px 86px;
}
.left {
p {
margin: auto;
max-width: 410px;
text-align: center;
}
.description {
font-size: 30px;
line-height: 1.3;
}
.dossier {
font-size: 18px;
font-weight: bold;
margin-top: 15px;
}
}
.right {
background-color: $light-grey;
h1 {
font-size: 36px;
font-weight: bold;
margin-bottom: 60px;
}
form {
max-width: 420px;
}
label,
input {
display: block;
width: 100%;
}
label {
font-size: 14px;
line-height: 1.57;
margin: 24px 0 8px;
}
input {
border: solid 1px $border-grey;
border-radius: 4px;
height: 56px;
padding: 0 15px;
font-family: Muli;
font-size: 14px;
&:disabled {
background-color: $border-grey;
}
}
button {
display: inline-block;
height: 60px;
line-height: 60px;
border: none;
border-radius: 60px;
background-color: $blue;
color: #FFFFFF;
font-size: 16px;
text-align: center;
width: 100%;
margin: 55px 0;
&:hover {
color: #FFFFFF;
text-decoration: none;
background-color: $light-blue;
cursor: pointer;
}
}
}
}

View file

@ -0,0 +1,15 @@
#beta {
text-align: center;
text-transform: uppercase;
position: fixed;
bottom: 26px;
right: -35px;
transform: rotate(-45deg);
width: 150px;
background-color: #008CBA;
color: #FFFFFF;
padding: 5px;
font-size: 15px;
font-weight: 700;
z-index: 10;
}

View file

@ -12,12 +12,30 @@
} }
.landing-panel-inner-content { .landing-panel-inner-content {
width: $page-width; @extend %page-width-container;
margin: 0 auto; }
$landing-breakpoint: 1040px;
.hero-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
@media (max-width: $landing-breakpoint) {
justify-content: center;
}
}
.hero-text {
max-width: 500px;
@media (max-width: $landing-breakpoint) {
margin: auto;
}
} }
.hero-tagline { .hero-tagline {
width: 500px;
font-size: 30px; font-size: 30px;
margin-bottom: 0px; margin-bottom: 0px;
} }
@ -29,12 +47,16 @@
font-weight: bold; font-weight: bold;
} }
.hero-text {
width: 500px;
}
.hero-illustration { .hero-illustration {
width: 500px; max-width: 500px;
img {
max-width: 100%;
}
@media (max-width: 1030px) {
margin: auto;
}
} }
.hero-button { .hero-button {
@ -69,6 +91,7 @@
} }
.landing-panel-title { .landing-panel-title {
width: 100%;
font-size: 30px; font-size: 30px;
font-weight: normal; font-weight: normal;
text-align: center; text-align: center;
@ -86,15 +109,22 @@
.features { .features {
@extend %horizontal-list; @extend %horizontal-list;
} width: 100%;
align-items: baseline;
justify-content: space-between;
$feature-width: 320px; @media (max-width: $landing-breakpoint) {
$features-count: 3; justify-content: center;
}
}
.feature { .feature {
@extend %horizontal-list-item; @extend %horizontal-list-item;
width: $feature-width; width: 320px;
margin-right: calc((#{$page-width} - (#{$feature-width} * #{$features-count})) / (#{$features-count} - 1));
@media (max-width: $landing-breakpoint) {
margin: 15px 20px;
}
} }
.feature-text { .feature-text {
@ -116,18 +146,27 @@ $features-count: 3;
.quotes { .quotes {
@extend %horizontal-list; @extend %horizontal-list;
} width: 100%;
justify-content: space-between;
$quote-width: 500px; @media (max-width: $landing-breakpoint) {
$quote-count: 2; justify-content: center;
}
}
.quote { .quote {
@extend %horizontal-list-item; @extend %horizontal-list-item;
width: $quote-width; max-width: 500px;
margin-right: calc((#{$page-width} - (#{$quote-width} * #{$quote-count}))/ (#{$quote-count} - 1));
background-color: #FFFFFF; background-color: #FFFFFF;
box-shadow: 0 4px 16px 0 rgba(153, 153, 153, 0.2); box-shadow: 0 4px 16px 0 rgba(153, 153, 153, 0.2);
padding: 24px; padding: 24px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
@media (max-width: $landing-breakpoint) {
margin: 15px 0;
}
} }
.quote-quotation-mark { .quote-quotation-mark {
@ -136,13 +175,16 @@ $quote-count: 2;
.quote-content { .quote-content {
font-size: 18px; font-size: 18px;
width: 388px;
margin-bottom: 24px; margin-bottom: 24px;
} }
.quote-content-wrapper {
margin-left: 20px;
width: 100%;
}
.quote-author { .quote-author {
font-size: 14px; font-size: 14px;
margin-left: 64px;
} }
.quote-author-name { .quote-author-name {
@ -155,16 +197,18 @@ $quote-count: 2;
.numbers { .numbers {
@extend %horizontal-list; @extend %horizontal-list;
justify-content: space-around;
width: 100%;
} }
$number-width: 320px;
$number-count: 3;
.number { .number {
@extend %horizontal-list-item; @extend %horizontal-list-item;
width: $number-width; width: 320px;
margin-right: calc((#{$page-width} - (#{$number-width} * #{$number-count}))/ (#{$number-count} - 1));
text-align: center; text-align: center;
@media (max-width: $landing-breakpoint) {
margin-bottom: 15px;
}
} }
.number-value { .number-value {
@ -178,25 +222,33 @@ $number-count: 3;
font-size: 20px; font-size: 20px;
} }
$users-breakpoint: 950px;
.users { .users {
@extend %horizontal-list; @extend %horizontal-list;
} justify-content: space-between;
width: 100%;
$image-width: 170px; @media (max-width: $users-breakpoint) {
$images-total-width: $image-width * 5; justify-content: space-around;
$images-count: 5; }
}
.user { .user {
@extend %horizontal-list-item; @extend %horizontal-list-item;
margin-right: calc((#{$page-width} - (#{$images-total-width}))/ (#{$images-count} - 1)); width: 170px;
&:hover { &:hover {
opacity: 0.6; opacity: 0.6;
} }
@media (max-width: $users-breakpoint) {
margin: 0 15px 15px;
}
} }
.user-image { .user-image {
width: $image-width; width: 170px;
} }
.cta-panel { .cta-panel {
@ -204,6 +256,13 @@ $images-count: 5;
color: #FFFFFF; color: #FFFFFF;
} }
.cta-panel-wrapper {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.cta-panel-title { .cta-panel-title {
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
@ -212,22 +271,21 @@ $images-count: 5;
.cta-panel-explanation { .cta-panel-explanation {
font-size: 24px; font-size: 24px;
margin-bottom: 0; margin-bottom: 10px;
} }
$cta-panel-button-height: 60px;
$cta-panel-button-border-size: 2px; $cta-panel-button-border-size: 2px;
.cta-panel-button { .cta-panel-button {
@include horizontal-padding(30px); @include horizontal-padding(30px);
display: block; display: block;
height: $cta-panel-button-height; padding: 10px;
line-height: $cta-panel-button-height - (2 * $cta-panel-button-border-size); border-radius: 100px;
border-radius: $cta-panel-button-height;
border: $cta-panel-button-border-size solid #FFFFFF; border: $cta-panel-button-border-size solid #FFFFFF;
color: #FFFFFF; color: #FFFFFF;
font-size: 24px; font-size: 24px;
text-align: center;
&:hover { &:hover {
color: #FFFFFF; color: #FFFFFF;

View file

@ -0,0 +1,6 @@
// = require ./reset
// = require ./custom_reset
// = require ./common
// = require ./utils
// = require ./fonts
// = require_tree .

View file

@ -10,23 +10,25 @@
} }
.footer-inner-content { .footer-inner-content {
width: $page-width; @extend %page-width-container;
margin: 0 auto;
} }
.footer-columns { .footer-columns {
@extend %horizontal-list; @extend %horizontal-list;
justify-content: flex-start;
} }
$footer-column-width: 320px;
$footer-column-count: 3;
.footer-column { .footer-column {
@extend %horizontal-list-item; @extend %horizontal-list-item;
width: $footer-column-width;
margin-right: calc((#{$page-width} - (#{$footer-column-width} * #{$footer-column-count})) / (#{$footer-column-count} - 1));
font-size: 14px; font-size: 14px;
vertical-align: top; vertical-align: top;
flex-grow: 1;
min-width: 320px;
@media (max-width: 1000px) {
width: 100%;
margin-bottom: 14px;
}
} }
.footer-logos, .footer-logos,

View file

@ -1,6 +1,7 @@
@import "constants"; @import "constants";
@import "colors"; @import "colors";
@import "mixins"; @import "mixins";
@import "placeholders";
// FIXME: Rename when the header is generalized // FIXME: Rename when the header is generalized
.new-header { .new-header {
@ -13,8 +14,9 @@
} }
.header-inner-content { .header-inner-content {
width: $page-width; @extend %page-width-container;
margin: 0 auto; display: flex;
justify-content: space-between;
} }
.header-logo { .header-logo {

View file

@ -1,5 +1,3 @@
@import "card";
$dark-grey: #333333; $dark-grey: #333333;
$light-grey: #999999; $light-grey: #999999;
$blue: rgba(61, 149, 236, 1); $blue: rgba(61, 149, 236, 1);
@ -8,12 +6,23 @@ $blue-hover: rgba(61, 149, 236, 0.8);
$default-space: 15px; $default-space: 15px;
$new-h1-margin-bottom: 4 * $default-space; $new-h1-margin-bottom: 4 * $default-space;
$new-h2-margin-bottom: 3 * $default-space;
.new-h1 { .new-h1,
.new-h2 {
color: $dark-grey; color: $dark-grey;
text-align: center; text-align: center;
margin-top: 0; font-weight: bold;
}
.new-h1 {
margin-bottom: $new-h1-margin-bottom; margin-bottom: $new-h1-margin-bottom;
font-size: 41px;
}
.new-h2 {
margin-bottom: $new-h2-margin-bottom;
font-size: 36px;
} }
$statistiques-padding-top: $default-space * 2; $statistiques-padding-top: $default-space * 2;
@ -33,6 +42,7 @@ $statistiques-padding-top: $default-space * 2;
$stat-card-margin-bottom: 3 * $default-space; $stat-card-margin-bottom: 3 * $default-space;
.stat-card { .stat-card {
padding: 15px;
margin-bottom: $stat-card-margin-bottom; margin-bottom: $stat-card-margin-bottom;
border-radius: 5px; border-radius: 5px;
box-shadow: none; box-shadow: none;
@ -49,7 +59,7 @@ $stat-card-half-horizontal-spacing: 4 * $default-space;
.stat-card-title { .stat-card-title {
color: $dark-grey; color: $dark-grey;
font-size: 26px; font-size: 26px;
font-weight: 500; font-weight: bold;
width: 200px; width: 200px;
} }

View file

@ -13,3 +13,7 @@
.center { .center {
text-align: center; text-align: center;
} }
.hidden {
display: none;
}

View file

@ -2,29 +2,33 @@ class Admin::MailTemplatesController < AdminController
before_action :retrieve_procedure before_action :retrieve_procedure
def index def index
@mails = mails @mail_templates = mail_templates
end end
def edit def edit
@mail_template = find_the_right_mail params[:id] @mail_template = find_mail_template_by_slug(params[:id])
@mail_template_name = params[:id]
end end
def update def update
mail_template = find_the_right_mail params[:id] mail_template = find_mail_template_by_slug(params[:id])
mail_template.update_attributes(update_params) mail_template.update_attributes(update_params)
redirect_to admin_procedure_mail_templates_path redirect_to admin_procedure_mail_templates_path
end end
private private
def mails def mail_templates
%w(initiated received closed refused without_continuation) [
.map { |name| @procedure.send(name + "_mail") } @procedure.initiated_mail_template,
@procedure.received_mail_template,
@procedure.closed_mail_template,
@procedure.refused_mail_template,
@procedure.without_continuation_mail_template
]
end end
def find_the_right_mail type def find_mail_template_by_slug(slug)
mails.find { |m| m.class.slug == type } mail_templates.find { |template| template.class.const_get(:SLUG) == slug }
end end
def update_params def update_params

View file

@ -10,9 +10,6 @@ class Admin::PrevisualisationsController < AdminController
@champs = @dossier.ordered_champs @champs = @dossier.ordered_champs
@headers = @champs.inject([]) do |acc, champ| @headers = @champs.select { |champ| champ.type_champ == 'header_section' }
acc.push(champ) if champ.type_champ == 'header_section'
acc
end
end end
end end

View file

@ -1,5 +1,4 @@
class API::StatistiquesController < ApplicationController class API::StatistiquesController < ApplicationController
def dossiers_stats def dossiers_stats
render json: { render json: {
total: total_dossiers, total: total_dossiers,

View file

@ -1,14 +1,10 @@
class API::V1::DossiersController < APIController class API::V1::DossiersController < APIController
api :GET, '/procedures/:procedure_id/dossiers/', 'Liste de tous les dossiers d\'une procédure' api :GET, '/procedures/:procedure_id/dossiers/', 'Liste de tous les dossiers d\'une procédure'
param :procedure_id, Integer, desc: "L'identifiant de la procédure", required: true param :procedure_id, Integer, desc: "L'identifiant de la procédure", required: true
param :token, String, desc: "Token administrateur", required: true param :token, String, desc: "Token administrateur", required: true
error code: 401, desc: "Non authorisé" error code: 401, desc: "Non authorisé"
error code: 404, desc: "Procédure inconnue" error code: 404, desc: "Procédure inconnue"
meta champs: {
}
def index def index
procedure = current_administrateur.procedures.find(params[:procedure_id]) procedure = current_administrateur.procedures.find(params[:procedure_id])
dossiers = procedure.dossiers.where.not(state: :draft).paginate(page: params[:page]) dossiers = procedure.dossiers.where.not(state: :draft).paginate(page: params[:page])
@ -25,10 +21,6 @@ class API::V1::DossiersController < APIController
error code: 401, desc: "Non authorisé" error code: 401, desc: "Non authorisé"
error code: 404, desc: "Procédure ou dossier inconnu" error code: 404, desc: "Procédure ou dossier inconnu"
meta champs: {
}
def show def show
procedure = current_administrateur.procedures.find(params[:procedure_id]) procedure = current_administrateur.procedures.find(params[:procedure_id])
dossier = procedure.dossiers.find(params[:id]) dossier = procedure.dossiers.find(params[:id])

View file

@ -5,10 +5,6 @@ class API::V1::ProceduresController < APIController
error code: 401, desc: "Non authorisé" error code: 401, desc: "Non authorisé"
error code: 404, desc: "Procédure inconnue" error code: 404, desc: "Procédure inconnue"
meta champs: {
}
def show def show
procedure = current_administrateur.procedures.find(params[:id]).decorate procedure = current_administrateur.procedures.find(params[:id]).decorate

View file

@ -0,0 +1,95 @@
class Backoffice::AvisController < ApplicationController
before_action :authenticate_gestionnaire!, except: [:sign_up, :create_gestionnaire]
before_action :redirect_if_no_sign_up_needed, only: [:sign_up]
before_action :check_avis_exists_and_email_belongs_to_avis, only: [:sign_up, :create_gestionnaire]
def create
avis = Avis.new(create_params.merge(claimant: current_gestionnaire))
avis.dossier = dossier
email = create_params[:email]
gestionnaire = Gestionnaire.find_by(email: email)
if gestionnaire
avis.gestionnaire = gestionnaire
avis.email = nil
end
if avis.save
flash[:notice] = "Votre demande d'avis a bien été envoyée à #{email}"
end
redirect_to backoffice_dossier_path(dossier)
end
def update
if avis.update(update_params)
NotificationService.new('avis', params[:dossier_id]).notify
flash[:notice] = 'Merci, votre avis a été enregistré.'
end
redirect_to backoffice_dossier_path(avis.dossier_id)
end
def sign_up
@email = params[:email]
@dossier = Avis.includes(:dossier).find(params[:id]).dossier
render layout: 'new_application'
end
def create_gestionnaire
email = params[:email]
password = params['gestionnaire']['password']
gestionnaire = Gestionnaire.new(email: email, password: password)
if gestionnaire.save
sign_in(gestionnaire, scope: :gestionnaire)
Avis.link_avis_to_gestionnaire(gestionnaire)
avis = Avis.find(params[:id])
redirect_to url_for(backoffice_dossier_path(avis.dossier_id))
else
flash[:alert] = gestionnaire.errors.full_messages.join('<br>')
redirect_to url_for(avis_sign_up_path(params[:id], email))
end
end
private
def dossier
current_gestionnaire.dossiers.find(params[:dossier_id])
end
def avis
current_gestionnaire.avis.find(params[:id])
end
def create_params
params.require(:avis).permit(:email, :introduction)
end
def update_params
params.require(:avis).permit(:answer)
end
def redirect_if_no_sign_up_needed
avis = Avis.find(params[:id])
if current_gestionnaire.present?
# a gestionnaire is authenticated ... lets see if it can view the dossier
redirect_to backoffice_dossier_url(avis.dossier)
elsif avis.gestionnaire.present? && avis.gestionnaire.email == params[:email]
# the avis gestionnaire has already signed up and it sould sign in
redirect_to new_gestionnaire_session_url
end
end
def check_avis_exists_and_email_belongs_to_avis
if !Avis.avis_exists_and_email_belongs_to_avis?(params[:id], params[:email])
redirect_to url_for(root_path)
end
end
end

View file

@ -1,5 +1,4 @@
class Backoffice::Dossiers::ProcedureController < Backoffice::DossiersListController class Backoffice::Dossiers::ProcedureController < Backoffice::DossiersListController
def index def index
super super
@ -22,4 +21,5 @@ class Backoffice::Dossiers::ProcedureController < Backoffice::DossiersListContro
def retrieve_procedure def retrieve_procedure
current_gestionnaire.procedures.find params[:id] current_gestionnaire.procedures.find params[:id]
end end
end end

View file

@ -1,13 +1,17 @@
class Backoffice::DossiersController < Backoffice::DossiersListController class Backoffice::DossiersController < Backoffice::DossiersListController
respond_to :html, :xlsx, :ods, :csv respond_to :html, :xlsx, :ods, :csv
prepend_before_action :store_current_location, only: :show
before_action :ensure_gestionnaire_is_authorized, only: :show before_action :ensure_gestionnaire_is_authorized, only: :show
def index def index
return redirect_to backoffice_invitations_path if current_gestionnaire.avis.any?
procedure = current_gestionnaire.procedure_filter procedure = current_gestionnaire.procedure_filter
if procedure.nil? if procedure.nil?
procedure_list = dossiers_list_facade.gestionnaire_procedures_name_and_id_list procedure_list = dossiers_list_facade.gestionnaire_procedures_name_and_id_list
if procedure_list.count == 0 if procedure_list.count == 0
flash.alert = "Vous n'avez aucune procédure d'affectée." flash.alert = "Vous n'avez aucune procédure d'affectée."
return redirect_to root_path return redirect_to root_path
@ -20,18 +24,22 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
end end
def show def show
create_dossier_facade params[:id] dossier_id = params[:id]
create_dossier_facade dossier_id
unless @facade.nil? unless @facade.nil?
@champs_private = @facade.champs_private @champs_private = @facade.champs_private
@headers_private = @champs_private.inject([]) do |acc, champ| @headers_private = @champs_private.select { |champ| champ.type_champ == 'header_section' }
acc.push(champ) if champ.type_champ == 'header_section'
acc
end
end end
Notification.where(dossier_id: params[:id].to_i).update_all already_read: true # if the current_gestionnaire does not own the dossier, it is here to give an advice
# and it should not remove the notifications
if current_gestionnaire.dossiers.find_by(id: dossier_id).present?
Notification.where(dossier_id: dossier_id).update_all(already_read: true)
end
@new_avis = Avis.new(introduction: "Bonjour, merci de me donner votre avis sur ce dossier.")
end end
def filter def filter
@ -92,8 +100,6 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
dossier.received! dossier.received!
flash.notice = 'Dossier considéré comme reçu.' flash.notice = 'Dossier considéré comme reçu.'
NotificationMailer.send_notification(dossier, dossier.procedure.received_mail).deliver_now!
redirect_to backoffice_dossier_path(id: dossier.id) redirect_to backoffice_dossier_path(id: dossier.id)
end end
@ -105,7 +111,7 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
dossier.next_step! 'gestionnaire', 'refuse' dossier.next_step! 'gestionnaire', 'refuse'
flash.notice = 'Dossier considéré comme refusé.' flash.notice = 'Dossier considéré comme refusé.'
NotificationMailer.send_notification(dossier, dossier.procedure.refused_mail).deliver_now! NotificationMailer.send_notification(dossier, dossier.procedure.refused_mail_template).deliver_now!
redirect_to backoffice_dossier_path(id: dossier.id) redirect_to backoffice_dossier_path(id: dossier.id)
end end
@ -118,7 +124,7 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
dossier.next_step! 'gestionnaire', 'without_continuation' dossier.next_step! 'gestionnaire', 'without_continuation'
flash.notice = 'Dossier considéré comme sans suite.' flash.notice = 'Dossier considéré comme sans suite.'
NotificationMailer.send_notification(dossier, dossier.procedure.without_continuation_mail).deliver_now! NotificationMailer.send_notification(dossier, dossier.procedure.without_continuation_mail_template).deliver_now!
redirect_to backoffice_dossier_path(id: dossier.id) redirect_to backoffice_dossier_path(id: dossier.id)
end end
@ -131,7 +137,7 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
dossier.next_step! 'gestionnaire', 'close' dossier.next_step! 'gestionnaire', 'close'
flash.notice = 'Dossier traité avec succès.' flash.notice = 'Dossier traité avec succès.'
NotificationMailer.send_notification(dossier, dossier.procedure.closed_mail).deliver_now! NotificationMailer.send_notification(dossier, dossier.procedure.closed_mail_template).deliver_now!
redirect_to backoffice_dossier_path(id: dossier.id) redirect_to backoffice_dossier_path(id: dossier.id)
end end
@ -187,13 +193,18 @@ class Backoffice::DossiersController < Backoffice::DossiersListController
private private
def ensure_gestionnaire_is_authorized def store_current_location
current_gestionnaire.dossiers.find(params[:id]) if !gestionnaire_signed_in?
store_location_for(:gestionnaire, request.url)
end
end
rescue ActiveRecord::RecordNotFound def ensure_gestionnaire_is_authorized
unless current_gestionnaire.can_view_dossier?(params[:id])
flash.alert = t('errors.messages.dossier_not_found') flash.alert = t('errors.messages.dossier_not_found')
redirect_to url_for(controller: '/backoffice') redirect_to url_for(controller: '/backoffice')
end end
end
def create_dossier_facade dossier_id def create_dossier_facade dossier_id
@facade = DossierFacades.new dossier_id, current_gestionnaire.email @facade = DossierFacades.new dossier_id, current_gestionnaire.email

View file

@ -1,4 +1,8 @@
class BackofficeController < ApplicationController class BackofficeController < ApplicationController
include SmartListing::Helper::ControllerExtensions
helper SmartListing::Helper
before_action :authenticate_gestionnaire!, only: [:invitations]
def index def index
if !gestionnaire_signed_in? if !gestionnaire_signed_in?
@ -7,4 +11,18 @@ class BackofficeController < ApplicationController
redirect_to(:backoffice_dossiers) redirect_to(:backoffice_dossiers)
end end
end end
def invitations
pending_avis = current_gestionnaire.avis.without_answer.includes(dossier: [:procedure]).by_latest
@pending_avis = smart_listing_create :pending_avis,
pending_avis,
partial: 'backoffice/dossiers/list_invitations',
array: true
avis_with_answer = current_gestionnaire.avis.with_answer.includes(dossier: [:procedure]).by_latest
@avis_with_answer = smart_listing_create :avis_with_answer,
avis_with_answer,
partial: 'backoffice/dossiers/list_invitations',
array: true
end
end end

View file

@ -1,5 +1,4 @@
class FranceConnect::ParticulierController < ApplicationController class FranceConnect::ParticulierController < ApplicationController
def login def login
client = FranceConnectParticulierClient.new client = FranceConnectParticulierClient.new

View file

@ -1,5 +1,4 @@
class PingController < ApplicationController class PingController < ApplicationController
def index def index
Rails.logger.silence do Rails.logger.silence do
if (ActiveRecord::Base.connected?) if (ActiveRecord::Base.connected?)
@ -9,5 +8,4 @@ class PingController < ApplicationController
end end
end end
end end
end end

View file

@ -1,19 +1,11 @@
class RootController < ApplicationController class RootController < ApplicationController
def index def index
if administrateur_signed_in?
begin
route = Rails.application.routes.recognize_path(request.referrer)
rescue ActionController::RoutingError
route = Rails.application.routes.recognize_path(new_user_session_path)
end
if user_signed_in? && !route[:controller].match('users').nil?
return redirect_to users_dossiers_path
elsif administrateur_signed_in? && !route[:controller].match('admin').nil?
return redirect_to admin_procedures_path return redirect_to admin_procedures_path
elsif gestionnaire_signed_in? elsif gestionnaire_signed_in?
return redirect_to backoffice_invitations_path if current_gestionnaire.avis.any?
procedure_id = current_gestionnaire.procedure_filter procedure_id = current_gestionnaire.procedure_filter
if procedure_id.nil? if procedure_id.nil?
procedure_list = current_gestionnaire.procedures procedure_list = current_gestionnaire.procedures
@ -30,15 +22,10 @@ class RootController < ApplicationController
elsif user_signed_in? elsif user_signed_in?
return redirect_to users_dossiers_path return redirect_to users_dossiers_path
elsif administrateur_signed_in?
return redirect_to admin_procedures_path
elsif administration_signed_in? elsif administration_signed_in?
return redirect_to administrations_path return redirect_to administrations_path
end end
@demo_environment_host = "https://tps-dev.apientreprise.fr" unless Rails.env.development?
render 'landing', :layout => 'new_application' render 'landing', :layout => 'new_application'
end end
end end

View file

@ -1,5 +1,4 @@
class Sessions::SessionsController < Devise::SessionsController class Sessions::SessionsController < Devise::SessionsController
before_action :before_sign_in, only: [:create] before_action :before_sign_in, only: [:create]
def before_sign_in def before_sign_in

View file

@ -1,45 +1,49 @@
class StatsController < ApplicationController class StatsController < ApplicationController
layout "new_application"
MEAN_NUMBER_OF_CHAMPS_IN_A_FORM = 24.0
def index def index
procedures = Procedure.where(:published => true) procedures = Procedure.where(:published => true)
dossiers = Dossier.where.not(:state => :draft) dossiers = Dossier.where.not(:state => :draft)
@procedures_30_days_flow = thirty_days_flow_hash(procedures)
@dossiers_30_days_flow = thirty_days_flow_hash(dossiers, :initiated_at)
@procedures_cumulative = cumulative_hash(procedures)
@dossiers_cumulative = cumulative_hash(dossiers, :initiated_at)
@procedures_count = procedures.count @procedures_count = procedures.count
@dossiers_count = dossiers.count @dossiers_count = dossiers.count
@procedures_cumulative = cumulative_hash(procedures)
@procedures_in_the_last_4_months = last_four_months_hash(procedures)
@dossiers_cumulative = cumulative_hash(dossiers, :initiated_at)
@dossiers_in_the_last_4_months = last_four_months_hash(dossiers, :initiated_at)
@procedures_count_per_administrateur = procedures_count_per_administrateur(procedures)
@dossier_instruction_mean_time = Rails.cache.fetch("dossier_instruction_mean_time", expires_in: 1.day) do
dossier_instruction_mean_time(dossiers)
end
@dossier_filling_mean_time = Rails.cache.fetch("dossier_filling_mean_time", expires_in: 1.day) do
dossier_filling_mean_time(dossiers)
end
@avis_usage = avis_usage
@avis_average_answer_time = avis_average_answer_time
@avis_answer_percentages = avis_answer_percentages
end end
private private
def thirty_days_flow_hash(association, date_attribute = :created_at) def last_four_months_hash(association, date_attribute = :created_at)
min_date = 30.days.ago.to_date min_date = 3.months.ago.beginning_of_month.to_date
max_date = Time.now.to_date max_date = Time.now.to_date
thirty_days_flow_hash = association association
.where(date_attribute => min_date..max_date) .where(date_attribute => min_date..max_date)
.group("date_trunc('day', #{date_attribute.to_s})") .group("DATE_TRUNC('month', #{date_attribute.to_s})")
.count .count
.to_a
clean_hash(thirty_days_flow_hash, min_date, max_date) .sort{ |x, y| x[0] <=> y[0] }
end .map { |e| [I18n.l(e.first, format: "%B %Y"), e.last] }
def clean_hash(h, min_date, max_date)
# Convert keys to date
h = Hash[h.map { |(k, v)| [k.to_date, v] }]
# Add missing vales where count is 0
(min_date..max_date).each do |date|
if h[date].nil?
h[date] = 0
end
end
h
end end
def cumulative_hash(association, date_attribute = :created_at) def cumulative_hash(association, date_attribute = :created_at)
@ -53,4 +57,152 @@ class StatsController < ApplicationController
.reduce({}, :merge) .reduce({}, :merge)
end end
def procedures_count_per_administrateur(procedures)
count_per_administrateur = procedures.group(:administrateur_id).count.values
{
'Une procédure' => count_per_administrateur.select { |count| count == 1 }.count,
'Entre deux et cinq procédures' => count_per_administrateur.select { |count| 2 <= count && count <= 5 }.count,
'Plus de cinq procédures' => count_per_administrateur.select { |count| 5 < count }.count
}
end
def mean(collection)
(collection.sum.to_f / collection.size).round(2)
end
def dossier_instruction_mean_time(dossiers)
# In the 12 last months, we compute for each month
# the average time it took to instruct a dossier
# We compute monthly averages by first making an average per procedure
# and then computing the average for all the procedures
min_date = 11.months.ago
max_date = Time.now.to_date
processed_dossiers = dossiers
.where(:processed_at => min_date..max_date)
.pluck(:procedure_id, :initiated_at, :processed_at)
# Group dossiers by month
processed_dossiers_by_month = processed_dossiers
.group_by do |dossier|
dossier[2].beginning_of_month.to_s
end
processed_dossiers_by_month.map do |month, value|
# Group the dossiers for this month by procedure
dossiers_grouped_by_procedure = value.group_by { |dossier| dossier[0] }
# Compute the mean time for this procedure
procedure_processing_times = dossiers_grouped_by_procedure.map do |procedure_id, procedure_dossiers|
procedure_dossiers_processing_time = procedure_dossiers.map do |dossier|
(dossier[2] - dossier[1]).to_f / (3600 * 24)
end
mean(procedure_dossiers_processing_time)
end
# Compute the average mean time for all the procedures of this month
month_average = mean(procedure_processing_times)
[month, month_average]
end.to_h
end
def dossier_filling_mean_time(dossiers)
# In the 12 last months, we compute for each month
# the average time it took to fill a dossier
# We compute monthly averages by first making an average per procedure
# and then computing the average for all the procedures
# For each procedure, we normalize the data: the time is calculated
# for a 24 champs form (the current form mean length)
min_date = 11.months.ago
max_date = Time.now.to_date
processed_dossiers = dossiers
.where(:processed_at => min_date..max_date)
.pluck(:procedure_id, :created_at, :initiated_at, :processed_at)
# Group dossiers by month
processed_dossiers_by_month = processed_dossiers
.group_by do |e|
e[3].beginning_of_month.to_s
end
processed_dossiers_by_month.map do |month, value|
# Group the dossiers for this month by procedure
dossiers_grouped_by_procedure = value.group_by { |dossier| dossier[0] }
# Compute the mean time for this procedure
procedure_processing_times = dossiers_grouped_by_procedure.map do |procedure_id, procedure_dossiers|
procedure_dossiers_processing_time = procedure_dossiers.map do |dossier|
(dossier[2] - dossier[1]).to_f / 60
end
procedure_mean = mean(procedure_dossiers_processing_time)
# We normalize the data for 24 fields
procedure_fields_count = Procedure.find(procedure_id).types_de_champ.count
procedure_mean * (MEAN_NUMBER_OF_CHAMPS_IN_A_FORM / procedure_fields_count)
end
# Compute the average mean time for all the procedures of this month
month_average = mean(procedure_processing_times)
[month, month_average]
end.to_h
end
def avis_usage
[3.week.ago, 2.week.ago, 1.week.ago].map do |min_date|
max_date = min_date + 1.week
weekly_dossiers = Dossier.includes(:avis).where(created_at: min_date..max_date).to_a
weekly_dossiers_count = weekly_dossiers.count
if weekly_dossiers_count == 0
result = 0
else
weekly_dossier_with_avis_count = weekly_dossiers.select { |dossier| dossier.avis.present? }.count
result = ((weekly_dossier_with_avis_count.to_f / weekly_dossiers_count) * 100).round(2)
end
[min_date.to_i, result]
end
end
def avis_average_answer_time
[3.week.ago, 2.week.ago, 1.week.ago].map do |min_date|
max_date = min_date + 1.week
average = Avis.with_answer
.where(created_at: min_date..max_date)
.average("EXTRACT(EPOCH FROM updated_at - created_at) / 86400")
result = average ? average.to_f.round(2) : 0
[min_date.to_i, result]
end
end
def avis_answer_percentages
[3.week.ago, 2.week.ago, 1.week.ago].map do |min_date|
max_date = min_date + 1.week
weekly_avis = Avis.where(created_at: min_date..max_date)
weekly_avis_count = weekly_avis.count
if weekly_avis_count == 0
[min_date.to_i, 0]
else
answered_weekly_avis_count = weekly_avis.with_answer.count
result = ((answered_weekly_avis_count.to_f / weekly_avis_count) * 100).round(2)
[min_date.to_i, result]
end
end
end
end end

View file

@ -1,5 +1,4 @@
class Users::CarteController < UsersController class Users::CarteController < UsersController
before_action only: [:show] do before_action only: [:show] do
authorized_routes? self.class authorized_routes? self.class
end end

View file

@ -52,7 +52,7 @@ class Users::DescriptionController < UsersController
else else
if dossier.draft? if dossier.draft?
dossier.initiated! dossier.initiated!
NotificationMailer.send_notification(dossier, procedure.initiated_mail).deliver_now! NotificationMailer.send_notification(dossier, procedure.initiated_mail_template).deliver_now!
end end
flash.notice = 'Félicitations, votre demande a bien été enregistrée.' flash.notice = 'Félicitations, votre demande a bien été enregistrée.'
redirect_to url_for(controller: :recapitulatif, action: :show, dossier_id: dossier.id) redirect_to url_for(controller: :recapitulatif, action: :show, dossier_id: dossier.id)

View file

@ -1,5 +1,4 @@
class Users::Dossiers::InvitesController < UsersController class Users::Dossiers::InvitesController < UsersController
def authenticate_user! def authenticate_user!
session["user_return_to"] = request.fullpath session["user_return_to"] = request.fullpath
return redirect_to new_user_registration_path(user_email: params[:email]) if !params[:email].blank? && User.find_by_email(params[:email]).nil? return redirect_to new_user_registration_path(user_email: params[:email]) if !params[:email].blank? && User.find_by_email(params[:email]).nil?

View file

@ -16,13 +16,13 @@ class Users::DossiersController < UsersController
@dossiers_filtered = case @liste @dossiers_filtered = case @liste
when 'brouillon' when 'brouillon'
@user_dossiers.brouillon.order_by_updated_at @user_dossiers.state_brouillon.order_by_updated_at
when 'a_traiter' when 'a_traiter'
@user_dossiers.en_construction.order_by_updated_at @user_dossiers.state_en_construction.order_by_updated_at
when 'en_instruction' when 'en_instruction'
@user_dossiers.en_instruction.order_by_updated_at @user_dossiers.state_en_instruction.order_by_updated_at
when 'termine' when 'termine'
@user_dossiers.termine.order_by_updated_at @user_dossiers.state_termine.order_by_updated_at
when 'invite' when 'invite'
current_user.invites current_user.invites
else else

View file

@ -1,5 +1,4 @@
class Users::RecapitulatifController < UsersController class Users::RecapitulatifController < UsersController
before_action only: [:show] do before_action only: [:show] do
authorized_routes? self.class authorized_routes? self.class
end end

View file

@ -33,7 +33,8 @@ class Users::SessionsController < Sessions::SessionsController
if user_signed_in? if user_signed_in?
redirect_to after_sign_in_path_for(:user) redirect_to after_sign_in_path_for(:user)
elsif gestionnaire_signed_in? elsif gestionnaire_signed_in?
redirect_to backoffice_path location = stored_location_for(:gestionnaire) || backoffice_path
redirect_to location
elsif administrateur_signed_in? elsif administrateur_signed_in?
redirect_to admin_path redirect_to admin_path
else else

View file

@ -2,10 +2,16 @@ class ChampDecorator < Draper::Decorator
delegate_all delegate_all
def value def value
return object.value == 'on' ? 'Oui' : 'Non' if type_champ == 'checkbox' if type_champ == "date" && object.value.present?
return JSON.parse(object.value).join(', ') if type_champ == 'multiple_drop_down_list' && object.value.present? Date.parse(object.value).strftime("%d/%m/%Y")
elsif type_champ == 'checkbox'
object.value == 'on' ? 'Oui' : 'Non'
elsif type_champ == 'multiple_drop_down_list' && object.value.present?
JSON.parse(object.value).join(', ')
else
object.value object.value
end end
end
def description_with_links def description_with_links
description.gsub(URI.regexp, '<a target="_blank" href="\0">\0</a>').html_safe if description description.gsub(URI.regexp, '<a target="_blank" href="\0">\0</a>').html_safe if description

View file

@ -0,0 +1,9 @@
class AvisMailer < ApplicationMailer
def avis_invitation(avis)
@avis = avis
email = @avis.gestionnaire.try(:email) || @avis.email
mail(to: email, subject: "Donnez votre avis sur le dossier nº #{@avis.dossier.id} (#{@avis.dossier.procedure.libelle})")
end
end

View file

@ -8,6 +8,11 @@ class GestionnaireMailer < ApplicationMailer
send_mail email, email_admin, "Vous avez été assigné à un nouvel administrateur sur la plateforme TPS" send_mail email, email_admin, "Vous avez été assigné à un nouvel administrateur sur la plateforme TPS"
end end
def last_week_overview(gestionnaire, overview)
headers['X-mailjet-campaign'] = 'last_week_overview'
send_mail gestionnaire.email, overview, 'Résumé de la semaine'
end
private private
def vars_mailer email, args def vars_mailer email, args

View file

@ -4,7 +4,7 @@ class NewAdminMailer < ApplicationMailer
@admin = admin @admin = admin
@password = password @password = password
mail(to: 'tech@apientreprise.fr', mail(to: 'tech@tps.apientreprise.fr',
subject: "Création d'un compte Admin TPS") subject: "Création d'un compte Admin TPS")
end end
end end

View file

@ -3,16 +3,16 @@ class NotificationMailer < ApplicationMailer
after_action :create_commentaire_for_notification, only: :send_notification after_action :create_commentaire_for_notification, only: :send_notification
def send_notification dossier, mail_template def send_notification(dossier, mail_template)
vars_mailer(dossier) vars_mailer(dossier)
@obj = mail_template.object_for_dossier dossier @object = mail_template.object_for_dossier dossier
@body = mail_template.body_for_dossier dossier @body = mail_template.body_for_dossier dossier
mail(subject: @obj) { |format| format.html { @body } } mail(subject: @object) { |format| format.html { @body } }
end end
def new_answer dossier def new_answer(dossier)
send_mail dossier, "Nouveau message pour votre dossier TPS nº #{dossier.id}" send_mail dossier, "Nouveau message pour votre dossier TPS nº #{dossier.id}"
end end
@ -22,16 +22,16 @@ class NotificationMailer < ApplicationMailer
Commentaire.create( Commentaire.create(
dossier: @dossier, dossier: @dossier,
email: I18n.t("dynamics.contact_email"), email: I18n.t("dynamics.contact_email"),
body: ["[#{@obj}]", @body].join("<br><br>") body: ["[#{@object}]", @body].join("<br><br>")
) )
end end
def vars_mailer dossier def vars_mailer(dossier)
@dossier = dossier @dossier = dossier
@user = dossier.user @user = dossier.user
end end
def send_mail dossier, subject def send_mail(dossier, subject)
vars_mailer dossier vars_mailer dossier
mail(subject: subject) mail(subject: subject)

View file

@ -27,5 +27,4 @@ class Administrateur < ActiveRecord::Base
break token unless Administrateur.find_by(api_token: token) break token unless Administrateur.find_by(api_token: token)
end end
end end
end end

29
app/models/avis.rb Normal file
View file

@ -0,0 +1,29 @@
class Avis < ApplicationRecord
belongs_to :dossier
belongs_to :gestionnaire
belongs_to :claimant, class_name: 'Gestionnaire'
after_create :notify_gestionnaire
scope :with_answer, -> { where.not(answer: nil) }
scope :without_answer, -> { where(answer: nil) }
scope :for_dossier, ->(dossier_id) { where(dossier_id: dossier_id) }
scope :by_latest, -> { order(updated_at: :desc) }
def email_to_display
gestionnaire.try(:email) || email
end
def notify_gestionnaire
AvisMailer.avis_invitation(self).deliver_now
end
def self.link_avis_to_gestionnaire(gestionnaire)
Avis.where(email: gestionnaire.email).update_all(email: nil, gestionnaire_id: gestionnaire.id)
end
def self.avis_exists_and_email_belongs_to_avis?(avis_id, email)
avis = Avis.find_by(id: avis_id)
avis.present? && avis.email == email
end
end

View file

@ -5,6 +5,7 @@ class Champ < ActiveRecord::Base
delegate :libelle, :type_champ, :order_place, :mandatory, :description, :drop_down_list, to: :type_de_champ delegate :libelle, :type_champ, :order_place, :mandatory, :description, :drop_down_list, to: :type_de_champ
before_save :format_date_to_iso, if: Proc.new { type_champ == 'date' }
after_save :internal_notification, if: Proc.new { !dossier.nil? } after_save :internal_notification, if: Proc.new { !dossier.nil? }
def mandatory? def mandatory?
@ -12,12 +13,12 @@ class Champ < ActiveRecord::Base
end end
def data_provide def data_provide
return 'datepicker' if (type_champ == 'datetime' || type_champ == 'date') && !(BROWSER.value.chrome? || BROWSER.value.edge?) return 'datepicker' if (type_champ == 'datetime') && !(BROWSER.value.chrome? || BROWSER.value.edge?)
return 'typeahead' if type_champ == 'address' return 'typeahead' if type_champ == 'address'
end end
def data_date_format def data_date_format
('dd/mm/yyyy' if type_champ == 'datetime' || type_champ == 'date') ('dd/mm/yyyy' if type_champ == 'datetime')
end end
def same_hour? num def same_hour? num
@ -55,6 +56,15 @@ class Champ < ActiveRecord::Base
private private
def format_date_to_iso
date = begin
Date.parse(value).strftime("%F")
rescue
nil
end
self.value = date
end
def internal_notification def internal_notification
unless dossier.state == 'draft' unless dossier.state == 'draft'
NotificationService.new('champs', self.dossier.id, self.libelle).notify NotificationService.new('champs', self.dossier.id, self.libelle).notify

View file

@ -4,50 +4,23 @@ module MailTemplateConcern
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include ActionView::Helpers::UrlHelper include ActionView::Helpers::UrlHelper
TAGS = { TAGS = []
numero_dossier: { TAGS << TAG_NUMERO_DOSSIER = {
description: "Permet d'afficher le numéro de dossier de l'utilisateur.", name: "numero_dossier",
templates: [ description: "Permet d'afficher le numéro de dossier de l'utilisateur."
"initiated_mail",
"received_mail",
"closed_mail",
"refused_mail",
"without_continuation_mail"
]
},
lien_dossier: {
description: "Permet d'afficher un lien vers le dossier de l'utilisateur.",
templates: [
"initiated_mail",
"received_mail",
"closed_mail",
"refused_mail",
"without_continuation_mail"
]
},
libelle_procedure: {
description: "Permet d'afficher le libellé de la procédure.",
templates: [
"initiated_mail",
"received_mail",
"closed_mail",
"refused_mail",
"without_continuation_mail"
]
},
date_de_decision: {
description: "Permet d'afficher la date à laquelle la décision finale (acceptation, refus, classement sans suite) sur le dossier a été prise.",
templates: [
"closed_mail",
"refused_mail",
"without_continuation_mail"
]
} }
TAGS << TAG_LIEN_DOSSIER = {
name: "lien_dossier",
description: "Permet d'afficher un lien vers le dossier de l'utilisateur."
}
TAGS << TAG_LIBELLE_PROCEDURE = {
name: "libelle_procedure",
description: "Permet d'afficher le libellé de la procédure."
}
TAGS << TAG_DATE_DE_DECISION = {
name: "date_de_decision",
description: "Permet d'afficher la date à laquelle la décision finale (acceptation, refus, classement sans suite) sur le dossier a été prise."
} }
def self.tags_for_template(template)
TAGS.select { |key, value| value[:templates].include?(template) }
end
def object_for_dossier(dossier) def object_for_dossier(dossier)
replace_tags(object, dossier) replace_tags(object, dossier)
@ -59,17 +32,13 @@ module MailTemplateConcern
def replace_tags(string, dossier) def replace_tags(string, dossier)
TAGS.inject(string) do |acc, tag| TAGS.inject(string) do |acc, tag|
acc.gsub!("--#{tag.first}--", replace_tag(tag.first.to_sym, dossier)) || acc acc.gsub!("--#{tag[:name]}--", replace_tag(tag, dossier)) || acc
end end
end end
module ClassMethods module ClassMethods
def slug
self.name.demodulize.underscore.parameterize
end
def default def default
body = ActionController::Base.new.render_to_string(template: self.name.underscore) body = ActionController::Base.new.render_to_string(template: self.const_get(:TEMPLATE_NAME))
self.new(object: self.const_get(:DEFAULT_OBJECT), body: body) self.new(object: self.const_get(:DEFAULT_OBJECT), body: body)
end end
end end
@ -78,13 +47,13 @@ module MailTemplateConcern
def replace_tag(tag, dossier) def replace_tag(tag, dossier)
case tag case tag
when :numero_dossier when TAG_NUMERO_DOSSIER
dossier.id.to_s dossier.id.to_s
when :lien_dossier when TAG_LIEN_DOSSIER
link_to users_dossier_recapitulatif_url(dossier), users_dossier_recapitulatif_url(dossier), target: '_blank' link_to users_dossier_recapitulatif_url(dossier), users_dossier_recapitulatif_url(dossier), target: '_blank'
when :libelle_procedure when TAG_LIBELLE_PROCEDURE
dossier.procedure.libelle dossier.procedure.libelle
when :date_de_decision when TAG_DATE_DE_DECISION
dossier.processed_at.present? ? dossier.processed_at.localtime.strftime("%d/%m/%Y") : "" dossier.processed_at.present? ? dossier.processed_at.localtime.strftime("%d/%m/%Y") : ""
else else
'--BALISE_NON_RECONNUE--' '--BALISE_NON_RECONNUE--'

View file

@ -1,14 +1,25 @@
class Dossier < ActiveRecord::Base class Dossier < ActiveRecord::Base
enum state: {draft: 'draft', enum state: {
draft: 'draft',
initiated: 'initiated', initiated: 'initiated',
replied: 'replied', #action utilisateur demandé replied: 'replied', # action utilisateur demandé
updated: 'updated', #etude par l'administration en cours updated: 'updated', # etude par l'administration en cours
received: 'received', received: 'received',
closed: 'closed', closed: 'closed',
refused: 'refused', refused: 'refused',
without_continuation: 'without_continuation' without_continuation: 'without_continuation'
} }
BROUILLON = %w(draft)
NOUVEAUX = %w(initiated)
OUVERT = %w(updated replied)
WAITING_FOR_GESTIONNAIRE = %w(updated)
WAITING_FOR_USER = %w(replied)
EN_CONSTRUCTION = %w(initiated updated replied)
EN_INSTRUCTION = %w(received)
A_INSTRUIRE = %w(received)
TERMINE = %w(closed refused without_continuation)
has_one :etablissement, dependent: :destroy has_one :etablissement, dependent: :destroy
has_one :entreprise, dependent: :destroy has_one :entreprise, dependent: :destroy
has_one :individual, dependent: :destroy has_one :individual, dependent: :destroy
@ -25,10 +36,36 @@ class Dossier < ActiveRecord::Base
has_many :invites_gestionnaires, class_name: 'InviteGestionnaire', dependent: :destroy has_many :invites_gestionnaires, class_name: 'InviteGestionnaire', dependent: :destroy
has_many :follows has_many :follows
has_many :notifications, dependent: :destroy has_many :notifications, dependent: :destroy
has_many :avis, dependent: :destroy
belongs_to :procedure belongs_to :procedure
belongs_to :user belongs_to :user
scope :state_brouillon, -> { where(state: BROUILLON) }
scope :state_not_brouillon, -> { where.not(state: BROUILLON) }
scope :state_nouveaux, -> { where(state: NOUVEAUX) }
scope :state_ouvert, -> { where(state: OUVERT) }
scope :state_waiting_for_gestionnaire, -> { where(state: WAITING_FOR_GESTIONNAIRE) }
scope :state_waiting_for_user, -> { where(state: WAITING_FOR_USER) }
scope :state_en_construction, -> { where(state: EN_CONSTRUCTION) }
scope :state_en_instruction, -> { where(state: EN_INSTRUCTION) }
scope :state_a_instruire, -> { where(state: A_INSTRUIRE) }
scope :state_termine, -> { where(state: TERMINE) }
scope :archived, -> { where(archived: true) }
scope :not_archived, -> { where(archived: false) }
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) }
scope :all_state, -> { not_archived.state_not_brouillon.order_by_updated_at(:asc) }
scope :nouveaux, -> { not_archived.state_nouveaux.order_by_updated_at(:asc) }
scope :ouvert, -> { not_archived.state_ouvert.order_by_updated_at(:asc) }
scope :waiting_for_gestionnaire, -> { not_archived.state_waiting_for_gestionnaire.order_by_updated_at(:asc) }
scope :waiting_for_user, -> { not_archived.state_waiting_for_user.order_by_updated_at(:asc) }
scope :a_instruire, -> { not_archived.state_a_instruire.order_by_updated_at(:asc) }
scope :termine, -> { not_archived.state_termine.order_by_updated_at(:asc) }
scope :downloadable, -> { state_not_brouillon.order_by_updated_at(:asc) }
accepts_nested_attributes_for :individual accepts_nested_attributes_for :individual
delegate :siren, to: :entreprise delegate :siren, to: :entreprise
@ -41,20 +78,10 @@ class Dossier < ActiveRecord::Base
after_save :build_default_champs, if: Proc.new { procedure_id_changed? } after_save :build_default_champs, if: Proc.new { procedure_id_changed? }
after_save :build_default_individual, if: Proc.new { procedure.for_individual? } after_save :build_default_individual, if: Proc.new { procedure.for_individual? }
after_save :send_notification_email
validates :user, presence: true validates :user, presence: true
BROUILLON = %w(draft)
NOUVEAUX = %w(initiated)
OUVERT = %w(updated replied)
WAITING_FOR_GESTIONNAIRE = %w(updated)
WAITING_FOR_USER = %w(replied)
EN_CONSTRUCTION = %w(initiated updated replied)
EN_INSTRUCTION = %w(received)
A_INSTRUIRE = %w(received)
TERMINE = %w(closed refused without_continuation)
ALL_STATE = %w(initiated updated replied received closed refused without_continuation)
def unreaded_notifications def unreaded_notifications
@unreaded_notif ||= notifications.where(already_read: false) @unreaded_notif ||= notifications.where(already_read: false)
end end
@ -158,49 +185,10 @@ class Dossier < ActiveRecord::Base
state state
end end
def self.all_state order = 'ASC'
where(state: ALL_STATE, archived: false).order("updated_at #{order}")
end
def brouillon? def brouillon?
BROUILLON.include?(state) BROUILLON.include?(state)
end end
scope :brouillon, -> { where(state: BROUILLON) }
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) }
def self.nouveaux order = 'ASC'
where(state: NOUVEAUX, archived: false).order("updated_at #{order}")
end
def self.waiting_for_gestionnaire order = 'ASC'
where(state: WAITING_FOR_GESTIONNAIRE, archived: false).order("updated_at #{order}")
end
def self.waiting_for_user order = 'ASC'
where(state: WAITING_FOR_USER, archived: false).order("updated_at #{order}")
end
scope :en_construction, -> { where(state: EN_CONSTRUCTION) }
def self.ouvert order = 'ASC'
where(state: OUVERT, archived: false).order("updated_at #{order}")
end
def self.a_instruire order = 'ASC'
where(state: A_INSTRUIRE, archived: false).order("updated_at #{order}")
end
scope :en_instruction, -> { where(state: EN_INSTRUCTION) }
scope :termine, -> { where(state: TERMINE) }
scope :archived, -> { where(archived: true) }
scope :not_archived, -> { where(archived: false) }
scope :downloadable, -> { all_state }
def cerfa_available? def cerfa_available?
procedure.cerfa_flag? && cerfa.size != 0 procedure.cerfa_flag? && cerfa.size != 0
end end
@ -315,4 +303,10 @@ class Dossier < ActiveRecord::Base
def serialize_value_for_export(value) def serialize_value_for_export(value)
value.nil? || value.kind_of?(Time) ? value : value.to_s value.nil? || value.kind_of?(Time) ? value : value.to_s
end end
def send_notification_email
if state_changed? && EN_INSTRUCTION.include?(state)
NotificationMailer.send_notification(self, procedure.received_mail_template).deliver_now!
end
end
end end

View file

@ -1,5 +1,4 @@
class Entreprise < ActiveRecord::Base class Entreprise < ActiveRecord::Base
belongs_to :dossier belongs_to :dossier
has_one :etablissement, dependent: :destroy has_one :etablissement, dependent: :destroy
has_one :rna_information, dependent: :destroy has_one :rna_information, dependent: :destroy

View file

@ -1,5 +1,4 @@
class Etablissement < ActiveRecord::Base class Etablissement < ActiveRecord::Base
belongs_to :dossier belongs_to :dossier
belongs_to :entreprise belongs_to :entreprise

View file

@ -2,5 +2,4 @@ class Exercice < ActiveRecord::Base
belongs_to :etablissement belongs_to :etablissement
validates :ca, presence: true, allow_blank: false, allow_nil: false validates :ca, presence: true, allow_blank: false, allow_nil: false
end end

View file

@ -1,5 +1,4 @@
class FranceConnectParticulierClient < OpenIDConnect::Client class FranceConnectParticulierClient < OpenIDConnect::Client
def initialize params={} def initialize params={}
super( super(
identifier: FRANCE_CONNECT.particulier_identifier, identifier: FRANCE_CONNECT.particulier_identifier,

View file

@ -12,6 +12,7 @@ class Gestionnaire < ActiveRecord::Base
has_many :followed_dossiers, through: :follows, source: :dossier has_many :followed_dossiers, through: :follows, source: :dossier
has_many :follows has_many :follows
has_many :preference_list_dossiers has_many :preference_list_dossiers
has_many :avis
after_create :build_default_preferences_list_dossier after_create :build_default_preferences_list_dossier
after_create :build_default_preferences_smart_listing_page after_create :build_default_preferences_smart_listing_page
@ -24,6 +25,11 @@ class Gestionnaire < ActiveRecord::Base
self[:procedure_filter] self[:procedure_filter]
end end
def can_view_dossier?(dossier_id)
avis.where(dossier_id: dossier_id).any? ||
dossiers.where(id: dossier_id).any?
end
def toggle_follow_dossier dossier_id def toggle_follow_dossier dossier_id
dossier = dossier_id dossier = dossier_id
dossier = Dossier.find(dossier_id) unless dossier_id.class == Dossier dossier = Dossier.find(dossier_id) unless dossier_id.class == Dossier
@ -41,6 +47,10 @@ class Gestionnaire < ActiveRecord::Base
Follow.where(gestionnaire_id: id, dossier_id: dossier_id).any? Follow.where(gestionnaire_id: id, dossier_id: dossier_id).any?
end end
def assigned_on_procedure?(procedure_id)
procedures.find_by(id: procedure_id).present?
end
def build_default_preferences_list_dossier procedure_id=nil def build_default_preferences_list_dossier procedure_id=nil
PreferenceListDossier.available_columns_for(procedure_id).each do |table| PreferenceListDossier.available_columns_for(procedure_id).each do |table|
@ -92,6 +102,26 @@ class Gestionnaire < ActiveRecord::Base
notifications.pluck(:dossier_id).uniq.count notifications.pluck(:dossier_id).uniq.count
end end
def last_week_overview
start_date = DateTime.now.beginning_of_week
active_procedure_overviews = procedures
.where(published: true)
.all
.map { |procedure| procedure.procedure_overview(start_date, dossiers_with_notifications_count_for_procedure(procedure)) }
.select(&:had_some_activities?)
if active_procedure_overviews.count == 0 && notifications.count == 0
nil
else
{
start_date: start_date,
procedure_overviews: active_procedure_overviews,
notifications: notifications
}
end
end
private private
def valid_couple_table_attr? table, column def valid_couple_table_attr? table, column

View file

@ -1,9 +1,11 @@
module Mails module Mails
class ClosedMail < ActiveRecord::Base class ClosedMail < ApplicationRecord
include MailTemplateConcern include MailTemplateConcern
SLUG = "closed_mail"
TEMPLATE_NAME = "mails/closed_mail"
DISPLAYED_NAME = "Accusé d'acceptation" DISPLAYED_NAME = "Accusé d'acceptation"
DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été accepté' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été accepté'
ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE, TAG_DATE_DE_DECISION]
end end
end end

View file

@ -1,9 +1,11 @@
module Mails module Mails
class InitiatedMail < ActiveRecord::Base class InitiatedMail < ApplicationRecord
include MailTemplateConcern include MailTemplateConcern
SLUG = "initiated_mail"
TEMPLATE_NAME = "mails/initiated_mail"
DISPLAYED_NAME = 'Accusé de réception' DISPLAYED_NAME = 'Accusé de réception'
DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été bien reçu' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été bien reçu'
ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE]
end end
end end

View file

@ -1,9 +1,11 @@
module Mails module Mails
class ReceivedMail < ActiveRecord::Base class ReceivedMail < ApplicationRecord
include MailTemplateConcern include MailTemplateConcern
SLUG = "received_mail"
TEMPLATE_NAME = "mails/received_mail"
DISPLAYED_NAME = 'Accusé de passage en instruction' DISPLAYED_NAME = 'Accusé de passage en instruction'
DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- va être instruit' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- va être instruit'
ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE]
end end
end end

View file

@ -2,8 +2,10 @@ module Mails
class RefusedMail < ApplicationRecord class RefusedMail < ApplicationRecord
include MailTemplateConcern include MailTemplateConcern
SLUG = "refused_mail"
TEMPLATE_NAME = "mails/refused_mail"
DISPLAYED_NAME = 'Accusé de rejet du dossier' DISPLAYED_NAME = 'Accusé de rejet du dossier'
DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été refusé' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été refusé'
ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE, TAG_DATE_DE_DECISION]
end end
end end

View file

@ -2,8 +2,10 @@ module Mails
class WithoutContinuationMail < ApplicationRecord class WithoutContinuationMail < ApplicationRecord
include MailTemplateConcern include MailTemplateConcern
SLUG = "without_continuation"
TEMPLATE_NAME = "mails/without_continuation_mail"
DISPLAYED_NAME = 'Accusé de classement sans suite' DISPLAYED_NAME = 'Accusé de classement sans suite'
DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été classé sans suite' DEFAULT_OBJECT = 'Votre dossier TPS nº --numero_dossier-- a été classé sans suite'
ALLOWED_TAGS = [TAG_NUMERO_DOSSIER, TAG_LIEN_DOSSIER, TAG_LIBELLE_PROCEDURE, TAG_DATE_DE_DECISION]
end end
end end

View file

@ -1,11 +1,14 @@
class Notification < ActiveRecord::Base class Notification < ActiveRecord::Base
belongs_to :dossier
enum type_notif: { enum type_notif: {
commentaire: 'commentaire', commentaire: 'commentaire',
cerfa: 'cerfa', cerfa: 'cerfa',
piece_justificative: 'piece_justificative', piece_justificative: 'piece_justificative',
champs: 'champs', champs: 'champs',
submitted: 'submitted' submitted: 'submitted',
avis: 'avis'
} }
belongs_to :dossier
scope :unread, -> { where(already_read: false) } scope :unread, -> { where(already_read: false) }
end end

View file

@ -3,7 +3,6 @@ class Procedure < ActiveRecord::Base
has_many :types_de_champ, class_name: 'TypeDeChampPublic', dependent: :destroy has_many :types_de_champ, class_name: 'TypeDeChampPublic', dependent: :destroy
has_many :types_de_champ_private, dependent: :destroy has_many :types_de_champ_private, dependent: :destroy
has_many :dossiers has_many :dossiers
has_many :notifications, through: :dossiers
has_one :procedure_path, dependent: :destroy has_one :procedure_path, dependent: :destroy
@ -16,6 +15,12 @@ class Procedure < ActiveRecord::Base
has_many :preference_list_dossiers has_many :preference_list_dossiers
has_one :initiated_mail, class_name: "Mails::InitiatedMail", dependent: :destroy
has_one :received_mail, class_name: "Mails::ReceivedMail", dependent: :destroy
has_one :closed_mail, class_name: "Mails::ClosedMail", dependent: :destroy
has_one :refused_mail, class_name: "Mails::RefusedMail", dependent: :destroy
has_one :without_continuation_mail, class_name: "Mails::WithoutContinuationMail", dependent: :destroy
delegate :use_api_carto, to: :module_api_carto delegate :use_api_carto, to: :module_api_carto
accepts_nested_attributes_for :types_de_champ, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true accepts_nested_attributes_for :types_de_champ, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true
@ -25,31 +30,11 @@ class Procedure < ActiveRecord::Base
mount_uploader :logo, ProcedureLogoUploader mount_uploader :logo, ProcedureLogoUploader
validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :description, presence: true, allow_blank: false, allow_nil: false
# for all those mails do
# has_one :initiated_mail, class_name: 'Mails::InitiatedMail'
#
# add a method to return default mail if none is saved
# def initiated_mail_with_override
# self.initiated_mail_without_override || InitiatedMail.default
# end
# alias_method_chain :initiated_mail, :override
MAIL_TEMPLATE_TYPES = %w(InitiatedMail ReceivedMail ClosedMail RefusedMail WithoutContinuationMail)
MAIL_TEMPLATE_TYPES.each do |name|
has_one "#{name.underscore}".to_sym, class_name: "Mails::#{name}", dependent: :destroy
define_method("#{name.underscore}_with_override") do
self.send("#{name.underscore}_without_override") || Object.const_get("Mails::#{name}").default
end
alias_method_chain "#{name.underscore.to_sym}".to_s, :override
end
scope :not_archived, -> { where(archived: false) } scope :not_archived, -> { where(archived: false) }
scope :by_libelle, -> { order(libelle: :asc) } scope :by_libelle, -> { order(libelle: :asc) }
validates :libelle, presence: true, allow_blank: false, allow_nil: false
validates :description, presence: true, allow_blank: false, allow_nil: false
def path def path
procedure_path.path unless procedure_path.nil? procedure_path.path unless procedure_path.nil?
@ -68,7 +53,7 @@ class Procedure < ActiveRecord::Base
end end
def self.active id def self.active id
Procedure.where(archived: false, published: true).find(id) not_archived.where(published: true).find(id)
end end
def switch_types_de_champ index_of_first_element def switch_types_de_champ index_of_first_element
@ -84,13 +69,18 @@ class Procedure < ActiveRecord::Base
end end
def switch_list_order(list, index_of_first_element) def switch_list_order(list, index_of_first_element)
return false if index_of_first_element < 0 if index_of_first_element < 0 ||
return false if index_of_first_element == list.count - 1 index_of_first_element == list.count - 1 ||
return false if list.count < 1 list.count < 1
false
else
list[index_of_first_element].update_attributes(order_place: index_of_first_element + 1) list[index_of_first_element].update_attributes(order_place: index_of_first_element + 1)
list[index_of_first_element + 1].update_attributes(order_place: index_of_first_element) list[index_of_first_element + 1].update_attributes(order_place: index_of_first_element)
true true
end end
end
def locked? def locked?
published? published?
@ -109,9 +99,11 @@ class Procedure < ActiveRecord::Base
procedure.logo_secure_token = nil procedure.logo_secure_token = nil
procedure.remote_logo_url = self.logo_url procedure.remote_logo_url = self.logo_url
MAIL_TEMPLATE_TYPES.each do |mtt| procedure.initiated_mail = initiated_mail.try(:dup)
procedure.send("#{mtt.underscore}=", self.send("#{mtt.underscore}_without_override").try(:dup)) procedure.received_mail = received_mail.try(:dup)
end procedure.closed_mail = closed_mail.try(:dup)
procedure.refused_mail = refused_mail.try(:dup)
procedure.without_continuation_mail = without_continuation_mail.try(:dup)
return procedure if procedure.save return procedure if procedure.save
end end
@ -141,4 +133,27 @@ class Procedure < ActiveRecord::Base
} }
end end
def procedure_overview(start_date, notifications_count)
ProcedureOverview.new(self, start_date, notifications_count)
end
def initiated_mail_template
initiated_mail || Mails::InitiatedMail.default
end
def received_mail_template
received_mail || Mails::ReceivedMail.default
end
def closed_mail_template
closed_mail || Mails::ClosedMail.default
end
def refused_mail_template
refused_mail || Mails::RefusedMail.default
end
def without_continuation_mail_template
without_continuation_mail || Mails::WithoutContinuationMail.default
end
end end

View file

@ -0,0 +1,82 @@
class ProcedureOverview
include Rails.application.routes.url_helpers
attr_accessor :libelle, :notifications_count, :received_dossiers_count, :created_dossiers_count, :processed_dossiers_count, :date
def initialize(procedure, start_date, notifications_count)
@libelle = procedure.libelle
@procedure_url = backoffice_dossiers_procedure_url(procedure)
@notifications_count = notifications_count
@received_dossiers_count = procedure.dossiers.where(state: :received).count
@created_dossiers_count = procedure.dossiers
.where(created_at: start_date..DateTime.now)
.where.not(state: :draft)
.count
@processed_dossiers_count = procedure.dossiers.where(processed_at: start_date..DateTime.now).count
end
def had_some_activities?
[received_dossiers_count,
created_dossiers_count,
processed_dossiers_count,
notifications_count].reduce(:+) > 0
end
def to_html
[libelle_description,
dossiers_en_instruction_description,
created_dossier_description,
processed_dossier_description,
notifications_description].compact.join('<br>')
end
private
def libelle_description
"<a href='#{@procedure_url}'><strong>#{libelle}</strong></a>"
end
def dossiers_en_instruction_description
case received_dossiers_count
when 0
nil
when 1
"1 dossier est en cours d'instruction"
else
"#{received_dossiers_count} dossiers sont en cours d'instruction"
end
end
def created_dossier_description
case created_dossiers_count
when 0
nil
when 1
'1 nouveau dossier a été déposé'
else
"#{created_dossiers_count} nouveaux dossiers ont été déposés"
end
end
def processed_dossier_description
case processed_dossiers_count
when 0
nil
when 1
'1 dossier a été instruit'
else
"#{processed_dossiers_count} dossiers ont été instruits"
end
end
def notifications_description
case notifications_count
when 0
nil
when 1
'1 notification en attente sur les dossiers que vous suivez'
else
"#{notifications_count} notifications en attente sur les dossiers que vous suivez"
end
end
end

View file

@ -1,6 +1,8 @@
class User < ActiveRecord::Base class User < ActiveRecord::Base
enum loged_in_with_france_connect: {particulier: 'particulier', enum loged_in_with_france_connect: {
entreprise: 'entreprise'} particulier: 'particulier',
entreprise: 'entreprise'
}
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable # :confirmable, :lockable, :timeoutable and :omniauthable
@ -35,5 +37,4 @@ class User < ActiveRecord::Base
def invite? dossier_id def invite? dossier_id
invites.pluck(:dossier_id).include?(dossier_id.to_i) invites.pluck(:dossier_id).include?(dossier_id.to_i)
end end
end end

View file

@ -45,7 +45,7 @@ class DossiersListGestionnaireService
end end
def termine def termine
@termine ||= filter_dossiers.termine.not_archived @termine ||= filter_dossiers.termine
end end
def filter_dossiers def filter_dossiers

View file

@ -35,6 +35,8 @@ class NotificationService
attribut attribut
when 'submitted' when 'submitted'
"Le dossier nº #{@dossier_id} a été déposé." "Le dossier nº #{@dossier_id} a été déposé."
when 'avis'
'Un nouvel avis a été rendu'
else else
'Notification par défaut' 'Notification par défaut'
end end

View file

@ -4,7 +4,7 @@
= simple_form_for @mail_template, = simple_form_for @mail_template,
as: 'mail_template', as: 'mail_template',
url: admin_procedure_mail_template_path(@procedure, @mail_template.class.slug), url: admin_procedure_mail_template_path(@procedure, @mail_template.class.const_get(:SLUG)),
method: :put do |f| method: :put do |f|
.row .row
.col-md-6 .col-md-6
@ -22,9 +22,9 @@
Balise Balise
%th %th
Description Description
- MailTemplateConcern.tags_for_template(@mail_template_name).each do |balise| - @mail_template.class.const_get(:ALLOWED_TAGS).each do |tag|
%tr %tr
%td.center %td.center
= "--#{balise.first}--" = "--#{tag[:name]}--"
%td %td
= balise.second[:description] = tag[:description]

View file

@ -5,9 +5,9 @@
%tr %tr
%th{ colspan: 2 } %th{ colspan: 2 }
Type d'email Type d'email
- @mails.each do |mail| - @mail_templates.each do |mail_template|
%tr %tr
%td %td
= mail.class.const_get(:DISPLAYED_NAME) = mail_template.class.const_get(:DISPLAYED_NAME)
%td.text-right %td.text-right
= link_to "Personnaliser l'e-mail", edit_admin_procedure_mail_template_path(@procedure, mail.class.slug) = link_to "Personnaliser l'e-mail", edit_admin_procedure_mail_template_path(@procedure, mail_template.class.const_get(:SLUG))

View file

@ -0,0 +1,28 @@
%html
%body
%p
Bonjour,
%br
= "Vous avez été invité par #{@avis.claimant.email} à donner votre avis sur le dossier nº #{@avis.dossier.id} de la procédure : #{@avis.dossier.procedure.libelle}."
%br
Message de votre interlocuteur :
%p{ style: 'border: 1px solid grey' }
= @avis.introduction
- if @avis.gestionnaire.present?
%p
= link_to "Connectez-vous pour donner votre avis", backoffice_dossier_url(@avis.dossier)
- else
%p
= link_to "Inscrivez-vous pour donner votre avis", avis_sign_up_url(@avis.id, @avis.email)
Bonne journée,
%br
%br
L'équipe Téléprocédures Simplifiées
%br
%br
%hr
%br
Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme.

View file

@ -0,0 +1,15 @@
.avis-sign-up
.left
%p.description= @dossier.procedure.libelle
%p.dossier Dossier nº #{@dossier.id}
.right
%h1 Créez-vous un compte
= form_for(Gestionnaire.new, url: { controller: 'backoffice/avis', action: :create_gestionnaire }, method: :post) do |f|
= f.label :email, 'Email'
= f.email_field :email, value: @email, disabled: true
= f.label :password, 'Mot de passe'
= f.password_field :password, autofocus: true, required: true, placeholder: '8 caractères minimum'
%button Créer un compte

View file

@ -0,0 +1,20 @@
- if smart_listing.collection.any?
%table#dossiers-list.table
%thead
%th
%th
Procédure
%th
Invité le
%tbody
- smart_listing.collection.each do |avis|
%tr.dossier-row{ id: "tr_dossier_#{avis.dossier.id}", 'data-dossier_url' => backoffice_dossier_url(id: avis.dossier.id) }
%td= avis.dossier.id
%td= avis.dossier.procedure.libelle
%td= avis.created_at.strftime('%d/%m/%Y %H:%M')
= smart_listing.paginate
- else
.center{ colspan: 2 }
%em Aucun dossier

View file

@ -0,0 +1,22 @@
.col-md-12
.default-data-block.default_visible
.row.show-block
.header
.title
.carret-right
.carret-down
= "#{@pending_avis.count} avis à rendre"
.body
= smart_listing_render :pending_avis
%br
.default-data-block
.row.show-block
.header
.title
.carret-right
.carret-down
= "#{@avis_with_answer.count} avis #{"rendu".pluralize(@avis_with_answer.count)}"
.body
= smart_listing_render :avis_with_answer

View file

@ -0,0 +1,4 @@
<%= smart_listing_update :pending_avis %>
<%= smart_listing_update :avis_with_answer %>
link_init();

View file

@ -1,4 +1,4 @@
<p>Bonjour <%= @resource.email %>!</p> <p>Bonjour,</p>
<p>Vous avez demandé à regénérer votre mot de passe sur la plateforme TPS. Pour ceci, merci de suivre le lien suivant :</p> <p>Vous avez demandé à regénérer votre mot de passe sur la plateforme TPS. Pour ceci, merci de suivre le lien suivant :</p>
@ -6,10 +6,6 @@
<p>Si vous n'avez pas effectué une telle demande, merci d'ignorer ce mail. Votre mot de passe ne sera pas changé.</p> <p>Si vous n'avez pas effectué une telle demande, merci d'ignorer ce mail. Votre mot de passe ne sera pas changé.</p>
<p>Bonne journée</p> <p>Bonne journée,</p>
<p>L'équipe Téléprocédures Simplifiées</p>
<p>
---
<br>
L'équipe Téléprocédures Simplifiées</p>

View file

@ -0,0 +1,56 @@
- if current_gestionnaire && current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id)
.default-data-block.default_visible
.row.show-block.infos
.header
.col-xs-12.title
.carret-right
.carret-down
AVIS EXTERNES
.body
.display-block-on-print
- dossier_facade.dossier.avis.by_latest.each do |avis|
- if avis.answer
.panel.panel-success
.panel-heading
%strong= avis.email_to_display
a donné son avis le
= avis.updated_at.localtime.strftime('%d/%m/%Y à %H:%M')
.panel-body
%strong Vous :
= avis.introduction
%hr
%strong= "#{avis.email_to_display} :"
= avis.answer
- else
.panel.panel-info
.panel-heading
Avis demandé à
%strong= avis.email_to_display
le
= avis.created_at.localtime.strftime('%d/%m/%Y à %H:%M')
.panel-body
%strong Vous :
= avis.introduction
%hr
.center
%em Avis en attente
-# FIXME prevent bug when the user is also a gestionnaire on the procedure #375
- if @new_avis.present?
.hidden-print
.panel.panel-default
.panel-heading
Demander un avis externe
.panel-body
.help-block
Invitez une personne externe à consulter le dossier et à vous donner un avis sur celui ci.
%br
Cette personne pourra également contribuer au fil de messagerie, mais ne pourra pas modifier le dossier.
= simple_form_for @new_avis, url: backoffice_dossier_avis_index_path(dossier_facade.dossier.object.id) do |f|
= f.input 'email', label: "Email de la personne qui doit donner un avis"
= f.input 'introduction', label: "Message"
= f.submit "Envoyer la demande d'avis", class: 'btn btn-default'

View file

@ -1,3 +1,5 @@
= render partial: 'dossiers/edit_avis', locals: { dossier_facade: @facade }
= render partial: 'dossiers/messagerie', locals: { dossier_facade: @facade } = render partial: 'dossiers/messagerie', locals: { dossier_facade: @facade }
- if @facade.procedure.individual_with_siret - if @facade.procedure.individual_with_siret
@ -51,8 +53,7 @@
= render partial: '/users/carte/map', locals: { dossier: @facade.dossier } = render partial: '/users/carte/map', locals: { dossier: @facade.dossier }
= render partial: 'users/carte/init_carto', locals: { dossier: @facade.dossier } = render partial: 'users/carte/init_carto', locals: { dossier: @facade.dossier }
- if @current_gestionnaire && gestionnaire_signed_in? && current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id) && @champs_private.count > 0
- if @current_gestionnaire && gestionnaire_signed_in? && @champs_private.count > 0
.default-data-block.default_visible .default-data-block.default_visible
.row.show-block#private-fields .row.show-block#private-fields
.header .header
@ -65,3 +66,5 @@
= (private_fields_count == 1) ? "1 champ" : "#{private_fields_count} champs" = (private_fields_count == 1) ? "1 champ" : "#{private_fields_count} champs"
.body .body
= render partial: '/dossiers/infos_private_fields' = render partial: '/dossiers/infos_private_fields'
= render partial: 'dossiers/avis', locals: { dossier_facade: @facade }

View file

@ -0,0 +1,13 @@
- if current_gestionnaire
- avis_for_dossier = current_gestionnaire.avis.for_dossier(dossier_facade.dossier.id).by_latest
- if avis_for_dossier.any?
.panel.panel-default
.panel-body
%h4 Votre avis est sollicité sur le dossier :
- avis_for_dossier.each do |avis|
%hr
%p= avis.introduction
= simple_form_for avis, url: backoffice_dossier_avis_path(dossier_facade.dossier, avis) do |f|
= f.input 'answer', label: "Votre avis"
- submit_label = if avis.answer then "Modifier votre avis" else "Enregistrer votre avis" end
= f.submit submit_label, class: 'btn btn-default'

View file

@ -0,0 +1,26 @@
%table{ align: 'center', border: '0', cellpadding: '0', cellspacing: '0', height: '100%', style: 'background-color: #fafafa', width: '100%' }
%tbody
%tr
%td{ align: 'center', style: 'height: 100%; margin: 0; padding: 30px; width: 100%; border-top: 0', valign: 'top' }
%table{ border: '0', cellpadding: '0', cellspacing: '0', style: 'border-collapse: collapse; border: 0; max-width: 600px!important;', width: '100%' }
%tbody
%tr
%td{ style: 'background: #ffffff none no-repeat center/cover; background-color: #ffffff; background-image: none; background-repeat: no-repeat; background-position: center; background-size: cover; border-top: 0; padding-top: 0;', valign: 'top' }
%table{ border: '0', cellpadding: '0', cellspacing: '0', style: 'min-width: 100%; border-collapse: collapse', width: '100%' }
%tr
%td{ style: 'padding: 0 30px; mso-line-height-rule: exactly; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; ', valign: 'top' }
%img{ align: 'middle', alt: 'Logo TPS', src: image_url('mailer/gestionnaire_mailer/logo.png'), style: 'max-width: 125px; padding: 30px 0; display: inline !important; vertical-align: bottom; border: 0; height: auto; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic;' }
%tr
%td{ style: 'padding: 0 30px 30px; word-break: break-word; color: #333333; font-family: Helvetica; font-size: 16px; line-height: 150%; text-align: left; border-bottom: 2px solid #4393F3;', valign: 'top' }
Bonjour, voici votre résumé de l'activité de la semaine du #{l(@args[:start_date], format: '%d %B')} au #{l(DateTime.now, format: '%d %B')}.
%br
%br
- @args[:procedure_overviews].each do |procedure_overview|
= procedure_overview.to_html.html_safe
%br
%br
Bonne journée,
%br
%br
L'équipe Téléprocédures Simplifiées

View file

@ -1,4 +1,4 @@
Bienvenue sur la plateforme TPS Bienvenue sur la plateforme TPS,
Vous venez d'être assigné à un administrateur sur la plateforme TPS. Voici quelques informations utiles : Vous venez d'être assigné à un administrateur sur la plateforme TPS. Voici quelques informations utiles :
@ -7,5 +7,4 @@ Vous venez d'être assigné à un administrateur sur la plateforme TPS. Voici qu
Bonne journée, Bonne journée,
---
L'équipe Téléprocédures Simplifiées L'équipe Téléprocédures Simplifiées

View file

@ -1,4 +1,4 @@
Bienvenue sur la plateforme TPS Bienvenue sur la plateforme TPS,
Vous venez d'être nommé accompagnateur sur la plateforme TPS. Pour mémoire, voici quelques informations utiles : Vous venez d'être nommé accompagnateur sur la plateforme TPS. Pour mémoire, voici quelques informations utiles :
@ -8,5 +8,4 @@ Vous venez d'être nommé accompagnateur sur la plateforme TPS. Pour mémoire, v
Bonne journée, Bonne journée,
---
L'équipe Téléprocédures Simplifiées L'équipe Téléprocédures Simplifiées

View file

@ -1,12 +1,11 @@
Bonjour <%= @invite.email %> Bonjour,
L'utilisateur <%= @invite.email_sender %> souhaite que vous participiez à l'élaboration d'un dossier sur la plateforme TPS. L'utilisateur <%= @invite.email_sender %> souhaite que vous participiez à l'élaboration d'un dossier sur la plateforme TPS.
Cette plateforme permet à ses utilisateurs d'établir des dossiers 100% en ligne et de dialoguer avec plusieurs interlocuteurs privilégiés avant d'instruire un dépot. Cette plateforme permet à ses utilisateurs d'établir des dossiers 100 % en ligne et de dialoguer avec plusieurs interlocuteurs privilégiés avant d'instruire un dépot.
Afin de répondre à cette invitation, merci de vous inscrit avec l'adresse email <%= @invite.email %> sur <%= users_dossiers_invite_url(@invite.id)+"?email=#{@invite.email}" %>. Afin de répondre à cette invitation, merci de vous inscrire avec l'adresse email <%= @invite.email %> sur <%= users_dossiers_invite_url(@invite.id)+"?email=#{@invite.email}" %>.
Bonne journée. Bonne journée,
---
L'équipe Téléprocédures Simplifiées L'équipe Téléprocédures Simplifiées

View file

@ -1,10 +1,9 @@
Bonjour <%= @invite.email %> Bonjour,
L'utilisateur <%= @invite.email_sender %> souhaite que vous participiez à l'élaboration d'un dossier sur la plateforme TPS. L'utilisateur <%= @invite.email_sender %> souhaite que vous participiez à l'élaboration d'un dossier sur la plateforme TPS.
Pour le consulter, merci de suivre ce lien : <%= users_dossiers_invite_url(@invite.id) %> Pour le consulter, merci de suivre ce lien : <%= users_dossiers_invite_url(@invite.id) %>
Bonne journée. Bonne journée,
---
L'équipe Téléprocédures Simplifiées L'équipe Téléprocédures Simplifiées

View file

@ -0,0 +1,9 @@
- ua_id = Rails.env.production? ? 'UA-63927236-2' : 'UA-63927236-4'
:javascript
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '#{ua_id}', 'auto');
ga('send', 'pageview');

View file

@ -1,7 +1,6 @@
.new-header{ class: current_page?(root_path) ? nil : 'new-header-with-border' } .new-header{ class: current_page?(root_path) ? nil : 'new-header-with-border' }
.header-inner-content .header-inner-content
%img.header-logo.pull-left{ src: image_url("header/logo-tps.svg") } = link_to root_path do
%img.header-logo{ src: image_url("header/logo-tps.svg") }
= link_to "Connexion", new_user_session_path, :class => "header-login-button pull-right" = link_to "Connexion", new_user_session_path, class: "header-login-button"
.clear-fix

View file

@ -20,6 +20,10 @@
#infos-block #infos-block
.split-hr-left .split-hr-left
#procedure-list #procedure-list
- if current_gestionnaire.avis.any?
= link_to backoffice_invitations_path do
.procedure-list-element{ class: ('active' if request.path == backoffice_invitations_path) }
Invitations
- current_gestionnaire.procedures.by_libelle.each do |procedure| - current_gestionnaire.procedures.by_libelle.each do |procedure|
= link_to backoffice_dossiers_procedure_path(procedure.id), { title: procedure.libelle } do = link_to backoffice_dossiers_procedure_path(procedure.id), { title: procedure.libelle } do
.procedure-list-element{ class: ('active' if procedure.id.to_s == params[:id]) } .procedure-list-element{ class: ('active' if procedure.id.to_s == params[:id]) }

View file

@ -2,8 +2,8 @@
.infos .infos
#dossier_id= t('dynamics.dossiers.numéro') + @facade.dossier.id.to_s #dossier_id= t('dynamics.dossiers.numéro') + @facade.dossier.id.to_s
#action-block - if current_gestionnaire && current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id)
- if gestionnaire_signed_in? #action-block
- if !@facade.dossier.read_only? || @facade.dossier.initiated? - if !@facade.dossier.read_only? || @facade.dossier.initiated?
= link_to 'Passer en instruction', backoffice_dossier_receive_path(@facade.dossier), method: :post, class: 'btn btn-danger btn-block', data: { confirm: "Confirmer vous le passage en instruction de ce dossier ?" } = link_to 'Passer en instruction', backoffice_dossier_receive_path(@facade.dossier), method: :post, class: 'btn btn-danger btn-block', data: { confirm: "Confirmer vous le passage en instruction de ce dossier ?" }
@ -51,12 +51,12 @@
- if notification.liste.size > 1 - if notification.liste.size > 1
.type= "Plusieurs attributs ont été changés, dont: #{notification.liste.join(" ")}" .type= "Plusieurs attributs ont été changés, dont: #{notification.liste.join(" ")}"
- else - else
.type= "Un attribut à été changé: #{notification.liste.last}" .type= "Un attribut a été changé: #{notification.liste.last}"
- elsif ['piece_justificative'].include?(notification.type_notif) - elsif ['piece_justificative'].include?(notification.type_notif)
- if notification.liste.size > 1 - if notification.liste.size > 1
.type= "Plusieurs pièces jointes ont été changés, dont: #{notification.liste.join(" ")}" .type= "Plusieurs pièces jointes ont été changés, dont: #{notification.liste.join(" ")}"
- else - else
.type= "Une pièce jointe à été changée: #{notification.liste.last}" .type= "Une pièce jointe a été changée: #{notification.liste.last}"
- else - else
.type= notification.liste.last .type= notification.liste.last
.split-hr .split-hr

View file

@ -0,0 +1 @@
= render partial: 'layouts/left_panels/left_panel_backoffice_dossierscontroller_index'

View file

@ -15,25 +15,25 @@
.procedure-list-element#brouillon{ class: ('active' if @liste == 'brouillon') } .procedure-list-element#brouillon{ class: ('active' if @liste == 'brouillon') }
Brouillons Brouillons
.badge.progress-bar-default .badge.progress-bar-default
= @user_dossiers.brouillon.count = @user_dossiers.state_brouillon.count
%a{ :href => "#{url_for users_dossiers_path(liste: 'a_traiter')}", 'data-toggle' => :tooltip, title: 'Les dossiers qui requièrent une action de votre part.' } %a{ :href => "#{url_for users_dossiers_path(liste: 'a_traiter')}", 'data-toggle' => :tooltip, title: 'Les dossiers qui requièrent une action de votre part.' }
.procedure-list-element#a_traiter{ class: ('active' if @liste == 'a_traiter') } .procedure-list-element#a_traiter{ class: ('active' if @liste == 'a_traiter') }
En construction En construction
.badge.progress-bar-danger .badge.progress-bar-danger
= @user_dossiers.en_construction.count = @user_dossiers.state_en_construction.count
%a{ :href => "#{url_for users_dossiers_path(liste: 'en_instruction')}", 'data-toggle' => :tooltip, title: 'Les dossiers en cours d\'examen par l\'administration compétante.' } %a{ :href => "#{url_for users_dossiers_path(liste: 'en_instruction')}", 'data-toggle' => :tooltip, title: 'Les dossiers en cours d\'examen par l\'administration compétante.' }
.procedure-list-element#en_instruction{ class: ('active' if @liste == 'en_instruction') } .procedure-list-element#en_instruction{ class: ('active' if @liste == 'en_instruction') }
En instruction En instruction
.badge.progress-bar-default .badge.progress-bar-default
= @user_dossiers.en_instruction.count = @user_dossiers.state_en_instruction.count
%a{ :href => "#{url_for users_dossiers_path(liste: 'termine')}", 'data-toggle' => :tooltip, title: 'Les dossiers cloturés qui peuvent être "Accepté", "Refusé" ou "Sans suite".' } %a{ :href => "#{url_for users_dossiers_path(liste: 'termine')}", 'data-toggle' => :tooltip, title: 'Les dossiers cloturés qui peuvent être "Accepté", "Refusé" ou "Sans suite".' }
.procedure-list-element#termine{ class: ('active' if @liste == 'termine') } .procedure-list-element#termine{ class: ('active' if @liste == 'termine') }
Terminé Terminé
.badge.progress-bar-success .badge.progress-bar-success
= @user_dossiers.termine.count = @user_dossiers.state_termine.count
%a{ :href => "#{url_for users_dossiers_path(liste: 'invite')}" } %a{ :href => "#{url_for users_dossiers_path(liste: 'invite')}" }
.procedure-list-element#invite{ class: ('active' if @liste == 'invite') } .procedure-list-element#invite{ class: ('active' if @liste == 'invite') }

View file

@ -1,6 +1,7 @@
.col-xs-7.main-info .col-xs-7.main-info
= @facade.dossier.procedure.libelle = @facade.dossier.procedure.libelle
.col-xs-3.options .col-xs-3.options
- if current_gestionnaire.assigned_on_procedure?(@facade.dossier.procedure_id)
.row .row
.col-xs-12 .col-xs-12
- if current_gestionnaire.follow?(@facade.dossier.id) - if current_gestionnaire.follow?(@facade.dossier.id)
@ -27,6 +28,8 @@
- else - else
= t('dynamics.dossiers.followers.empty') = t('dynamics.dossiers.followers.empty')
%h4= t('dynamics.dossiers.invites.title') %h4= t('dynamics.dossiers.invites.title')
%p
%b Attention, les invitations sur les dossiers vont disparaître en faveur des avis externes situés en bas de la page
%ul %ul
- unless @facade.invites.empty? - unless @facade.invites.empty?
- @facade.invites.each do |invite| - @facade.invites.each do |invite|

View file

@ -0,0 +1,2 @@
.col-xs-10.main-info
INVITATIONS

View file

@ -3,6 +3,7 @@
%meta{ "http-equiv" => "Content-Type", :content => "text/html; charset=UTF-8" } %meta{ "http-equiv" => "Content-Type", :content => "text/html; charset=UTF-8" }
%meta{ "http-equiv" => "X-UA-Compatible", :content => "IE=edge" } %meta{ "http-equiv" => "X-UA-Compatible", :content => "IE=edge" }
%meta{ :name => "turbolinks-cache-control", :content => "no-cache" } %meta{ :name => "turbolinks-cache-control", :content => "no-cache" }
%meta{ name: "viewport", content: "width=device-width, initial-scale=1" }
= csrf_meta_tags = csrf_meta_tags
= action_cable_meta_tag = action_cable_meta_tag
@ -13,7 +14,7 @@
= favicon_link_tag(image_url("favicons/32x32.png"), type: "image/png", sizes: "32x32") = favicon_link_tag(image_url("favicons/32x32.png"), type: "image/png", sizes: "32x32")
= favicon_link_tag(image_url("favicons/96x96.png"), type: "image/png", sizes: "96x96") = favicon_link_tag(image_url("favicons/96x96.png"), type: "image/png", sizes: "96x96")
= stylesheet_link_tag "new_application", :media => "all", "data-turbolinks-track" => true = stylesheet_link_tag "new_design/new_application", :media => "all", "data-turbolinks-track" => true
= stylesheet_link_tag "print", :media => "print", "data-turbolinks-track" => true = stylesheet_link_tag "print", :media => "print", "data-turbolinks-track" => true
%body %body
@ -36,6 +37,7 @@
= render partial: "layouts/mailjet_newsletter" = render partial: "layouts/mailjet_newsletter"
= javascript_include_tag "application", "data-turbolinks-track" => true = javascript_include_tag "application", "data-turbolinks-track" => true
= yield :charts_js
- if Rails.env == "test" - if Rails.env == "test"
%script{ :type => "text/javascript" } %script{ :type => "text/javascript" }
(typeof jQuery !== "undefined") && (jQuery.fx.off = true); (typeof jQuery !== "undefined") && (jQuery.fx.off = true);

View file

@ -1,4 +1,4 @@
Bonjour Bonjour,
%br %br
%br %br
Votre dossier nº --numero_dossier-- a été accepté le --date_de_decision--. Votre dossier nº --numero_dossier-- a été accepté le --date_de_decision--.
@ -7,11 +7,13 @@ Votre dossier nº --numero_dossier-- a été accepté le --date_de_decision--.
A tout moment, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier-- A tout moment, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier--
%br %br
%br %br
Bonne journée Bonne journée,
%br %br
%br %br
\--- L'équipe Téléprocédures Simplifiées
%br %br
Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme.
%br %br
\---
%br
%br
Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme.

View file

@ -1,4 +1,4 @@
Bonjour Bonjour,
%br %br
%br %br
Votre administration vous confirme la bonne réception de votre dossier nº --numero_dossier--. Votre administration vous confirme la bonne réception de votre dossier nº --numero_dossier--.
@ -7,11 +7,13 @@ Votre administration vous confirme la bonne réception de votre dossier nº --n
A tout moment, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier-- A tout moment, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier--
%br %br
%br %br
Bonne journée Bonne journée,
%br %br
%br %br
\--- L'équipe Téléprocédures Simplifiées
%br %br
Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme.
%br %br
\---
%br
%br
Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme.

View file

@ -1,14 +1,16 @@
Bonjour Bonjour,
%br %br
%br %br
Votre administration vous confirme la bonne réception de votre dossier nº --numero_dossier--. Celui-ci sera instruit dans le délai légal déclaré par votre interlocuteur. Votre administration vous confirme la bonne réception de votre dossier nº --numero_dossier--. Celui-ci sera instruit dans le délai légal déclaré par votre interlocuteur.
%br %br
%br %br
Bonne journée Bonne journée,
%br %br
%br %br
\--- L'équipe Téléprocédures Simplifiées
%br %br
Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme.
%br %br
\---
%br
%br
Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme.

View file

@ -1,4 +1,4 @@
Bonjour Bonjour,
%br %br
%br %br
Votre dossier nº --numero_dossier-- a été refusé le --date_de_decision--. Votre dossier nº --numero_dossier-- a été refusé le --date_de_decision--.
@ -7,11 +7,13 @@ Votre dossier nº --numero_dossier-- a été refusé le --date_de_decision--.
Pour en savoir plus sur le motif du refus, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier-- Pour en savoir plus sur le motif du refus, vous pouvez consulter le contenu de vos dossiers et les éventuels commentaires de l'administration à cette adresse : --lien_dossier--
%br %br
%br %br
Bonne journée Bonne journée,
%br %br
%br %br
\--- L'équipe Téléprocédures Simplifiées
%br %br
Merci de ne pas répondre à ce mail. Postez directement vos questions dans votre dossier sur la plateforme.
%br %br
\---
%br
%br
Merci de ne pas répondre à cet email. Postez directement vos questions dans votre dossier sur la plateforme.

Some files were not shown because too many files have changed in this diff Show more