Merge pull request #6287 from betagouv/main

2021-06-18-01
This commit is contained in:
krichtof 2021-06-18 12:56:26 +02:00 committed by GitHub
commit bbad67d271
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 280 additions and 137 deletions

View file

@ -91,7 +91,7 @@ module Experts
redirect_to url_for(expert_all_avis_path)
else
flash[:alert] = user.errors.full_messages
redirect_to url_for(sign_up_expert_avis_path(procedure_id, avis_id, email))
redirect_to sign_up_expert_avis_path(procedure_id, avis_id, email: email)
end
end

View file

@ -210,6 +210,7 @@ module Instructeurs
@dossiers_funnel = @procedure.stats_dossiers_funnel
@termines_states = @procedure.stats_termines_states
@termines_by_week = @procedure.stats_termines_by_week
@usual_traitement_time_by_month = @procedure.stats_usual_traitement_time_by_month_in_days
end
private

View file

@ -5,6 +5,7 @@ module Users
return procedure_not_found if @procedure.blank? || @procedure.brouillon?
@usual_traitement_time = @procedure.stats_usual_traitement_time
@usual_traitement_time_by_month = @procedure.stats_usual_traitement_time_by_month_in_days
@dossiers_funnel = @procedure.stats_dossiers_funnel
@termines_states = @procedure.stats_termines_states
@termines_by_week = @procedure.stats_termines_by_week

View file

@ -1,9 +1,21 @@
module ProcedureStatsConcern
extend ActiveSupport::Concern
NB_DAYS_RECENT_DOSSIERS = 30
# Percentage of dossiers considered to compute the 'usual traitement time'.
# For instance, a value of '90' means that the usual traitement time will return
# the duration under which 90% of the given dossiers are closed.
USUAL_TRAITEMENT_TIME_PERCENTILE = 90
def stats_usual_traitement_time
Rails.cache.fetch("#{cache_key_with_version}/stats_usual_traitement_time", expires_in: 12.hours) do
usual_traitement_time
usual_traitement_time_for_recent_dossiers(NB_DAYS_RECENT_DOSSIERS)
end
end
def stats_usual_traitement_time_by_month_in_days
Rails.cache.fetch("#{cache_key_with_version}/stats_usual_traitement_time_by_month_in_days", expires_in: 12.hours) do
usual_traitement_time_by_month_in_days
end
end
@ -45,4 +57,51 @@ module ProcedureStatsConcern
end
end
end
def traitement_times(date_range)
Traitement.for_traitement_time_stats(self)
.where(processed_at: date_range)
.pluck('dossiers.en_construction_at', :processed_at)
.map { |en_construction_at, processed_at| { en_construction_at: en_construction_at, processed_at: processed_at } }
end
def usual_traitement_time_by_month_in_days
traitement_times(first_processed_at..last_considered_processed_at)
.group_by { |t| t[:processed_at].beginning_of_month }
.transform_values { |month| month.map { |h| h[:processed_at] - h[:en_construction_at] } }
.transform_values { |traitement_times_for_month| traitement_times_for_month.percentile(USUAL_TRAITEMENT_TIME_PERCENTILE).ceil }
.transform_values { |seconds| seconds == 0 ? nil : seconds }
.transform_values { |seconds| convert_seconds_in_days(seconds) }
.transform_keys { |month| pretty_month(month) }
end
def usual_traitement_time_for_recent_dossiers(nb_days)
now = Time.zone.now
traitement_time =
traitement_times((now - nb_days.days)..now)
.map { |times| times[:processed_at] - times[:en_construction_at] }
.percentile(USUAL_TRAITEMENT_TIME_PERCENTILE)
.ceil
traitement_time = nil if traitement_time == 0
traitement_time
end
private
def first_processed_at
Traitement.for_traitement_time_stats(self).pick(:processed_at)
end
def last_considered_processed_at
(Time.zone.now - 1.month).end_of_month
end
def convert_seconds_in_days(seconds)
(seconds / 60.0 / 60.0 / 24.0).ceil
end
def pretty_month(month)
I18n.l(month, format: "%B %Y")
end
end

View file

@ -565,19 +565,6 @@ class Procedure < ApplicationRecord
end
end
def usual_traitement_time
times = Traitement.includes(:dossier)
.where(dossier: self.dossiers)
.where.not('dossiers.en_construction_at' => nil, :processed_at => nil)
.where(processed_at: 1.month.ago..Time.zone.now)
.pluck('dossiers.en_construction_at', :processed_at)
.map { |(en_construction_at, processed_at)| processed_at - en_construction_at }
if times.present?
times.percentile(90).ceil
end
end
def populate_champ_stable_ids
TypeDeChamp
.joins(:revisions)

View file

@ -19,10 +19,17 @@ class Traitement < ApplicationRecord
.where("traitements.processed_at + (procedures.duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: Dossier::INTERVAL_BEFORE_EXPIRATION })
end
scope :for_traitement_time_stats, -> (procedure) do
includes(:dossier)
.where(dossier: procedure.dossiers)
.where.not('dossiers.en_construction_at' => nil, :processed_at => nil)
.order(:processed_at)
end
def self.count_dossiers_termines_by_month(groupe_instructeurs)
last_traitements_per_dossier = Traitement
.select('max(traitements.processed_at) as processed_at')
.where(dossier: Dossier.termine.where(groupe_instructeur: groupe_instructeurs))
.where(dossier: Dossier.state_termine.where(groupe_instructeur: groupe_instructeurs))
.group(:dossier_id)
.to_sql

View file

@ -56,7 +56,9 @@ class PiecesJustificativesService
include_infos_administration: true,
dossier: dossier
})
dossier.pdf_export_for_instructeur.attach(io: StringIO.open(pdf), filename: "export-#{dossier.id}.pdf", content_type: 'application/pdf')
ActiveRecord::Base.no_touching do
dossier.pdf_export_for_instructeur.attach(io: StringIO.open(pdf), filename: "export-#{dossier.id}.pdf", content_type: 'application/pdf')
end
dossier.pdf_export_for_instructeur
end

View file

@ -1,5 +1,5 @@
- content_for(:title, 'Invitation à donner votre avis')
- avis_link = @avis.expert.user.active?.present? ? expert_avis_url(@avis.procedure, @avis) : sign_up_expert_avis_url(@avis.procedure, @avis.id, @avis.expert.email)
- avis_link = @avis.expert.user.active?.present? ? expert_avis_url(@avis.procedure, @avis) : sign_up_expert_avis_url(@avis.procedure, @avis.id, email: @avis.expert.email)
- content_for(:footer) do
Merci de ne pas répondre à cet email. Donnez votre avis

View file

@ -4,7 +4,7 @@
%p.description= @dossier.procedure.libelle
%p.dossier Dossier nº #{@dossier.id}
.column
= form_for(User.new, url: { controller: "experts/avis", action: :update_expert }, method: :post, html: { class: "form" }) do |f|
= form_for(User.new, url: sign_up_expert_avis_path(email: @email), method: :post, html: { class: "form" }) do |f|
%h1 Créez-vous un compte
= f.label :email, "Email"

View file

@ -1,4 +1,29 @@
- if Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state)
- if Dossier::TERMINE.include?(state)
.dropdown.user-dossier-actions
%button.button.dropdown-button{ 'aria-expanded' => 'false', 'aria-controls' => 'actions-menu' }
Actions
#actions-menu.dropdown-content.fade-in-down
%ul.dropdown-items
- if archived
%li
= link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do
%span.icon.unarchive
.dropdown-description
Désarchiver le dossier
- else
%li
= link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch do
%span.icon.archive
.dropdown-description
Archiver le dossier
%li.danger
= link_to supprimer_dossier_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, data: { confirm: "Voulez vous vraiment supprimer le dossier #{dossier_id} ? Cette action est irréversible. \nNous vous suggérons de télécharger le dossier au format PDF au préalable." } do
%span.icon.delete
.dropdown-description
Supprimer le dossier
- elsif Dossier::EN_CONSTRUCTION_OU_INSTRUCTION.include?(state)
- if dossier_is_followed
= link_to unfollow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
%span.icon.unfollow>
@ -7,13 +32,3 @@
= link_to follow_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
%span.icon.follow>
Suivre le dossier
- elsif Dossier::TERMINE.include?(state)
- if archived
= link_to unarchive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
%span.icon.unarchive>
Désarchiver le dossier
- else
= link_to archive_instructeur_dossier_path(procedure_id, dossier_id), method: :patch, class: 'button' do
%span.icon.archive>
Archiver le dossier

View file

@ -11,15 +11,23 @@
%span.big-number-card-number
= distance_of_time_in_words(@usual_traitement_time)
%span.big-number-card-detail
90% des demandes du mois dernier ont été traitées en moins de #{distance_of_time_in_words(@usual_traitement_time)}.
#{ProcedureStatsConcern::USUAL_TRAITEMENT_TIME_PERCENTILE}% des demandes des #{ProcedureStatsConcern::NB_DAYS_RECENT_DOSSIERS} derniers jours ont été traitées en moins de #{distance_of_time_in_words(@usual_traitement_time)}.
.stat-cards
.stat-card.stat-card-half.pull-left
%span.stat-card-title TEMPS DE TRAITEMENT
.chart-container
.chart
- colors = %w(#C3D9FF #0069CC #1C7EC9) # from _colors.scss
= column_chart @usual_traitement_time_by_month, ytitle: "Nb Jours", legend: "bottom", label: "Temps de traitement entre le passage en instruction et la réponse (accepté, refusé, ou classé sans suite) pour 90% des dossiers"
.stat-card.stat-card-half.pull-left
%span.stat-card-title AVANCÉE DES DOSSIERS
.chart-container
.chart
= area_chart @dossiers_funnel
.stat-cards
.stat-card.stat-card-half.pull-left
%span.stat-card-title TAUX DACCEPTATION
.chart-container
@ -27,9 +35,8 @@
- colors = %w(#C3D9FF #0069CC #1C7EC9) # from _colors.scss
= pie_chart @termines_states, colors: colors
.stat-cards
.stat-card.stat-card-half.pull-left
%span.stat-card-title RÉPARTITION PAR SEMAINE
.chart-container
.chart
= line_chart @termines_by_week, colors: ["#387EC3", "#AE2C2B", "#FAD859"]
.stat-card.stat-card-half.pull-left
%span.stat-card-title RÉPARTITION PAR SEMAINE
.chart-container
.chart
= line_chart @termines_by_week, colors: ["#387EC3", "#AE2C2B", "#FAD859"]

View file

@ -4,8 +4,8 @@
- show_time_means = procedure.id != procedure_id_for_which_we_hide_the_estimated_delay && procedure.path != procedure_path_for_which_we_hide_the_estimated_delay
- cache(procedure.id, expires_in: 1.day) do
- if procedure.usual_traitement_time && show_time_means
- if procedure.usual_traitement_time_for_recent_dossiers(ProcedureStatsConcern::NB_DAYS_RECENT_DOSSIERS) && show_time_means
%p
Habituellement, les dossiers de cette démarche sont traités dans un délai de #{distance_of_time_in_words(procedure.usual_traitement_time)}.
Habituellement, les dossiers de cette démarche sont traités dans un délai de #{distance_of_time_in_words(procedure.usual_traitement_time_for_recent_dossiers(ProcedureStatsConcern::NB_DAYS_RECENT_DOSSIERS))}.
%p
Cette estimation est calculée automatiquement à partir des délais dinstruction constatés précédemment. Le délai réel peut être différent, en fonction du type de démarche (par exemple pour un appel à projet avec date de décision fixe).
Cette estimation est calculée automatiquement à partir des délais dinstruction constatés sur #{ProcedureStatsConcern::USUAL_TRAITEMENT_TIME_PERCENTILE}% des demandes qui ont été traitées lors des #{ProcedureStatsConcern::NB_DAYS_RECENT_DOSSIERS} derniers jours. Le délai réel peut être différent, en fonction du type de démarche (par exemple pour un appel à projet avec date de décision fixe).

View file

@ -105,7 +105,8 @@ Rails.application.routes.draw do
devise_scope :user do
get '/users/no_procedure' => 'users/sessions#no_procedure'
get 'connexion-par-jeton/:id' => 'users/sessions#sign_in_by_link', as: 'sign_in_by_link'
get 'lien-envoye/:email' => 'users/sessions#link_sent', constraints: { email: /.*/ }, as: 'link_sent'
get 'lien-envoye' => 'users/sessions#link_sent', as: 'link_sent'
get 'lien-envoye/:email' => 'users/sessions#link_sent', constraints: { email: /.*/ }, as: 'link_sent_legacy' # legacy, can be removed as soon as the previous line is deployed to production servers
get '/users/password/reset-link-sent' => 'users/passwords#reset_link_sent'
end
@ -300,10 +301,6 @@ Rails.application.routes.draw do
#
scope module: 'experts', as: 'expert' do
get 'avis', to: 'avis#index', as: 'all_avis'
# this redirections are ephemeral, to ensure that emails sent to experts before are still valid
# TODO : they will be removed in September, 2020
get 'avis/:id', to: redirect('/procedures/old/avis/%{id}')
get 'avis/:id/sign_up/email/:email', to: redirect("/procedures/old/avis/%{id}/sign_up/email/%{email}"), constraints: { email: /.*/ }
resources :procedures, only: [], param: :procedure_id do
member do
@ -316,8 +313,13 @@ Rails.application.routes.draw do
post 'avis' => 'avis#create_avis'
get 'bilans_bdf'
get 'sign_up/email/:email' => 'avis#sign_up', constraints: { email: /.*/ }, as: 'sign_up'
post 'sign_up/email/:email' => 'avis#update_expert', constraints: { email: /.*/ }
get 'sign_up' => 'avis#sign_up'
post 'sign_up' => 'avis#update_expert'
# This redirections are ephemeral, to ensure that emails sent to experts before are still valid
# TODO : remove these lines after September, 2021
get 'sign_up/email/:email' => 'avis#sign_up', constraints: { email: /.*/ }, as: 'sign_up_legacy'
post 'sign_up/email/:email' => 'avis#update_expert', constraints: { email: /.*/ }, as: 'update_expert_legacy'
end
end
end
@ -329,11 +331,6 @@ Rails.application.routes.draw do
#
scope module: 'instructeurs', as: 'instructeur' do
# this redirections are ephemeral, to ensure that emails sent to experts before are still valid
# TODO : they will be removed in September, 2020
get 'avis/:id', to: redirect('/procedures/old/avis/%{id}')
get 'avis/:id/sign_up/email/:email', to: redirect("/procedures/old/avis/%{id}/sign_up/email/%{email}"), constraints: { email: /.*/ }
resources :procedures, only: [:index, :show], param: :procedure_id do
member do
resources :groupes, only: [:index, :show], controller: 'groupe_instructeurs' do

View file

@ -17,7 +17,7 @@ class Array
values = self.sort
if values.empty?
return []
return 0
elsif values.size == 1
return values.first
elsif p == 100

View file

@ -12,7 +12,7 @@ feature 'Inviting an expert:' do
context 'when I dont already have an account' do
scenario 'I can sign up' do
visit sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email)
visit sign_up_expert_avis_path(avis.dossier.procedure, avis, email: avis.expert.email)
expect(page).to have_field('Email', with: avis.expert.email, disabled: true)
fill_in 'Mot de passe', with: 'This is a very complicated password !'
@ -29,7 +29,7 @@ feature 'Inviting an expert:' do
avis.expert.user.reload
end
scenario 'I can sign in' do
visit sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email)
visit sign_up_expert_avis_path(avis.dossier.procedure, avis, email: avis.expert.email)
expect(page).to have_current_path(new_user_session_path)
login_as avis.expert.user, scope: :user

View file

@ -45,7 +45,7 @@ feature 'Inviting an expert:', js: true do
invitation_email = open_email(expert.email.to_s)
avis = expert.avis.find_by(dossier: dossier)
sign_up_link = sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email)
sign_up_link = sign_up_expert_avis_path(avis.dossier.procedure, avis, email: avis.expert.email)
expect(invitation_email.body).to include(sign_up_link)
end

View file

@ -66,6 +66,15 @@ feature 'Instructing a dossier:', js: true do
dossier.reload
expect(dossier.state).to eq(Dossier.states.fetch(:accepte))
expect(dossier.motivation).to eq('a good reason')
click_on procedure.libelle
click_on 'traité'
click_on 'Actions'
accept_confirm do
click_on 'Supprimer le dossier'
end
click_on 'traité'
expect(page).not_to have_button('Actions')
end
scenario 'A instructeur can follow/unfollow a dossier' do

View file

@ -14,7 +14,7 @@ RSpec.describe AvisMailer, type: :mailer do
it { expect(subject.body).to include(instructeur_avis_url(avis.dossier.procedure.id, avis)) }
context 'when the recipient is not already registered' do
it { expect(subject.body).to include(sign_up_expert_avis_url(avis.dossier.procedure.id, avis.id, avis.expert.email)) }
it { expect(subject.body).to include(sign_up_expert_avis_url(avis.dossier.procedure.id, avis.id, email: avis.expert.email)) }
end
context 'when the dossier has been deleted before the avis was sent' do

View file

@ -0,0 +1,94 @@
describe ProcedureStatsConcern do
describe '#usual_traitement_time_for_recent_dossiers' do
let(:procedure) { create(:procedure) }
before do
Timecop.freeze(Time.utc(2019, 6, 1, 12, 0))
delays.each do |delay|
create_dossier(construction_date: 1.week.ago - delay, instruction_date: 1.week.ago - delay + 12.hours, processed_date: 1.week.ago)
end
end
after { Timecop.return }
context 'when there are several processed dossiers' do
let(:delays) { [1.day, 2.days, 2.days, 2.days, 2.days, 3.days, 3.days, 3.days, 3.days, 12.days] }
it 'returns a time representative of the dossier instruction delay' do
expect(procedure.usual_traitement_time_for_recent_dossiers(30)).to be_between(3.days, 4.days)
end
end
context 'when there are very old dossiers' do
let(:delays) { [2.days, 2.days] }
let!(:old_dossier) { create_dossier(construction_date: 3.months.ago, instruction_date: 2.months.ago, processed_date: 2.months.ago) }
it 'ignores dossiers older than 1 month' do
expect(procedure.usual_traitement_time_for_recent_dossiers(30)).to be_within(1.hour).of(2.days)
end
end
context 'when there is a dossier with bad data' do
let(:delays) { [2.days, 2.days] }
let!(:bad_dossier) { create_dossier(construction_date: nil, instruction_date: nil, processed_date: 10.days.ago) }
it 'ignores bad dossiers' do
expect(procedure.usual_traitement_time_for_recent_dossiers(30)).to be_within(1.hour).of(2.days)
end
end
context 'when there is only one processed dossier' do
let(:delays) { [1.day] }
it { expect(procedure.usual_traitement_time_for_recent_dossiers(30)).to be_within(1.hour).of(1.day) }
end
context 'where there is no processed dossier' do
let(:delays) { [] }
it { expect(procedure.usual_traitement_time_for_recent_dossiers(30)).to eq nil }
end
end
describe '.usual_traitement_time_by_month_in_days' do
let(:procedure) { create(:procedure) }
def create_dossiers(delays_by_month)
delays_by_month.each_with_index do |delays, index|
delays.each do |delay|
processed_date = (index.months + 1.week).ago
create_dossier(construction_date: processed_date - delay, instruction_date: processed_date - delay + 12.hours, processed_date: processed_date)
end
end
end
before do
Timecop.freeze(Time.utc(2019, 6, 25, 12, 0))
create_dossiers(delays_by_month)
end
after { Timecop.return }
context 'when there are several processed dossiers' do
let(:delays_by_month) {
[
[90.days, 90.days],
[1.day, 2.days, 2.days, 2.days, 2.days, 3.days, 3.days, 3.days, 3.days, 12.days],
[30.days, 60.days, 60.days, 60.days]
]
}
it 'computes a time representative of the dossier instruction delay for each month except current month' do
expect(procedure.usual_traitement_time_by_month_in_days['avril 2019']).to eq 60
expect(procedure.usual_traitement_time_by_month_in_days['mai 2019']).to eq 4
expect(procedure.usual_traitement_time_by_month_in_days['juin 2019']).to eq nil
end
end
end
private
def create_dossier(construction_date:, instruction_date:, processed_date:)
dossier = create(:dossier, :accepte, procedure: procedure, en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date)
end
end

View file

@ -980,60 +980,6 @@ describe Procedure do
end
end
describe '#usual_traitement_time' do
let(:procedure) { create(:procedure) }
def create_dossier(construction_date:, instruction_date:, processed_date:)
dossier = create(:dossier, :accepte, procedure: procedure, en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date)
end
before do
Timecop.freeze(Time.utc(2019, 6, 1, 12, 0))
delays.each do |delay|
create_dossier(construction_date: 1.week.ago - delay, instruction_date: 1.week.ago - delay + 12.hours, processed_date: 1.week.ago)
end
end
after { Timecop.return }
context 'when there are several processed dossiers' do
let(:delays) { [1.day, 2.days, 2.days, 2.days, 2.days, 3.days, 3.days, 3.days, 3.days, 12.days] }
it 'returns a time representative of the dossier instruction delay' do
expect(procedure.usual_traitement_time).to be_between(3.days, 4.days)
end
end
context 'when there are very old dossiers' do
let(:delays) { [2.days, 2.days] }
let!(:old_dossier) { create_dossier(construction_date: 3.months.ago, instruction_date: 2.months.ago, processed_date: 2.months.ago) }
it 'ignores dossiers older than 1 month' do
expect(procedure.usual_traitement_time).to be_within(1.hour).of(2.days)
end
end
context 'when there is a dossier with bad data' do
let(:delays) { [2.days, 2.days] }
let!(:bad_dossier) { create_dossier(construction_date: nil, instruction_date: nil, processed_date: 10.days.ago) }
it 'ignores bad dossiers' do
expect(procedure.usual_traitement_time).to be_within(1.hour).of(2.days)
end
end
context 'when there is only one processed dossier' do
let(:delays) { [1.day] }
it { expect(procedure.usual_traitement_time).to be_within(1.hour).of(1.day) }
end
context 'where there is no processed dossier' do
let(:delays) { [] }
it { expect(procedure.usual_traitement_time).to be_nil }
end
end
describe '.ensure_a_groupe_instructeur_exists' do
let!(:procedure) { create(:procedure) }
@ -1082,12 +1028,12 @@ describe Procedure do
it 'estimates average dossier weight' do
expect(procedure.reload.average_dossier_weight).to eq 5
end
end
private
private
def create_dossier_with_pj_of_size(size, procedure)
dossier = create(:dossier, :accepte, procedure: procedure)
create(:champ_piece_justificative, size: size, dossier: dossier)
end
def create_dossier_with_pj_of_size(size, procedure)
dossier = create(:dossier, :accepte, procedure: procedure)
create(:champ_piece_justificative, size: size, dossier: dossier)
end
end

View file

@ -2,23 +2,28 @@ describe Traitement do
describe '#count_dossiers_termines_by_month' do
let(:procedure) { create(:procedure, :published, groupe_instructeurs: [groupe_instructeurs]) }
let(:groupe_instructeurs) { create(:groupe_instructeur) }
let(:result) { Traitement.count_dossiers_termines_by_month(groupe_instructeurs) }
before do
create_dossier_for_month(procedure, 2021, 3)
create_dossier_for_month(procedure, 2021, 3)
create_archived_dossier_for_month(procedure, 2021, 3)
create_dossier_for_month(procedure, 2021, 2)
Timecop.freeze(Time.zone.local(2021, 3, 5))
end
subject do
Timecop.freeze(Time.zone.local(2021, 3, 5)) do
Traitement.count_dossiers_termines_by_month(groupe_instructeurs)
end
end
it 'count dossiers_termines by month' do
expect(count_for_month(result, 3)).to eq 2
expect(count_for_month(result, 2)).to eq 1
expect(count_for_month(subject, 3)).to eq 3
expect(count_for_month(subject, 2)).to eq 1
end
it 'returns descending order by month' do
expect(result[0]["month"].to_date.month).to eq 3
expect(result[1]["month"].to_date.month).to eq 2
expect(subject[0]["month"].to_date.month).to eq 3
expect(subject[1]["month"].to_date.month).to eq 2
end
end
@ -31,7 +36,14 @@ describe Traitement do
end
def create_dossier_for_month(procedure, year, month)
Timecop.freeze(Time.zone.local(year, month, 5))
create(:dossier, :accepte, :with_attestation, procedure: procedure)
Timecop.freeze(Time.zone.local(year, month, 5)) do
create(:dossier, :accepte, :with_attestation, procedure: procedure)
end
end
def create_archived_dossier_for_month(procedure, year, month)
Timecop.freeze(Time.zone.local(year, month, 5)) do
create(:dossier, :accepte, :archived, :with_attestation, procedure: procedure)
end
end
end

View file

@ -44,4 +44,18 @@ describe PiecesJustificativesService do
expect(subject.any? { |piece| piece.name == 'serialized' }).to be_truthy
end
end
describe '.generate_dossier_export' do
subject { PiecesJustificativesService.generate_dossier_export(dossier) }
it "generates pdf export for instructeur" do
subject
expect(dossier.pdf_export_for_instructeur).to be_attached
end
it "doesn't update dossier" do
before_export = Time.zone.now
subject
expect(dossier.updated_at).to be <= before_export
end
end
end

View file

@ -1,5 +1,5 @@
describe 'users/dossiers/show/_status_overview.html.haml', type: :view do
before { allow(dossier.procedure).to receive(:usual_traitement_time).and_return(1.day) }
before { allow(dossier.procedure).to receive(:usual_traitement_time_for_recent_dossiers).and_return(1.day) }
subject! { render 'users/dossiers/show/status_overview.html.haml', dossier: dossier }

View file

@ -3248,15 +3248,7 @@ chalk@^3.0.0, chalk@^3.0.0-beta.2:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72"
integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.1.0:
chalk@^4.0.0, chalk@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
@ -10090,9 +10082,9 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1:
uniq "^1.0.1"
postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.30"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.30.tgz#cc9378beffe46a02cbc4506a0477d05fcea9a8e2"
integrity sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ==
version "7.0.36"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb"
integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"