commit
5f7aef663f
26 changed files with 361 additions and 26 deletions
|
@ -68,6 +68,7 @@ En local, un utilisateur de test est créé automatiquement, avec les identifian
|
|||
FindDubiousProceduresJob.set(cron: "0 0 * * *").perform_later
|
||||
Administrateurs::ActivateBeforeExpirationJob.set(cron: "0 8 * * *").perform_later
|
||||
WarnExpiringDossiersJob.set(cron: "0 0 1 * *").perform_later
|
||||
GestionnaireEmailNotificationJob.set(cron: "0 10 * * 1,2,3,4,5,6").perform_later
|
||||
|
||||
### Voir les emails envoyés en local
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ $default-padding: 2 * $default-spacer;
|
|||
|
||||
// layouts
|
||||
$two-columns-padding: 60px;
|
||||
$two-columns-breakpoint: $page-width + (2 * $two-columns-padding);
|
||||
$two-columns-breakpoint: 980px;
|
||||
|
||||
// z-order
|
||||
$alert-z-index: 100;
|
||||
|
|
|
@ -2,7 +2,6 @@ $default-space: 15px;
|
|||
$contact-padding: $default-space * 2;
|
||||
|
||||
#contact-form {
|
||||
width: 1040px;
|
||||
margin: 0 auto;
|
||||
padding-top: $contact-padding;
|
||||
|
||||
|
|
|
@ -3,3 +3,7 @@
|
|||
.mb-1 {
|
||||
margin-bottom: $default-spacer;
|
||||
}
|
||||
|
||||
.mr-1 {
|
||||
margin-right: $default-spacer;
|
||||
}
|
||||
|
|
|
@ -13,29 +13,32 @@
|
|||
@extend .container;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
@media (min-width: $two-columns-breakpoint) {
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
padding: $two-columns-padding 0 0;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
padding: ($default-padding * 2) 0 0 0;
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: $default-padding * 2;
|
||||
}
|
||||
|
||||
@media (min-width: $two-columns-breakpoint) {
|
||||
padding: $two-columns-padding;
|
||||
width: 50%;
|
||||
width: 45%;
|
||||
margin: 0;
|
||||
padding: $two-columns-padding 0 $two-columns-padding 0;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
&:last-of-type {
|
||||
padding-bottom: $two-columns-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,17 +26,19 @@ $procedure-context-breakpoint: $two-columns-breakpoint;
|
|||
|
||||
.procedure-title {
|
||||
font-size: 30px;
|
||||
margin: 20px 0 0;
|
||||
margin: 0 0 20px 0;
|
||||
text-align: center;
|
||||
|
||||
@media (min-width: $procedure-context-breakpoint) {
|
||||
margin: 50px 0 32px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.procedure-description {
|
||||
font-size: 16px;
|
||||
|
||||
p {
|
||||
p:not(:last-of-type) {
|
||||
margin-bottom: 2 * $default-spacer;
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ $procedure-context-breakpoint: $two-columns-breakpoint;
|
|||
img {
|
||||
max-height: 50px;
|
||||
max-width: 100%;
|
||||
margin: 0 10px;
|
||||
margin: 0 10px 20px 10px;
|
||||
|
||||
@media (min-width: $procedure-context-breakpoint) {
|
||||
max-height: 130px;
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
h1 {
|
||||
color: $black;
|
||||
font-size: 22px;
|
||||
margin-bottom: 2 * $default-padding;
|
||||
margin-bottom: 1 * $default-padding;
|
||||
}
|
||||
|
||||
a.notifications {
|
||||
display: inline-block;
|
||||
margin-bottom: 1 * $default-padding;
|
||||
}
|
||||
|
||||
.dossiers-table {
|
||||
|
|
|
@ -186,6 +186,18 @@ module NewGestionnaire
|
|||
end
|
||||
end
|
||||
|
||||
def email_notifications
|
||||
@procedure = procedure
|
||||
@assign_to = assign_to
|
||||
end
|
||||
|
||||
def update_email_notifications
|
||||
assign_to.update!(email_notifications_enabled: params[:assign_to][:email_notifications_enabled])
|
||||
|
||||
flash.notice = 'Vos notifications sont enregistrées.'
|
||||
redirect_to gestionnaire_procedure_path(procedure)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_field(table, column)
|
||||
|
@ -196,6 +208,10 @@ module NewGestionnaire
|
|||
field.values_at('table', 'column').join('/')
|
||||
end
|
||||
|
||||
def assign_to
|
||||
current_gestionnaire.assign_to.find_by(procedure: procedure)
|
||||
end
|
||||
|
||||
def statut
|
||||
@statut ||= (params[:statut].presence || 'a-suivre')
|
||||
end
|
||||
|
|
7
app/jobs/gestionnaire_email_notification_job.rb
Normal file
7
app/jobs/gestionnaire_email_notification_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class GestionnaireEmailNotificationJob < ApplicationJob
|
||||
queue_as :cron
|
||||
|
||||
def perform(*args)
|
||||
NotificationService.send_gestionnaire_email_notification
|
||||
end
|
||||
end
|
|
@ -43,4 +43,11 @@ class GestionnaireMailer < ApplicationMailer
|
|||
|
||||
mail(to: gestionnaire.email, subject: subject)
|
||||
end
|
||||
|
||||
def send_notifications(gestionnaire, data)
|
||||
@data = data
|
||||
subject = "Vous avez du nouveau sur vos démarches"
|
||||
|
||||
mail(to: gestionnaire.email, subject: subject)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@ class AssignTo < ApplicationRecord
|
|||
belongs_to :gestionnaire
|
||||
has_one :procedure_presentation, dependent: :destroy
|
||||
|
||||
scope :with_email_notifications, -> { where(email_notifications_enabled: true) }
|
||||
|
||||
def procedure_presentation_or_default_and_errors
|
||||
errors = reset_procedure_presentation_if_invalid
|
||||
[procedure_presentation || build_procedure_presentation, errors]
|
||||
|
|
|
@ -11,6 +11,10 @@ class Gestionnaire < ApplicationRecord
|
|||
|
||||
has_many :assign_to, dependent: :destroy
|
||||
has_many :procedures, through: :assign_to
|
||||
|
||||
has_many :assign_to_with_email_notifications, -> { with_email_notifications }, class_name: 'AssignTo'
|
||||
has_many :procedures_with_email_notifications, through: :assign_to_with_email_notifications, source: :procedure
|
||||
|
||||
has_many :dossiers, -> { state_not_brouillon }, through: :procedures
|
||||
has_many :follows
|
||||
has_many :followed_dossiers, through: :follows, source: :dossier
|
||||
|
@ -205,6 +209,25 @@ class Gestionnaire < ApplicationRecord
|
|||
trusted_device_token&.token_young?
|
||||
end
|
||||
|
||||
def email_notification_data
|
||||
procedures_with_email_notifications
|
||||
.reduce([]) do |acc, procedure|
|
||||
|
||||
h = {
|
||||
nb_en_construction: procedure.dossiers.en_construction.count,
|
||||
nb_notification: notifications_per_procedure(procedure).count
|
||||
}
|
||||
|
||||
if h[:nb_en_construction] > 0 || h[:nb_notification] > 0
|
||||
h[:procedure_id] = procedure.id
|
||||
h[:procedure_libelle] = procedure.libelle
|
||||
acc << h
|
||||
end
|
||||
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def annotations_hash(demande, annotations_privees, avis, messagerie)
|
||||
|
|
19
app/services/notification_service.rb
Normal file
19
app/services/notification_service.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class NotificationService
|
||||
class << self
|
||||
def send_gestionnaire_email_notification
|
||||
Gestionnaire
|
||||
.includes(assign_to: { procedure: :dossiers })
|
||||
.where(assign_tos: { email_notifications_enabled: true })
|
||||
.find_in_batches { |gestionnaires| send_batch_of_gestionnaires_email_notification(gestionnaires) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_batch_of_gestionnaires_email_notification(gestionnaires)
|
||||
gestionnaires
|
||||
.map { |gestionnaire| [gestionnaire, gestionnaire.email_notification_data] }
|
||||
.reject { |(_gestionnaire, data)| data.empty? }
|
||||
.each { |(gestionnaire, data)| GestionnaireMailer.send_notifications(gestionnaire, data).deliver_later }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -107,11 +107,6 @@
|
|||
qui doivent donc s'identifier en tant que personne physique.
|
||||
|
||||
%ul#individual-with-siret
|
||||
%li
|
||||
.checkbox
|
||||
%label
|
||||
= f.check_box :individual_with_siret
|
||||
Donner la possibilité de renseigner un SIRET au cours de la construction du dossier.
|
||||
%li
|
||||
.checkbox
|
||||
%label
|
||||
|
|
20
app/views/gestionnaire_mailer/send_notifications.html.haml
Normal file
20
app/views/gestionnaire_mailer/send_notifications.html.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
- content_for(:title, 'Du nouveau sur vos démarches')
|
||||
|
||||
%p
|
||||
Bonjour,
|
||||
|
||||
%p
|
||||
Vous avez du nouveau sur demarches-simplifiees.fr.
|
||||
|
||||
%ul
|
||||
- @data.each do |datum|
|
||||
%li
|
||||
= link_to(datum[:procedure_libelle], procedure_url(datum[:procedure_id]))
|
||||
- if datum[:nb_en_construction] > 0
|
||||
%br
|
||||
#{datum[:nb_en_construction]} #{'dossier'.pluralize(datum[:nb_en_construction])} en construction
|
||||
- if datum[:nb_notification] > 0
|
||||
%br
|
||||
#{datum[:nb_notification]} #{'notification'.pluralize(datum[:nb_notification])}
|
||||
|
||||
= render partial: "layouts/mailers/signature"
|
|
@ -0,0 +1,24 @@
|
|||
- content_for(:title, "Notifications pour #{@procedure.libelle}")
|
||||
|
||||
= render partial: 'new_administrateur/breadcrumbs',
|
||||
locals: { steps: [link_to(@procedure.libelle, procedure_path(@procedure)),
|
||||
'Notifications'] }
|
||||
|
||||
.container
|
||||
%h1 Notifications par email
|
||||
|
||||
= form_for @assign_to, url: update_email_notifications_gestionnaire_procedure_path(@procedure), html: { class: 'form' } do |form|
|
||||
= form.label :email_notification, "Recevoir une fois par jour, du lundi au samedi vers 10 h, un email de notification me signalant le dépôt de nouveaux dossiers ou des changements sur mes dossiers en cours"
|
||||
|
||||
.radios
|
||||
%label
|
||||
= form.radio_button :email_notifications_enabled, true
|
||||
Oui
|
||||
|
||||
%label
|
||||
= form.radio_button :email_notifications_enabled, false
|
||||
Non
|
||||
|
||||
.send-wrapper
|
||||
= link_to "Revenir à la procédure", gestionnaire_procedure_path(@procedure), class: 'button mr-1'
|
||||
= form.submit "Enregistrer", class: "button primary"
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
.procedure-header
|
||||
%h1= procedure_libelle @procedure
|
||||
= link_to 'configurez vos notifications', email_notifications_gestionnaire_procedure_path(@procedure), class: 'notifications'
|
||||
|
||||
|
||||
%ul.tabs
|
||||
= tab_item('à suivre',
|
||||
|
@ -64,7 +66,7 @@
|
|||
- if i > 0
|
||||
ou
|
||||
%span.filter
|
||||
= link_to remove_filter_gestionnaire_procedure_path(@procedure, statut: @statut, table: filter['table'], column: filter['column'], value: filter['value']) do
|
||||
= link_to remove_filter_gestionnaire_procedure_path(@procedure, { statut: @statut, table: filter['table'], column: filter['column'], value: filter['value'] }) do
|
||||
%img.close-icon{ src: image_url("close.svg") }
|
||||
= "#{filter['label'].truncate(50)} : #{filter['value']}"
|
||||
%table.table.dossiers-table.hoverable
|
||||
|
|
|
@ -313,8 +313,10 @@ Rails.application.routes.draw do
|
|||
patch 'update_displayed_fields'
|
||||
get 'update_sort/:table/:column' => 'procedures#update_sort', as: 'update_sort'
|
||||
post 'add_filter'
|
||||
get 'remove_filter/:statut/:table/:column/:value' => 'procedures#remove_filter', as: 'remove_filter'
|
||||
get 'remove_filter' => 'procedures#remove_filter', as: 'remove_filter'
|
||||
get 'download_dossiers'
|
||||
get 'email_notifications'
|
||||
patch 'update_email_notifications'
|
||||
|
||||
resources :dossiers, only: [:show], param: :dossier_id do
|
||||
member do
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddEmailNotificationsColumnToAssignTo < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :assign_tos, :email_notifications_enabled, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2019_03_11_140926) do
|
||||
ActiveRecord::Schema.define(version: 2019_03_18_154812) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -99,6 +99,7 @@ ActiveRecord::Schema.define(version: 2019_03_11_140926) do
|
|||
t.integer "procedure_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "email_notifications_enabled", default: false, null: false
|
||||
t.index ["gestionnaire_id", "procedure_id"], name: "index_assign_tos_on_gestionnaire_id_and_procedure_id", unique: true
|
||||
t.index ["gestionnaire_id"], name: "index_assign_tos_on_gestionnaire_id"
|
||||
t.index ["procedure_id"], name: "index_assign_tos_on_procedure_id"
|
||||
|
|
|
@ -336,4 +336,25 @@ describe NewGestionnaire::ProceduresController, type: :controller do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_email_notifications' do
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
let!(:procedure) { create(:procedure, gestionnaires: [gestionnaire]) }
|
||||
|
||||
context "when logged in" do
|
||||
before { sign_in(gestionnaire) }
|
||||
|
||||
it { expect(gestionnaire.procedures_with_email_notifications).to be_empty }
|
||||
|
||||
context 'when the gestionnaire update its preferences' do
|
||||
let(:assign_to) { gestionnaire.assign_to.find_by(procedure: procedure) }
|
||||
|
||||
before do
|
||||
patch :update_email_notifications, params: { procedure_id: procedure.id, assign_to: { id: assign_to.id, email_notifications_enabled: true } }
|
||||
end
|
||||
|
||||
it { expect(gestionnaire.procedures_with_email_notifications).to eq([procedure]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -115,7 +115,7 @@ feature "procedure filters" do
|
|||
end
|
||||
|
||||
def remove_filter(filter_value)
|
||||
find(:xpath, "(//span[contains(@class, 'filter')]/a[contains(@href, '#{URI.encode(filter_value)}')])[1]").click
|
||||
find(:xpath, "(//span[contains(@class, 'filter')]/a[contains(@href, '#{CGI.escape(filter_value)}')])[1]").click
|
||||
end
|
||||
|
||||
def add_filter(column_name, filter_value)
|
||||
|
|
|
@ -19,6 +19,24 @@ class GestionnaireMailerPreview < ActionMailer::Preview
|
|||
GestionnaireMailer.user_to_gestionnaire(gestionnaire.email)
|
||||
end
|
||||
|
||||
def send_notifications
|
||||
data = [
|
||||
{
|
||||
procedure_libelle: 'une superbe démarche',
|
||||
procedure_id: 213,
|
||||
nb_en_construction: 2,
|
||||
nb_notification: 2
|
||||
},
|
||||
{
|
||||
procedure_libelle: 'une démarche incroyable',
|
||||
procedure_id: 213,
|
||||
nb_en_construction: 1,
|
||||
nb_notification: 1
|
||||
}
|
||||
]
|
||||
GestionnaireMailer.send_notifications(gestionnaire, data)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def gestionnaire
|
||||
|
@ -30,6 +48,10 @@ class GestionnaireMailerPreview < ActionMailer::Preview
|
|||
end
|
||||
|
||||
def procedure
|
||||
Procedure.new(id: 15)
|
||||
Procedure.new(id: 15, libelle: 'libelle')
|
||||
end
|
||||
|
||||
def dossier
|
||||
Dossier.new(id: 15, procedure: procedure)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -413,6 +413,53 @@ describe Gestionnaire, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#email_notification_data' do
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
let(:procedure_to_assign) { create(:procedure) }
|
||||
|
||||
before do
|
||||
create(:assign_to, gestionnaire: gestionnaire, procedure: procedure_to_assign, email_notifications_enabled: true)
|
||||
end
|
||||
|
||||
context 'when a dossier in construction exists' do
|
||||
let!(:dossier) { create(:dossier, procedure: procedure_to_assign, state: Dossier.states.fetch(:en_construction)) }
|
||||
|
||||
it do
|
||||
expect(gestionnaire.email_notification_data).to eq([
|
||||
{
|
||||
nb_en_construction: 1,
|
||||
nb_notification: 0,
|
||||
procedure_id: procedure_to_assign.id,
|
||||
procedure_libelle: procedure_to_assign.libelle
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a notification exists' do
|
||||
before do
|
||||
allow(gestionnaire).to receive(:notifications_per_procedure)
|
||||
.with(procedure_to_assign)
|
||||
.and_return([1, 2, 3])
|
||||
end
|
||||
|
||||
it do
|
||||
expect(gestionnaire.email_notification_data).to eq([
|
||||
{
|
||||
nb_en_construction: 0,
|
||||
nb_notification: 3,
|
||||
procedure_id: procedure_to_assign.id,
|
||||
procedure_libelle: procedure_to_assign.libelle
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
context 'otherwise' do
|
||||
it { expect(gestionnaire.email_notification_data).to eq([]) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign(procedure_to_assign)
|
||||
|
|
63
spec/services/notification_service_spec.rb
Normal file
63
spec/services/notification_service_spec.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
describe NotificationService do
|
||||
describe '.send_gestionnaire_email_notification' do
|
||||
let(:procedure) { create(:procedure) }
|
||||
|
||||
before do
|
||||
allow(GestionnaireMailer).to receive(:send_notifications)
|
||||
.and_return(double(deliver_later: true))
|
||||
end
|
||||
|
||||
subject { NotificationService.send_gestionnaire_email_notification }
|
||||
|
||||
context 'when a gestionnaire does not enable its email notification' do
|
||||
let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
|
||||
before { create(:assign_to, gestionnaire: gestionnaire, procedure: procedure) }
|
||||
|
||||
it do
|
||||
subject
|
||||
expect(GestionnaireMailer).not_to have_received(:send_notifications)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a gestionnaire enables its email_notification on one procedure' do
|
||||
let(:gestionnaire_with_email_notifications) { create(:gestionnaire) }
|
||||
|
||||
before do
|
||||
create(:assign_to,
|
||||
gestionnaire: gestionnaire_with_email_notifications,
|
||||
procedure: procedure,
|
||||
email_notifications_enabled: true)
|
||||
end
|
||||
|
||||
context "when there is no activity on the gestionnaire's procedures" do
|
||||
it do
|
||||
subject
|
||||
expect(GestionnaireMailer).not_to have_received(:send_notifications)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a dossier en construction exists on this procedure' do
|
||||
let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||
|
||||
it do
|
||||
subject
|
||||
expect(GestionnaireMailer).to have_received(:send_notifications)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a notification on this procedure' do
|
||||
before do
|
||||
allow_any_instance_of(Gestionnaire).to receive(:notifications_per_procedure)
|
||||
.and_return([12])
|
||||
end
|
||||
|
||||
it do
|
||||
subject
|
||||
expect(GestionnaireMailer).to have_received(:send_notifications)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'gestionnaire_mailer/send_notifications.html.haml', type: :view do
|
||||
let(:gestionnaire) { create(:gestionnaire) }
|
||||
|
||||
before do
|
||||
assign(:data, data)
|
||||
|
||||
render
|
||||
end
|
||||
|
||||
context 'when there is one dossier in contruction' do
|
||||
let(:data) do
|
||||
[
|
||||
{
|
||||
procedure_libelle: 'une superbe démarche',
|
||||
procedure_id: 213,
|
||||
nb_en_construction: 1,
|
||||
nb_notification: 0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it { expect(rendered).to have_link('une superbe démarche', href: procedure_url(213)) }
|
||||
it { expect(rendered).to have_text('une superbe démarche') }
|
||||
it { expect(rendered).to have_text('1 dossier en construction') }
|
||||
it { expect(rendered).not_to have_text('notification') }
|
||||
end
|
||||
|
||||
context 'when there is one notification' do
|
||||
let(:data) do
|
||||
[
|
||||
{
|
||||
procedure_libelle: 'une superbe démarche',
|
||||
procedure_id: 213,
|
||||
nb_en_construction: 0,
|
||||
nb_notification: 1
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
it { expect(rendered).not_to have_text('en construction') }
|
||||
it { expect(rendered).to have_text('1 notification') }
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue