Merge pull request #5361 from betagouv/dev

2020-07-09-01
This commit is contained in:
Keirua 2020-07-09 14:13:53 +02:00 committed by GitHub
commit 811c8daacd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 366 additions and 157 deletions

View file

@ -689,6 +689,7 @@ Rails/CreateTableWithTimestamps:
- db/migrate/2016*.rb - db/migrate/2016*.rb
- db/migrate/2017*.rb - db/migrate/2017*.rb
- db/migrate/2018*.rb - db/migrate/2018*.rb
- db/migrate/20200630140356_create_traitements.rb
Rails/Date: Rails/Date:
Enabled: false Enabled: false

View file

@ -97,7 +97,7 @@ GEM
aes_key_wrap (1.0.1) aes_key_wrap (1.0.1)
after_party (1.11.2) after_party (1.11.2)
anchored (1.1.0) anchored (1.1.0)
ast (2.4.0) ast (2.4.1)
attr_required (1.0.1) attr_required (1.0.1)
autoprefixer-rails (9.7.6) autoprefixer-rails (9.7.6)
execjs execjs
@ -342,7 +342,7 @@ GEM
rails-dom-testing (>= 1, < 3) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json-jwt (1.11.0) json-jwt (1.13.0)
activesupport (>= 4.2) activesupport (>= 4.2)
aes_key_wrap aes_key_wrap
bindata bindata
@ -407,7 +407,7 @@ GEM
nenv (0.3.0) nenv (0.3.0)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.2) nio4r (2.5.2)
nokogiri (1.10.9) nokogiri (1.10.10)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
notiffany (0.1.3) notiffany (0.1.3)
nenv (~> 0.1) nenv (~> 0.1)
@ -442,9 +442,9 @@ GEM
validate_url validate_url
webfinger (>= 1.0.1) webfinger (>= 1.0.1)
orm_adapter (0.5.0) orm_adapter (0.5.0)
parallel (1.19.1) parallel (1.19.2)
parser (2.7.1.0) parser (2.7.1.4)
ast (~> 2.4.0) ast (~> 2.4.1)
pdf-core (0.7.0) pdf-core (0.7.0)
pg (1.2.3) pg (1.2.3)
phonelib (0.6.43) phonelib (0.6.43)
@ -475,22 +475,22 @@ GEM
byebug (~> 11.0) byebug (~> 11.0)
pry (~> 0.13.0) pry (~> 0.13.0)
public_suffix (4.0.4) public_suffix (4.0.4)
puma (4.3.3) puma (4.3.5)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.1.6) raabro (1.1.6)
rack (2.0.9) rack (2.2.3)
rack-attack (6.2.2) rack-attack (6.2.2)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rack-mini-profiler (2.0.1) rack-mini-profiler (2.0.1)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-oauth2 (1.12.0) rack-oauth2 (1.15.0)
activesupport activesupport
attr_required attr_required
httpclient httpclient
json-jwt (>= 1.11.0) json-jwt (>= 1.11.0)
rack (< 2.1) rack (>= 2.1.0)
rack-protection (2.0.8.1) rack-protection (2.0.8.1)
rack rack
rack-proxy (0.6.5) rack-proxy (0.6.5)

View file

@ -1,2 +1,2 @@
server: bin/rails server -p 3000 server: RAILS_QUEUE_ADAPTER=delayed_job bin/rails server -p 3000
jobs: bin/delayed_job run jobs: bin/rake jobs:work

View file

@ -22,9 +22,6 @@ Vous souhaitez y apporter des changements ou des améliorations ? Lisez notre [
- rbenv : voir https://github.com/rbenv/rbenv-installer#rbenv-installer--doctor-scripts - rbenv : voir https://github.com/rbenv/rbenv-installer#rbenv-installer--doctor-scripts
- Yarn : voir https://yarnpkg.com/en/docs/install - Yarn : voir https://yarnpkg.com/en/docs/install
- Overmind :
* Mac : `brew install overmind`
* Linux : voir https://github.com/DarthSim/overmind#installation
#### Tests #### Tests
@ -60,9 +57,18 @@ Afin d'initialiser l'environnement de développement, exécutez la commande suiv
### Lancement de l'application ### Lancement de l'application
overmind start On lance le serveur d'application ainsi :
L'application tourne à l'adresse `http://localhost:3000`. bin/rails server
L'application tourne alors à l'adresse `http://localhost:3000`, et utilise le mécanisme par défaut de rails pour les tâches asynchrones.
C'est ce qu'on veut dans la plupart des cas. Une exception: ça ne joue pas les tâches cron.
Pour être une peu plus proche du comportement de production, et jouer les tâches cron, on peut lancer la message queue
dans un service dédié, et indiquer à rails d'utiliser delayed_job:
bin/rake jobs:work
RAILS_QUEUE_ADAPTER=delayed_job bin/rails server
### Utilisateurs de test ### Utilisateurs de test
@ -107,12 +113,6 @@ Pour exécuter les tests de l'application, plusieurs possibilités :
rails generate after_party:task task_name rails generate after_party:task task_name
### Debug
Une fois `overmind` lancé, et un breakpoint `byebug` inséré dans le code, il faut se connecter au process `server` dans un nouveau terminal afin d'intéragir avec byebug :
overmind connect server
### Linting ### Linting
Le projet utilise plusieurs linters pour vérifier la lisibilité et la qualité du code. Le projet utilise plusieurs linters pour vérifier la lisibilité et la qualité du code.

View file

@ -249,9 +249,11 @@ class StatsController < ApplicationController
min_date = 11.months.ago min_date = 11.months.ago
max_date = Time.zone.now.to_date max_date = Time.zone.now.to_date
processed_dossiers = dossiers processed_dossiers = Traitement.includes(:dossier)
.where(dossier_id: dossiers)
.where('dossiers.state' => Dossier::TERMINE)
.where(:processed_at => min_date..max_date) .where(:processed_at => min_date..max_date)
.pluck(:groupe_instructeur_id, :en_construction_at, :processed_at) .pluck('dossiers.groupe_instructeur_id', 'dossiers.en_construction_at', :processed_at)
# Group dossiers by month # Group dossiers by month
processed_dossiers_by_month = processed_dossiers processed_dossiers_by_month = processed_dossiers
@ -290,11 +292,13 @@ class StatsController < ApplicationController
min_date = 11.months.ago min_date = 11.months.ago
max_date = Time.zone.now.to_date max_date = Time.zone.now.to_date
processed_dossiers = dossiers processed_dossiers = Traitement.includes(:dossier)
.where(dossier: dossiers)
.where('dossiers.state' => Dossier::TERMINE)
.where(:processed_at => min_date..max_date) .where(:processed_at => min_date..max_date)
.pluck( .pluck(
:groupe_instructeur_id, 'dossiers.groupe_instructeur_id',
Arel.sql('EXTRACT(EPOCH FROM (en_construction_at - created_at)) / 60 AS processing_time'), Arel.sql('EXTRACT(EPOCH FROM (dossiers.en_construction_at - dossiers.created_at)) / 60 AS processing_time'),
:processed_at :processed_at
) )

View file

@ -149,7 +149,7 @@ module Users
errors = update_dossier_and_compute_errors errors = update_dossier_and_compute_errors
if passage_en_construction? && errors.blank? if passage_en_construction? && errors.blank?
@dossier.en_construction! @dossier.passer_en_construction!
NotificationMailer.send_initiated_notification(@dossier).deliver_later NotificationMailer.send_initiated_notification(@dossier).deliver_later
@dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur| @dossier.groupe_instructeur.instructeurs.with_instant_email_dossier_notifications.each do |instructeur|
DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later DossierMailer.notify_new_dossier_depose_to_instructeur(@dossier, instructeur.email).deliver_later

View file

@ -9,6 +9,10 @@ class ApiEntreprise::Job < ApplicationJob
error(self, exception) error(self, exception)
end end
def error(job, exception)
# override ApplicationJob#error to avoid reporting to sentry
end
def max_attempts def max_attempts
ENV.fetch("MAX_ATTEMPTS_API_ENTREPRISE_JOBS", DEFAULT_MAX_ATTEMPTS_API_ENTREPRISE_JOBS).to_i ENV.fetch("MAX_ATTEMPTS_API_ENTREPRISE_JOBS", DEFAULT_MAX_ATTEMPTS_API_ENTREPRISE_JOBS).to_i
end end

View file

@ -42,6 +42,7 @@ class Dossier < ApplicationRecord
has_many :followers_instructeurs, through: :follows, source: :instructeur has_many :followers_instructeurs, through: :follows, source: :instructeur
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
has_many :avis, inverse_of: :dossier, dependent: :destroy has_many :avis, inverse_of: :dossier, dependent: :destroy
has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy
has_many :dossier_operation_logs, -> { order(:created_at) }, dependent: :nullify, inverse_of: :dossier has_many :dossier_operation_logs, -> { order(:created_at) }, dependent: :nullify, inverse_of: :dossier
@ -128,6 +129,7 @@ class Dossier < ApplicationRecord
:individual, :individual,
:followers_instructeurs, :followers_instructeurs,
:avis, :avis,
:traitements,
etablissement: :champ, etablissement: :champ,
champs: { champs: {
etablissement: :champ, etablissement: :champ,
@ -172,6 +174,7 @@ class Dossier < ApplicationRecord
justificatif_motivation_attachment: :blob, justificatif_motivation_attachment: :blob,
attestation: [], attestation: [],
avis: { piece_justificative_file_attachment: :blob }, avis: { piece_justificative_file_attachment: :blob },
traitements: [],
etablissement: [], etablissement: [],
individual: [], individual: [],
user: []) user: [])
@ -198,10 +201,9 @@ class Dossier < ApplicationRecord
.joins(:procedure) .joins(:procedure)
.where("dossiers.en_instruction_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION }) .where("dossiers.en_instruction_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end end
scope :termine_close_to_expiration, -> do def self.termine_close_to_expiration
state_termine dossier_ids = Traitement.termine_close_to_expiration.pluck(:dossier_id).uniq
.joins(:procedure) Dossier.where(id: dossier_ids)
.where("dossiers.processed_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
end end
scope :brouillon_expired, -> do scope :brouillon_expired, -> do
@ -249,7 +251,7 @@ class Dossier < ApplicationRecord
end end
scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) } scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) }
scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: []) } scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: [], traitements: []) }
scope :with_notifications, -> do scope :with_notifications, -> do
# This scope is meant to be composed, typically with Instructeur.followed_dossiers, which means that the :follows table is already INNER JOINed; # This scope is meant to be composed, typically with Instructeur.followed_dossiers, which means that the :follows table is already INNER JOINed;
@ -282,7 +284,6 @@ class Dossier < ApplicationRecord
delegate :types_de_champ, to: :procedure delegate :types_de_champ, to: :procedure
delegate :france_connect_information, to: :user delegate :france_connect_information, to: :user
before_validation :update_state_dates, if: -> { state_changed? }
before_save :build_default_champs, if: Proc.new { groupe_instructeur_id_was.nil? } before_save :build_default_champs, if: Proc.new { groupe_instructeur_id_was.nil? }
before_save :update_search_terms before_save :update_search_terms
@ -294,6 +295,16 @@ class Dossier < ApplicationRecord
validates :individual, presence: true, if: -> { procedure.for_individual? } validates :individual, presence: true, if: -> { procedure.for_individual? }
validates :groupe_instructeur, presence: true validates :groupe_instructeur, presence: true
def motivation
return nil if !termine?
traitements.any? ? traitements.last.motivation : read_attribute(:motivation)
end
def processed_at
return nil if !termine?
traitements.any? ? traitements.last.processed_at : read_attribute(:processed_at)
end
def update_search_terms def update_search_terms
self.search_terms = [ self.search_terms = [
user&.email, user&.email,
@ -508,27 +519,29 @@ class Dossier < ApplicationRecord
end end
end end
def after_passer_en_construction
update!(en_construction_at: Time.zone.now) if self.en_construction_at.nil?
end
def after_passer_en_instruction(instructeur) def after_passer_en_instruction(instructeur)
instructeur.follow(self) instructeur.follow(self)
update!(en_instruction_at: Time.zone.now) if self.en_instruction_at.nil?
log_dossier_operation(instructeur, :passer_en_instruction) log_dossier_operation(instructeur, :passer_en_instruction)
end end
def after_passer_automatiquement_en_instruction def after_passer_automatiquement_en_instruction
update!(en_instruction_at: Time.zone.now) if self.en_instruction_at.nil?
log_automatic_dossier_operation(:passer_en_instruction) log_automatic_dossier_operation(:passer_en_instruction)
end end
def after_repasser_en_construction(instructeur) def after_repasser_en_construction(instructeur)
self.en_instruction_at = nil
save!
log_dossier_operation(instructeur, :repasser_en_construction) log_dossier_operation(instructeur, :repasser_en_construction)
end end
def after_repasser_en_instruction(instructeur) def after_repasser_en_instruction(instructeur)
self.archived = false self.archived = false
self.processed_at = nil self.en_instruction_at = Time.zone.now
self.motivation = nil
attestation&.destroy attestation&.destroy
save! save!
@ -537,7 +550,7 @@ class Dossier < ApplicationRecord
end end
def after_accepter(instructeur, motivation, justificatif = nil) def after_accepter(instructeur, motivation, justificatif = nil)
self.motivation = motivation self.traitements.build(state: Dossier.states.fetch(:accepte), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -553,6 +566,7 @@ class Dossier < ApplicationRecord
end end
def after_accepter_automatiquement def after_accepter_automatiquement
self.traitements.build(state: Dossier.states.fetch(:accepte), instructeur_email: nil, motivation: nil, processed_at: Time.zone.now)
self.en_instruction_at ||= Time.zone.now self.en_instruction_at ||= Time.zone.now
if attestation.nil? if attestation.nil?
@ -565,7 +579,7 @@ class Dossier < ApplicationRecord
end end
def after_refuser(instructeur, motivation, justificatif = nil) def after_refuser(instructeur, motivation, justificatif = nil)
self.motivation = motivation self.traitements.build(state: Dossier.states.fetch(:refuse), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -577,7 +591,7 @@ class Dossier < ApplicationRecord
end end
def after_classer_sans_suite(instructeur, motivation, justificatif = nil) def after_classer_sans_suite(instructeur, motivation, justificatif = nil)
self.motivation = motivation self.traitements.build(state: Dossier.states.fetch(:sans_suite), instructeur_email: instructeur.email, motivation: motivation, processed_at: Time.zone.now)
if justificatif if justificatif
self.justificatif_motivation.attach(justificatif) self.justificatif_motivation.attach(justificatif)
@ -766,16 +780,6 @@ class Dossier < ApplicationRecord
end end
end end
def update_state_dates
if en_construction? && !self.en_construction_at
self.en_construction_at = Time.zone.now
elsif en_instruction? && !self.en_instruction_at
self.en_instruction_at = Time.zone.now
elsif TERMINE.include?(state) && !self.processed_at
self.processed_at = Time.zone.now
end
end
def send_dossier_received def send_dossier_received
if saved_change_to_state? && en_instruction? && !procedure.declarative_accepte? if saved_change_to_state? && en_instruction? && !procedure.declarative_accepte?
NotificationMailer.send_dossier_received(self).deliver_later NotificationMailer.send_dossier_received(self).deliver_later

View file

@ -161,7 +161,7 @@ class Instructeur < ApplicationRecord
h = { h = {
nb_en_construction: groupe.dossiers.en_construction.count, nb_en_construction: groupe.dossiers.en_construction.count,
nb_en_instruction: groupe.dossiers.en_instruction.count, nb_en_instruction: groupe.dossiers.en_instruction.count,
nb_accepted: groupe.dossiers.accepte.where(processed_at: Time.zone.yesterday.beginning_of_day..Time.zone.yesterday.end_of_day).count, nb_accepted: Traitement.where(dossier: groupe.dossiers.accepte, processed_at: Time.zone.yesterday.beginning_of_day..Time.zone.yesterday.end_of_day).count,
nb_notification: notifications_for_procedure(procedure, :not_archived).count nb_notification: notifications_for_procedure(procedure, :not_archived).count
} }

View file

@ -108,6 +108,8 @@ class Procedure < ApplicationRecord
], size: { less_than: 20.megabytes } ], size: { less_than: 20.megabytes }
validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes } validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes }
validates :api_entreprise_token, jwt_token: true, allow_blank: true
before_save :update_juridique_required before_save :update_juridique_required
after_initialize :ensure_path_exists after_initialize :ensure_path_exists
before_save :ensure_path_exists before_save :ensure_path_exists
@ -438,7 +440,15 @@ class Procedure < ApplicationRecord
end end
def usual_traitement_time def usual_traitement_time
percentile_time(:en_construction_at, :processed_at, 90) times = Traitement.includes(:dossier)
.where(state: Dossier::TERMINE)
.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 end
def populate_champ_stable_ids def populate_champ_stable_ids
@ -610,18 +620,6 @@ class Procedure < ApplicationRecord
end end
end end
def percentile_time(start_attribute, end_attribute, p)
times = dossiers
.where.not(start_attribute => nil, end_attribute => nil)
.where(end_attribute => 1.month.ago..Time.zone.now)
.pluck(start_attribute, end_attribute)
.map { |(start_date, end_date)| end_date - start_date }
if times.present?
times.percentile(p).ceil
end
end
def ensure_path_exists def ensure_path_exists
if self.path.blank? if self.path.blank?
self.path = SecureRandom.uuid self.path = SecureRandom.uuid

10
app/models/traitement.rb Normal file
View file

@ -0,0 +1,10 @@
class Traitement < ApplicationRecord
belongs_to :dossier
scope :termine_close_to_expiration, -> do
joins(dossier: :procedure)
.where(state: Dossier::TERMINE)
.where('dossiers.state' => Dossier::TERMINE)
.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
end

View file

@ -82,7 +82,12 @@ class TypeDeChamp < ApplicationRecord
before_validation :check_mandatory before_validation :check_mandatory
before_save :remove_piece_justificative_template, if: -> { type_champ_changed? } before_save :remove_piece_justificative_template, if: -> { type_champ_changed? }
before_validation :remove_drop_down_list, if: -> { type_champ_changed? } before_save :remove_drop_down_list, if: -> { type_champ_changed? }
before_save :remove_repetition, if: -> { type_champ_changed? }
after_save if: -> { @remove_piece_justificative_template } do
piece_justificative_template.purge_later
end
def valid?(context = nil) def valid?(context = nil)
super super
@ -292,7 +297,7 @@ class TypeDeChamp < ApplicationRecord
def remove_piece_justificative_template def remove_piece_justificative_template
if !piece_justificative? && piece_justificative_template.attached? if !piece_justificative? && piece_justificative_template.attached?
piece_justificative_template.purge_later @remove_piece_justificative_template = true
end end
end end
@ -302,4 +307,10 @@ class TypeDeChamp < ApplicationRecord
self.drop_down_options = nil self.drop_down_options = nil
end end
end end
def remove_repetition
if !repetition?
types_de_champ.destroy_all
end
end
end end

View file

@ -0,0 +1,9 @@
class JwtTokenValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
begin
JWT.decode value, nil, false
rescue
record.errors[attribute] << (options[:message] || "n'est pas un jeton valide")
end
end
end

View file

@ -156,7 +156,7 @@ def add_etats_dossier(pdf, dossier)
if dossier.en_instruction_at.present? if dossier.en_instruction_at.present?
format_in_2_columns(pdf, "En instruction le", try_format_date(dossier.en_instruction_at)) format_in_2_columns(pdf, "En instruction le", try_format_date(dossier.en_instruction_at))
end end
if dossier.processed_at?.present? if dossier.processed_at.present?
format_in_2_columns(pdf, "Décision le", try_format_date(dossier.processed_at)) format_in_2_columns(pdf, "Décision le", try_format_date(dossier.processed_at))
end end

View file

@ -0,0 +1,19 @@
.tab-title Décisions rendues
- if traitements.any?
%ul.tab-list
- traitements.each do |traitement|
- if traitement.instructeur_email.present?
%li
= "Le #{l(traitement.processed_at, format: '%d %B %Y à %R')}, "
= traitement.instructeur_email
a
%strong= t(traitement.state, scope: 'activerecord.attributes.traitement.state').downcase
ce dossier
- else
%li
= "Le #{l(traitement.processed_at, format: '%d %B %Y à %R')}, "
ce dossier a été
%strong= t(traitement.state, scope: 'activerecord.attributes.traitement.state').downcase
- else
%p.tab-paragraph Aucune décision n'a été rendue

View file

@ -13,3 +13,5 @@
= render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @avis_emails, title: "Personnes à qui un avis a été demandé", blank: "Aucun avis n'a été demandé" } = render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @avis_emails, title: "Personnes à qui un avis a été demandé", blank: "Aucun avis n'a été demandé" }
= render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @invites_emails, title: "Personnes invitées à consulter ce dossier", blank: "Aucune personne n'a été invitée à consulter ce dossier" } = render partial: 'instructeurs/dossiers/personnes_impliquees_block', locals: { emails_collection: @invites_emails, title: "Personnes invitées à consulter ce dossier", blank: "Aucune personne n'a été invitée à consulter ce dossier" }
= render partial: 'instructeurs/dossiers/decisions_rendues_block', locals: { traitements: @dossier.traitements }

