commit
3b48815604
37 changed files with 307 additions and 447 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
13
app/views/manager/administrateurs/_form.html.haml
Normal file
13
app/views/manager/administrateurs/_form.html.haml
Normal file
|
@ -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
|
|
@ -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 :"
|
|
@ -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."
|
|
@ -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 <b>un mois</b>. Vous n'avez rien à faire."
|
||||
other: "Vous pouvez retrouver vos dossiers pendant encore <b>un mois</b>. Vous n'avez rien à faire."
|
||||
send_other_draft:
|
||||
one: "Vous avez <b>un mois</b> pour traiter le dossier."
|
||||
other: "Vous avez <b>un mois</b> pour traiter les dossiers."
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <b>un mois</b>. 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 <b>un mois</b> pour traiter le dossier.") }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
[
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue