Merge pull request #6833 from betagouv/main

2022-01-25-01
This commit is contained in:
Kara Diaby 2022-01-25 14:23:49 +01:00 committed by GitHub
commit 4778eaaa9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 433 additions and 427 deletions

View file

@ -54,6 +54,10 @@
font-size: 14px; font-size: 14px;
} }
.text-lg {
font-size: 18px;
}
.mt-1 { .mt-1 {
margin-top: $default-spacer; margin-top: $default-spacer;
} }

View file

@ -3,12 +3,17 @@ module Administrateurs
before_action :retrieve_procedure before_action :retrieve_procedure
def edit def edit
@attestation_template = @procedure.attestation_template || AttestationTemplate.new(procedure: @procedure) @attestation_template = build_attestation
end end
def update def update
attestation_template = @procedure.attestation_template attestation_template = @procedure.draft_attestation_template.revise!
if attestation_template.update(activated_attestation_params) if attestation_template.update(activated_attestation_params)
AttestationTemplate
.where(id: @procedure.revisions.pluck(:attestation_template_id).compact)
.update_all(activated: attestation_template.activated?)
flash.notice = "L'attestation a bien été modifiée" flash.notice = "L'attestation a bien été modifiée"
else else
flash.alert = attestation_template.errors.full_messages.join('<br>') flash.alert = attestation_template.errors.full_messages.join('<br>')
@ -18,7 +23,7 @@ module Administrateurs
end end
def create def create
attestation_template = AttestationTemplate.new(activated_attestation_params.merge(procedure_id: @procedure.id)) attestation_template = build_attestation(activated_attestation_params)
if attestation_template.save if attestation_template.save
flash.notice = "L'attestation a bien été sauvegardée" flash.notice = "L'attestation a bien été sauvegardée"
@ -30,14 +35,19 @@ module Administrateurs
end end
def preview def preview
attestation = @procedure.attestation_template || AttestationTemplate.new @attestation = build_attestation.render_attributes_for({})
@attestation = attestation.render_attributes_for({})
render 'administrateurs/attestation_templates/show', formats: [:pdf] render 'administrateurs/attestation_templates/show', formats: [:pdf]
end end
private private
def build_attestation(attributes = {})
attestation_template = @procedure.draft_attestation_template || @procedure.draft_revision.build_attestation_template
attestation_template.attributes = attributes
attestation_template
end
def activated_attestation_params def activated_attestation_params
# cache result to avoid multiple uninterlaced computations # cache result to avoid multiple uninterlaced computations
if @activated_attestation_params.nil? if @activated_attestation_params.nil?

View file

@ -208,13 +208,12 @@ module Administrateurs
end end
def import def import
if procedure.publiee?
if !CSV_ACCEPTED_CONTENT_TYPES.include?(group_csv_file.content_type) && !CSV_ACCEPTED_CONTENT_TYPES.include?(marcel_content_type) if !CSV_ACCEPTED_CONTENT_TYPES.include?(group_csv_file.content_type) && !CSV_ACCEPTED_CONTENT_TYPES.include?(marcel_content_type)
flash[:alert] = "Importation impossible : veuillez importer un fichier CSV" flash[:alert] = "Importation impossible : veuillez importer un fichier CSV"
redirect_to admin_procedure_groupe_instructeurs_path(procedure)
elsif group_csv_file.size > CSV_MAX_SIZE elsif group_csv_file.size > CSV_MAX_SIZE
flash[:alert] = "Importation impossible : le poids du fichier est supérieur à #{number_to_human_size(CSV_MAX_SIZE)}" flash[:alert] = "Importation impossible : le poids du fichier est supérieur à #{number_to_human_size(CSV_MAX_SIZE)}"
redirect_to admin_procedure_groupe_instructeurs_path(procedure)
else else
file = group_csv_file.read file = group_csv_file.read
@ -235,7 +234,7 @@ module Administrateurs
flash[:alert] = "Import terminé. Cependant les emails suivants ne sont pas pris en compte: #{add_instructeurs_and_get_errors.join(', ')}" flash[:alert] = "Import terminé. Cependant les emails suivants ne sont pas pris en compte: #{add_instructeurs_and_get_errors.join(', ')}"
end end
end end
end
redirect_to admin_procedure_groupe_instructeurs_path(procedure) redirect_to admin_procedure_groupe_instructeurs_path(procedure)
end end
end end

View file

@ -276,8 +276,9 @@ class ApplicationController < ActionController::Base
matomo = Rails.application.secrets.matomo matomo = Rails.application.secrets.matomo
{ {
key: matomo[:client_key], enabled: matomo[:enabled],
enabled: matomo[:enabled] host: matomo[:host],
key: matomo[:client_key]
} }
end end

View file

@ -13,7 +13,7 @@ module Instructeurs
.order(closed_at: :desc, unpublished_at: :desc, published_at: :desc, created_at: :desc) .order(closed_at: :desc, unpublished_at: :desc, published_at: :desc, created_at: :desc)
dossiers = current_instructeur.dossiers.joins(:groupe_instructeur) dossiers = current_instructeur.dossiers.joins(:groupe_instructeur)
@dossiers_count_per_procedure = dossiers.all_state.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_count_per_procedure = dossiers.all_state.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_a_suivre_count_per_procedure = dossiers.without_followers.en_cours.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_a_suivre_count_per_procedure = dossiers.without_followers.en_cours.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
@dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count @dossiers_archived_count_per_procedure = dossiers.archived.group('groupe_instructeurs.procedure_id').count
@dossiers_termines_count_per_procedure = dossiers.termine.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count @dossiers_termines_count_per_procedure = dossiers.termine.visible_by_administration.group('groupe_instructeurs.procedure_id').reorder(nil).count
@ -71,7 +71,7 @@ module Instructeurs
@followed_dossiers_id = @followed_dossiers.pluck(:id) @followed_dossiers_id = @followed_dossiers.pluck(:id)
@termines_dossiers = dossiers_visibles.termine.visible_by_administration @termines_dossiers = dossiers_visibles.termine.visible_by_administration
@all_state_dossiers = dossiers_visibles.all_state @all_state_dossiers = dossiers_visibles.all_state.visible_by_administration
@archived_dossiers = dossiers_visibles.archived @archived_dossiers = dossiers_visibles.archived
@expirant_dossiers = dossiers_visibles.termine_or_en_construction_close_to_expiration @expirant_dossiers = dossiers_visibles.termine_or_en_construction_close_to_expiration

View file

@ -16,39 +16,6 @@ module Manager
end end
end end
#
# Custom actions
#
def discard
dossier = Dossier.find(params[:id])
dossier.discard_and_keep_track!(current_super_admin, :manager_request)
logger.info("Le dossier #{dossier.id} est supprimé par #{current_super_admin.email}")
flash[:notice] = "Le dossier #{dossier.id} a été supprimé."
redirect_to manager_dossier_path(dossier)
end
def restore
dossier = Dossier.with_discarded.find(params[:id])
dossier.restore(current_super_admin)
flash[:notice] = "Le dossier #{dossier.id} a été restauré."
redirect_to manager_dossier_path(dossier)
end
def repasser_en_instruction
dossier = Dossier.find(params[:id])
dossier.repasser_en_instruction(instructeur: current_super_admin)
logger.info("Le dossier #{dossier.id} est repassé en instruction par #{current_super_admin.email}")
flash[:notice] = "Le dossier #{dossier.id} est repassé en instruction."
redirect_to manager_dossier_path(dossier)
end
private private
def unfiltered_list? def unfiltered_list?

View file

@ -110,9 +110,17 @@ function ComboMultiple({
extraOptions[0] && extraOptions[0] &&
extraOptions[0][0] == selectedValue extraOptions[0][0] == selectedValue
) { ) {
setNewValues((newValues) => [...newValues, selectedValue]); setNewValues((newValues) => {
const set = new Set(newValues);
set.add(selectedValue);
return [...set];
});
} }
saveSelection((selections) => [...selections, selectedValue]); saveSelection((selections) => {
const set = new Set(selections);
set.add(selectedValue);
return [...set];
});
} }
setTerm(''); setTerm('');
awaitFormSubmit.done(); awaitFormSubmit.done();
@ -162,18 +170,17 @@ function ComboMultiple({
}; };
const onBlur = () => { const onBlur = () => {
if ( const shouldSelect =
term && term &&
[...extraOptions, ...options].map(([label]) => label).includes(term) [...extraOptions, ...options].map(([label]) => label).includes(term);
) {
awaitFormSubmit(() => { awaitFormSubmit(() => {
if (shouldSelect) {
onSelect(term); onSelect(term);
});
} else { } else {
setTimeout(() => {
hidePopover(); hidePopover();
}, 200);
} }
});
}; };
return ( return (
@ -232,17 +239,11 @@ function ComboMultiple({
</button> </button>
</li> </li>
)} )}
{results.map(([label, value], index) => { {results.map(([label], index) => {
if (label.startsWith('--')) { if (label.startsWith('--')) {
return <ComboboxSeparator key={index} value={label} />; return <ComboboxSeparator key={index} value={label} />;
} }
return ( return <ComboboxOption key={index} value={label} />;
<ComboboxOption
key={index}
value={label}
data-option-value={value}
/>
);
})} })}
</ComboboxList> </ComboboxList>
</ComboboxPopover> </ComboboxPopover>

View file

@ -122,13 +122,7 @@ function ComboSearch({
const label = getLabel(result); const label = getLabel(result);
const [key, value] = transformResult(result); const [key, value] = transformResult(result);
resultsMap.current[label] = { key, value, result }; resultsMap.current[label] = { key, value, result };
return ( return <ComboboxOption key={`${key}-${index}`} value={label} />;
<ComboboxOption
key={`${key}-${index}`}
value={label}
data-option-value={value}
/>
);
})} })}
</ComboboxList> </ComboboxList>
) : ( ) : (

View file

@ -1,11 +1,10 @@
const { key, enabled } = gon.matomo || {}; const { enabled, host, key } = gon.matomo || {};
if (enabled) { if (enabled) {
window._paq = window._paq || []; window._paq = window._paq || [];
const url = '//stats.data.gouv.fr/'; const jsUrl = `//${host}/piwik.js`;
const trackerUrl = `${url}piwik.php`; const trackerUrl = `//${host}/piwik.php`;
const jsUrl = `${url}piwik.js`;
// //
// Configure Matomo analytics // Configure Matomo analytics

View file

@ -1,10 +1,7 @@
class Cron::DiscardedProceduresDeletionJob < Cron::CronJob class Cron::DiscardedProceduresDeletionJob < Cron::CronJob
self.schedule_expression = "every day at 1 am" self.schedule_expression = "every day at 1 am"
def perform(*args) def perform
Procedure.discarded_expired.find_each do |procedure| Procedure.purge_discarded
procedure.dossiers.with_discarded.destroy_all
procedure.destroy
end
end end
end end

View file

@ -47,7 +47,7 @@ class ActiveJob::ApplicationLogSubscriber < ::ActiveJob::LogSubscriber
end end
data[:tags] = tags data[:tags] = tags
data[:type] = 'tps' data[:type] = 'tps'
data[:source] = ENV['SOURCE'] data[:source] = ENV['LOGRAGE_SOURCE']
log(data) log(data)
end end

View file

@ -84,23 +84,12 @@ class DossierMailer < ApplicationMailer
mail(to: to_email, subject: @subject) mail(to: to_email, subject: @subject)
end end
def notify_deletion_to_user(deleted_dossier, to_email) def notify_en_construction_deletion_to_administration(dossier, to_email)
I18n.with_locale(deleted_dossier.user_locale) do @subject = default_i18n_subject(dossier_id: dossier.id)
@subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id) @dossier = dossier
@deleted_dossier = deleted_dossier
mail(to: to_email, subject: @subject) mail(to: to_email, subject: @subject)
end end
end
def notify_instructeur_deletion_to_user(deleted_dossier, to_email)
I18n.with_locale(deleted_dossier.user_locale) do
@subject = default_i18n_subject(libelle_demarche: deleted_dossier.procedure.libelle)
@deleted_dossier = deleted_dossier
mail(to: to_email, subject: @subject)
end
end
def notify_deletion_to_administration(deleted_dossier, to_email) def notify_deletion_to_administration(deleted_dossier, to_email)
@subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id) @subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id)

View file

@ -15,7 +15,8 @@ class AttestationTemplate < ApplicationRecord
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
include TagsSubstitutionConcern include TagsSubstitutionConcern
belongs_to :procedure, optional: false belongs_to :procedure, optional: true
has_many :revisions, class_name: 'ProcedureRevision', inverse_of: :attestation_template, dependent: :nullify
has_one_attached :logo has_one_attached :logo
has_one_attached :signature has_one_attached :signature
@ -103,6 +104,25 @@ class AttestationTemplate < ApplicationRecord
} }
end end
def revise!
if revisions.size > 1
attestation_template = dup
attestation_template.save!
revisions
.last
.procedure
.draft_revision
.update!(attestation_template: attestation_template)
attestation_template
else
self
end
end
def procedure
revisions.last&.procedure || super
end
private private
def used_tags def used_tags

View file

@ -209,7 +209,7 @@ class Dossier < ApplicationRecord
scope :hidden_by_user, -> { where.not(hidden_by_user_at: nil) } scope :hidden_by_user, -> { where.not(hidden_by_user_at: nil) }
scope :hidden_by_administration, -> { where.not(hidden_by_administration_at: nil) } scope :hidden_by_administration, -> { where.not(hidden_by_administration_at: nil) }
scope :visible_by_user, -> { where(hidden_by_user_at: nil) } scope :visible_by_user, -> { where(hidden_by_user_at: nil) }
scope :visible_by_administration, -> { where("hidden_by_administration_at IS NULL AND NOT (hidden_by_user_at IS NOT NULL AND state = 'en_construction')") } scope :visible_by_administration, -> { where("hidden_by_administration_at IS NULL AND NOT (hidden_by_user_at IS NOT NULL AND dossiers.state = 'en_construction')") }
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) } scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) }
scope :order_by_created_at, -> (order = :asc) { order(depose_at: order, created_at: order, id: order) } scope :order_by_created_at, -> (order = :asc) { order(depose_at: order, created_at: order, id: order) }
@ -539,10 +539,6 @@ class Dossier < ApplicationRecord
en_construction? || termine? en_construction? || termine?
end end
def can_be_deleted_by_manager?
kept? && can_be_deleted_by_user?
end
def messagerie_available? def messagerie_available?
!brouillon? && !user_deleted? && !archived !brouillon? && !user_deleted? && !archived
end end
@ -704,10 +700,6 @@ class Dossier < ApplicationRecord
termine? && hidden_by_administration? && hidden_by_user? termine? && hidden_by_administration? && hidden_by_user?
end end
def can_be_restored_by_manager?
hidden_by_administration? || discarded? && !procedure.discarded?
end
def expose_legacy_carto_api? def expose_legacy_carto_api?
procedure.expose_legacy_carto_api? procedure.expose_legacy_carto_api?
end end
@ -728,9 +720,11 @@ class Dossier < ApplicationRecord
{ lon: lon, lat: lat, zoom: zoom } { lon: lon, lat: lat, zoom: zoom }
end end
def unspecified_attestation_champs def attestation_template
attestation_template = procedure.attestation_template revision.attestation_template
end
def unspecified_attestation_champs
if attestation_template&.activated? if attestation_template&.activated?
attestation_template.unspecified_champs_for_dossier(self) attestation_template.unspecified_champs_for_dossier(self)
else else
@ -739,8 +733,8 @@ class Dossier < ApplicationRecord
end end
def build_attestation def build_attestation
if procedure.attestation_template&.activated? if attestation_template&.activated?
procedure.attestation_template.attestation_for(self) attestation_template.attestation_for(self)
end end
end end
@ -780,7 +774,6 @@ class Dossier < ApplicationRecord
update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil) update(hidden_by_user_at: Time.zone.now, dossier_transfer_id: nil)
end end
user_email = user_deleted? ? nil : user_email_for(:notification)
deleted_dossier = nil deleted_dossier = nil
transaction do transaction do
@ -803,14 +796,6 @@ class Dossier < ApplicationRecord
DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later
end end
end end
if user_email.present?
if reason == :user_request
DossierMailer.notify_deletion_to_user(deleted_dossier, user_email).deliver_later
else
DossierMailer.notify_instructeur_deletion_to_user(deleted_dossier, user_email).deliver_later
end
end
end end
end end

View file

@ -97,7 +97,8 @@ class Export < ApplicationRecord
end end
def io(since: nil) def io(since: nil)
dossiers = Dossier.where(groupe_instructeur: groupe_instructeurs) dossiers = Dossier.visible_by_administration
.where(groupe_instructeur: groupe_instructeurs)
if since.present? if since.present?
dossiers = dossiers.where('dossiers.depose_at > ?', since) dossiers = dossiers.where('dossiers.depose_at > ?', since)
end end

View file

@ -235,7 +235,7 @@ class Instructeur < ApplicationRecord
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction') AND dossiers.state in ('en_construction', 'en_instruction') AND follows.id IS NULL) AS a_suivre, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction') AND dossiers.state in ('en_construction', 'en_instruction') AND follows.id IS NULL) AS a_suivre,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('en_construction', 'en_instruction') AND follows.instructeur_id = :instructeur_id) AS suivis,
COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND dossiers.state in ('accepte', 'refuse', 'sans_suite')) AS traites,
COUNT(DISTINCT dossiers.id) FILTER (where not archived) AS tous, COUNT(DISTINCT dossiers.id) FILTER (where not archived AND NOT (dossiers.hidden_by_user_at IS NOT NULL AND state = 'en_construction')) AS tous,
COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives, COUNT(DISTINCT dossiers.id) FILTER (where archived) AS archives,
COUNT(DISTINCT dossiers.id) FILTER (where COUNT(DISTINCT dossiers.id) FILTER (where
procedures.procedure_expires_when_termine_enabled procedures.procedure_expires_when_termine_enabled

View file

@ -77,12 +77,15 @@ class Procedure < ApplicationRecord
has_many :published_types_de_champ_private, through: :published_revision, source: :types_de_champ_private has_many :published_types_de_champ_private, through: :published_revision, source: :types_de_champ_private
has_many :draft_types_de_champ, through: :draft_revision, source: :types_de_champ has_many :draft_types_de_champ, through: :draft_revision, source: :types_de_champ
has_many :draft_types_de_champ_private, through: :draft_revision, source: :types_de_champ_private has_many :draft_types_de_champ_private, through: :draft_revision, source: :types_de_champ_private
has_one :draft_attestation_template, through: :draft_revision, source: :attestation_template
has_one :published_attestation_template, through: :published_revision, source: :attestation_template
has_many :experts_procedures, dependent: :destroy has_many :experts_procedures, dependent: :destroy
has_many :experts, through: :experts_procedures has_many :experts, through: :experts_procedures
has_one :module_api_carto, dependent: :destroy has_one :module_api_carto, dependent: :destroy
has_one :attestation_template, dependent: :destroy has_one :attestation_template, dependent: :destroy
has_many :attestation_templates, through: :revisions, source: :attestation_template
belongs_to :parent_procedure, class_name: 'Procedure', optional: true belongs_to :parent_procedure, class_name: 'Procedure', optional: true
belongs_to :canonical_procedure, class_name: 'Procedure', optional: true belongs_to :canonical_procedure, class_name: 'Procedure', optional: true
@ -373,7 +376,7 @@ class Procedure < ApplicationRecord
end end
def draft_changed? def draft_changed?
publiee? && published_revision.changed?(draft_revision) && revision_changes.present? publiee? && published_revision.different_from?(draft_revision) && revision_changes.present?
end end
def revision_changes def revision_changes
@ -434,7 +437,8 @@ class Procedure < ApplicationRecord
}, },
revision_types_de_champ_private: { revision_types_de_champ_private: {
type_de_champ: :types_de_champ type_de_champ: :types_de_champ
} },
attestation_template: []
} }
} }
include_list[:groupe_instructeurs] = :instructeurs if !is_different_admin include_list[:groupe_instructeurs] = :instructeurs if !is_different_admin
@ -572,13 +576,17 @@ class Procedure < ApplicationRecord
touch(:whitelisted_at) touch(:whitelisted_at)
end end
def active_attestation_template
published_attestation_template || draft_attestation_template
end
def closed_mail_template_attestation_inconsistency_state def closed_mail_template_attestation_inconsistency_state
# As an optimization, dont check the predefined templates (they are presumed correct) # As an optimization, dont check the predefined templates (they are presumed correct)
if closed_mail.present? if closed_mail.present?
tag_present = closed_mail.body.to_s.include?("--lien attestation--") tag_present = closed_mail.body.to_s.include?("--lien attestation--")
if attestation_template&.activated? && !tag_present if active_attestation_template&.activated? && !tag_present
:missing_tag :missing_tag
elsif !attestation_template&.activated? && tag_present elsif !active_attestation_template&.activated? && tag_present
:extraneous_tag :extraneous_tag
end end
end end
@ -649,7 +657,7 @@ class Procedure < ApplicationRecord
end end
def can_be_deleted_by_administrateur? def can_be_deleted_by_administrateur?
brouillon? || dossiers.state_instruction_commencee.empty? brouillon? || dossiers.state_en_instruction.empty?
end end
def can_be_deleted_by_manager? def can_be_deleted_by_manager?
@ -663,18 +671,25 @@ class Procedure < ApplicationRecord
close! close!
end end
dossiers.each do |dossier| dossiers.termine.visible_by_administration.each do |dossier|
dossier.discard_and_keep_track!(author, :procedure_removed) dossier.discard_and_keep_track!(author, :procedure_removed)
end end
discard! discard!
end end
def purge_discarded
if !dossiers.with_discarded.exists?
destroy
end
end
def self.purge_discarded
discarded_expired.find_each(&:purge_discarded)
end
def restore(author) def restore(author)
if discarded? && undiscard if discarded? && undiscard
dossiers.with_discarded.discarded.find_each do |dossier|
dossier.restore(author)
end
dossiers.hidden_by_administration.find_each do |dossier| dossiers.hidden_by_administration.find_each do |dossier|
dossier.restore(author) dossier.restore(author)
end end

View file

@ -6,11 +6,13 @@
# published_at :datetime # published_at :datetime
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# attestation_template_id :bigint
# procedure_id :bigint not null # procedure_id :bigint not null
# #
class ProcedureRevision < ApplicationRecord class ProcedureRevision < ApplicationRecord
self.implicit_order_column = :created_at self.implicit_order_column = :created_at
belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false
belongs_to :attestation_template, inverse_of: :revisions, optional: true, dependent: :destroy
has_many :dossiers, inverse_of: :revision, foreign_key: :revision_id has_many :dossiers, inverse_of: :revision, foreign_key: :revision_id
@ -105,7 +107,7 @@ class ProcedureRevision < ApplicationRecord
!draft? !draft?
end end
def changed?(revision) def different_from?(revision)
types_de_champ != revision.types_de_champ || types_de_champ_private != revision.types_de_champ_private types_de_champ != revision.types_de_champ || types_de_champ_private != revision.types_de_champ_private
end end
@ -125,6 +127,10 @@ class ProcedureRevision < ApplicationRecord
) )
end end
def attestation_template
super || procedure.attestation_template
end
private private
def compare_types_de_champ(from_tdc, to_tdc) def compare_types_de_champ(from_tdc, to_tdc)

View file

@ -117,6 +117,7 @@ class ExpiredDossiersDeletionService
def self.group_by_user_email(dossiers, notify_on_closed_procedures_to_user: false) def self.group_by_user_email(dossiers, notify_on_closed_procedures_to_user: false)
dossiers dossiers
.visible_by_user
.with_notifiable_procedure(notify_on_closed: notify_on_closed_procedures_to_user) .with_notifiable_procedure(notify_on_closed: notify_on_closed_procedures_to_user)
.includes(:user, :procedure) .includes(:user, :procedure)
.group_by(&:user) .group_by(&:user)
@ -125,6 +126,7 @@ class ExpiredDossiersDeletionService
def self.group_by_fonctionnaire_email(dossiers) def self.group_by_fonctionnaire_email(dossiers)
dossiers dossiers
.visible_by_administration
.with_notifiable_procedure(notify_on_closed: true) .with_notifiable_procedure(notify_on_closed: true)
.includes(:followers_instructeurs, procedure: [:administrateurs]) .includes(:followers_instructeurs, procedure: [:administrateurs])
.each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |dossier, h| .each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |dossier, h|

View file

@ -24,11 +24,13 @@ class ProcedureArchiveService
end end
def new_collect_files_archive(archive, instructeur) def new_collect_files_archive(archive, instructeur)
## faux, ca ne doit prendre que certains groupe instructeur ? dossiers = Dossier.visible_by_administration
if archive.time_span_type == 'everything' .where(groupe_instructeur: archive.groupe_instructeurs)
dossiers = @procedure.dossiers.state_termine
dossiers = if archive.time_span_type == 'everything'
dossiers.state_termine
else else
dossiers = @procedure.dossiers.processed_in_month(archive.month) dossiers.processed_in_month(archive.month)
end end
attachments = create_list_of_attachments(dossiers) attachments = create_list_of_attachments(dossiers)
@ -44,10 +46,13 @@ class ProcedureArchiveService
end end
def old_collect_files_archive(archive, instructeur) def old_collect_files_archive(archive, instructeur)
if archive.time_span_type == 'everything' dossiers = Dossier.visible_by_administration
dossiers = @procedure.dossiers.state_termine .where(groupe_instructeur: archive.groupe_instructeurs)
dossiers = if archive.time_span_type == 'everything'
dossiers.state_termine
else else
dossiers = @procedure.dossiers.processed_in_month(archive.month) dossiers.processed_in_month(archive.month)
end end
files = create_list_of_attachments(dossiers) files = create_list_of_attachments(dossiers)

View file

@ -1,7 +1,7 @@
class MonAvisEmbedValidator < ActiveModel::Validator class MonAvisEmbedValidator < ActiveModel::Validator
def validate(record) def validate(record)
# We need to ensure the embed code is not any random string in order to avoid injections # We need to ensure the embed code is not any random string in order to avoid injections
r = Regexp.new('<a href="https://monavis|voxusagers.numerique.gouv.fr/Demarches/\d+.*key=[[:alnum:]]+.*">\s*<img src="https://monavis|voxusagers.numerique.gouv.fr/(monavis-)?static/bouton-blanc|bleu.png|svg" alt="Je donne mon avis" title="Je donne mon avis sur cette démarche" />\s*</a>', Regexp::MULTILINE) r = Regexp.new('<a href="https://monavis|voxusagers.numerique.gouv.fr/Demarches/\d+.*key=[[:alnum:]]+.*">\s*<img src="https://monavis|voxusagers.numerique.gouv.fr/(monavis-)?static/bouton-blanc|bleu.png|svg" alt="Je donne mon avis" (title="Je donne mon avis sur cette démarche" )?/>\s*</a>', Regexp::MULTILINE)
if record.monavis_embed.present? && !r.match?(record.monavis_embed) if record.monavis_embed.present? && !r.match?(record.monavis_embed)
record.errors[:base] << "Le code fourni ne correspond pas au format des codes MonAvis reconnus par la plateforme." record.errors[:base] << "Le code fourni ne correspond pas au format des codes MonAvis reconnus par la plateforme."
end end

View file

@ -21,6 +21,7 @@
= f.submit 'Ajouter le groupe', class: "button primary send" = f.submit 'Ajouter le groupe', class: "button primary send"
- csv_max_size = Administrateurs::GroupeInstructeursController::CSV_MAX_SIZE - csv_max_size = Administrateurs::GroupeInstructeursController::CSV_MAX_SIZE
- if procedure.publiee?
= form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do = form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do
= label_tag "Importer par fichier CSV" = label_tag "Importer par fichier CSV"
%p.notice Le fichier csv doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules. L'import n'écrase pas les groupes et les instructeurs existants. %p.notice Le fichier csv doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules. L'import n'écrase pas les groupes et les instructeurs existants.
@ -28,6 +29,9 @@
%p.mt-2.mb-2= link_to "Télécharger l'exemple de fichier CSV", "/csv/#{I18n.locale}/import-groupe-test.csv" %p.mt-2.mb-2= link_to "Télécharger l'exemple de fichier CSV", "/csv/#{I18n.locale}/import-groupe-test.csv"
= file_field_tag :group_csv_file, required: true, accept: 'text/csv', size: "1" = file_field_tag :group_csv_file, required: true, accept: 'text/csv', size: "1"
= submit_tag "Importer le fichier", class: 'button primary send', data: { disable_with: "Envoi..." } = submit_tag "Importer le fichier", class: 'button primary send', data: { disable_with: "Envoi..." }
- else
%p.mt-4.form.bold.mb-2.text-lg Importer par fichier CSV
%p.notice Limport dinstructeurs par fichier CSV est disponible une fois la démarche publiée
%table.table.mt-2 %table.table.mt-2
%thead %thead

View file

@ -138,7 +138,7 @@
.procedure-grid .procedure-grid
= link_to edit_admin_procedure_attestation_template_path(@procedure), class: 'card-admin' do = link_to edit_admin_procedure_attestation_template_path(@procedure), class: 'card-admin' do
- if @procedure.attestation_template.present? && @procedure.attestation_template.activated - if @procedure.draft_attestation_template&.activated?
%div %div
%span.icon.accept %span.icon.accept
%p.card-admin-status-accept Activée %p.card-admin-status-accept Activée

View file

@ -3,6 +3,6 @@
%p= t(:hello, scope: [:views, :shared, :greetings]) %p= t(:hello, scope: [:views, :shared, :greetings])
%p %p
= t('.body', dossier_id: @deleted_dossier.dossier_id, procedure: @deleted_dossier.procedure.libelle) = t('.body', dossier_id: @dossier.id, procedure: @dossier.procedure.libelle)
= render partial: "layouts/mailers/signature" = render partial: "layouts/mailers/signature"

View file

@ -1,8 +0,0 @@
- content_for(:title, "#{@subject}")
%p= t(:hello, scope: [:views, :shared, :greetings])
%p
= t('.body_html', dossier_id: @deleted_dossier.dossier_id, libelle_demarche: @deleted_dossier.procedure.libelle, deleted_dossiers_link: dossiers_url(statut: 'dossiers-supprimes'))
= render partial: "layouts/mailers/signature"

View file

@ -26,17 +26,6 @@ as well as a link to its edit page.
(Supprimé) (Supprimé)
<% end %> <% end %>
</h1> </h1>
<div>
<% if dossier.accepte? %>
<%= link_to 'Repasser en instruction', repasser_en_instruction_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous le passage en instruction du dossier ?" } %>
<% end %>
<% if dossier.can_be_deleted_by_manager? %>
<%= link_to 'Supprimer le dossier', discard_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la suppression du dossier ?" } %>
<% elsif dossier.can_be_restored_by_manager? %>
<%= link_to 'Restaurer le dossier', restore_manager_dossier_path(dossier), method: :post, class: 'button', data: { confirm: "Confirmez vous la restauration du dossier ?" } %>
<% end %>
</div>
</header> </header>
<section class="main-content__body"> <section class="main-content__body">

View file

