From 1644fd905915acd9e4b4641b2b388c2f06514407 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Mon, 18 Mar 2019 14:43:05 +0100 Subject: [PATCH 1/4] Notification: add gestionnaire.procedures_with_email_notifications scope --- app/models/assign_to.rb | 2 ++ app/models/gestionnaire.rb | 4 ++++ ...0318154812_add_email_notifications_column_to_assign_to.rb | 5 +++++ db/schema.rb | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20190318154812_add_email_notifications_column_to_assign_to.rb diff --git a/app/models/assign_to.rb b/app/models/assign_to.rb index 1e5621fe3..43da53ea3 100644 --- a/app/models/assign_to.rb +++ b/app/models/assign_to.rb @@ -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] diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index 82516d4de..8d00dda66 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -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 diff --git a/db/migrate/20190318154812_add_email_notifications_column_to_assign_to.rb b/db/migrate/20190318154812_add_email_notifications_column_to_assign_to.rb new file mode 100644 index 000000000..9443922fc --- /dev/null +++ b/db/migrate/20190318154812_add_email_notifications_column_to_assign_to.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 9cb1ebfe5..6b3b6e255 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" From ed6828c66c98e2578dcee93e8a7b82d39c3ccb68 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 13 Mar 2019 17:37:07 +0100 Subject: [PATCH 2/4] Notification: UI --- .../stylesheets/new_design/helpers.scss | 4 ++++ .../new_design/procedure_show.scss | 7 +++++- .../new_gestionnaire/procedures_controller.rb | 16 +++++++++++++ .../procedures/email_notifications.html.haml | 24 +++++++++++++++++++ .../procedures/show.html.haml | 2 ++ config/routes.rb | 2 ++ .../procedures_controller_spec.rb | 21 ++++++++++++++++ 7 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 app/views/new_gestionnaire/procedures/email_notifications.html.haml diff --git a/app/assets/stylesheets/new_design/helpers.scss b/app/assets/stylesheets/new_design/helpers.scss index 976e4c0d1..189bd8252 100644 --- a/app/assets/stylesheets/new_design/helpers.scss +++ b/app/assets/stylesheets/new_design/helpers.scss @@ -3,3 +3,7 @@ .mb-1 { margin-bottom: $default-spacer; } + +.mr-1 { + margin-right: $default-spacer; +} diff --git a/app/assets/stylesheets/new_design/procedure_show.scss b/app/assets/stylesheets/new_design/procedure_show.scss index 15a89047e..272cd985d 100644 --- a/app/assets/stylesheets/new_design/procedure_show.scss +++ b/app/assets/stylesheets/new_design/procedure_show.scss @@ -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 { diff --git a/app/controllers/new_gestionnaire/procedures_controller.rb b/app/controllers/new_gestionnaire/procedures_controller.rb index 428543ef6..46de9ce85 100644 --- a/app/controllers/new_gestionnaire/procedures_controller.rb +++ b/app/controllers/new_gestionnaire/procedures_controller.rb @@ -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 diff --git a/app/views/new_gestionnaire/procedures/email_notifications.html.haml b/app/views/new_gestionnaire/procedures/email_notifications.html.haml new file mode 100644 index 000000000..751be27bf --- /dev/null +++ b/app/views/new_gestionnaire/procedures/email_notifications.html.haml @@ -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" diff --git a/app/views/new_gestionnaire/procedures/show.html.haml b/app/views/new_gestionnaire/procedures/show.html.haml index 003929fd6..c9a897c11 100644 --- a/app/views/new_gestionnaire/procedures/show.html.haml +++ b/app/views/new_gestionnaire/procedures/show.html.haml @@ -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', diff --git a/config/routes.rb b/config/routes.rb index ddb0e18d5..764d04eae 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -315,6 +315,8 @@ Rails.application.routes.draw do post 'add_filter' get 'remove_filter/:statut/:table/:column/:value' => '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 diff --git a/spec/controllers/new_gestionnaire/procedures_controller_spec.rb b/spec/controllers/new_gestionnaire/procedures_controller_spec.rb index 772a9bcff..91d6f8886 100644 --- a/spec/controllers/new_gestionnaire/procedures_controller_spec.rb +++ b/spec/controllers/new_gestionnaire/procedures_controller_spec.rb @@ -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 From 6607de48278a6653d23aa77abe960d35418fb562 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 13 Mar 2019 17:59:33 +0100 Subject: [PATCH 3/4] Notification: add service to send notifications --- app/mailers/gestionnaire_mailer.rb | 7 +++ app/models/gestionnaire.rb | 19 ++++++ app/services/notification_service.rb | 19 ++++++ .../send_notifications.html.haml | 20 ++++++ .../previews/gestionnaire_mailer_preview.rb | 24 ++++++- spec/models/gestionnaire_spec.rb | 47 ++++++++++++++ spec/services/notification_service_spec.rb | 63 +++++++++++++++++++ .../send_notifications.html.haml_spec.rb | 45 +++++++++++++ 8 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 app/services/notification_service.rb create mode 100644 app/views/gestionnaire_mailer/send_notifications.html.haml create mode 100644 spec/services/notification_service_spec.rb create mode 100644 spec/views/gestionnaire_mailer/send_notifications.html.haml_spec.rb diff --git a/app/mailers/gestionnaire_mailer.rb b/app/mailers/gestionnaire_mailer.rb index 09f7af238..472f09dae 100644 --- a/app/mailers/gestionnaire_mailer.rb +++ b/app/mailers/gestionnaire_mailer.rb @@ -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 diff --git a/app/models/gestionnaire.rb b/app/models/gestionnaire.rb index 8d00dda66..9bfa8e211 100644 --- a/app/models/gestionnaire.rb +++ b/app/models/gestionnaire.rb @@ -209,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) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb new file mode 100644 index 000000000..da8a1bb44 --- /dev/null +++ b/app/services/notification_service.rb @@ -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 diff --git a/app/views/gestionnaire_mailer/send_notifications.html.haml b/app/views/gestionnaire_mailer/send_notifications.html.haml new file mode 100644 index 000000000..50f76c55f --- /dev/null +++ b/app/views/gestionnaire_mailer/send_notifications.html.haml @@ -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" diff --git a/spec/mailers/previews/gestionnaire_mailer_preview.rb b/spec/mailers/previews/gestionnaire_mailer_preview.rb index ddafd66b4..eda8b78aa 100644 --- a/spec/mailers/previews/gestionnaire_mailer_preview.rb +++ b/spec/mailers/previews/gestionnaire_mailer_preview.rb @@ -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 diff --git a/spec/models/gestionnaire_spec.rb b/spec/models/gestionnaire_spec.rb index 6b7a6febb..ee3cfbafa 100644 --- a/spec/models/gestionnaire_spec.rb +++ b/spec/models/gestionnaire_spec.rb @@ -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) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb new file mode 100644 index 000000000..7c9a9e116 --- /dev/null +++ b/spec/services/notification_service_spec.rb @@ -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 diff --git a/spec/views/gestionnaire_mailer/send_notifications.html.haml_spec.rb b/spec/views/gestionnaire_mailer/send_notifications.html.haml_spec.rb new file mode 100644 index 000000000..df3fbbecc --- /dev/null +++ b/spec/views/gestionnaire_mailer/send_notifications.html.haml_spec.rb @@ -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 From 9b4d9dc26c853f02a7054ba13e85ddc128e7e438 Mon Sep 17 00:00:00 2001 From: simon lehericey Date: Wed, 13 Mar 2019 17:27:36 +0100 Subject: [PATCH 4/4] [fix #1449] Notification: add job --- README.md | 1 + app/jobs/gestionnaire_email_notification_job.rb | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 app/jobs/gestionnaire_email_notification_job.rb diff --git a/README.md b/README.md index 50665adff..500dc1c49 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/jobs/gestionnaire_email_notification_job.rb b/app/jobs/gestionnaire_email_notification_job.rb new file mode 100644 index 000000000..1920457df --- /dev/null +++ b/app/jobs/gestionnaire_email_notification_job.rb @@ -0,0 +1,7 @@ +class GestionnaireEmailNotificationJob < ApplicationJob + queue_as :cron + + def perform(*args) + NotificationService.send_gestionnaire_email_notification + end +end