View file

@ -68,13 +68,20 @@
.cta-panel-wrapper .cta-panel-wrapper
%div %div
%h2.cta-panel-title Une question, un problème ? %h2.cta-panel-title Une question, un problème ?
%p.cta-panel-explanation Notre équipe est disponible pour vous renseigner et vous aider %p.cta-panel-explanation La réponse est dans laide en ligne
%div %div
= contact_link "Contactez-nous", = link_to "Accéder à laide en ligne", FAQ_URL,
tags: 'landing',
class: "cta-panel-button-white", class: "cta-panel-button-white",
target: "_blank", target: "_blank",
rel: "noopener noreferrer" rel: "noopener noreferrer"
-# We temporarily disable the link to the contact page on the homepage
-# %p.cta-panel-explanation Notre équipe est disponible pour vous renseigner et vous aider
-# %div
-# = contact_link "Contactez-nous",
-# tags: 'landing',
-# class: "cta-panel-button-white",
-# target: "_blank",
-# rel: "noopener noreferrer"
.landing-panel .landing-panel
.container .container

View file

@ -1,6 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require 'fileutils' require 'fileutils'
include FileUtils
# path to your application root. # path to your application root.
APP_ROOT = File.expand_path('..', __dir__) APP_ROOT = File.expand_path('..', __dir__)
@ -9,13 +8,15 @@ def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==") system(*args) || abort("\n== Command #{args} failed ==")
end end
chdir APP_ROOT do FileUtils.chdir APP_ROOT do
# This script is a starting point to setup your application. # This script is a starting point to setup your application.
# Add necessary setup steps to this file. # Add necessary setup steps to this file.
puts "\n== Installing dependencies ==" puts "\n== Installing dependencies =="
system! 'gem install bundler --conservative' system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install') system('bundle check') || system!('bundle install')
# Install JavaScript dependencies
system! 'bin/yarn install' system! 'bin/yarn install'
puts "\n== Updating webdrivers ==" puts "\n== Updating webdrivers =="
@ -23,7 +24,7 @@ chdir APP_ROOT do
puts "\n== Copying sample files ==" puts "\n== Copying sample files =="
unless File.exist?('.env') unless File.exist?('.env')
cp 'config/env.example', '.env' FileUtils.cp 'config/env.example', '.env'
end end
# Create the database, load the schema, and initialize it with the seed data # Create the database, load the schema, and initialize it with the seed data
@ -34,5 +35,5 @@ chdir APP_ROOT do
system! 'bin/rails log:clear tmp:clear' system! 'bin/rails log:clear tmp:clear'
puts "\n== Done ==" puts "\n== Done =="
puts "You can now start the application server with `overmind start`." puts "You can now start the application server with `bin/rails server`."
end end

View file

@ -1,6 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require 'fileutils' require 'fileutils'
include FileUtils
# path to your application root. # path to your application root.
APP_ROOT = File.expand_path('..', __dir__) APP_ROOT = File.expand_path('..', __dir__)
@ -9,7 +8,7 @@ def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==") system(*args) || abort("\n== Command #{args} failed ==")
end end
chdir APP_ROOT do FileUtils.chdir APP_ROOT do
# This script is a way to update your development environment automatically. # This script is a way to update your development environment automatically.
# Add necessary update steps to this file. # Add necessary update steps to this file.
@ -31,5 +30,5 @@ chdir APP_ROOT do
system! 'bin/rails log:clear' system! 'bin/rails log:clear'
puts "\n== Done ==" puts "\n== Done =="
puts "You can now start (or restart) the application server with `overmind start`." puts "You can now start (or restart) the application server with `bin/rails server`."
end end

View file

@ -36,7 +36,7 @@ Rails.application.configure do
# Debug mode disables concatenation and preprocessing of assets. # Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large # This option may cause significant delays in view rendering with a large
# number of complex assets. # number of complex assets.
config.assets.debug = false config.assets.debug = true
# Asset digests allow you to set far-future HTTP expiration dates on all assets, # Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params. # yet still be able to expire them through the digest params.
@ -83,10 +83,9 @@ Rails.application.configure do
# Raises error for missing translations # Raises error for missing translations
# config.action_view.raise_on_missing_translations = true # config.action_view.raise_on_missing_translations = true
# This is useful to run rails in development with :async queue adapter # We use the async adapter by default, but delayed_job can be set using
if ENV['RAILS_QUEUE_ADAPTER'] # RAILS_QUEUE_ADAPTER=delayed_job bin/rails server
config.active_job.queue_adapter = ENV['RAILS_QUEUE_ADAPTER'].to_sym config.active_job.queue_adapter = ENV.fetch('RAILS_QUEUE_ADAPTER', 'async').to_sym
end
config.file_watcher = ActiveSupport::EventedFileUpdateChecker config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end end

View file

@ -9,13 +9,13 @@ fr:
montant_projet: 'Le montant du projet' montant_projet: 'Le montant du projet'
montant_aide_demande: "Le montant daide demandée" montant_aide_demande: "Le montant daide demandée"
date_previsionnelle: "La date de début prévisionnelle" date_previsionnelle: "La date de début prévisionnelle"
state: state: &state
brouillon: "Brouillon" brouillon: "Brouillon"
en_construction: "En construction" en_construction: "En construction"
en_instruction: "En instruction" en_instruction: "En instruction"
accepte: "Accepté" accepte: "Accepté"
refuse: "Refusé" refuse: "Refusé"
sans_suite: "Sans suite" sans_suite: "Classé sans suite"
autorisation_donnees: Acceptation des CGU autorisation_donnees: Acceptation des CGU
state/brouillon: Brouillon state/brouillon: Brouillon
state/en_construction: En construction state/en_construction: En construction
@ -23,3 +23,6 @@ fr:
state/accepte: Accepté state/accepte: Accepté
state/refuse: Refusé state/refuse: Refusé
state/sans_suite: Sans suite state/sans_suite: Sans suite
traitement:
state:
<<: *state

View file

@ -77,7 +77,7 @@ test:
secret_key_base: aa52abc3f3a629d04a61e9899a24c12f52b24c679cbf45f8ec0cdcc64ab9526d673adca84212882dff3911ac98e0c32ec4729ca7b3429ba18ef4dfd1bd18bc7a secret_key_base: aa52abc3f3a629d04a61e9899a24c12f52b24c679cbf45f8ec0cdcc64ab9526d673adca84212882dff3911ac98e0c32ec4729ca7b3429ba18ef4dfd1bd18bc7a
signing_key: aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017 signing_key: aef3153a9829fa4ba10acb02927ac855df6b92795b1ad265d654443c4b14a017
api_entreprise: api_entreprise:
key: api_entreprise_test_key key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9oIHllYWgiLCJpYXQiOjE1MTYyMzkwMjJ9.f06sBo3q2Yxnw_TYPFUEs0CozBmcV-XniH_DeKNWzKE"
pipedrive: pipedrive:
key: pipedrive_test_key key: pipedrive_test_key
france_connect_particulier: france_connect_particulier:

View file

@ -0,0 +1,11 @@
class CreateTraitements < ActiveRecord::Migration[5.2]
def change
create_table :traitements do |t|
t.references :dossier, foreign_key: true
t.references :instructeur, foreign_key: true
t.string :motivation
t.string :state
t.timestamp :processed_at
end
end
end

View file

@ -0,0 +1,6 @@
class RemoveInstructeurIdAndAddInstructeurEmailToTraitements < ActiveRecord::Migration[5.2]
def change
add_column :traitements, :instructeur_email, :string
remove_column :traitements, :instructeur_id
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: 2020_06_11_122406) do ActiveRecord::Schema.define(version: 2020_07_07_082256) 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"
@ -560,6 +560,15 @@ ActiveRecord::Schema.define(version: 2020_06_11_122406) do
t.string "version", null: false t.string "version", null: false
end end
create_table "traitements", force: :cascade do |t|
t.bigint "dossier_id"
t.string "motivation"
t.string "state"
t.datetime "processed_at"
t.string "instructeur_email"
t.index ["dossier_id"], name: "index_traitements_on_dossier_id"
end
create_table "trusted_device_tokens", force: :cascade do |t| create_table "trusted_device_tokens", force: :cascade do |t|
t.string "token", null: false t.string "token", null: false
t.bigint "instructeur_id" t.bigint "instructeur_id"
@ -660,6 +669,7 @@ ActiveRecord::Schema.define(version: 2020_06_11_122406) do
add_foreign_key "received_mails", "procedures" add_foreign_key "received_mails", "procedures"
add_foreign_key "refused_mails", "procedures" add_foreign_key "refused_mails", "procedures"
add_foreign_key "services", "administrateurs" add_foreign_key "services", "administrateurs"
add_foreign_key "traitements", "dossiers"
add_foreign_key "trusted_device_tokens", "instructeurs" add_foreign_key "trusted_device_tokens", "instructeurs"
add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id" add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id"
add_foreign_key "users", "administrateurs" add_foreign_key "users", "administrateurs"

View file

@ -0,0 +1,18 @@
namespace :after_party do
desc 'Deployment task: add_traitements_from_dossiers'
task add_traitements_from_dossiers: :environment do
puts "Running deploy task 'add_traitements_from_dossiers'"
dossiers_termines = Dossier.state_termine
progress = ProgressReport.new(dossiers_termines.count)
dossiers_termines.find_each do |dossier|
dossier.traitements.create!(state: dossier.state, motivation: dossier.motivation, processed_at: dossier.processed_at)
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: '20200630154829'
end
end

View file

@ -596,7 +596,7 @@ describe API::V2::GraphqlController do
it "should fail" do it "should fail" do
expect(gql_errors).to eq(nil) expect(gql_errors).to eq(nil)
expect(gql_data).to eq(dossierRefuser: { expect(gql_data).to eq(dossierRefuser: {
errors: [{ message: "Le dossier est déjà sans suite" }], errors: [{ message: "Le dossier est déjà classé sans suite" }],
dossier: nil dossier: nil
}) })
end end

View file

@ -194,7 +194,7 @@ describe Instructeurs::DossiersController, type: :controller do
describe '#terminer' do describe '#terminer' do
context "with refuser" do context "with refuser" do
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
sign_in(instructeur.user) sign_in(instructeur.user)
end end
@ -235,7 +235,7 @@ describe Instructeurs::DossiersController, type: :controller do
context "with classer_sans_suite" do context "with classer_sans_suite" do
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
sign_in(instructeur.user) sign_in(instructeur.user)
end end
context 'without attachment' do context 'without attachment' do
@ -277,7 +277,7 @@ describe Instructeurs::DossiersController, type: :controller do
context "with accepter" do context "with accepter" do
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
sign_in(instructeur.user) sign_in(instructeur.user)
expect(NotificationMailer).to receive(:send_closed_notification) expect(NotificationMailer).to receive(:send_closed_notification)

View file