@ -1,126 +1,135 @@
# Nom d'hôte de l'appli # Application host name
# * Pour du dev local: localhost:3000 #
# * pour de la preprod: preprod.ds.organisme.fr (par exemple) # Examples:
# * pour de la prod: www.demarches-simplifiees.fr # * For local development: localhost:3000
# * For preproduction: staging.ds.organisme.fr
# * For production: ds.organisme.fr
APP_HOST="localhost:3000" APP_HOST="localhost:3000"
# Utilisé pour les logs LogRage # Rails key for signing sensitive data
SOURCE="tps_local" # See https://guides.rubyonrails.org/security.html
#
# Clé de chiffrement de rails, cf https://api.rubyonrails.org/classes/Rails/Application.html # For production you MUST generate a new key, and keep it secret.
# Secrets must be long and random. Use bin/rails secret to get new unique secrets.
SECRET_KEY_BASE="05a2d479d8e412198dabd08ef0eee9d6e180f5cbb48661a35fd1cae287f0a93d40b5f1da08f06780d698bbd458a0ea97f730f83ee780de5d4e31f649a0130cf0" SECRET_KEY_BASE="05a2d479d8e412198dabd08ef0eee9d6e180f5cbb48661a35fd1cae287f0a93d40b5f1da08f06780d698bbd458a0ea97f730f83ee780de5d4e31f649a0130cf0"
SIGNING_KEY="aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017"
# Clé de chiffrement OTP, pour 2FA # Secret key for One-Time-Password codes, used for 2-factors authentication
OTP_SECRET_KEY="" OTP_SECRET_KEY=""
# SAML IdP # SAML IdP
# SAML_IDP_CERTIFICATE="billybop" # SAML_IDP_CERTIFICATE="billybop"
# SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n" # SAML_IDP_SECRET_KEY="-----BEGIN RSA PRIVATE KEY-----\nblabla+blabla\n-----END RSA PRIVATE KEY-----\n"
# SENDINBLUE_LOGIN_URL="https://app.sendinblue.com/account/saml/login/truc"
# Database # Database credentials
DB_DATABASE="tps_development" DB_DATABASE="tps_development"
DB_HOST="localhost" DB_HOST="localhost"
DB_POOL="" DB_POOL=""
DB_USERNAME="tps_development" DB_USERNAME="tps_development"
DB_PASSWORD="tps_development" DB_PASSWORD="tps_development"
# Protection simple de l'instance par mot de passe (utile pour la pre-prod) # Protect access to the instance with a static login/password (useful for staging environments)
BASIC_AUTH_ENABLED="disabled" BASIC_AUTH_ENABLED="disabled"
BASIC_AUTH_USERNAME="" BASIC_AUTH_USERNAME=""
BASIC_AUTH_PASSWORD="" BASIC_AUTH_PASSWORD=""
# Object Storage pour les pièces jointes # Object Storage for attachments
FOG_ENABLED="disabled"
FOG_OPENSTACK_API_KEY="" FOG_OPENSTACK_API_KEY=""
FOG_OPENSTACK_USERNAME="" FOG_OPENSTACK_USERNAME=""
FOG_OPENSTACK_URL="" FOG_OPENSTACK_URL=""
FOG_OPENSTACK_REGION="" FOG_OPENSTACK_REGION=""
FOG_ENABLED="" # valeur attendue: enabled
DS_PROXY_URL="" DS_PROXY_URL=""
# Service externe: authentification France Connect # External service: authentication through France Connect
FC_PARTICULIER_ID="" FC_PARTICULIER_ID=""
FC_PARTICULIER_SECRET="" FC_PARTICULIER_SECRET=""
FC_PARTICULIER_BASE_URL="" FC_PARTICULIER_BASE_URL=""
# Service externe: authentification Agent Connect # External service: authentication through Agent Connect
AGENT_CONNECT_ID="" AGENT_CONNECT_ID=""
AGENT_CONNECT_SECRET="" AGENT_CONNECT_SECRET=""
AGENT_CONNECT_BASE_URL="" AGENT_CONNECT_BASE_URL=""
AGENT_CONNECT_JWKS="" AGENT_CONNECT_JWKS=""
AGENT_CONNECT_REDIRECT="" AGENT_CONNECT_REDIRECT=""
# Service externe: Support Utilisateur HelpScout | Spécifique démarches-simplifiées.fr # External service: integration with HelpScout (optional)
HELPSCOUT_MAILBOX_ID="" HELPSCOUT_MAILBOX_ID=""
HELPSCOUT_CLIENT_ID="" HELPSCOUT_CLIENT_ID=""
HELPSCOUT_CLIENT_SECRET="" HELPSCOUT_CLIENT_SECRET=""
HELPSCOUT_WEBHOOK_SECRET="" HELPSCOUT_WEBHOOK_SECRET=""
# Service externe: Supervision exterieure | Spécifique démarches-simplifiées.fr # External service: external supervision
SENTRY_ENABLED="disabled" SENTRY_ENABLED="disabled"
SENTRY_CURRENT_ENV="development" SENTRY_CURRENT_ENV="development"
SENTRY_DSN_RAILS="" SENTRY_DSN_RAILS=""
SENTRY_DSN_JS="" SENTRY_DSN_JS=""
# Statistiques web # External service: Matomo web analytics
MATOMO_ENABLED="disabled" MATOMO_ENABLED="disabled"
MATOMO_ID="73" MATOMO_ID=""
# Missing MATOMO_HOST (thus hardcoded) MATOMO_HOST="matomo.organisme.fr"
# SMTP Provider: Send In Blue # SMTP Provider: Send In Blue
SENDINBLUE_ENABLED="disabled"
SENDINBLUE_BALANCING="" SENDINBLUE_BALANCING=""
SENDINBLUE_BALANCING_VALUE="" SENDINBLUE_BALANCING_VALUE=""
SENDINBLUE_ENABLED=""
SENDINBLUE_CLIENT_KEY="" SENDINBLUE_CLIENT_KEY=""
SENDINBLUE_SMTP_KEY="" SENDINBLUE_SMTP_KEY=""
SENDINBLUE_USER_NAME="" SENDINBLUE_USER_NAME=""
# SENDINBLUE_LOGIN_URL="https://app.sendinblue.com/account/saml/login/truc"
# Service externe: Fournisseur de tchat pour administrateur | Spécifique démarches-simplifiées.fr
CRISP_ENABLED="disabled"
CRISP_CLIENT_KEY=""
# Service externe: rattrapage de mails envoyés, utile en préprod | Spécifique démarches-simplifiées.fr
MAILTRAP_ENABLED="disabled"
MAILTRAP_USERNAME=""
MAILTRAP_PASSWORD=""
# SMTP Provider: Mailjet # SMTP Provider: Mailjet
MAILJET_API_KEY="" MAILJET_API_KEY=""
MAILJET_SECRET_KEY="" MAILJET_SECRET_KEY=""
# API Entreprise https://api.gouv.fr/api/api-entreprise.html # External service: live chat for admins (specific to démarches-simplifiées.fr)
CRISP_ENABLED="disabled"
CRISP_CLIENT_KEY=""
# External service: mail catcher for staging environments (specific to démarches-simplifiées.fr)
MAILTRAP_ENABLED="disabled"
MAILTRAP_USERNAME=""
MAILTRAP_PASSWORD=""
# API Entreprise credentials
# https://api.gouv.fr/api/api-entreprise.html
API_ENTREPRISE_KEY="" API_ENTREPRISE_KEY=""
# Service externe: CRM de suivi de création d'administrateur | Spécifique démarches-simplifiées.fr # External service: CRM for following admin accounts pipeline (specific to démarches-simplifiées.fr)
PIPEDRIVE_KEY="" PIPEDRIVE_KEY=""
# Liste des réseaux qui passent outre la génération de token pour identifier un device, ainsi que le throttling par rack-attack # Networks bypassing the email login token that verifies new devices, and rack-attack throttling
TRUSTED_NETWORKS="" TRUSTED_NETWORKS=""
# Service externe: mesure de performance d'appli Rails | Spécifique démarches-simplifiées.fr # External service: mesuring performance of the Rails app (specific to démarches-simplifiées.fr)
SKYLIGHT_AUTHENTICATION_KEY="" SKYLIGHT_AUTHENTICATION_KEY=""
# Activer ou non les logs LogRage # Enable or disable Lograge logs
LOGRAGE_ENABLED="disabled" LOGRAGE_ENABLED="disabled"
# Service externe d'horodatage des changements de statut des dossiers (effectué quotidiennement) # Logs source for Lograge
#
# Examples:
# * For local development: tps_local
# * For preproduction: tps_staging
# * For production: tps_prod
LOGRAGE_SOURCE="tps_local"
# External service: timestamping a daily archive of dossiers status changes
UNIVERSIGN_API_URL="https://ws.universign.eu/tsa/post/" UNIVERSIGN_API_URL="https://ws.universign.eu/tsa/post/"
UNIVERSIGN_USERPWD="" UNIVERSIGN_USERPWD=""
# API Geo / Adresse # External service: API Geo / Adresse
API_ADRESSE_URL="https://api-adresse.data.gouv.fr" API_ADRESSE_URL="https://api-adresse.data.gouv.fr"
API_GEO_URL="https://geo.api.gouv.fr" API_GEO_URL="https://geo.api.gouv.fr"
# API Education # External service: API Education
API_EDUCATION_URL="https://data.education.gouv.fr/api/records/1.0" API_EDUCATION_URL="https://data.education.gouv.fr/api/records/1.0"
# Modifier le nb de tentatives de relance de job si echec # Encryption key for sensitive columns in the database
# MAX_ATTEMPTS_JOBS=25
# MAX_ATTEMPTS_API_ENTREPRISE_JOBS=5
# Clé de chriffrement des données sensibles en base
ENCRYPTION_SERVICE_SALT="" ENCRYPTION_SERVICE_SALT=""
# Salt for invisible_captcha session data.
# Must be the same value for all app instances behind a load-balancer.
INVISIBLE_CAPTCHA_SECRET="kikooloool" INVISIBLE_CAPTCHA_SECRET="kikooloool"

View file

