diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 298cc862a..425f78132 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -232,6 +232,7 @@ module Instructeurs def update_email_notifications assign_to.update!(assign_to_params) + assign_to.update!(daily_email_notifications_enabled: params[:assign_to][:email_notifications_enabled]) flash.notice = 'Vos notifications sont enregistrées.' redirect_to instructeur_procedure_path(procedure) end diff --git a/app/dashboards/administrateur_dashboard.rb b/app/dashboards/administrateur_dashboard.rb index 48fd715dd..ba6f91072 100644 --- a/app/dashboards/administrateur_dashboard.rb +++ b/app/dashboards/administrateur_dashboard.rb @@ -16,7 +16,7 @@ class AdministrateurDashboard < Administrate::BaseDashboard registration_state: Field::String.with_options(searchable: false), current_sign_in_at: Field::DateTime, features: FeaturesField, - email: Field::Email + email: Field::Email.with_options(searchable: false) }.freeze # COLLECTION_ATTRIBUTES diff --git a/app/jobs/cleanup_stale_exports_job.rb b/app/jobs/cleanup_stale_exports_job.rb deleted file mode 100644 index 9344d4f14..000000000 --- a/app/jobs/cleanup_stale_exports_job.rb +++ /dev/null @@ -1,26 +0,0 @@ -class CleanupStaleExportsJob < ApplicationJob - queue_as :cron - - def perform(*args) - attachments = ActiveStorage::Attachment.where( - "name in ('csv_export_file', 'ods_export_file', 'xlsx_export_file') and created_at < ?", - Procedure::MAX_DUREE_CONSERVATION_EXPORT.ago - ) - attachments.each do |attachment| - procedure = Procedure.find(attachment.record_id) - # export can't be queued if it's already attached - #  so we clean the flag up just in case it was not removed during - # the asynchronous generation - case attachment.name - when 'csv_export_file' - procedure.update(csv_export_queued: false) - when 'ods_export_file' - procedure.update(ods_export_queued: false) - when 'xlsx_export_file' - procedure.update(xlsx_export_queued: false) - end - # and we remove the stale attachment - attachment.purge_later - end - end -end diff --git a/app/jobs/export_procedure_job.rb b/app/jobs/export_procedure_job.rb deleted file mode 100644 index 7e935d8ac..000000000 --- a/app/jobs/export_procedure_job.rb +++ /dev/null @@ -1,6 +0,0 @@ -class ExportProcedureJob < ApplicationJob - def perform(procedure, instructeur, export_format) - procedure.prepare_export_download(export_format) - InstructeurMailer.notify_procedure_export_available(instructeur, procedure, export_format).deliver_later - end -end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3504f1a7a..b444548f4 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -3,6 +3,11 @@ class ApplicationMailer < ActionMailer::Base default from: "demarches-simplifiees.fr <#{CONTACT_EMAIL}>" layout 'mailer' + # Don’t retry to send a message if the server rejects the recipient address + rescue_from Net::SMTPSyntaxError do |_error| + message.perform_deliveries = false + end + # Attach the procedure logo to the email (if any). # Returns the attachment url. def attach_logo(procedure) diff --git a/app/mailers/devise_user_mailer.rb b/app/mailers/devise_user_mailer.rb index a19c04768..de07cc1b7 100644 --- a/app/mailers/devise_user_mailer.rb +++ b/app/mailers/devise_user_mailer.rb @@ -4,6 +4,11 @@ class DeviseUserMailer < Devise::Mailer include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url` layout 'mailers/layout' + # Don’t retry to send a message if the server rejects the recipient address + rescue_from Net::SMTPSyntaxError do |_error| + message.perform_deliveries = false + end + def template_paths ['devise_mailer'] end diff --git a/app/mailers/dossier_mailer.rb b/app/mailers/dossier_mailer.rb index 8813730fe..ce7d05964 100644 --- a/app/mailers/dossier_mailer.rb +++ b/app/mailers/dossier_mailer.rb @@ -43,21 +43,6 @@ class DossierMailer < ApplicationMailer mail(to: to_email, subject: subject) end - def notify_unhide_to_user(dossier) - @dossier = dossier - subject = "Votre dossier nº #{@dossier.id} n'a pas pu être supprimé" - - mail(to: dossier.user.email, subject: subject) - end - - def notify_undelete_to_user(dossier) - @dossier = dossier - @dossier_kind = dossier.brouillon? ? 'brouillon' : 'dossier' - @subject = "Votre #{@dossier_kind} nº #{@dossier.id} est à nouveau accessible" - - mail(to: dossier.user.email, subject: @subject) - end - def notify_revert_to_instruction(dossier) @dossier = dossier @service = dossier.procedure.service @@ -83,4 +68,26 @@ class DossierMailer < ApplicationMailer mail(to: user.email, subject: @subject) end + + def notify_automatic_deletion_to_user(user, dossier_hashes) + @subject = default_i18n_subject(count: dossier_hashes.count) + @dossier_hashes = dossier_hashes + + mail(to: user.email, subject: @subject) + end + + def notify_automatic_deletion_to_administration(user, dossier_hashes) + @subject = default_i18n_subject(count: dossier_hashes.count) + @dossier_hashes = dossier_hashes + + mail(to: user.email, subject: @subject) + end + + def notify_en_construction_near_deletion(user, dossiers, for_user) + @subject = default_i18n_subject(count: dossiers.count) + @dossiers = dossiers + @for_user = for_user + + mail(to: user.email, subject: @subject) + end end diff --git a/app/mailers/instructeur_mailer.rb b/app/mailers/instructeur_mailer.rb index d132c0005..aa28622d2 100644 --- a/app/mailers/instructeur_mailer.rb +++ b/app/mailers/instructeur_mailer.rb @@ -42,12 +42,4 @@ class InstructeurMailer < ApplicationMailer mail(to: instructeur.email, subject: subject) end - - def notify_procedure_export_available(instructeur, procedure, export_format) - @procedure = procedure - @export_format = export_format - subject = "Votre export de la démarche nº #{procedure.id} est disponible" - - mail(to: instructeur.email, subject: subject) - end end diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index 908b95be3..3d3e878ef 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -1,5 +1,4 @@ class Administrateur < ApplicationRecord - self.ignored_columns = ['features', 'encrypted_password', 'reset_password_token', 'reset_password_sent_at', 'remember_created_at', 'sign_in_count', 'current_sign_in_at', 'last_sign_in_at', 'current_sign_in_ip', 'last_sign_in_ip', 'failed_attempts', 'unlock_token', 'locked_at'] include ActiveRecord::SecureToken has_and_belongs_to_many :instructeurs diff --git a/app/models/assign_to.rb b/app/models/assign_to.rb index 96bb9713b..e3264ea40 100644 --- a/app/models/assign_to.rb +++ b/app/models/assign_to.rb @@ -3,9 +3,14 @@ class AssignTo < ApplicationRecord belongs_to :groupe_instructeur has_one :procedure_presentation, dependent: :destroy has_one :procedure, through: :groupe_instructeur + before_save :save_to_new_daily_email_column scope :with_email_notifications, -> { where(email_notifications_enabled: true) } + def save_to_new_daily_email_column + self.daily_email_notifications_enabled = email_notifications_enabled + end + def procedure_presentation_or_default_and_errors errors = reset_procedure_presentation_if_invalid [procedure_presentation || build_procedure_presentation, errors] diff --git a/app/models/concerns/tags_substitution_concern.rb b/app/models/concerns/tags_substitution_concern.rb index 1410f4479..cb2464c93 100644 --- a/app/models/concerns/tags_substitution_concern.rb +++ b/app/models/concerns/tags_substitution_concern.rb @@ -130,6 +130,15 @@ module TagsSubstitutionConcern } ] + ROUTAGE_TAGS = [ + { + libelle: 'groupe instructeur', + description: 'Le groupe instructeur en charge du dossier', + lambda: -> (d) { d.groupe_instructeur.label }, + available_for_states: Dossier::SOUMIS + } + ] + def tags if procedure.for_individual? identity_tags = INDIVIDUAL_TAGS @@ -137,7 +146,12 @@ module TagsSubstitutionConcern identity_tags = ENTREPRISE_TAGS end - filter_tags(identity_tags + dossier_tags + champ_public_tags + champ_private_tags) + routage_tags = [] + if procedure.routee? + routage_tags = ROUTAGE_TAGS + end + + filter_tags(identity_tags + dossier_tags + champ_public_tags + champ_private_tags + routage_tags) end private @@ -207,6 +221,7 @@ module TagsSubstitutionConcern [champ_public_tags, dossier.champs], [champ_private_tags, dossier.champs_private], [dossier_tags, dossier], + [ROUTAGE_TAGS, dossier], [INDIVIDUAL_TAGS, dossier.individual], [ENTREPRISE_TAGS, dossier.etablissement&.entreprise] ] diff --git a/app/models/instructeur.rb b/app/models/instructeur.rb index cb5bbbca1..83909d7ec 100644 --- a/app/models/instructeur.rb +++ b/app/models/instructeur.rb @@ -1,6 +1,4 @@ class Instructeur < ApplicationRecord - self.ignored_columns = ['features', 'encrypted_password', 'reset_password_token', 'reset_password_sent_at', 'remember_created_at', 'sign_in_count', 'current_sign_in_at', 'last_sign_in_at', 'current_sign_in_ip', 'last_sign_in_ip', 'failed_attempts', 'unlock_token', 'locked_at'] - has_and_belongs_to_many :administrateurs has_many :assign_to, dependent: :destroy diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 081de7cc9..6171d5791 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -1,7 +1,7 @@ require Rails.root.join('lib', 'percentile') class Procedure < ApplicationRecord - self.ignored_columns = ['archived_at'] + self.ignored_columns = ['archived_at', 'csv_export_queued', 'xlsx_export_queued', 'ods_export_queued'] include ProcedureStatsConcern @@ -41,10 +41,6 @@ class Procedure < ApplicationRecord has_one_attached :notice has_one_attached :deliberation - has_one_attached :csv_export_file - has_one_attached :xlsx_export_file - has_one_attached :ods_export_file - accepts_nested_attributes_for :types_de_champ, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true accepts_nested_attributes_for :types_de_champ_private, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true @@ -133,100 +129,11 @@ class Procedure < ApplicationRecord end end - def csv_export_stale? - !csv_export_file.attached? || csv_export_file.created_at < MAX_DUREE_CONSERVATION_EXPORT.ago - end - - def xlsx_export_stale? - !xlsx_export_file.attached? || xlsx_export_file.created_at < MAX_DUREE_CONSERVATION_EXPORT.ago - end - - def ods_export_stale? - !ods_export_file.attached? || ods_export_file.created_at < MAX_DUREE_CONSERVATION_EXPORT.ago - end - - def export_queued?(format) - case format.to_sym - when :csv - return csv_export_queued? - when :xlsx - return xlsx_export_queued? - when :ods - return ods_export_queued? - end - false - end - - def should_generate_export?(format) - case format.to_sym - when :csv - return csv_export_stale? && !csv_export_queued? - when :xlsx - return xlsx_export_stale? && !xlsx_export_queued? - when :ods - return ods_export_stale? && !ods_export_queued? - end - false - end - - def export_file(export_format) - case export_format.to_sym - when :csv - csv_export_file - when :xlsx - xlsx_export_file - when :ods - ods_export_file - end - end - - def queue_export(instructeur, export_format) - case export_format.to_sym - when :csv - update(csv_export_queued: true) - when :xlsx - update(xlsx_export_queued: true) - when :ods - update(ods_export_queued: true) - end - ExportProcedureJob.perform_later(self, instructeur, export_format) - end - - def prepare_export_download(format) - service = ProcedureExportService.new(self, self.dossiers) - filename = export_filename(format) - - case format.to_sym - when :csv - csv_export_file.attach( - io: StringIO.new(service.to_csv), - filename: filename, - content_type: 'text/csv' - ) - update(csv_export_queued: false) - when :xlsx - xlsx_export_file.attach( - io: StringIO.new(service.to_xlsx), - filename: filename, - content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - ) - update(xlsx_export_queued: false) - when :ods - ods_export_file.attach( - io: StringIO.new(service.to_ods), - filename: filename, - content_type: 'application/vnd.oasis.opendocument.spreadsheet' - ) - update(ods_export_queued: false) - end - end - def reset! if locked? raise "Can not reset a locked procedure." else groupe_instructeurs.each { |gi| gi.dossiers.destroy_all } - purge_export_files end end @@ -268,14 +175,6 @@ class Procedure < ApplicationRecord procedure.blank? || administrateur.owns?(procedure) end - def purge_export_files - xlsx_export_file.purge_later - ods_export_file.purge_later - csv_export_file.purge_later - - update(csv_export_queued: false, xlsx_export_queued: false, ods_export_queued: false) - end - def locked? publiee? || close? || depubliee? end @@ -594,7 +493,6 @@ class Procedure < ApplicationRecord def hide! discard! dossiers.discard_all - purge_export_files end private @@ -626,7 +524,6 @@ class Procedure < ApplicationRecord def after_close now = Time.zone.now update!(closed_at: now) - purge_export_files end def after_unpublish diff --git a/app/views/dossier_mailer/notify_automatic_deletion_to_administration.html.haml b/app/views/dossier_mailer/notify_automatic_deletion_to_administration.html.haml new file mode 100644 index 000000000..e466b459b --- /dev/null +++ b/app/views/dossier_mailer/notify_automatic_deletion_to_administration.html.haml @@ -0,0 +1,12 @@ +- content_for(:title, "#{@subject}") + +%p + Bonjour, + +%p= t('.automatic_dossier_deletion', count: @dossier_hashes.count) + +%ul + - @dossier_hashes.each do |d| + %li= "n° #{d[:id]} (#{d[:procedure_libelle]})" + += render partial: "layouts/mailers/signature" diff --git a/app/views/dossier_mailer/notify_automatic_deletion_to_user.html.haml b/app/views/dossier_mailer/notify_automatic_deletion_to_user.html.haml new file mode 100644 index 000000000..337151682 --- /dev/null +++ b/app/views/dossier_mailer/notify_automatic_deletion_to_user.html.haml @@ -0,0 +1,14 @@ +- content_for(:title, "#{@subject}") + +%p + Bonjour, + +%p= t('.automatic_dossier_deletion', count: @dossier_hashes.count) + +%ul + - @dossier_hashes.each do |d| + %li= link_to("n° #{d[:id]} (#{d[:procedure_libelle]})", dossier_url(d)) + +%p= t('.dossier_will_not_be_processed', count: @dossier_hashes.count) + += render partial: "layouts/mailers/signature" diff --git a/app/views/dossier_mailer/notify_en_construction_near_deletion.html.haml b/app/views/dossier_mailer/notify_en_construction_near_deletion.html.haml new file mode 100644 index 000000000..896138408 --- /dev/null +++ b/app/views/dossier_mailer/notify_en_construction_near_deletion.html.haml @@ -0,0 +1,27 @@ +- content_for(:title, "#{@subject}") + +%p + Bonjour, + +%p + - if !@for_user + Afin de limiter la conservation de vos données personnelles, + + = t('.automatic_dossier_deletion', count: @dossiers.count) + + %ul + - @dossiers.each do |d| + - if !@for_user + %li + #{link_to("n° #{d.id} (#{d.procedure.libelle})", dossier_url(d))}. Retrouvez le dossier au format #{link_to("PDF", instructeur_dossier_url(d.procedure, d, format: :pdf))} + - else + %li + #{link_to("n° #{d.id} (#{d.procedure.libelle})", dossier_url(d))}. Retrouvez le dossier au format #{link_to("PDF", dossier_url(d, format: :pdf))} + +%p + - if @for_user + = sanitize(t('.send_user_draft', count: @dossiers.count)) + - else + = sanitize(t('.send_other_draft', count: @dossiers.count)) + += render partial: "layouts/mailers/signature" diff --git a/app/views/dossier_mailer/notify_undelete_to_user.html.haml b/app/views/dossier_mailer/notify_undelete_to_user.html.haml deleted file mode 100644 index a3b2c58e8..000000000 --- a/app/views/dossier_mailer/notify_undelete_to_user.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- content_for(:title, @subject) - -%h1 Bonjour, - -%p - En raison d’un incident, votre - = link_to("#{@dossier_kind} n° #{@dossier.id}", dossier_url(@dossier)) - sur la procédure « #{@dossier.procedure.libelle} » a été inaccessible pendant quelques jours. - - L’accès est à présent à nouveau possible. Nous vous présentons nos excuses pour toute gène occasionnée. - -= render partial: "layouts/mailers/signature" diff --git a/app/views/dossier_mailer/notify_unhide_to_user.html.haml b/app/views/dossier_mailer/notify_unhide_to_user.html.haml deleted file mode 100644 index b3ea4eef5..000000000 --- a/app/views/dossier_mailer/notify_unhide_to_user.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -- content_for(:title, "Votre dossier n° #{@dossier.id} n'a pas pu être supprimé") - -%h1 Bonjour, - -%p - L'instruction de votre dossier n° #{@dossier.id} (« #{@dossier.procedure.libelle} ») - ayant commencé, il n'a pas pu être supprimé. - Le dossier a été rétabli dans votre tableau de bord. - -= render partial: "layouts/mailers/signature" diff --git a/app/views/instructeur_mailer/notify_procedure_export_available.html.haml b/app/views/instructeur_mailer/notify_procedure_export_available.html.haml deleted file mode 100644 index 678dfd08b..000000000 --- a/app/views/instructeur_mailer/notify_procedure_export_available.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -%p - Bonjour, - -%p - Votre export des dossiers de la démarche nº #{@procedure.id} « #{@procedure.libelle} » au format #{@export_format} est prêt. - -%p - Cliquez sur le lien ci-dessous pour le télécharger : - = link_to('Télécharger l\'export des dossiers', download_export_instructeur_procedure_url(@procedure, :export_format => @export_format)) - -= render partial: "layouts/mailers/signature" diff --git a/app/views/instructeurs/procedures/_list.html.haml b/app/views/instructeurs/procedures/_list.html.haml index e8e6fb21f..0ed5f470c 100644 --- a/app/views/instructeurs/procedures/_list.html.haml +++ b/app/views/instructeurs/procedures/_list.html.haml @@ -15,7 +15,7 @@ = link_to(instructeur_procedure_path(p, statut: 'a-suivre')) do - a_suivre_count = dossiers_a_suivre_count_per_procedure[p.id] || 0 .stats-number - = a_suivre_count + = number_with_html_delimiter(a_suivre_count) .stats-legend à suivre %li @@ -25,7 +25,7 @@ %span.notifications{ 'aria-label': "notifications" } - followed_count = followed_dossiers_count_per_procedure[p.id] || 0 .stats-number - = followed_count + = number_with_html_delimiter(followed_count) .stats-legend = t('pluralize.followed', count: followed_count) %li @@ -35,7 +35,7 @@ %span.notifications{ 'aria-label': "notifications" } - termines_count = dossiers_termines_count_per_procedure[p.id] || 0 .stats-number - = termines_count + = number_with_html_delimiter(termines_count) .stats-legend = t('pluralize.processed', count: termines_count) %li @@ -43,7 +43,7 @@ = link_to(instructeur_procedure_path(p, statut: 'tous')) do - dossier_count = dossiers_count_per_procedure[p.id] || 0 .stats-number - = dossier_count + = number_with_html_delimiter(dossier_count) .stats-legend = t('pluralize.case', count: dossier_count) %li @@ -51,7 +51,7 @@ = link_to(instructeur_procedure_path(p, statut: 'archives')) do - archived_count = dossiers_archived_count_per_procedure[p.id] || 0 .stats-number - = archived_count + = number_with_html_delimiter(archived_count) .stats-legend = t('pluralize.archived', count: archived_count) diff --git a/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 828079590..a93ef3493 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -24,29 +24,29 @@ = tab_item('à suivre', instructeur_procedure_path(@procedure, statut: 'a-suivre'), active: @statut == 'a-suivre', - badge: @a_suivre_dossiers.count) + badge: number_with_html_delimiter(@a_suivre_dossiers.count)) = tab_item(t('pluralize.followed', count: @followed_dossiers.count), instructeur_procedure_path(@procedure, statut: 'suivis'), active: @statut == 'suivis', - badge: @followed_dossiers.count, + badge: number_with_html_delimiter(@followed_dossiers.count), notification: current_instructeur.notifications_for_procedure(@procedure, :en_cours).exists?) = tab_item(t('pluralize.processed', count: @termines_dossiers.count), instructeur_procedure_path(@procedure, statut: 'traites'), active: @statut == 'traites', - badge: @termines_dossiers.count, + badge: number_with_html_delimiter(@termines_dossiers.count), notification: current_instructeur.notifications_for_procedure(@procedure, :termine).exists?) = tab_item('tous les dossiers', instructeur_procedure_path(@procedure, statut: 'tous'), active: @statut == 'tous', - badge: @all_state_dossiers.count) + badge: number_with_html_delimiter(@all_state_dossiers.count)) = tab_item(t('pluralize.archived', count: @archived_dossiers.count), instructeur_procedure_path(@procedure, statut: 'archives'), active: @statut == 'archives', - badge: @archived_dossiers.count) + badge: number_with_html_delimiter(@archived_dossiers.count)) .procedure-actions = render partial: "download_dossiers", diff --git a/app/views/manager/administrateurs/_form.html.haml b/app/views/manager/administrateurs/_form.html.haml new file mode 100644 index 000000000..fb64c934a --- /dev/null +++ b/app/views/manager/administrateurs/_form.html.haml @@ -0,0 +1,13 @@ += form_for([namespace, Administrateur.new(user: User.new)], html: { class: "form" }) do |f| + - if page.resource.errors.any? + #error_explanation + %h2 + = t("administrate.form.errors", pluralized_errors: pluralize(page.resource.errors.count, t("administrate.form.error")), resource_name: display_resource_name(page.resource_name)) + %ul + - page.resource.errors.full_messages.each do |message| + %li.flash-error= message + - page.attributes.each do |attribute| + %div{ :class => "field-unit field-unit--#{attribute.html_class}" } + = render_field attribute, f: f + .form-actions + = f.submit diff --git a/config/locales/views/dossier_mailer/notify_automatic_deletion_to_administration/fr.yml b/config/locales/views/dossier_mailer/notify_automatic_deletion_to_administration/fr.yml new file mode 100644 index 000000000..c0f77813f --- /dev/null +++ b/config/locales/views/dossier_mailer/notify_automatic_deletion_to_administration/fr.yml @@ -0,0 +1,9 @@ +fr: + dossier_mailer: + notify_automatic_deletion_to_administration: + subject: + one: "Un dossier a été supprimé automatiquement" + other: "Des dossiers ont été supprimés automatiquement" + automatic_dossier_deletion: + one: "Le délai maximum de conservation du dossier suivant a été atteint, il a donc été supprimé :" + other: "Le délai maximum de conservation des dossiers suivants a été atteint, ils ont donc été supprimés :" diff --git a/config/locales/views/dossier_mailer/notify_automatic_deletion_to_user/fr.yml b/config/locales/views/dossier_mailer/notify_automatic_deletion_to_user/fr.yml new file mode 100644 index 000000000..7f5aef939 --- /dev/null +++ b/config/locales/views/dossier_mailer/notify_automatic_deletion_to_user/fr.yml @@ -0,0 +1,12 @@ +fr: + dossier_mailer: + notify_automatic_deletion_to_user: + subject: + one: "Un dossier a été supprimé automatiquement" + other: "Des dossiers ont été supprimés automatiquement" + automatic_dossier_deletion: + one: "Le délai maximum de conservation du dossier suivant a été atteint, il a donc été supprimé :" + other: "Le délai maximum de conservation des dossiers suivants a été atteint, ils ont donc été supprimés :" + dossier_will_not_be_processed: + one: "Le dossier ne sera pas traité, nous nous excusons de la gène occasionnée." + other: "Les dossiers ne seront pas traités, nous nous excusons de la gène occasionnée." diff --git a/config/locales/views/dossier_mailer/notify_en_construction_near_deletion/fr.yml b/config/locales/views/dossier_mailer/notify_en_construction_near_deletion/fr.yml new file mode 100644 index 000000000..6a4955e0b --- /dev/null +++ b/config/locales/views/dossier_mailer/notify_en_construction_near_deletion/fr.yml @@ -0,0 +1,15 @@ +fr: + dossier_mailer: + notify_en_construction_near_deletion: + subject: + one: Un dossier en construction va bientôt être supprimé + other: Des dossiers en construction vont bientôt être supprimés + automatic_dossier_deletion: + one: "le dossier en construction suivant sera bientôt automatiquement supprimé :" + other: "les dossiers en construction suivant seront bientôt automatiquement supprimés :" + send_user_draft: + one: "Vous pouvez retrouver votre dossier pendant encore un mois. Vous n'avez rien à faire." + other: "Vous pouvez retrouver vos dossiers pendant encore un mois. Vous n'avez rien à faire." + send_other_draft: + one: "Vous avez un mois pour traiter le dossier." + other: "Vous avez un mois pour traiter les dossiers." diff --git a/db/migrate/20200218144724_add_daily_email_notifications_enabled_to_assign_tos.rb b/db/migrate/20200218144724_add_daily_email_notifications_enabled_to_assign_tos.rb new file mode 100644 index 000000000..67938c458 --- /dev/null +++ b/db/migrate/20200218144724_add_daily_email_notifications_enabled_to_assign_tos.rb @@ -0,0 +1,5 @@ +class AddDailyEmailNotificationsEnabledToAssignTos < ActiveRecord::Migration[5.2] + def change + add_column :assign_tos, :daily_email_notifications_enabled, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 7417ba719..d6fa3ff97 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: 2020_02_11_170134) do +ActiveRecord::Schema.define(version: 2020_02_18_144724) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -103,6 +103,7 @@ ActiveRecord::Schema.define(version: 2020_02_11_170134) do t.boolean "email_notifications_enabled", default: false, null: false t.bigint "groupe_instructeur_id" t.boolean "weekly_email_notifications_enabled", default: true, null: false + t.boolean "daily_email_notifications_enabled", default: false, null: false t.index ["groupe_instructeur_id", "instructeur_id"], name: "unique_couple_groupe_instructeur_instructeur", unique: true t.index ["groupe_instructeur_id"], name: "index_assign_tos_on_groupe_instructeur_id" t.index ["instructeur_id", "procedure_id"], name: "index_assign_tos_on_instructeur_id_and_procedure_id", unique: true diff --git a/lib/tasks/deployment/20200220142710_backfill_daily_email_notification_columns.rake b/lib/tasks/deployment/20200220142710_backfill_daily_email_notification_columns.rake new file mode 100644 index 000000000..919441434 --- /dev/null +++ b/lib/tasks/deployment/20200220142710_backfill_daily_email_notification_columns.rake @@ -0,0 +1,16 @@ +namespace :after_party do + desc 'Deployment task: backfill_daily_email_notification_columns' + task backfill_daily_email_notification_columns: :environment do + puts "Running deploy task 'backfill_daily_email_notification_columns'" + AssignTo.find_each do |assign_to| + columns_to_update = {} + if assign_to.email_notifications_enabled != assign_to.daily_email_notifications_enabled + columns_to_update[:daily_email_notifications_enabled] = assign_to.email_notifications_enabled + end + assign_to.update_columns(columns_to_update) unless columns_to_update.empty? + end + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord.create version: '20200220142710' + end # task :backfill_daily_email_notification_columns +end # namespace :after_party diff --git a/spec/controllers/manager/administrateurs_controller_spec.rb b/spec/controllers/manager/administrateurs_controller_spec.rb index 0d954ee3c..b1504511a 100644 --- a/spec/controllers/manager/administrateurs_controller_spec.rb +++ b/spec/controllers/manager/administrateurs_controller_spec.rb @@ -1,14 +1,22 @@ describe Manager::AdministrateursController, type: :controller do let(:administration) { create(:administration) } + before do + sign_in administration + end + + describe 'GET #new' do + render_views + it 'displays form to create a new admin' do + get :new + expect(response).to be_success + end + end + describe 'POST #create' do let(:email) { 'plop@plop.com' } let(:password) { 'démarches-simplifiées-pwd' } - before do - sign_in administration - end - subject { post :create, params: { administrateur: { email: email } } } context 'when email and password are correct' do @@ -35,8 +43,6 @@ describe Manager::AdministrateursController, type: :controller do describe '#delete' do let!(:admin) { create(:administrateur) } - before { sign_in administration } - subject { delete :delete, params: { id: admin.id } } it 'deletes the admin' do @@ -45,4 +51,14 @@ describe Manager::AdministrateursController, type: :controller do expect(Administrateur.find_by(id: admin.id)).to be_nil end end + + describe '#index' do + render_views + let(:admin) { create(:administrateur) } + + it 'searches admin by email' do + get :index, params: { search: admin.email } + expect(response).to be_success + end + end end diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index 1cf7e1b03..9385382c1 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -249,47 +249,5 @@ FactoryBot.define do end end end - - trait :with_csv_export_file do - after(:create) do |procedure, _evaluator| - procedure.csv_export_file.attach(io: StringIO.new("some csv data"), filename: "export.csv", content_type: "text/plain") - procedure.csv_export_file.update(created_at: 5.minutes.ago) - end - end - - trait :with_stale_csv_export_file do - after(:create) do |procedure, _evaluator| - procedure.csv_export_file.attach(io: StringIO.new("some csv data"), filename: "export.csv", content_type: "text/plain") - procedure.csv_export_file.update(created_at: 4.hours.ago) - end - end - - trait :with_ods_export_file do - after(:create) do |procedure, _evaluator| - procedure.ods_export_file.attach(io: StringIO.new("some ods data"), filename: "export.ods", content_type: "text/plain") - procedure.ods_export_file.update(created_at: 5.minutes.ago) - end - end - - trait :with_stale_ods_export_file do - after(:create) do |procedure, _evaluator| - procedure.ods_export_file.attach(io: StringIO.new("some ods data"), filename: "export.ods", content_type: "text/plain") - procedure.ods_export_file.update(created_at: 4.hours.ago) - end - end - - trait :with_xlsx_export_file do - after(:create) do |procedure, _evaluator| - procedure.xlsx_export_file.attach(io: StringIO.new("some xlsx data"), filename: "export.xlsx", content_type: "text/plain") - procedure.xlsx_export_file.update(created_at: 5.minutes.ago) - end - end - - trait :with_stale_xlsx_export_file do - after(:create) do |procedure, _evaluator| - procedure.xlsx_export_file.attach(io: StringIO.new("some xlsx data"), filename: "export.xlsx", content_type: "text/plain") - procedure.xlsx_export_file.update(created_at: 4.hours.ago) - end - end end end diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index 75dbc8bec..2e365f10f 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -41,8 +41,9 @@ feature 'Inviting an expert:' do expect(Avis.count).to eq(4) expect(all_emails.size).to eq(2) + invitation_email = open_email('expert2@exemple.fr') - avis = Avis.find_by(email: 'expert2@exemple.fr') + avis = Avis.find_by(email: 'expert2@exemple.fr', dossier: dossier) sign_up_link = sign_up_instructeur_avis_path(avis.id, avis.email) expect(invitation_email.body).to include(sign_up_link) end diff --git a/spec/mailers/application_mailer_spec.rb b/spec/mailers/application_mailer_spec.rb index f4464b7ba..9cfa295f9 100644 --- a/spec/mailers/application_mailer_spec.rb +++ b/spec/mailers/application_mailer_spec.rb @@ -3,6 +3,16 @@ RSpec.describe ApplicationMailer, type: :mailer do let(:dossier) { create(:dossier, procedure: build(:simple_procedure)) } subject { DossierMailer.notify_new_draft(dossier) } + describe 'invalid emails are not sent' do + before do + allow_any_instance_of(DossierMailer) + .to receive(:notify_new_draft) + .and_raise(Net::SMTPSyntaxError) + end + + it { expect(subject.message).to be_an_instance_of(ActionMailer::Base::NullMail) } + end + describe 'valid emails are sent' do it { expect(subject.message).not_to be_an_instance_of(ActionMailer::Base::NullMail) } end diff --git a/spec/mailers/dossier_mailer_spec.rb b/spec/mailers/dossier_mailer_spec.rb index 38c121a51..b7b50111c 100644 --- a/spec/mailers/dossier_mailer_spec.rb +++ b/spec/mailers/dossier_mailer_spec.rb @@ -61,17 +61,6 @@ RSpec.describe DossierMailer, type: :mailer do it { expect(subject.body).to include(deleted_dossier.procedure.libelle) } end - describe '.notify_unhide_to_user' do - let(:dossier) { create(:dossier) } - - subject { described_class.notify_unhide_to_user(dossier) } - - it { expect(subject.subject).to eq("Votre dossier nº #{dossier.id} n'a pas pu être supprimé") } - it { expect(subject.body).to include(dossier.id) } - it { expect(subject.body).to include("n'a pas pu être supprimé") } - it { expect(subject.body).to include(dossier.procedure.libelle) } - end - describe '.notify_revert_to_instruction' do let(:dossier) { create(:dossier, procedure: build(:simple_procedure)) } @@ -106,4 +95,63 @@ RSpec.describe DossierMailer, type: :mailer do it { expect(subject.subject).to eq("Un dossier en brouillon a été supprimé automatiquement") } it { expect(subject.body).to include("n° #{dossier.id} (#{dossier.procedure.libelle})") } end + + describe '.notify_automatic_deletion_to_user' do + let(:dossier) { create(:dossier) } + + before do + duree = dossier.procedure.duree_conservation_dossiers_dans_ds + @date_suppression = dossier.created_at + duree.months + end + + subject { described_class.notify_automatic_deletion_to_user(dossier.user, [dossier.hash_for_deletion_mail]) } + + it { expect(subject.subject).to eq("Un dossier a été supprimé automatiquement") } + it { expect(subject.body).to include("n° #{dossier.id} ") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + it { expect(subject.body).to include("nous nous excusons de la gène occasionnée") } + end + + describe '.notify_automatic_deletion_to_administration' do + let(:dossier) { create(:dossier) } + + subject { described_class.notify_automatic_deletion_to_administration(dossier.user, [dossier.hash_for_deletion_mail]) } + + it { expect(subject.subject).to eq("Un dossier a été supprimé automatiquement") } + it { expect(subject.body).to include("n° #{dossier.id} (#{dossier.procedure.libelle})") } + end + + describe '.notify_en_construction_near_deletion_to_administration' do + let(:dossier) { create(:dossier) } + + before do + duree = dossier.procedure.duree_conservation_dossiers_dans_ds + @date_suppression = dossier.created_at + duree.months + end + + subject { described_class.notify_en_construction_near_deletion(dossier.user, [dossier], true) } + + it { expect(subject.subject).to eq("Un dossier en construction va bientôt être supprimé") } + it { expect(subject.body).to include("n° #{dossier.id} ") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + it { expect(subject.body).to include("PDF") } + it { expect(subject.body).to include("Vous pouvez retrouver votre dossier pendant encore un mois. Vous n'avez rien à faire.") } + end + + describe '.notify_en_construction_near_deletion_to_user' do + let(:dossier) { create(:dossier) } + + before do + duree = dossier.procedure.duree_conservation_dossiers_dans_ds + @date_suppression = dossier.created_at + duree.months + end + + subject { described_class.notify_en_construction_near_deletion(dossier.user, [dossier], false) } + + it { expect(subject.subject).to eq("Un dossier en construction va bientôt être supprimé") } + it { expect(subject.body).to include("n° #{dossier.id} ") } + it { expect(subject.body).to include(dossier.procedure.libelle) } + it { expect(subject.body).to include("PDF") } + it { expect(subject.body).to include("Vous avez un mois pour traiter le dossier.") } + end end diff --git a/spec/mailers/instructeur_mailer_spec.rb b/spec/mailers/instructeur_mailer_spec.rb index ffcda1f25..aafaf85d2 100644 --- a/spec/mailers/instructeur_mailer_spec.rb +++ b/spec/mailers/instructeur_mailer_spec.rb @@ -46,18 +46,4 @@ RSpec.describe InstructeurMailer, type: :mailer do end end end - - describe '#notify_procedure_export_available' do - let(:instructeur) { create(:instructeur) } - let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } - let(:dossier) { create(:dossier, procedure: procedure) } - let(:format) { 'xlsx' } - - context 'when the mail is sent' do - subject { described_class.notify_procedure_export_available(instructeur, procedure, format) } - it 'contains a download link' do - expect(subject.body).to include download_export_instructeur_procedure_url(procedure, :export_format => format) - end - end - end end diff --git a/spec/mailers/previews/instructeur_mailer_preview.rb b/spec/mailers/previews/instructeur_mailer_preview.rb index 0a906a7dc..35c2f85a1 100644 --- a/spec/mailers/previews/instructeur_mailer_preview.rb +++ b/spec/mailers/previews/instructeur_mailer_preview.rb @@ -37,10 +37,6 @@ class InstructeurMailerPreview < ActionMailer::Preview InstructeurMailer.send_notifications(instructeur, data) end - def notify_procedure_export_available - InstructeurMailer.notify_procedure_export_available(instructeur, procedure, 'xlsx') - end - private def instructeur diff --git a/spec/models/concern/tags_substitution_concern_spec.rb b/spec/models/concern/tags_substitution_concern_spec.rb index 288c0075a..9488976fd 100644 --- a/spec/models/concern/tags_substitution_concern_spec.rb +++ b/spec/models/concern/tags_substitution_concern_spec.rb @@ -70,6 +70,29 @@ describe TagsSubstitutionConcern, type: :model do end end + context 'when the template use the groupe instructeur tags' do + let(:template) { '--groupe instructeur--' } + let(:state) { Dossier.states.fetch(:en_instruction) } + let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement, state: state) } + context 'and the dossier has a groupe instructeur' do + label = 'Ville de Bordeaux' + before do + gi = procedure.groupe_instructeurs.create(label: label) + gi.dossiers << dossier + dossier.update(groupe_instructeur: gi) + dossier.reload + end + + it { expect(procedure.routee?).to eq(true) } + it { is_expected.to eq(label) } + end + + context 'and the dossier has no groupe instructeur' do + it { expect(procedure.routee?).to eq(false) } + it { is_expected.to eq('défaut') } + end + end + context 'when the procedure has a type de champ named libelleA et libelleB' do let(:types_de_champ) do [ diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index c0c949bbd..3b7a9b26c 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -1075,165 +1075,4 @@ describe Procedure do it { is_expected.to be false } end end - - describe '.ods_export_stale?' do - subject { procedure.ods_export_stale? } - - context 'with no ods export' do - let(:procedure) { create(:procedure) } - it { is_expected.to be true } - end - - context 'with a recent ods export' do - let(:procedure) { create(:procedure, :with_ods_export_file) } - it { is_expected.to be false } - end - - context 'with an old ods export' do - let(:procedure) { create(:procedure, :with_stale_ods_export_file) } - it { is_expected.to be true } - end - end - - describe '.csv_export_stale?' do - subject { procedure.csv_export_stale? } - - context 'with no csv export' do - let(:procedure) { create(:procedure) } - it { is_expected.to be true } - end - - context 'with a recent csv export' do - let(:procedure) { create(:procedure, :with_csv_export_file) } - it { is_expected.to be false } - end - - context 'with an old csv export' do - let(:procedure) { create(:procedure, :with_stale_csv_export_file) } - it { is_expected.to be true } - end - end - - describe '.xlsx_export_stale?' do - subject { procedure.xlsx_export_stale? } - - context 'with no xlsx export' do - let(:procedure) { create(:procedure) } - it { is_expected.to be true } - end - - context 'with a recent xlsx export' do - let(:procedure) { create(:procedure, :with_xlsx_export_file) } - it { is_expected.to be false } - end - - context 'with an old xlsx export' do - let(:procedure) { create(:procedure, :with_stale_xlsx_export_file) } - it { is_expected.to be true } - end - end - - describe '.should_generate_export?' do - context 'xlsx' do - subject { procedure.should_generate_export?('xlsx') } - context 'with no export' do - let(:procedure) { create(:procedure) } - it { is_expected.to be true } - end - - context 'with a recent export' do - context 'when its not queued' do - let(:procedure) { create(:procedure, :with_xlsx_export_file, xlsx_export_queued: false) } - it { is_expected.to be false } - end - - context 'when its already queued' do - let(:procedure) { create(:procedure, :with_xlsx_export_file, xlsx_export_queued: true) } - it { expect(procedure.xlsx_export_queued).to be true } - it { is_expected.to be false } - end - end - - context 'with an old export' do - context 'when its not queued' do - let(:procedure) { create(:procedure, :with_stale_xlsx_export_file, xlsx_export_queued: false) } - it { is_expected.to be true } - end - - context 'when its already queued' do - let(:procedure) { create(:procedure, :with_stale_xlsx_export_file, xlsx_export_queued: true) } - it { expect(procedure.xlsx_export_queued).to be true } - it { is_expected.to be false } - end - end - end - - context 'csv' do - subject { procedure.should_generate_export?('csv') } - context 'with no export' do - let(:procedure) { create(:procedure) } - it { is_expected.to be true } - end - - context 'with a recent export' do - context 'when its not queued' do - let(:procedure) { create(:procedure, :with_csv_export_file, csv_export_queued: false) } - it { is_expected.to be false } - end - - context 'when its already queued' do - let(:procedure) { create(:procedure, :with_csv_export_file, csv_export_queued: true) } - it { expect(procedure.csv_export_queued).to be true } - it { is_expected.to be false } - end - end - - context 'with an old export' do - context 'when its not queued' do - let(:procedure) { create(:procedure, :with_stale_csv_export_file, csv_export_queued: false) } - it { is_expected.to be true } - end - - context 'when its already queued' do - let(:procedure) { create(:procedure, :with_stale_csv_export_file, csv_export_queued: true) } - it { expect(procedure.csv_export_queued).to be true } - it { is_expected.to be false } - end - end - end - - context 'ods' do - subject { procedure.should_generate_export?('ods') } - context 'with no export' do - let(:procedure) { create(:procedure) } - it { is_expected.to be true } - end - - context 'with a recent export' do - context 'when its not queued' do - let(:procedure) { create(:procedure, :with_ods_export_file, ods_export_queued: false) } - it { is_expected.to be false } - end - - context 'when its already queued' do - let(:procedure) { create(:procedure, :with_ods_export_file, ods_export_queued: true) } - it { expect(procedure.ods_export_queued).to be true } - it { is_expected.to be false } - end - end - - context 'with an old export' do - context 'when its not queued' do - let(:procedure) { create(:procedure, :with_stale_ods_export_file, ods_export_queued: false) } - it { is_expected.to be true } - end - - context 'when its already queued' do - let(:procedure) { create(:procedure, :with_stale_ods_export_file, ods_export_queued: true) } - it { expect(procedure.ods_export_queued).to be true } - it { is_expected.to be false } - end - end - end - end end