@ -312,10 +312,11 @@ describe NewAdministrateur::ProceduresController, type: :controller do
describe 'PATCH #jeton' do describe 'PATCH #jeton' do
let(:procedure) { create(:procedure, administrateur: admin) } let(:procedure) { create(:procedure, administrateur: admin) }
let(:valid_token) { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
it "update api_entreprise_token" do it "update api_entreprise_token" do
patch :update_jeton, params: { id: procedure.id, procedure: { api_entreprise_token: 'ceci-est-un-jeton' } } patch :update_jeton, params: { id: procedure.id, procedure: { api_entreprise_token: valid_token } }
expect(procedure.reload.api_entreprise_token).to eq('ceci-est-un-jeton') expect(procedure.reload.api_entreprise_token).to eq(valid_token)
end end
end end
end end

View file

@ -106,26 +106,23 @@ describe StatsController, type: :controller do
before do before do
procedure_1 = FactoryBot.create(:procedure) procedure_1 = FactoryBot.create(:procedure)
procedure_2 = FactoryBot.create(:procedure) procedure_2 = FactoryBot.create(:procedure)
dossier_p1_a = FactoryBot.create(:dossier, dossier_p1_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:en_construction_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 3.days) :processed_at => 2.months.ago.beginning_of_month + 3.days)
dossier_p1_b = FactoryBot.create(:dossier, dossier_p1_b = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:en_construction_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = FactoryBot.create(:dossier, dossier_p1_c = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:en_construction_at => 1.month.ago.beginning_of_month, :en_construction_at => 1.month.ago.beginning_of_month,
:processed_at => 1.month.ago.beginning_of_month + 5.days) :processed_at => 1.month.ago.beginning_of_month + 5.days)
dossier_p2_a = FactoryBot.create(:dossier, dossier_p2_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_2, :procedure => procedure_2,
:en_construction_at => 2.months.ago.beginning_of_month, :en_construction_at => 2.months.ago.beginning_of_month,
:processed_at => 2.months.ago.beginning_of_month + 4.days) :processed_at => 2.months.ago.beginning_of_month + 4.days)
# Write directly in the DB to avoid the before_validation hook
Dossier.update_all(state: Dossier.states.fetch(:accepte))
@expected_hash = { @expected_hash = {
(2.months.ago.beginning_of_month).to_s => 3.0, (2.months.ago.beginning_of_month).to_s => 3.0,
(1.month.ago.beginning_of_month).to_s => 5.0 (1.month.ago.beginning_of_month).to_s => 5.0
@ -154,30 +151,27 @@ describe StatsController, type: :controller do
before do before do
procedure_1 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 24) procedure_1 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 24)
procedure_2 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 48) procedure_2 = FactoryBot.create(:procedure, :with_type_de_champ, :types_de_champ_count => 48)
dossier_p1_a = FactoryBot.create(:dossier, dossier_p1_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:created_at => 2.months.ago.beginning_of_month, :created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 30.minutes, :en_construction_at => 2.months.ago.beginning_of_month + 30.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_b = FactoryBot.create(:dossier, dossier_p1_b = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:created_at => 2.months.ago.beginning_of_month, :created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 10.minutes, :en_construction_at => 2.months.ago.beginning_of_month + 10.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
dossier_p1_c = FactoryBot.create(:dossier, dossier_p1_c = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_1, :procedure => procedure_1,
:created_at => 1.month.ago.beginning_of_month, :created_at => 1.month.ago.beginning_of_month,
:en_construction_at => 1.month.ago.beginning_of_month + 50.minutes, :en_construction_at => 1.month.ago.beginning_of_month + 50.minutes,
:processed_at => 1.month.ago.beginning_of_month + 1.day) :processed_at => 1.month.ago.beginning_of_month + 1.day)
dossier_p2_a = FactoryBot.create(:dossier, dossier_p2_a = FactoryBot.create(:dossier, :accepte,
:procedure => procedure_2, :procedure => procedure_2,
:created_at => 2.months.ago.beginning_of_month, :created_at => 2.months.ago.beginning_of_month,
:en_construction_at => 2.months.ago.beginning_of_month + 80.minutes, :en_construction_at => 2.months.ago.beginning_of_month + 80.minutes,
:processed_at => 2.months.ago.beginning_of_month + 1.day) :processed_at => 2.months.ago.beginning_of_month + 1.day)
# Write directly in the DB to avoid the before_validation hook
Dossier.update_all(state: Dossier.states.fetch(:accepte))
@expected_hash = { @expected_hash = {
(2.months.ago.beginning_of_month).to_s => 30.0, (2.months.ago.beginning_of_month).to_s => 30.0,
(1.month.ago.beginning_of_month).to_s => 50.0 (1.month.ago.beginning_of_month).to_s => 50.0

View file

@ -657,7 +657,7 @@ describe Users::DossiersController, type: :controller do
let!(:invite) { create(:invite, dossier: dossier, user: user) } let!(:invite) { create(:invite, dossier: dossier, user: user) }
before do before do
dossier.en_construction! dossier.passer_en_construction!
subject subject
end end

View file

@ -127,11 +127,23 @@ FactoryBot.define do
end end
trait :accepte do trait :accepte do
after(:create) do |dossier, _evaluator| transient do
motivation { nil }
processed_at { nil }
end
after(:create) do |dossier, evaluator|
dossier.state = Dossier.states.fetch(:accepte) dossier.state = Dossier.states.fetch(:accepte)
dossier.en_construction_at ||= dossier.created_at + 1.minute processed_at = evaluator.processed_at
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute if processed_at.present?
dossier.processed_at ||= dossier.en_instruction_at + 1.minute dossier.en_construction_at ||= processed_at - 2.minutes
dossier.en_instruction_at ||= processed_at - 1.minute
dossier.traitements.build(state: Dossier.states.fetch(:accepte), processed_at: processed_at, motivation: evaluator.motivation)
else
dossier.en_construction_at ||= dossier.created_at + 1.minute
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
dossier.traitements.build(state: Dossier.states.fetch(:accepte), processed_at: dossier.en_instruction_at + 1.minute, motivation: evaluator.motivation)
end
dossier.save! dossier.save!
end end
end end
@ -141,7 +153,7 @@ FactoryBot.define do
dossier.state = Dossier.states.fetch(:refuse) dossier.state = Dossier.states.fetch(:refuse)
dossier.en_construction_at ||= dossier.created_at + 1.minute dossier.en_construction_at ||= dossier.created_at + 1.minute
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
dossier.processed_at ||= dossier.en_instruction_at + 1.minute dossier.traitements.build(state: Dossier.states.fetch(:refuse), processed_at: dossier.en_instruction_at + 1.minute)
dossier.save! dossier.save!
end end
end end
@ -151,14 +163,14 @@ FactoryBot.define do
dossier.state = Dossier.states.fetch(:sans_suite) dossier.state = Dossier.states.fetch(:sans_suite)
dossier.en_construction_at ||= dossier.created_at + 1.minute dossier.en_construction_at ||= dossier.created_at + 1.minute
dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute dossier.en_instruction_at ||= dossier.en_construction_at + 1.minute
dossier.processed_at ||= dossier.en_instruction_at + 1.minute dossier.traitements.build(state: Dossier.states.fetch(:sans_suite), processed_at: dossier.en_instruction_at + 1.minute)
dossier.save! dossier.save!
end end
end end
trait :with_motivation do trait :with_motivation do
after(:create) do |dossier, _evaluator| after(:create) do |dossier, _evaluator|
dossier.motivation = case dossier.state motivation = case dossier.state
when Dossier.states.fetch(:refuse) when Dossier.states.fetch(:refuse)
'Lentreprise concernée nest pas agréée.' 'Lentreprise concernée nest pas agréée.'
when Dossier.states.fetch(:sans_suite) when Dossier.states.fetch(:sans_suite)
@ -166,6 +178,7 @@ FactoryBot.define do
else else
'Vous avez validé les conditions.' 'Vous avez validé les conditions.'
end end
dossier.traitements.last.update!(motivation: motivation)
end end
end end

View file

@ -108,6 +108,12 @@ FactoryBot.define do
end end
factory :type_de_champ_repetition do factory :type_de_champ_repetition do
type_champ { TypeDeChamp.type_champs.fetch(:repetition) } type_champ { TypeDeChamp.type_champs.fetch(:repetition) }
trait :with_types_de_champ do
after(:build) do |type_de_champ, _evaluator|
type_de_champ.types_de_champ << create(:type_de_champ, libelle: 'sub type de champ')
end
end
end end
trait :private do trait :private do

View file

@ -156,7 +156,7 @@ RSpec.describe DossierHelper, type: :helper do
it 'sans_suite is traité' do it 'sans_suite is traité' do
dossier.sans_suite! dossier.sans_suite!
expect(subject).to eq('Sans suite') expect(subject).to eq('Classé sans suite')
end end
it 'refuse is traité' do it 'refuse is traité' do

View file

@ -62,13 +62,13 @@ describe ApiEntreprise::API do
end end
context 'with specific token for procedure' do context 'with specific token for procedure' do
let(:token) { 'token-for-demarche' } let(:token) { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
let(:procedure) { create(:procedure, api_entreprise_token: token) } let(:procedure) { create(:procedure, api_entreprise_token: token) }
let(:procedure_id) { procedure.id } let(:procedure_id) { procedure.id }
it 'call api-entreprise with specfic token' do it 'call api-entreprise with specfic token' do
subject subject
expect(WebMock).to have_requested(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/entreprises\/#{siren}?.*token=token-for-demarche/) expect(WebMock).to have_requested(:get, /https:\/\/entreprise.api.gouv.fr\/v2\/entreprises\/#{siren}?.*token=#{token}/)
end end
end end

View file

@ -35,6 +35,7 @@ describe TagsSubstitutionConcern, type: :model do
let(:individual) { nil } let(:individual) { nil }
let(:etablissement) { create(:etablissement) } let(:etablissement) { create(:etablissement) }
let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) } let!(:dossier) { create(:dossier, procedure: procedure, individual: individual, etablissement: etablissement) }
let(:instructeur) { create(:instructeur) }
before { Timecop.freeze(Time.zone.now) } before { Timecop.freeze(Time.zone.now) }
@ -242,7 +243,7 @@ describe TagsSubstitutionConcern, type: :model do
end end
context 'when the dossier has a motivation' do context 'when the dossier has a motivation' do
let(:dossier) { create(:dossier, motivation: 'motivation') } let(:dossier) { create(:dossier, :accepte, motivation: 'motivation') }
context 'and the template has some dossier tags' do context 'and the template has some dossier tags' do
let(:template) { '--motivation-- --numéro du dossier--' } let(:template) { '--motivation-- --numéro du dossier--' }
@ -318,9 +319,13 @@ describe TagsSubstitutionConcern, type: :model do
context "when using a date tag" do context "when using a date tag" do
before do before do
dossier.en_construction_at = Time.zone.local(2001, 2, 3) Timecop.freeze(Time.zone.local(2001, 2, 3))
dossier.en_instruction_at = Time.zone.local(2004, 5, 6) dossier.passer_en_construction!
dossier.processed_at = Time.zone.local(2007, 8, 9) Timecop.freeze(Time.zone.local(2004, 5, 6))
dossier.passer_en_instruction!(instructeur)
Timecop.freeze(Time.zone.local(2007, 8, 9))
dossier.accepter!(instructeur, nil, nil)
Timecop.return
end end
context "with date de dépôt" do context "with date de dépôt" do

View file

@ -209,8 +209,17 @@ describe Dossier do
let(:date1) { 1.day.ago } let(:date1) { 1.day.ago }
let(:date2) { 1.hour.ago } let(:date2) { 1.hour.ago }
let(:date3) { 1.minute.ago } let(:date3) { 1.minute.ago }
let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure, en_construction_at: date1, en_instruction_at: date2, processed_at: date3, motivation: "Motivation") } let(:dossier) do
let!(:follow) { create(:follow, instructeur: instructeur, dossier: dossier) } d = create(:dossier, :with_entreprise, user: user, procedure: procedure)
Timecop.freeze(date1)
d.passer_en_construction!
Timecop.freeze(date2)
d.passer_en_instruction!(instructeur)
Timecop.freeze(date3)
d.accepter!(instructeur, "Motivation", nil)
Timecop.return
d
end
describe "followers_instructeurs" do describe "followers_instructeurs" do
let(:non_following_instructeur) { create(:instructeur) } let(:non_following_instructeur) { create(:instructeur) }
@ -346,13 +355,14 @@ describe Dossier do
let(:state) { Dossier.states.fetch(:brouillon) } let(:state) { Dossier.states.fetch(:brouillon) }
let(:dossier) { create(:dossier, state: state) } let(:dossier) { create(:dossier, state: state) }
let(:beginning_of_day) { Time.zone.now.beginning_of_day } let(:beginning_of_day) { Time.zone.now.beginning_of_day }
let(:instructeur) { create(:instructeur) }
before { Timecop.freeze(beginning_of_day) } before { Timecop.freeze(beginning_of_day) }
after { Timecop.return } after { Timecop.return }
context 'when dossier is en_construction' do context 'when dossier is en_construction' do
before do before do
dossier.en_construction! dossier.passer_en_construction!
dossier.reload dossier.reload
end end
@ -361,8 +371,8 @@ describe Dossier do
it 'should keep first en_construction_at date' do it 'should keep first en_construction_at date' do
Timecop.return Timecop.return
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
dossier.en_construction! dossier.repasser_en_construction!(instructeur)
expect(dossier.en_construction_at).to eq(beginning_of_day) expect(dossier.en_construction_at).to eq(beginning_of_day)
end end
@ -370,9 +380,10 @@ describe Dossier do
context 'when dossier is en_instruction' do context 'when dossier is en_instruction' do
let(:state) { Dossier.states.fetch(:en_construction) } let(:state) { Dossier.states.fetch(:en_construction) }
let(:instructeur) { create(:instructeur) }
before do before do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
dossier.reload dossier.reload
end end
@ -381,39 +392,48 @@ describe Dossier do
it 'should keep first en_instruction_at date if dossier is set to en_construction again' do it 'should keep first en_instruction_at date if dossier is set to en_construction again' do
Timecop.return Timecop.return
dossier.en_construction! dossier.repasser_en_construction!(instructeur)
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
expect(dossier.en_instruction_at).to eq(beginning_of_day) expect(dossier.en_instruction_at).to eq(beginning_of_day)
end end
end end
shared_examples 'dossier is processed' do |new_state|
before do
dossier.update(state: new_state)
dossier.reload
end
it { expect(dossier.state).to eq(new_state) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end
context 'when dossier is accepte' do context 'when dossier is accepte' do
let(:state) { Dossier.states.fetch(:en_instruction) } let(:state) { Dossier.states.fetch(:en_instruction) }
it_behaves_like 'dossier is processed', Dossier.states.fetch(:accepte) before do
dossier.accepter!(instructeur, nil, nil)
dossier.reload
end
it { expect(dossier.state).to eq(Dossier.states.fetch(:accepte)) }
it { expect(dossier.traitements.last.processed_at).to eq(beginning_of_day) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end end
context 'when dossier is refuse' do context 'when dossier is refuse' do
let(:state) { Dossier.states.fetch(:en_instruction) } let(:state) { Dossier.states.fetch(:en_instruction) }
it_behaves_like 'dossier is processed', Dossier.states.fetch(:refuse) before do
dossier.refuser!(instructeur, nil, nil)
dossier.reload
end
it { expect(dossier.state).to eq(Dossier.states.fetch(:refuse)) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end end
context 'when dossier is sans_suite' do context 'when dossier is sans_suite' do
let(:state) { Dossier.states.fetch(:en_instruction) } let(:state) { Dossier.states.fetch(:en_instruction) }
it_behaves_like 'dossier is processed', Dossier.states.fetch(:sans_suite) before do
dossier.classer_sans_suite!(instructeur, nil, nil)
dossier.reload
end
it { expect(dossier.state).to eq(Dossier.states.fetch(:sans_suite)) }
it { expect(dossier.processed_at).to eq(beginning_of_day) }
end end
end end
@ -478,13 +498,14 @@ describe Dossier do
describe "#send_dossier_received" do describe "#send_dossier_received" do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) } let(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) }
let(:instructeur) { create(:instructeur) }
before do before do
allow(NotificationMailer).to receive(:send_dossier_received).and_return(double(deliver_later: nil)) allow(NotificationMailer).to receive(:send_dossier_received).and_return(double(deliver_later: nil))
end end
it "sends an email when the dossier becomes en_instruction" do it "sends an email when the dossier becomes en_instruction" do
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
expect(NotificationMailer).to have_received(:send_dossier_received).with(dossier) expect(NotificationMailer).to have_received(:send_dossier_received).with(dossier)
end end
@ -777,6 +798,7 @@ describe Dossier do
describe 'webhook' do describe 'webhook' do
let(:dossier) { create(:dossier) } let(:dossier) { create(:dossier) }
let(:instructeur) { create(:instructeur) }
it 'should not call webhook' do it 'should not call webhook' do
expect { expect {
@ -788,19 +810,19 @@ describe Dossier do
dossier.procedure.update_column(:web_hook_url, '/webhook.json') dossier.procedure.update_column(:web_hook_url, '/webhook.json')
expect { expect {
dossier.update_column(:motivation, 'bonjour') dossier.update_column(:search_terms, 'bonjour')
}.to_not have_enqueued_job(WebHookJob) }.to_not have_enqueued_job(WebHookJob)
expect { expect {
dossier.en_construction! dossier.passer_en_construction!
}.to have_enqueued_job(WebHookJob) }.to have_enqueued_job(WebHookJob)
expect { expect {
dossier.update_column(:motivation, 'bonjour2') dossier.update_column(:search_terms, 'bonjour2')
}.to_not have_enqueued_job(WebHookJob) }.to_not have_enqueued_job(WebHookJob)
expect { expect {
dossier.en_instruction! dossier.passer_en_instruction!(instructeur)
}.to have_enqueued_job(WebHookJob) }.to have_enqueued_job(WebHookJob)
end end
end end
@ -891,8 +913,11 @@ describe Dossier do
after { Timecop.return } after { Timecop.return }
it { expect(dossier.traitements.last.motivation).to eq('motivation') }
it { expect(dossier.motivation).to eq('motivation') } it { expect(dossier.motivation).to eq('motivation') }
it { expect(dossier.traitements.last.instructeur_email).to eq(instructeur.email) }
it { expect(dossier.en_instruction_at).to eq(dossier.en_instruction_at) } it { expect(dossier.en_instruction_at).to eq(dossier.en_instruction_at) }
it { expect(dossier.traitements.last.processed_at).to eq(now) }
it { expect(dossier.processed_at).to eq(now) } it { expect(dossier.processed_at).to eq(now) }
it { expect(dossier.state).to eq('accepte') } it { expect(dossier.state).to eq('accepte') }
it { expect(last_operation.operation).to eq('accepter') } it { expect(last_operation.operation).to eq('accepter') }

View file

@ -478,7 +478,7 @@ describe Instructeur, type: :model do
before do before do
procedure_to_assign.update(declarative_with_state: "accepte") procedure_to_assign.update(declarative_with_state: "accepte")
DeclarativeProceduresJob.new.perform DeclarativeProceduresJob.new.perform
dossier.update(processed_at: Time.zone.yesterday.beginning_of_day) dossier.traitements.last.update(processed_at: Time.zone.yesterday.beginning_of_day)
dossier.reload dossier.reload
end end

View file

@ -205,6 +205,13 @@ describe Procedure do
it { expect(procedure.valid?).to eq(false) } it { expect(procedure.valid?).to eq(false) }
end end
end end
context 'api_entreprise_token' do
let(:valid_token) { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
let(:invalid_token) { 'plouf' }
it { is_expected.to allow_value(valid_token).for(:api_entreprise_token) }
it { is_expected.not_to allow_value(invalid_token).for(:api_entreprise_token) }
end
end end
context 'when juridique_required is false' do context 'when juridique_required is false' do
@ -335,7 +342,7 @@ describe Procedure do
end end
describe 'api_entreprise_token_expired?' do describe 'api_entreprise_token_expired?' do
let(:token) { "mon-token" } let(:token) { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
let(:procedure) { create(:procedure, api_entreprise_token: token) } let(:procedure) { create(:procedure, api_entreprise_token: token) }
let(:payload) { let(:payload) {
[ [
@ -958,8 +965,7 @@ describe Procedure do
let(:procedure) { create(:procedure) } let(:procedure) { create(:procedure) }
def create_dossier(construction_date:, instruction_date:, processed_date:) def create_dossier(construction_date:, instruction_date:, processed_date:)
dossier = create(:dossier, :accepte, procedure: procedure) dossier = create(:dossier, :accepte, procedure: procedure, en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date)
dossier.update!(en_construction_at: construction_date, en_instruction_at: instruction_date, processed_at: processed_date)
end end
before do before do

View file

@ -48,7 +48,7 @@ shared_examples 'type_de_champ_spec' do
} }
end end
context 'remove piece_justificative_template' do context 'changing the type_champ from a piece_justificative' do
context 'when the tdc is piece_justificative' do context 'when the tdc is piece_justificative' do
let(:template_double) { double('template', attached?: attached, purge_later: true) } let(:template_double) { double('template', attached?: attached, purge_later: true) }
let(:tdc) { create(:type_de_champ_piece_justificative) } let(:tdc) { create(:type_de_champ_piece_justificative) }
@ -89,6 +89,48 @@ shared_examples 'type_de_champ_spec' do
end end
end end
describe 'changing the type_champ from a repetition' do
let(:tdc) { create(:type_de_champ_repetition, :with_types_de_champ) }
before do
tdc.update_attribute('type_champ', target_type_champ)
end
context 'when the target type_champ is not repetition' do
let(:target_type_champ) { TypeDeChamp.type_champs.fetch(:text) }
it 'removes the children types de champ' do
expect(tdc.types_de_champ).to be_empty
end
end
end
describe 'changing the type_champ from a drop_down_list' do
let(:tdc) { create(:type_de_champ_drop_down_list) }
before do
tdc.update_attribute('type_champ', target_type_champ)
end
context 'when the target type_champ is not drop_down_list' do
let(:target_type_champ) { TypeDeChamp.type_champs.fetch(:text) }
it { expect(tdc.drop_down_options).to be_nil }
end
context 'when the target type_champ is linked_drop_down_list' do
let(:target_type_champ) { TypeDeChamp.type_champs.fetch(:linked_drop_down_list) }
it { expect(tdc.drop_down_options).to be_present }
end
context 'when the target type_champ is multiple_drop_down_list' do
let(:target_type_champ) { TypeDeChamp.type_champs.fetch(:multiple_drop_down_list) }
it { expect(tdc.drop_down_options).to be_present }
end
end
context 'delegate validation to dynamic type' do context 'delegate validation to dynamic type' do
subject { build(:type_de_champ_text) } subject { build(:type_de_champ_text) }
let(:dynamic_type) do let(:dynamic_type) do

View file

@ -8,7 +8,8 @@ describe ApiEntrepriseService do
let(:siret) { '41816609600051' } let(:siret) { '41816609600051' }
let(:etablissements_status) { 200 } let(:etablissements_status) { 200 }
let(:etablissements_body) { File.read('spec/fixtures/files/api_entreprise/etablissements.json') } let(:etablissements_body) { File.read('spec/fixtures/files/api_entreprise/etablissements.json') }
let(:procedure) { create(:procedure, api_entreprise_token: 'un-jeton') } let(:valid_token) { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
let(:procedure) { create(:procedure, api_entreprise_token: valid_token) }
let(:dossier) { create(:dossier, procedure: procedure) } let(:dossier) { create(:dossier, procedure: procedure) }
let(:subject) { ApiEntrepriseService.create_etablissement(dossier, siret, procedure.id) } let(:subject) { ApiEntrepriseService.create_etablissement(dossier, siret, procedure.id) }

View file

@ -66,7 +66,7 @@ describe NotificationService do
before do before do
procedure.update(declarative_with_state: "accepte") procedure.update(declarative_with_state: "accepte")
DeclarativeProceduresJob.new.perform DeclarativeProceduresJob.new.perform
dossier.update(processed_at: Time.zone.yesterday.beginning_of_day) dossier.traitements.last.update!(processed_at: Time.zone.yesterday.beginning_of_day)
dossier.reload dossier.reload
end end