@ -1,24 +1,28 @@
# Optional environment variables
# Variables d'environnement optionnelles # Application name, for display and generating links
# Les paramètres pour l'affichage du nom l'application, et pour la génération des liens
APPLICATION_NAME="demarches-simplifiees.fr" APPLICATION_NAME="demarches-simplifiees.fr"
APPLICATION_SHORTNAME="d-s.fr" APPLICATION_SHORTNAME="d-s.fr"
APPLICATION_BASE_URL="https://www.demarches-simplifiees.fr" APPLICATION_BASE_URL="https://www.demarches-simplifiees.fr"
# If defined to "staging", tell the app that it's running on a staging instance
DS_ENV="staging" DS_ENV="staging"
# Utilisation de France Connect # France Connect usage
# FRANCE_CONNECT_ENABLED="disabled" # "enabled" par défaut # FRANCE_CONNECT_ENABLED="disabled" # "enabled" by default
# Utilisation de Agent Connect # Agent Connect usage
# AGENT_CONNECT_ENABLED="disabled" # "enabled" par défaut # AGENT_CONNECT_ENABLED="disabled" # "enabled" by default
# Personnalisation d'instance - URLs des CGU et des mentions légales # Configure the maximum number of times a job is retried
# MAX_ATTEMPTS_JOBS=25
# MAX_ATTEMPTS_API_ENTREPRISE_JOBS=5
# Instance customization: URLs for GTS and legal mentions
# CGU_URL="" # CGU_URL=""
# MENTIONS_LEGALES_URL="" # MENTIONS_LEGALES_URL=""
# Personnalisation d'instance - Adresses Email de l'application et téléphone # Instance customization: support emails addresses and phone
# CONTACT_EMAIL="" # CONTACT_EMAIL=""
# EQUIPE_EMAIL="" # EQUIPE_EMAIL=""
# TECH_EMAIL="" # TECH_EMAIL=""
@ -26,61 +30,62 @@ DS_ENV="staging"
# OLD_CONTACT_EMAIL="" # OLD_CONTACT_EMAIL=""
# CONTACT_PHONE="" # CONTACT_PHONE=""
# Personnalisation d'instance - Adresses postale de l'opérateur de l'instance # Instance customization: postal address of the instance operator
# CONTACT_ADDRESS="Incubateur de Services Numériques / beta.gouv.fr\nServices du Premier Ministre, 20 avenue de Ségur, 75007 Paris" # CONTACT_ADDRESS="Incubateur de Services Numériques / beta.gouv.fr\nServices du Premier Ministre, 20 avenue de Ségur, 75007 Paris"
# Personnalisation d'instance - URL pour la création de compte administrateur sur l'instance # Instance customization: URL for creating an admin account on the instance
# DEMANDE_INSCRIPTION_ADMIN_PAGE_URL="" # DEMANDE_INSCRIPTION_ADMIN_PAGE_URL=""
# Personnalisation d'instance - URL du site web de documentation # Instance customization: URL of the documentation website
# DOC_URL="https://doc.demarches-simplifiees.fr" # DOC_URL="https://doc.demarches-simplifiees.fr"
# Personnalisation d'instance - URL du site web FAQ # Instance customization: URL of the documentation support website
# FAQ_URL="https://faq.demarches-simplifiees.fr" # FAQ_URL="https://faq.demarches-simplifiees.fr"
# Personnalisation d'instance - URL de la déclaration d'accessibilité # Instance customization: URL of the accessibility statement
# ACCESSIBILITE_URL="" # ACCESSIBILITE_URL=""
# Personnalisation d'instance - Page externe "Disponibilité" (status page) # Instance customization: URL of the availability/status webpage
# STATUS_PAGE_URL="" # STATUS_PAGE_URL=""
# Personnalisation d'instance - Favicons ---> à placer dans "app/assets/images" # Instance customization: Favicons ---> to be put in "app/assets/images"
# FAVICON_16PX_SRC="favicons/16x16.png" # FAVICON_16PX_SRC="favicons/16x16.png"
# FAVICON_32PX_SRC="favicons/32x32.png" # FAVICON_32PX_SRC="favicons/32x32.png"
# FAVICON_96PX_SRC="favicons/96x96.png" # FAVICON_96PX_SRC="favicons/96x96.png"
# Personnalisation d'instance - Logo de l'application ---> à placer dans "app/assets/images" # Instance customization: Application logo ---> to be put in "app/assets/images"
# HEADER_LOGO_SRC="marianne.png" # HEADER_LOGO_SRC="marianne.png"
# HEADER_LOGO_ALT="" # HEADER_LOGO_ALT=""
# HEADER_LOGO_WIDTH="65" # HEADER_LOGO_WIDTH="65"
# HEADER_LOGO_HEIGHT="56" # HEADER_LOGO_HEIGHT="56"
# Personnalisation d'instance - Logo dans l'entête des emails ---> à placer dans "app/assets/images" # Instance customization: Emails header logo ---> to be put in "app/assets/images"
# MAILER_LOGO_SRC="mailer/instructeur_mailer/logo.png" # MAILER_LOGO_SRC="mailer/instructeur_mailer/logo.png"
# Personnalisation d'instance - Logo dans le pied de page des emails ---> à placer dans "app/assets/images" # Instance customization: Emails footer logo ---> to be put in "app/assets/images"
# MAILER_FOOTER_LOGO_SRC="mailer/instructeur_mailer/logo-beta-gouv-fr.png" # MAILER_FOOTER_LOGO_SRC="mailer/instructeur_mailer/logo-beta-gouv-fr.png"
# Personnalisation d'instance - Logo par défaut d'une procédure ---> à placer dans "app/assets/images" # Instance customization: Procedure default logo ---> to be put in "app/assets/images"
# PROCEDURE_DEFAULT_LOGO_SRC="republique-francaise-logo.svg" # PROCEDURE_DEFAULT_LOGO_SRC="republique-francaise-logo.svg"
# Personnalisation d'instance - Logo dans le PDF d'export d'un dossier ---> à placer dans "app/assets/images" # Instance customization: PDF export logo ---> to be put in "app/assets/images"
# DOSSIER_PDF_EXPORT_LOGO_SRC="app/assets/images/header/logo-ds-wide.svg" # DOSSIER_PDF_EXPORT_LOGO_SRC="app/assets/images/header/logo-ds-wide.svg"
# Personnalisation d'instance - fichier utilisé pour poser un filigrane sur les pièces d'identité # Instance customization: watermark for identity documents
# WATERMARK_FILE="" # WATERMARK_FILE=""
# Active le mode maintenance # Enabling maintenance mode
# MAINTENANCE_MODE="true" # MAINTENANCE_MODE="true"
# Active la localisation # Enabling localization
# LOCALIZATION_ENABLED="true" # LOCALIZATION_ENABLED="true"
# Désactivé l'OTP pour SuperAdmin # Disabling 2FA for Super-Admins
# SUPER_ADMIN_OTP_ENABLED = "disabled" # "enabled" par défaut # SUPER_ADMIN_OTP_ENABLED = "disabled" # "enabled" par défaut
# API Particulier https://api.gouv.fr/les-api/api-particulier # API Particulier
# https://api.gouv.fr/les-api/api-particulier
# API_PARTICULIER_URL="https://particulier.api.gouv.fr/api" # API_PARTICULIER_URL="https://particulier.api.gouv.fr/api"
# Les instructeurs et administrateurs peuvent changer leur email vers ces domaines # Admins and instructeurs can freely change their email to these domains
# LEGIT_ADMIN_DOMAINS = "domaine_1.com;domaine_2.com" # LEGIT_ADMIN_DOMAINS = "domaine_1.com;domaine_2.com"

View file

@ -1,11 +0,0 @@
fr:
dossier_mailer:
notify_deletion_to_user:
subject: Votre dossier nº %{dossier_id} a bien été supprimé
body: Votre dossier n° %{dossier_id} (%{procedure}) a bien été supprimé. Une trace de ce traitement sera conservée pour ladministration.
notify_instructeur_deletion_to_user:
subject: Votre dossier sur la démarche « %{libelle_demarche} » est supprimé
body_html: |
Afin de limiter la conservation de vos données personnelles, votre dossier n° %{dossier_id} concernant la démarche <b>« %{libelle_demarche} »</b> est <b>supprimé</b>.<br><br>
Cette suppression ne modifie pas le statut final (accepté, refusé ou sans suite) de votre dossier.<br><br>
Une trace de ce dossier est visible dans votre interface : <a href='%{deleted_dossiers_link}'>%{deleted_dossiers_link}</a>.

View file

@ -0,0 +1,5 @@
fr:
dossier_mailer:
notify_en_construction_deletion_to_administration:
subject: Le dossier nº %{dossier_id} a été supprimé à la demande de lusager
body: À la demande de lusager, le dossier n° %{dossier_id} (%{procedure}) a été supprimé.

View file

@ -4,7 +4,7 @@ fr:
subject: subject:
one: Une demande de transfert de dossier vous est adressée one: Une demande de transfert de dossier vous est adressée
other: Une demande de transfert de dossiers vous est adressée other: Une demande de transfert de dossiers vous est adressée
transfer_link: demande de transfer transfer_link: demande de transfert
body: body:
one: | one: |
Accéder à la demande de transfert du dossier en cliquant sur le lien suivant : Accéder à la demande de transfert du dossier en cliquant sur le lien suivant :

View file

@ -12,7 +12,6 @@
defaults: &defaults defaults: &defaults
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
encryption_service_salt: <%= ENV["ENCRYPTION_SERVICE_SALT"] %> encryption_service_salt: <%= ENV["ENCRYPTION_SERVICE_SALT"] %>
signing_key: <%= ENV["SIGNING_KEY"] %>
otp_secret_key: <%= ENV["OTP_SECRET_KEY"] %> otp_secret_key: <%= ENV["OTP_SECRET_KEY"] %>
basic_auth: basic_auth:
username: <%= ENV['BASIC_AUTH_USERNAME'] %> username: <%= ENV['BASIC_AUTH_USERNAME'] %>
@ -56,6 +55,7 @@ defaults: &defaults
api_v3_key: <%= ENV['SENDINBLUE_API_V3_KEY'] %> api_v3_key: <%= ENV['SENDINBLUE_API_V3_KEY'] %>
matomo: matomo:
enabled: <%= ENV['MATOMO_ENABLED'] == 'enabled' %> enabled: <%= ENV['MATOMO_ENABLED'] == 'enabled' %>
host: <%= ENV['MATOMO_HOST'] %>
client_key: <%= ENV['MATOMO_ID'] %> client_key: <%= ENV['MATOMO_ID'] %>
sentry: sentry:
enabled: <%= ENV['SENTRY_ENABLED'] == 'enabled' %> enabled: <%= ENV['SENTRY_ENABLED'] == 'enabled' %>
@ -85,7 +85,6 @@ test:
<<: *defaults <<: *defaults
secret_key_base: aa52abc3f3a629d04a61e9899a24c12f52b24c679cbf45f8ec0cdcc64ab9526d673adca84212882dff3911ac98e0c32ec4729ca7b3429ba18ef4dfd1bd18bc7a secret_key_base: aa52abc3f3a629d04a61e9899a24c12f52b24c679cbf45f8ec0cdcc64ab9526d673adca84212882dff3911ac98e0c32ec4729ca7b3429ba18ef4dfd1bd18bc7a
encryption_service_salt: QUDyMoXyw2YXU8pHnpts3w9MyMpsMQ6BgP62obgCf7PQv encryption_service_salt: QUDyMoXyw2YXU8pHnpts3w9MyMpsMQ6BgP62obgCf7PQv
signing_key: aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017
otp_secret_key: 78ddda3679dc0ba2c99f50bcff04f49d862358dbeb7ead50368fdd6de14392be884ee10a204a0375b4b382e1a842fafe40d7858b7ab4796ec3a67c518d31112b otp_secret_key: 78ddda3679dc0ba2c99f50bcff04f49d862358dbeb7ead50368fdd6de14392be884ee10a204a0375b4b382e1a842fafe40d7858b7ab4796ec3a67c518d31112b
api_entreprise: api_entreprise:
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9oIHllYWgiLCJpYXQiOjE1MTYyMzkwMjJ9.f06sBo3q2Yxnw_TYPFUEs0CozBmcV-XniH_DeKNWzKE" key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9oIHllYWgiLCJpYXQiOjE1MTYyMzkwMjJ9.f06sBo3q2Yxnw_TYPFUEs0CozBmcV-XniH_DeKNWzKE"

View file

@ -0,0 +1,5 @@
class AddAttestationTemplateIdToProcedureRevisions < ActiveRecord::Migration[6.1]
def change
add_reference :procedure_revisions, :attestation_template, foreign_key: { to_table: :attestation_templates }, null: true, index: true
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_12_02_133139) do ActiveRecord::Schema.define(version: 2022_01_10_133139) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -593,6 +593,8 @@ ActiveRecord::Schema.define(version: 2021_12_02_133139) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.datetime "published_at" t.datetime "published_at"
t.bigint "attestation_template_id"
t.index ["attestation_template_id"], name: "index_procedure_revisions_on_attestation_template_id"
t.index ["procedure_id"], name: "index_procedure_revisions_on_procedure_id" t.index ["procedure_id"], name: "index_procedure_revisions_on_procedure_id"
end end
@ -872,6 +874,7 @@ ActiveRecord::Schema.define(version: 2021_12_02_133139) do
add_foreign_key "procedure_revision_types_de_champ", "procedure_revision_types_de_champ", column: "parent_id" add_foreign_key "procedure_revision_types_de_champ", "procedure_revision_types_de_champ", column: "parent_id"
add_foreign_key "procedure_revision_types_de_champ", "procedure_revisions", column: "revision_id" add_foreign_key "procedure_revision_types_de_champ", "procedure_revisions", column: "revision_id"
add_foreign_key "procedure_revision_types_de_champ", "types_de_champ" add_foreign_key "procedure_revision_types_de_champ", "types_de_champ"
add_foreign_key "procedure_revisions", "attestation_templates"
add_foreign_key "procedure_revisions", "procedures" add_foreign_key "procedure_revisions", "procedures"
add_foreign_key "procedures", "procedure_revisions", column: "draft_revision_id" add_foreign_key "procedures", "procedure_revisions", column: "draft_revision_id"
add_foreign_key "procedures", "procedure_revisions", column: "published_revision_id" add_foreign_key "procedures", "procedure_revisions", column: "published_revision_id"

View file

@ -0,0 +1,23 @@
namespace :after_party do
desc 'Deployment task: moving Attestations from Procedure to ProcedureRevision'
task revise_attestation_templates: :environment do
rake_puts "Running deploy task 'revise_attestation_templates'"
attestation_templates = AttestationTemplate.where.not(procedure_id: nil)
progress = ProgressReport.new(attestation_templates.count)
attestation_templates.find_each do |attestation_template|
ProcedureRevision
.where(procedure_id: attestation_template.procedure_id, attestation_template_id: nil)
.update_all(attestation_template_id: attestation_template)
attestation_template.update_column(:procedure_id, nil)
progress.inc
end
progress.finish
# 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: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end

View file

@ -1,8 +1,8 @@
include ActionDispatch::TestProcess include ActionDispatch::TestProcess
describe Administrateurs::AttestationTemplatesController, type: :controller do describe Administrateurs::AttestationTemplatesController, type: :controller do
let!(:attestation_template) { create(:attestation_template) }
let(:admin) { create(:administrateur) } let(:admin) { create(:administrateur) }
let(:attestation_template) { build(:attestation_template) }
let!(:procedure) { create :procedure, administrateur: admin, attestation_template: attestation_template } let!(:procedure) { create :procedure, administrateur: admin, attestation_template: attestation_template }
let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } let(:logo) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
let(:logo2) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') } let(:logo2) { fixture_file_upload('spec/fixtures/files/white.png', 'image/png') }
@ -41,7 +41,7 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
end end
context 'if an attestation template exists on the procedure' do context 'if an attestation template exists on the procedure' do
after { procedure.attestation_template.destroy } after { procedure.draft_revision.attestation_template&.destroy }
context 'with images' do context 'with images' do
let!(:attestation_template) do let!(:attestation_template) do
@ -115,14 +115,14 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
procedure.reload procedure.reload
end end
it { expect(procedure.attestation_template).to have_attributes(attestation_params) } it { expect(procedure.draft_attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.activated).to be true } it { expect(procedure.draft_attestation_template.activated).to be true }
it { expect(procedure.attestation_template.logo.download).to eq(logo2.read) } it { expect(procedure.draft_attestation_template.logo.download).to eq(logo2.read) }
it { expect(procedure.attestation_template.signature.download).to eq(signature2.read) } it { expect(procedure.draft_attestation_template.signature.download).to eq(signature2.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) } it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été sauvegardée") } it { expect(flash.notice).to eq("L'attestation a bien été sauvegardée") }
after { procedure.attestation_template.destroy } after { procedure.draft_attestation_template.destroy }
end end
context 'when something wrong happens in the attestation template creation' do context 'when something wrong happens in the attestation template creation' do
@ -140,7 +140,7 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) } it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.alert).to be_present } it { expect(flash.alert).to be_present }
it { expect(procedure.attestation_template).to be nil } it { expect(procedure.draft_attestation_template).to be nil }
end end
end end
@ -158,13 +158,13 @@ describe Administrateurs::AttestationTemplatesController, type: :controller do
procedure.reload procedure.reload
end end
it { expect(procedure.attestation_template).to have_attributes(attestation_params) } it { expect(procedure.draft_attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.logo.download).to eq(logo2.read) } it { expect(procedure.draft_attestation_template.logo.download).to eq(logo2.read) }
it { expect(procedure.attestation_template.signature.download).to eq(signature2.read) } it { expect(procedure.draft_attestation_template.signature.download).to eq(signature2.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) } it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été modifiée") } it { expect(flash.notice).to eq("L'attestation a bien été modifiée") }
after { procedure.attestation_template.destroy } after { procedure.draft_attestation_template&.destroy }
end end
context 'when something wrong happens in the attestation template creation' do context 'when something wrong happens in the attestation template creation' do

View file

@ -411,9 +411,21 @@ describe Administrateurs::ProceduresController, type: :controller do
context 'when dossier is en_construction' do context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) } let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.close?).to be_truthy } it do
it { expect(procedure.reload.discarded?).to be_truthy } expect(procedure.reload.close?).to be_truthy
it { expect(dossier.reload.discarded?).to be_truthy } expect(procedure.discarded?).to be_truthy
expect(dossier.reload.kept?).to be_truthy
end
end
context 'when dossier is accepte' do
let(:dossier) { create(:dossier, :accepte, procedure: procedure_published) }
it do
expect(procedure.reload.close?).to be_truthy
expect(procedure.discarded?).to be_truthy
expect(dossier.reload.hidden_by_administration?).to be_truthy
end
end end
end end
@ -427,8 +439,19 @@ describe Administrateurs::ProceduresController, type: :controller do
context 'when dossier is en_construction' do context 'when dossier is en_construction' do
let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) } let(:dossier) { create(:dossier, :en_construction, procedure: procedure_published) }
it { expect(procedure.reload.discarded?).to be_truthy } it do
it { expect(dossier.reload.discarded?).to be_truthy } expect(procedure.reload.discarded?).to be_truthy
expect(dossier.reload.kept?).to be_truthy
end
end
context 'when dossier is accepte' do
let(:dossier) { create(:dossier, :accepte, procedure: procedure_published) }
it do
expect(procedure.reload.discarded?).to be_truthy
expect(dossier.reload.hidden_by_administration?).to be_truthy
end
end end
end end

View file

@ -766,7 +766,6 @@ describe Instructeurs::DossiersController, type: :controller do
before do before do
dossier.accepter!(instructeur: instructeur, motivation: "le dossier est correct") dossier.accepter!(instructeur: instructeur, motivation: "le dossier est correct")
dossier.update!(hidden_by_user_at: Time.zone.now.beginning_of_day.utc) dossier.update!(hidden_by_user_at: Time.zone.now.beginning_of_day.utc)
allow(DossierMailer).to receive(:notify_instructeur_deletion_to_user).and_return(double(deliver_later: nil))
subject subject
end end
@ -775,10 +774,6 @@ describe Instructeurs::DossiersController, type: :controller do
expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer') expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).to eq('supprimer')
end end
it 'send an email to the user' do
expect(DossierMailer).to have_received(:notify_instructeur_deletion_to_user).with(DeletedDossier.where(dossier_id: dossier.id).first, dossier.user.email)
end
it 'add a record into deleted_dossiers table' do it 'add a record into deleted_dossiers table' do
expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(1) expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(1)
expect(DeletedDossier.where(dossier_id: dossier.id).first.revision_id).to eq(dossier.revision_id) expect(DeletedDossier.where(dossier_id: dossier.id).first.revision_id).to eq(dossier.revision_id)
@ -794,7 +789,6 @@ describe Instructeurs::DossiersController, type: :controller do
context 'when the instructeur want to delete a dossier with a decision and not hidden by user' do context 'when the instructeur want to delete a dossier with a decision and not hidden by user' do
before do before do
dossier.accepter!(instructeur: instructeur, motivation: "le dossier est correct") dossier.accepter!(instructeur: instructeur, motivation: "le dossier est correct")
allow(DossierMailer).to receive(:notify_instructeur_deletion_to_user).and_return(double(deliver_later: nil))
subject subject
end end
@ -803,10 +797,6 @@ describe Instructeurs::DossiersController, type: :controller do
expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).not_to eq('supprimer') expect(DossierOperationLog.where(dossier_id: dossier.id).last.operation).not_to eq('supprimer')
end end
it 'does not send an email to the user' do
expect(DossierMailer).not_to have_received(:notify_instructeur_deletion_to_user).with(DeletedDossier.where(dossier_id: dossier.id).first, dossier.user.email)
end
it 'add a record into deleted_dossiers table' do it 'add a record into deleted_dossiers table' do
expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0) expect(DeletedDossier.where(dossier_id: dossier.id).count).to eq(0)
end end

View file

@ -107,6 +107,7 @@ describe Instructeurs::ProceduresController, type: :controller do
before do before do
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction))
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction), hidden_by_user_at: 1.hour.ago)
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction)) create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_instruction))
create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), archived: true) create(:dossier, procedure: procedure, state: Dossier.states.fetch(:sans_suite), archived: true)
@ -238,13 +239,26 @@ describe Instructeurs::ProceduresController, type: :controller do
let!(:new_unfollow_dossier) { create(:dossier, :en_instruction, procedure: procedure) } let!(:new_unfollow_dossier) { create(:dossier, :en_instruction, procedure: procedure) }
before { subject } before { subject }
it { expect(assigns(:a_suivre_dossiers)).to match_array([new_unfollow_dossier]) } it { expect(assigns(:a_suivre_dossiers)).to match_array([new_unfollow_dossier]) }
it { expect(assigns(:followed_dossiers)).to be_empty } it { expect(assigns(:followed_dossiers)).to be_empty }
it { expect(assigns(:termines_dossiers)).to be_empty } it { expect(assigns(:termines_dossiers)).to be_empty }
it { expect(assigns(:all_state_dossiers)).to match_array([new_unfollow_dossier]) } it { expect(assigns(:all_state_dossiers)).to match_array([new_unfollow_dossier]) }
it { expect(assigns(:archived_dossiers)).to be_empty } it { expect(assigns(:archived_dossiers)).to be_empty }
context 'with a dossier en contruction hidden by user' do
let!(:hidden_dossier) { create(:dossier, groupe_instructeur: gi_2, state: Dossier.states.fetch(:en_construction), hidden_by_user_at: 1.hour.ago) }
before { subject }
it { expect(assigns(:all_state_dossiers)).to match_array([new_unfollow_dossier]) }
end
context 'with a dossier en contruction not hidden by user' do
let!(:en_construction_dossier) { create(:dossier, groupe_instructeur: gi_2, state: Dossier.states.fetch(:en_construction)) }
before { subject }
it { expect(assigns(:all_state_dossiers)).to match_array([new_unfollow_dossier, en_construction_dossier]) }
end
context 'and dossiers without follower on each of the others groups' do context 'and dossiers without follower on each of the others groups' do
let!(:new_unfollow_dossier_on_gi_2) { create(:dossier, groupe_instructeur: gi_2, state: Dossier.states.fetch(:en_instruction)) } let!(:new_unfollow_dossier_on_gi_2) { create(:dossier, groupe_instructeur: gi_2, state: Dossier.states.fetch(:en_instruction)) }
let!(:new_unfollow_dossier_on_gi_3) { create(:dossier, groupe_instructeur: gi_3, state: Dossier.states.fetch(:en_instruction)) } let!(:new_unfollow_dossier_on_gi_3) { create(:dossier, groupe_instructeur: gi_3, state: Dossier.states.fetch(:en_instruction)) }

View file

@ -1,47 +0,0 @@
describe Manager::DossiersController, type: :controller do
let(:super_admin) { create :super_admin }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier) }
let(:operations) { dossier.dossier_operation_logs.map(&:operation).map(&:to_sym) }
before { sign_in super_admin }
describe '#discard' do
let(:dossier) { create(:dossier, :en_construction) }
before do
post :discard, params: { id: dossier.id }
dossier.reload
end
it { expect(dossier.discarded?).to be_truthy }
it { expect(deleted_dossier).not_to be_nil }
it { expect(deleted_dossier.reason).to eq("manager_request") }
it { expect(operations).to eq([:supprimer]) }
end
describe '#restore' do
let(:dossier) { create(:dossier, :en_construction, :with_individual) }
before do
dossier.discard_and_keep_track!(super_admin, :manager_request)
post :restore, params: { id: dossier.id }
dossier.reload
end
it { expect(dossier.kept?).to be_truthy }
it { expect(deleted_dossier).to be_nil }
it { expect(operations).to eq([:supprimer, :restaurer]) }
end
describe '#repasser_en_instruction' do
let(:dossier) { create(:dossier, :accepte) }
before do
post :repasser_en_instruction, params: { id: dossier.id }
dossier.reload
end
it { expect(dossier.en_instruction?).to be_truthy }
end
end

View file

@ -27,10 +27,8 @@ describe Manager::ProceduresController, type: :controller do
end end
describe '#discard' do describe '#discard' do
let(:dossier) { create(:dossier, :en_construction) } let(:dossier) { create(:dossier, :accepte) }
let(:procedure) { dossier.procedure } let(:procedure) { dossier.procedure }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
let(:operations) { dossier.dossier_operation_logs.map(&:operation).map(&:to_sym) }
before do before do
post :discard, params: { id: procedure.id } post :discard, params: { id: procedure.id }
@ -39,17 +37,12 @@ describe Manager::ProceduresController, type: :controller do
end end
it { expect(procedure.discarded?).to be_truthy } it { expect(procedure.discarded?).to be_truthy }
it { expect(dossier.discarded?).to be_truthy } it { expect(dossier.hidden_by_administration?).to be_truthy }
it { expect(deleted_dossier).not_to be_nil }
it { expect(deleted_dossier.reason).to eq("procedure_removed") }
it { expect(operations).to eq([:supprimer]) }
end end
describe '#restore' do describe '#restore' do
let(:dossier) { create(:dossier, :en_construction, :with_individual) } let(:dossier) { create(:dossier, :accepte, :with_individual) }
let(:procedure) { dossier.procedure } let(:procedure) { dossier.procedure }
let(:deleted_dossier) { DeletedDossier.find_by(dossier_id: dossier.id) }
let(:operations) { dossier.dossier_operation_logs.map(&:operation).map(&:to_sym) }
before do before do
procedure.discard_and_keep_track!(super_admin) procedure.discard_and_keep_track!(super_admin)
@ -59,9 +52,7 @@ describe Manager::ProceduresController, type: :controller do
end end
it { expect(procedure.kept?).to be_truthy } it { expect(procedure.kept?).to be_truthy }
it { expect(dossier.kept?).to be_truthy } it { expect(dossier.hidden_by_administration?).to be_falsey }
it { expect(deleted_dossier).to be_nil }
it { expect(operations).to eq([:supprimer, :restaurer]) }
end end
describe '#index' do describe '#index' do

View file

@ -1008,7 +1008,6 @@ describe Users::DossiersController, type: :controller do
shared_examples_for "the dossier can not be deleted" do shared_examples_for "the dossier can not be deleted" do
it "doesnt notify the deletion" do it "doesnt notify the deletion" do
expect(DossierMailer).not_to receive(:notify_deletion_to_administration) expect(DossierMailer).not_to receive(:notify_deletion_to_administration)
expect(DossierMailer).not_to receive(:notify_deletion_to_user)
subject subject
end end
@ -1024,7 +1023,6 @@ describe Users::DossiersController, type: :controller do
it "notifies the user and the admin of the deletion" do it "notifies the user and the admin of the deletion" do
expect(DossierMailer).to receive(:notify_deletion_to_administration).with(kind_of(DeletedDossier), dossier.procedure.administrateurs.first.email).and_return(double(deliver_later: nil)) expect(DossierMailer).to receive(:notify_deletion_to_administration).with(kind_of(DeletedDossier), dossier.procedure.administrateurs.first.email).and_return(double(deliver_later: nil))
expect(DossierMailer).to receive(:notify_deletion_to_user).with(kind_of(DeletedDossier), dossier.user.email).and_return(double(deliver_later: nil))
subject subject
end end

View file

@ -4,8 +4,6 @@ FactoryBot.define do
body { 'body' } body { 'body' }
footer { 'footer' } footer { 'footer' }
activated { true } activated { true }
association :procedure
end end
trait :with_files do trait :with_files do

View file

@ -23,10 +23,11 @@ FactoryBot.define do
types_de_champ { [] } types_de_champ { [] }
types_de_champ_private { [] } types_de_champ_private { [] }
updated_at { nil } updated_at { nil }
attestation_template { }
end end
after(:build) do |procedure, evaluator| after(:build) do |procedure, evaluator|
initial_revision = build(:procedure_revision, procedure: procedure) initial_revision = build(:procedure_revision, procedure: procedure, attestation_template: evaluator.attestation_template)
add_types_de_champs(evaluator.types_de_champ, to: initial_revision, scope: :public) add_types_de_champs(evaluator.types_de_champ, to: initial_revision, scope: :public)
add_types_de_champs(evaluator.types_de_champ_private, to: initial_revision, scope: :private) add_types_de_champs(evaluator.types_de_champ_private, to: initial_revision, scope: :private)

View file

@ -60,16 +60,11 @@ RSpec.describe DossierMailer, type: :mailer do
it { expect(subject.perform_deliveries).to be_falsy } it { expect(subject.perform_deliveries).to be_falsy }
end end
describe '.notify_deletion_to_user' do def notify_deletion_to_administration(deleted_dossier, to_email)
let(:deleted_dossier) { build(:deleted_dossier) } @subject = default_i18n_subject(dossier_id: deleted_dossier.dossier_id)
@deleted_dossier = deleted_dossier
subject { described_class.notify_deletion_to_user(deleted_dossier, to_email) } mail(to: to_email, subject: @subject)
it { expect(subject.subject).to eq("Votre dossier nº #{deleted_dossier.dossier_id} a bien été supprimé") }
it { expect(subject.body).to include("Votre dossier") }
it { expect(subject.body).to include(deleted_dossier.dossier_id) }
it { expect(subject.body).to include("a bien été supprimé") }
it { expect(subject.body).to include(deleted_dossier.procedure.libelle) }
end end
describe '.notify_deletion_to_administration' do describe '.notify_deletion_to_administration' do

View file

@ -45,10 +45,6 @@ class DossierMailerPreview < ActionMailer::Preview
DossierMailer.notify_brouillon_deletion(dossier_hashes, usager_email) DossierMailer.notify_brouillon_deletion(dossier_hashes, usager_email)
end end
def notify_deletion_to_user
DossierMailer.notify_deletion_to_user(deleted_dossier, usager_email)
end
def notify_instructeur_deletion_to_user def notify_instructeur_deletion_to_user
DossierMailer.notify_instructeur_deletion_to_user(deleted_dossier, usager_email) DossierMailer.notify_instructeur_deletion_to_user(deleted_dossier, usager_email)
end end

View file

@ -799,7 +799,6 @@ describe Dossier do
let(:reason) { :user_request } let(:reason) { :user_request }
before do before do
allow(DossierMailer).to receive(:notify_deletion_to_user).and_return(double(deliver_later: nil))
allow(DossierMailer).to receive(:notify_deletion_to_administration).and_return(double(deliver_later: nil)) allow(DossierMailer).to receive(:notify_deletion_to_administration).and_return(double(deliver_later: nil))
end end
@ -835,10 +834,6 @@ describe Dossier do
expect(deleted_dossier.deleted_at).to be_present expect(deleted_dossier.deleted_at).to be_present
end end
it 'notifies the user' do
expect(DossierMailer).to have_received(:notify_deletion_to_user).with(deleted_dossier, dossier.user.email)
end
it 'records the operation in the log' do it 'records the operation in the log' do
expect(last_operation.operation).to eq("supprimer") expect(last_operation.operation).to eq("supprimer")
expect(last_operation.automatic_operation?).to be_falsey expect(last_operation.automatic_operation?).to be_falsey
@ -1327,29 +1322,31 @@ describe Dossier do
end end
describe 'discarded_brouillon_expired and discarded_en_construction_expired' do describe 'discarded_brouillon_expired and discarded_en_construction_expired' do
let(:super_admin) { create(:super_admin) } let(:administrateur) { create(:administrateur) }
let(:user) { administrateur.user }
let(:reason) { DeletedDossier.reasons.fetch(:user_request) }
before do before do
create(:dossier) create(:dossier, user: user)
create(:dossier, :en_construction) create(:dossier, :en_construction, user: user)
create(:dossier).discard! create(:dossier, user: user).discard_and_keep_track!(user, reason)
create(:dossier, :en_construction).discard! create(:dossier, :en_construction, user: user).discard_and_keep_track!(user, reason)
Timecop.travel(2.months.ago) do Timecop.travel(2.months.ago) do
create(:dossier).discard! create(:dossier, user: user).discard_and_keep_track!(user, reason)
create(:dossier, :en_construction).discard! create(:dossier, :en_construction, user: user).discard_and_keep_track!(user, reason)
create(:dossier).procedure.discard_and_keep_track!(super_admin) create(:dossier, user: user).procedure.discard_and_keep_track!(administrateur)
create(:dossier, :en_construction).procedure.discard_and_keep_track!(super_admin) create(:dossier, :en_construction, user: user).procedure.discard_and_keep_track!(administrateur)
end end
Timecop.travel(1.week.ago) do Timecop.travel(1.week.ago) do
create(:dossier).discard! create(:dossier, user: user).discard_and_keep_track!(user, reason)
create(:dossier, :en_construction).discard! create(:dossier, :en_construction, user: user).discard_and_keep_track!(user, reason)
end end
end end
it { expect(Dossier.discarded_brouillon_expired.count).to eq(3) } it { expect(Dossier.discarded_brouillon_expired.count).to eq(2) }
it { expect(Dossier.discarded_en_construction_expired.count).to eq(3) } it { expect(Dossier.discarded_en_construction_expired.count).to eq(0) }
end end
describe "discarded procedure dossier should be able to access it's procedure" do describe "discarded procedure dossier should be able to access it's procedure" do

View file

@ -169,7 +169,7 @@ describe ProcedureRevision do
expect(new_revision.revision_types_de_champ.last.position).to eq(2) expect(new_revision.revision_types_de_champ.last.position).to eq(2)
expect(new_revision.revision_types_de_champ.last.type_de_champ).to eq(new_type_de_champ) expect(new_revision.revision_types_de_champ.last.type_de_champ).to eq(new_type_de_champ)
expect(new_revision.revision_types_de_champ.last.type_de_champ.revision).to eq(new_revision) expect(new_revision.revision_types_de_champ.last.type_de_champ.revision).to eq(new_revision)
expect(procedure.active_revision.changed?(new_revision)).to be_truthy expect(procedure.active_revision.different_from?(new_revision)).to be_truthy
expect(procedure.active_revision.compare(new_revision)).to eq([ expect(procedure.active_revision.compare(new_revision)).to eq([
{ {
op: :add, op: :add,

View file

@ -66,16 +66,12 @@ describe Procedure do
end end
describe '#closed_mail_template_attestation_inconsistency_state' do describe '#closed_mail_template_attestation_inconsistency_state' do
let(:procedure_without_attestation) { create(:procedure, closed_mail: closed_mail) } let(:procedure_without_attestation) { create(:procedure, closed_mail: closed_mail, attestation_template: nil) }
let(:procedure_with_active_attestation) do let(:procedure_with_active_attestation) do
procedure = create(:procedure, closed_mail: closed_mail) create(:procedure, closed_mail: closed_mail, attestation_template: build(:attestation_template, activated: true))
create(:attestation_template, procedure: procedure, activated: true)
procedure
end end
let(:procedure_with_inactive_attestation) do let(:procedure_with_inactive_attestation) do
procedure = create(:procedure, closed_mail: closed_mail) create(:procedure, closed_mail: closed_mail, attestation_template: build(:attestation_template, activated: false))
create(:attestation_template, procedure: procedure, activated: false)
procedure
end end
subject { procedure.closed_mail_template_attestation_inconsistency_state } subject { procedure.closed_mail_template_attestation_inconsistency_state }
@ -272,6 +268,16 @@ describe Procedure do
let(:procedure) { build(:procedure, monavis_embed: monavis_issue_phillipe) } let(:procedure) { build(:procedure, monavis_embed: monavis_issue_phillipe) }
it { expect(procedure.valid?).to eq(true) } it { expect(procedure.valid?).to eq(true) }
end end
context 'Monavis embed code without title allowed' do
monavis_issue_bouchra = <<-MSG
<a href="https://voxusagers.numerique.gouv.fr/Demarches/3193?&view-mode=formulaire-avis&nd_mode=en-ligne-enti%C3%A8rement&nd_source=button&key=58e099a09c02abe629c14905ed2b055d">
<img src="https://voxusagers.numerique.gouv.fr/static/bouton-bleu.svg" alt="Je donne mon avis" />
</a>
MSG
let(:procedure) { build(:procedure, monavis_embed: monavis_issue_bouchra) }
it { expect(procedure.valid?).to eq(true) }
end
end end
shared_examples 'duree de conservation' do shared_examples 'duree de conservation' do

View file

@ -416,5 +416,26 @@ describe ExpiredDossiersDeletionService do
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_1], dossier_1.procedure.administrateurs.first.email) } it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_1], dossier_1.procedure.administrateurs.first.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) } it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) }
end end
context 'with 1 dossier deleted by user and 1 dossier deleted by administration' do
let!(:dossier_1) { create(:dossier, :accepte, procedure: procedure, user: user, hidden_by_administration_at: 1.hour.ago, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let!(:dossier_2) { create(:dossier, :refuse, procedure: procedure_2, user: user, hidden_by_user_at: 1.hour.ago, termine_close_to_expiration_notice_sent_at: (warning_period + 1.day).ago) }
let(:deleted_dossier_1) { DeletedDossier.find_by(dossier_id: dossier_1.id) }
let(:deleted_dossier_2) { DeletedDossier.find_by(dossier_id: dossier_2.id) }
let!(:instructeur) { create(:instructeur) }
before do
instructeur.followed_dossiers << dossier_1 << dossier_2
ExpiredDossiersDeletionService.delete_expired_termine_and_notify
end
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).once }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_user).with(match_array([deleted_dossier_1]), user.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).twice }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with(match_array([deleted_dossier_2]), instructeur.email) }
it { expect(DossierMailer).to have_received(:notify_automatic_deletion_to_administration).with([deleted_dossier_2], dossier_2.procedure.administrateurs.first.email) }
end
end end
end end

View file

@ -5,6 +5,11 @@ describe ProcedureArchiveService do
let(:year) { 2020 } let(:year) { 2020 }
let(:month) { 3 } let(:month) { 3 }
let(:date_month) { Date.strptime("#{year}-#{month}", "%Y-%m") } let(:date_month) { Date.strptime("#{year}-#{month}", "%Y-%m") }
let(:groupe_instructeurs) { instructeur.groupe_instructeurs }
before do
procedure.defaut_groupe_instructeur.add(instructeur)
end
describe '#create_pending_archive' do describe '#create_pending_archive' do
context 'for a specific month' do context 'for a specific month' do
@ -37,7 +42,7 @@ describe ProcedureArchiveService do
after { Timecop.return } after { Timecop.return }
context 'for a specific month' do context 'for a specific month' do
let(:archive) { create(:archive, time_span_type: 'monthly', status: 'pending', month: date_month) } let(:archive) { create(:archive, time_span_type: 'monthly', status: 'pending', month: date_month, groupe_instructeurs: groupe_instructeurs) }
let(:year) { 2021 } let(:year) { 2021 }
let(:mailer) { double('mailer', deliver_later: true) } let(:mailer) { double('mailer', deliver_later: true) }
@ -99,7 +104,7 @@ describe ProcedureArchiveService do
end end
context 'for all months' do context 'for all months' do
let(:archive) { create(:archive, time_span_type: 'everything', status: 'pending') } let(:archive) { create(:archive, time_span_type: 'everything', status: 'pending', groupe_instructeurs: groupe_instructeurs) }
let(:mailer) { double('mailer', deliver_later: true) } let(:mailer) { double('mailer', deliver_later: true) }
it 'collect files' do it 'collect files' do
@ -125,7 +130,7 @@ describe ProcedureArchiveService do
after { Timecop.return } after { Timecop.return }
context 'for a specific month' do context 'for a specific month' do
let(:archive) { create(:archive, time_span_type: 'monthly', status: 'pending', month: date_month) } let(:archive) { create(:archive, time_span_type: 'monthly', status: 'pending', month: date_month, groupe_instructeurs: groupe_instructeurs) }
let(:year) { 2021 } let(:year) { 2021 }
let(:mailer) { double('mailer', deliver_later: true) } let(:mailer) { double('mailer', deliver_later: true) }
@ -228,7 +233,7 @@ describe ProcedureArchiveService do
end end
context 'for all months' do context 'for all months' do
let(:archive) { create(:archive, time_span_type: 'everything', status: 'pending') } let(:archive) { create(:archive, time_span_type: 'everything', status: 'pending', groupe_instructeurs: groupe_instructeurs) }
let(:mailer) { double('mailer', deliver_later: true) } let(:mailer) { double('mailer', deliver_later: true) }
it 'collect files' do it 'collect files' do

View file

@ -105,8 +105,7 @@ module SystemHelpers
def select_combobox(libelle, fill_with, value, check: true) def select_combobox(libelle, fill_with, value, check: true)
fill_in libelle, with: fill_with fill_in libelle, with: fill_with
selector = "li[data-option-value=\"#{value}\"]" find('li[role="option"]', text: value).click
find(selector).click
if check if check
check_selected_value(libelle, with: value) check_selected_value(libelle, with: value)
end end

View file

@ -165,8 +165,8 @@ describe 'Instructing a dossier:', js: true do
click_on 'Personnes impliquées' click_on 'Personnes impliquées'
select_combobox('Emails', instructeur_2.email, instructeur_2.id, check: false) select_combobox('Emails', instructeur_2.email, instructeur_2.email, check: false)
select_combobox('Emails', instructeur_3.email, instructeur_3.id, check: false) select_combobox('Emails', instructeur_3.email, instructeur_3.email, check: false)
click_on 'Envoyer' click_on 'Envoyer'

View file

@ -32,7 +32,7 @@ describe "procedure filters" do
end end
scenario "should add be able to add created_at column", js: true do scenario "should add be able to add created_at column", js: true do
add_column("Créé le", "self/created_at") add_column("Créé le")
within ".dossiers-table" do within ".dossiers-table" do
expect(page).to have_link("Créé le") expect(page).to have_link("Créé le")
expect(page).to have_link(new_unfollow_dossier.created_at.strftime('%d/%m/%Y')) expect(page).to have_link(new_unfollow_dossier.created_at.strftime('%d/%m/%Y'))
@ -40,7 +40,7 @@ describe "procedure filters" do
end end
scenario "should add be able to add and remove custom type_de_champ column", js: true do scenario "should add be able to add and remove custom type_de_champ column", js: true do
add_column(type_de_champ.libelle, "type_de_champ/#{type_de_champ.stable_id}") add_column(type_de_champ.libelle)
within ".dossiers-table" do within ".dossiers-table" do
expect(page).to have_link(type_de_champ.libelle) expect(page).to have_link(type_de_champ.libelle)
expect(page).to have_link(champ.value) expect(page).to have_link(champ.value)
@ -123,9 +123,9 @@ describe "procedure filters" do
click_button "Ajouter le filtre" click_button "Ajouter le filtre"
end end
def add_column(column_name, column_path) def add_column(column_name)
click_on 'Personnaliser' click_on 'Personnaliser'
select_combobox('Colonne à afficher', column_name, column_path, check: false) select_combobox('Colonne à afficher', column_name, column_name, check: false)
click_button "Enregistrer" click_button "Enregistrer"
end end

View file

@ -1,5 +1,6 @@
describe 'admin/_closed_mail_template_attestation_inconsistency_alert.html.haml', type: :view do describe 'admin/_closed_mail_template_attestation_inconsistency_alert.html.haml', type: :view do
let(:procedure) { create(:procedure, closed_mail: closed_mail) } let(:procedure) { create(:procedure, closed_mail: closed_mail, attestation_template: attestation_template) }
let(:attestation_template) { nil }
def alert def alert
assign(:procedure, procedure) assign(:procedure, procedure)
@ -23,7 +24,7 @@ describe 'admin/_closed_mail_template_attestation_inconsistency_alert.html.haml'
context 'when there is an active attestation but the closed mail template does not mention it' do context 'when there is an active attestation but the closed mail template does not mention it' do
let(:closed_mail) { create(:closed_mail) } let(:closed_mail) { create(:closed_mail) }
let!(:attestation_template) { create(:attestation_template, procedure: procedure, activated: true) } let(:attestation_template) { build(:attestation_template) }
it { expect(alert).to include("Cette démarche comporte une attestation, mais laccusé dacceptation ne la mentionne pas") } it { expect(alert).to include("Cette démarche comporte une attestation, mais laccusé dacceptation ne la mentionne pas") }
it { expect(alert).to include(edit_admin_procedure_mail_template_path(procedure, Mails::ClosedMail::SLUG)) } it { expect(alert).to include(edit_admin_procedure_mail_template_path(procedure, Mails::ClosedMail::SLUG)) }

View file

@ -8703,9 +8703,9 @@ nano-time@1.0.0:
big-integer "^1.6.16" big-integer "^1.6.16"
nanoid@^3.1.30: nanoid@^3.1.30:
version "3.1.30" version "3.2.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
nanomatch@^1.2.9: nanomatch@^1.2.9:
version "1.2.13" version "1.2.13"