2020-08-06 16:35:45 +02:00
|
|
|
|
# == Schema Information
|
|
|
|
|
#
|
|
|
|
|
# Table name: procedures
|
|
|
|
|
#
|
|
|
|
|
# id :integer not null, primary key
|
|
|
|
|
# aasm_state :string default("brouillon")
|
2020-09-30 18:22:06 +02:00
|
|
|
|
# allow_expert_review :boolean default(TRUE), not null
|
2020-08-06 16:35:45 +02:00
|
|
|
|
# api_entreprise_token :string
|
|
|
|
|
# ask_birthday :boolean default(FALSE), not null
|
|
|
|
|
# auto_archive_on :date
|
|
|
|
|
# cadre_juridique :string
|
|
|
|
|
# cerfa_flag :boolean default(FALSE)
|
|
|
|
|
# cloned_from_library :boolean default(FALSE)
|
|
|
|
|
# closed_at :datetime
|
|
|
|
|
# declarative_with_state :string
|
|
|
|
|
# description :string
|
|
|
|
|
# direction :string
|
|
|
|
|
# duree_conservation_dossiers_dans_ds :integer
|
|
|
|
|
# duree_conservation_dossiers_hors_ds :integer
|
|
|
|
|
# durees_conservation_required :boolean default(TRUE)
|
|
|
|
|
# euro_flag :boolean default(FALSE)
|
|
|
|
|
# for_individual :boolean default(FALSE)
|
|
|
|
|
# hidden_at :datetime
|
|
|
|
|
# juridique_required :boolean default(TRUE)
|
|
|
|
|
# libelle :string
|
|
|
|
|
# lien_demarche :string
|
|
|
|
|
# lien_notice :string
|
|
|
|
|
# lien_site_web :string
|
|
|
|
|
# monavis_embed :text
|
|
|
|
|
# organisation :string
|
|
|
|
|
# path :string not null
|
|
|
|
|
# published_at :datetime
|
|
|
|
|
# routing_criteria_name :text default("Votre ville")
|
|
|
|
|
# test_started_at :datetime
|
|
|
|
|
# unpublished_at :datetime
|
|
|
|
|
# web_hook_url :string
|
|
|
|
|
# whitelisted_at :datetime
|
|
|
|
|
# created_at :datetime not null
|
|
|
|
|
# updated_at :datetime not null
|
|
|
|
|
# canonical_procedure_id :bigint
|
|
|
|
|
# draft_revision_id :bigint
|
|
|
|
|
# parent_procedure_id :bigint
|
|
|
|
|
# published_revision_id :bigint
|
|
|
|
|
# service_id :bigint
|
|
|
|
|
#
|
2018-11-08 18:09:27 +01:00
|
|
|
|
require Rails.root.join('lib', 'percentile')
|
|
|
|
|
|
2018-03-06 13:44:29 +01:00
|
|
|
|
class Procedure < ApplicationRecord
|
2020-02-19 17:25:25 +01:00
|
|
|
|
self.ignored_columns = ['archived_at', 'csv_export_queued', 'xlsx_export_queued', 'ods_export_queued']
|
2019-08-28 16:14:20 +02:00
|
|
|
|
|
2019-09-17 15:52:38 +02:00
|
|
|
|
include ProcedureStatsConcern
|
|
|
|
|
|
2020-02-05 16:09:03 +01:00
|
|
|
|
include Discard::Model
|
|
|
|
|
self.discard_column = :hidden_at
|
|
|
|
|
default_scope -> { kept }
|
|
|
|
|
|
2018-05-24 23:29:33 +02:00
|
|
|
|
MAX_DUREE_CONSERVATION = 36
|
2019-10-21 17:14:56 +02:00
|
|
|
|
MAX_DUREE_CONSERVATION_EXPORT = 3.hours
|
2018-05-24 23:29:33 +02:00
|
|
|
|
|
2020-09-17 11:15:21 +02:00
|
|
|
|
has_many :revisions, -> { order(:id) }, class_name: 'ProcedureRevision', inverse_of: :procedure
|
2020-08-27 19:55:10 +02:00
|
|
|
|
belongs_to :draft_revision, class_name: 'ProcedureRevision', optional: false
|
2020-06-26 11:37:28 +02:00
|
|
|
|
belongs_to :published_revision, class_name: 'ProcedureRevision', optional: true
|
2018-05-30 11:36:48 +02:00
|
|
|
|
has_many :deleted_dossiers, dependent: :destroy
|
2017-03-07 10:25:28 +01:00
|
|
|
|
|
2020-09-08 15:53:07 +02:00
|
|
|
|
has_many :published_types_de_champ, -> { ordered }, through: :published_revision, source: :types_de_champ
|
|
|
|
|
has_many :published_types_de_champ_private, -> { ordered }, through: :published_revision, source: :types_de_champ_private
|
|
|
|
|
has_many :draft_types_de_champ, -> { ordered }, through: :draft_revision, source: :types_de_champ
|
|
|
|
|
has_many :draft_types_de_champ_private, -> { ordered }, through: :draft_revision, source: :types_de_champ_private
|
|
|
|
|
|
|
|
|
|
has_many :all_types_de_champ, -> { joins(:procedure).where('types_de_champ.revision_id != procedures.draft_revision_id').ordered }, through: :revisions, source: :types_de_champ
|
|
|
|
|
has_many :all_types_de_champ_private, -> { joins(:procedure).where('types_de_champ.revision_id != procedures.draft_revision_id').ordered }, through: :revisions, source: :types_de_champ_private
|
2020-08-27 19:55:10 +02:00
|
|
|
|
|
2016-01-18 16:20:51 +01:00
|
|
|
|
has_one :module_api_carto, dependent: :destroy
|
2017-06-08 14:16:48 +02:00
|
|
|
|
has_one :attestation_template, dependent: :destroy
|
2016-01-18 16:20:51 +01:00
|
|
|
|
|
2020-07-20 16:06:03 +02:00
|
|
|
|
belongs_to :parent_procedure, class_name: 'Procedure', optional: true
|
|
|
|
|
belongs_to :canonical_procedure, class_name: 'Procedure', optional: true
|
|
|
|
|
belongs_to :service, optional: true
|
2016-01-18 16:20:51 +01:00
|
|
|
|
|
2020-06-26 11:37:28 +02:00
|
|
|
|
def active_revision
|
|
|
|
|
brouillon? ? draft_revision : published_revision
|
|
|
|
|
end
|
|
|
|
|
|
2020-08-27 19:55:10 +02:00
|
|
|
|
def types_de_champ
|
|
|
|
|
brouillon? ? draft_types_de_champ : published_types_de_champ
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def types_de_champ_private
|
|
|
|
|
brouillon? ? draft_types_de_champ_private : published_types_de_champ_private
|
|
|
|
|
end
|
|
|
|
|
|
2020-09-08 15:53:07 +02:00
|
|
|
|
def types_de_champ_for_export
|
|
|
|
|
if brouillon?
|
|
|
|
|
draft_types_de_champ
|
|
|
|
|
else
|
|
|
|
|
all_types_de_champ
|
|
|
|
|
.uniq
|
|
|
|
|
.reject(&:exclude_from_export?)
|
|
|
|
|
.filter(&:active_revision?)
|
|
|
|
|
.group_by(&:stable_id).values.map do |types_de_champ|
|
|
|
|
|
types_de_champ.sort_by(&:created_at).last
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def types_de_champ_private_for_export
|
|
|
|
|
if brouillon?
|
|
|
|
|
draft_types_de_champ_private
|
|
|
|
|
else
|
|
|
|
|
all_types_de_champ_private
|
|
|
|
|
.uniq
|
|
|
|
|
.reject(&:exclude_from_export?)
|
|
|
|
|
.filter(&:active_revision?)
|
|
|
|
|
.group_by(&:stable_id).values.map do |types_de_champ|
|
|
|
|
|
types_de_champ.sort_by(&:created_at).last
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-03-23 11:39:36 +01:00
|
|
|
|
has_many :administrateurs_procedures
|
2019-04-29 18:27:44 +02:00
|
|
|
|
has_many :administrateurs, through: :administrateurs_procedures, after_remove: -> (procedure, _admin) { procedure.validate! }
|
2019-08-19 16:12:30 +02:00
|
|
|
|
has_many :groupe_instructeurs, dependent: :destroy
|
2020-04-09 09:22:37 +02:00
|
|
|
|
has_many :instructeurs, through: :groupe_instructeurs
|
2016-05-20 15:39:17 +02:00
|
|
|
|
|
2020-09-17 11:15:21 +02:00
|
|
|
|
# This relationship is used in following dossiers through. We can not use revisions relationship
|
|
|
|
|
# as order scope introduces invalid sql in some combinations.
|
|
|
|
|
has_many :unordered_revisions, class_name: 'ProcedureRevision', inverse_of: :procedure, dependent: :destroy
|
|
|
|
|
has_many :dossiers, through: :unordered_revisions, dependent: :restrict_with_exception
|
2019-08-22 17:58:31 +02:00
|
|
|
|
|
2017-05-26 21:55:19 +02:00
|
|
|
|
has_one :initiated_mail, class_name: "Mails::InitiatedMail", dependent: :destroy
|
|
|
|
|
has_one :received_mail, class_name: "Mails::ReceivedMail", dependent: :destroy
|
|
|
|
|
has_one :closed_mail, class_name: "Mails::ClosedMail", dependent: :destroy
|
|
|
|
|
has_one :refused_mail, class_name: "Mails::RefusedMail", dependent: :destroy
|
|
|
|
|
has_one :without_continuation_mail, class_name: "Mails::WithoutContinuationMail", dependent: :destroy
|
|
|
|
|
|
2019-10-15 09:28:21 +02:00
|
|
|
|
has_one :defaut_groupe_instructeur, -> { order(:id) }, class_name: 'GroupeInstructeur', inverse_of: :procedure
|
2019-09-16 15:37:12 +02:00
|
|
|
|
|
2019-08-28 13:11:58 +02:00
|
|
|
|
has_one_attached :logo
|
2018-04-11 14:35:52 +02:00
|
|
|
|
has_one_attached :notice
|
2018-05-31 10:59:38 +02:00
|
|
|
|
has_one_attached :deliberation
|
2019-10-03 15:35:31 +02:00
|
|
|
|
|
2018-05-16 17:21:12 +02:00
|
|
|
|
scope :brouillons, -> { where(aasm_state: :brouillon) }
|
|
|
|
|
scope :publiees, -> { where(aasm_state: :publiee) }
|
2019-12-18 13:28:29 +01:00
|
|
|
|
scope :closes, -> { where(aasm_state: [:close, :depubliee]) }
|
|
|
|
|
scope :publiees_ou_closes, -> { where(aasm_state: [:publiee, :close, :depubliee]) }
|
2017-07-17 15:06:36 +02:00
|
|
|
|
scope :by_libelle, -> { order(libelle: :asc) }
|
2018-04-12 18:36:09 +02:00
|
|
|
|
scope :created_during, -> (range) { where(created_at: range) }
|
|
|
|
|
scope :cloned_from_library, -> { where(cloned_from_library: true) }
|
2019-05-23 14:28:14 +02:00
|
|
|
|
scope :declarative, -> { where.not(declarative_with_state: nil) }
|
2017-05-26 21:30:11 +02:00
|
|
|
|
|
2020-03-26 17:37:55 +01:00
|
|
|
|
scope :discarded_expired, -> do
|
|
|
|
|
with_discarded
|
|
|
|
|
.discarded
|
|
|
|
|
.where('hidden_at < ?', 1.month.ago)
|
|
|
|
|
end
|
|
|
|
|
|
2018-11-01 14:04:32 +01:00
|
|
|
|
scope :for_api, -> {
|
|
|
|
|
includes(
|
2019-02-26 16:18:04 +01:00
|
|
|
|
:administrateurs,
|
2020-08-27 19:55:10 +02:00
|
|
|
|
:module_api_carto,
|
|
|
|
|
published_revision: [
|
|
|
|
|
:types_de_champ_private,
|
|
|
|
|
:types_de_champ
|
|
|
|
|
],
|
|
|
|
|
draft_revision: [
|
|
|
|
|
:types_de_champ_private,
|
|
|
|
|
:types_de_champ
|
|
|
|
|
]
|
2018-11-01 14:04:32 +01:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-23 14:28:14 +02:00
|
|
|
|
enum declarative_with_state: {
|
|
|
|
|
en_instruction: 'en_instruction',
|
|
|
|
|
accepte: 'accepte'
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-19 20:55:24 +01:00
|
|
|
|
scope :for_api_v2, -> {
|
2020-07-16 12:48:35 +02:00
|
|
|
|
includes(:draft_revision, :published_revision, administrateurs: :user)
|
2018-11-19 20:55:24 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-09-22 11:21:52 +02:00
|
|
|
|
validates :libelle, presence: true, allow_blank: false, allow_nil: false
|
|
|
|
|
validates :description, presence: true, allow_blank: false, allow_nil: false
|
2019-04-29 18:27:44 +02:00
|
|
|
|
validates :administrateurs, presence: true
|
2019-07-29 16:01:13 +02:00
|
|
|
|
validates :lien_site_web, presence: true, if: :publiee?
|
2019-07-30 16:54:43 +02:00
|
|
|
|
validate :validate_for_publication, on: :publication
|
2018-05-31 10:59:38 +02:00
|
|
|
|
validate :check_juridique
|
2020-10-06 14:24:34 +02:00
|
|
|
|
validates :path, presence: true, format: { with: /\A[a-z0-9_\-]{3,200}\z/ }, uniqueness: { scope: [:path, :closed_at, :hidden_at, :unpublished_at], case_sensitive: false }
|
2020-04-14 15:49:54 +02:00
|
|
|
|
validates :duree_conservation_dossiers_dans_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: MAX_DUREE_CONSERVATION }
|
|
|
|
|
validates :duree_conservation_dossiers_hors_ds, allow_nil: false, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
2019-07-17 17:13:08 +02:00
|
|
|
|
validates_with MonAvisEmbedValidator
|
2020-03-16 17:55:16 +01:00
|
|
|
|
validates :notice, content_type: [
|
|
|
|
|
"application/msword",
|
|
|
|
|
"application/pdf",
|
|
|
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
|
|
|
"application/vnd.ms-powerpoint",
|
|
|
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
|
|
|
"application/vnd.oasis.opendocument.text",
|
|
|
|
|
"application/vnd.oasis.opendocument.presentation",
|
|
|
|
|
"text/plain"
|
|
|
|
|
], size: { less_than: 20.megabytes }
|
|
|
|
|
|
|
|
|
|
validates :deliberation, content_type: [
|
|
|
|
|
"application/msword",
|
|
|
|
|
"application/pdf",
|
|
|
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
|
|
|
"text/plain",
|
|
|
|
|
"application/vnd.oasis.opendocument.text"
|
|
|
|
|
], size: { less_than: 20.megabytes }
|
|
|
|
|
|
|
|
|
|
validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'], size: { less_than: 5.megabytes }
|
2020-07-08 17:00:21 +02:00
|
|
|
|
validates :api_entreprise_token, jwt_token: true, allow_blank: true
|
|
|
|
|
|
2018-06-01 11:06:12 +02:00
|
|
|
|
before_save :update_juridique_required
|
2019-09-16 17:00:37 +02:00
|
|
|
|
after_initialize :ensure_path_exists
|
|
|
|
|
before_save :ensure_path_exists
|
2019-08-27 15:28:02 +02:00
|
|
|
|
after_create :ensure_default_groupe_instructeur
|
2018-06-01 11:06:12 +02:00
|
|
|
|
|
2018-05-17 15:38:49 +02:00
|
|
|
|
include AASM
|
|
|
|
|
|
|
|
|
|
aasm whiny_persistence: true do
|
|
|
|
|
state :brouillon, initial: true
|
|
|
|
|
state :publiee
|
2019-11-14 09:43:45 +01:00
|
|
|
|
state :close
|
2019-12-04 15:45:06 +01:00
|
|
|
|
state :depubliee
|
2018-05-17 15:38:49 +02:00
|
|
|
|
|
2019-09-16 17:00:37 +02:00
|
|
|
|
event :publish, before: :before_publish, after: :after_publish do
|
2018-05-17 15:38:49 +02:00
|
|
|
|
transitions from: :brouillon, to: :publiee
|
2019-11-14 09:43:45 +01:00
|
|
|
|
transitions from: :close, to: :publiee
|
2019-12-04 15:45:06 +01:00
|
|
|
|
transitions from: :depubliee, to: :publiee
|
2018-05-17 15:38:49 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-11-14 09:43:45 +01:00
|
|
|
|
event :close, after: :after_close do
|
|
|
|
|
transitions from: :publiee, to: :close
|
2018-05-17 15:38:49 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-04 15:45:06 +01:00
|
|
|
|
event :unpublish, after: :after_unpublish do
|
|
|
|
|
transitions from: :publiee, to: :depubliee
|
|
|
|
|
end
|
2018-05-17 15:38:49 +02:00
|
|
|
|
end
|
2018-05-17 15:39:37 +02:00
|
|
|
|
|
2019-07-30 16:54:43 +02:00
|
|
|
|
def publish_or_reopen!(administrateur)
|
2019-09-16 17:00:37 +02:00
|
|
|
|
Procedure.transaction do
|
|
|
|
|
if brouillon?
|
|
|
|
|
reset!
|
|
|
|
|
end
|
2019-07-30 16:54:43 +02:00
|
|
|
|
|
|
|
|
|
other_procedure = other_procedure_with_path(path)
|
|
|
|
|
if other_procedure.present? && administrateur.owns?(other_procedure)
|
2019-12-04 15:45:06 +01:00
|
|
|
|
other_procedure.unpublish!
|
2019-12-04 16:58:36 +01:00
|
|
|
|
publish!(other_procedure.canonical_procedure || other_procedure)
|
|
|
|
|
else
|
|
|
|
|
publish!
|
2019-07-30 16:54:43 +02:00
|
|
|
|
end
|
2018-09-07 18:42:12 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-08-13 17:49:15 +02:00
|
|
|
|
def reset!
|
|
|
|
|
if locked?
|
|
|
|
|
raise "Can not reset a locked procedure."
|
|
|
|
|
else
|
2019-08-22 17:58:31 +02:00
|
|
|
|
groupe_instructeurs.each { |gi| gi.dossiers.destroy_all }
|
2018-08-13 17:49:15 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-30 16:54:43 +02:00
|
|
|
|
def validate_for_publication
|
2019-12-18 13:28:29 +01:00
|
|
|
|
old_attributes = self.slice(:aasm_state, :closed_at)
|
2019-07-30 16:54:43 +02:00
|
|
|
|
self.aasm_state = :publiee
|
2019-11-14 09:43:45 +01:00
|
|
|
|
self.closed_at = nil
|
2019-07-30 16:54:43 +02:00
|
|
|
|
|
|
|
|
|
is_valid = validate
|
|
|
|
|
|
|
|
|
|
self.attributes = old_attributes
|
|
|
|
|
|
|
|
|
|
is_valid
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def suggested_path(administrateur)
|
|
|
|
|
if path_customized?
|
|
|
|
|
return path
|
|
|
|
|
end
|
|
|
|
|
slug = libelle&.parameterize&.first(50)
|
|
|
|
|
suggestion = slug
|
|
|
|
|
counter = 1
|
|
|
|
|
while !path_available?(administrateur, suggestion)
|
|
|
|
|
counter = counter + 1
|
|
|
|
|
suggestion = "#{slug}-#{counter}"
|
|
|
|
|
end
|
|
|
|
|
suggestion
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def other_procedure_with_path(path)
|
|
|
|
|
Procedure.publiees
|
|
|
|
|
.where.not(id: self.id)
|
|
|
|
|
.find_by(path: path)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def path_available?(administrateur, path)
|
|
|
|
|
procedure = other_procedure_with_path(path)
|
|
|
|
|
|
2020-03-13 15:32:30 +01:00
|
|
|
|
procedure.blank? || (administrateur.owns?(procedure) && canonical_procedure_child?(procedure))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def canonical_procedure_child?(procedure)
|
|
|
|
|
!canonical_procedure || canonical_procedure == procedure || canonical_procedure == procedure.canonical_procedure
|
2019-07-30 16:54:43 +02:00
|
|
|
|
end
|
|
|
|
|
|
2018-05-17 15:41:44 +02:00
|
|
|
|
def locked?
|
2019-12-04 15:45:06 +01:00
|
|
|
|
publiee? || close? || depubliee?
|
2018-05-17 15:41:44 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-03-27 11:43:04 +01:00
|
|
|
|
def accepts_new_dossiers?
|
2019-12-04 15:45:06 +01:00
|
|
|
|
publiee? || brouillon?
|
2019-03-27 11:43:04 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-04 15:45:06 +01:00
|
|
|
|
def dossier_can_transition_to_en_construction?
|
|
|
|
|
accepts_new_dossiers? || depubliee?
|
2018-05-17 15:41:44 +02:00
|
|
|
|
end
|
|
|
|
|
|
2018-10-31 13:28:39 +01:00
|
|
|
|
def expose_legacy_carto_api?
|
2018-11-27 15:53:13 +01:00
|
|
|
|
module_api_carto&.use_api_carto? && module_api_carto&.migrated?
|
2018-10-31 13:28:39 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-05-23 14:28:14 +02:00
|
|
|
|
def declarative?
|
|
|
|
|
declarative_with_state.present?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def declarative_accepte?
|
|
|
|
|
declarative_with_state == Procedure.declarative_with_states.fetch(:accepte)
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-04 12:06:51 +01:00
|
|
|
|
def self.declarative_attributes_for_select
|
|
|
|
|
declarative_with_states.map do |state, _|
|
|
|
|
|
[I18n.t("activerecord.attributes.#{model_name.i18n_key}.declarative_with_state/#{state}"), state]
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-04-13 16:11:37 +02:00
|
|
|
|
# Warning: dossier after_save build_default_champs must be removed
|
|
|
|
|
# to save a dossier created from this method
|
|
|
|
|
def new_dossier
|
2019-08-26 15:31:44 +02:00
|
|
|
|
Dossier.new(
|
2020-06-26 12:00:21 +02:00
|
|
|
|
revision: active_revision,
|
2020-08-27 19:55:10 +02:00
|
|
|
|
champs: active_revision.build_champs,
|
|
|
|
|
champs_private: active_revision.build_champs_private,
|
2019-08-26 15:31:44 +02:00
|
|
|
|
groupe_instructeur: defaut_groupe_instructeur
|
|
|
|
|
)
|
2019-02-07 10:44:15 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-07-05 14:38:20 +02:00
|
|
|
|
def path_customized?
|
|
|
|
|
!path.match?(/[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}/)
|
|
|
|
|
end
|
|
|
|
|
|
2018-07-03 20:18:49 +02:00
|
|
|
|
def organisation_name
|
|
|
|
|
service&.nom || organisation
|
|
|
|
|
end
|
|
|
|
|
|
2018-03-20 17:47:37 +01:00
|
|
|
|
def self.active(id)
|
2017-07-11 15:52:06 +02:00
|
|
|
|
publiees.find(id)
|
2016-06-09 17:49:38 +02:00
|
|
|
|
end
|
|
|
|
|
|
2018-04-12 18:35:13 +02:00
|
|
|
|
def clone(admin, from_library)
|
2019-01-29 11:49:28 +01:00
|
|
|
|
is_different_admin = !admin.owns?(self)
|
2019-01-10 13:42:40 +01:00
|
|
|
|
|
2018-11-27 14:52:20 +01:00
|
|
|
|
populate_champ_stable_ids
|
2020-04-06 17:42:09 +02:00
|
|
|
|
include_list = {
|
2020-06-26 12:00:21 +02:00
|
|
|
|
attestation_template: [],
|
|
|
|
|
draft_revision: {
|
|
|
|
|
revision_types_de_champ: {
|
|
|
|
|
type_de_champ: :types_de_champ
|
|
|
|
|
},
|
|
|
|
|
revision_types_de_champ_private: {
|
|
|
|
|
type_de_champ: :types_de_champ
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-06 17:42:09 +02:00
|
|
|
|
}
|
|
|
|
|
include_list[:groupe_instructeurs] = :instructeurs if !is_different_admin
|
|
|
|
|
procedure = self.deep_clone(include: include_list, &method(:clone_attachments))
|
2019-07-04 16:15:40 +02:00
|
|
|
|
procedure.path = SecureRandom.uuid
|
2018-05-28 14:58:40 +02:00
|
|
|
|
procedure.aasm_state = :brouillon
|
2019-11-14 09:43:45 +01:00
|
|
|
|
procedure.closed_at = nil
|
2019-12-04 15:45:06 +01:00
|
|
|
|
procedure.unpublished_at = nil
|
2017-07-11 14:21:10 +02:00
|
|
|
|
procedure.published_at = nil
|
2018-12-06 13:51:41 +01:00
|
|
|
|
procedure.lien_notice = nil
|
2020-07-28 16:39:32 +02:00
|
|
|
|
procedure.published_revision = nil
|
2020-06-26 12:00:21 +02:00
|
|
|
|
procedure.draft_revision.procedure = procedure
|
2019-01-10 13:43:22 +01:00
|
|
|
|
|
2019-01-29 11:49:28 +01:00
|
|
|
|
if is_different_admin
|
|
|
|
|
procedure.administrateurs = [admin]
|
2020-04-29 16:55:52 +02:00
|
|
|
|
procedure.api_entreprise_token = nil
|
2019-01-29 11:49:28 +01:00
|
|
|
|
else
|
|
|
|
|
procedure.administrateurs = administrateurs
|
|
|
|
|
end
|
|
|
|
|
|
2018-05-30 18:45:46 +02:00
|
|
|
|
procedure.initiated_mail = initiated_mail&.dup
|
|
|
|
|
procedure.received_mail = received_mail&.dup
|
|
|
|
|
procedure.closed_mail = closed_mail&.dup
|
|
|
|
|
procedure.refused_mail = refused_mail&.dup
|
|
|
|
|
procedure.without_continuation_mail = without_continuation_mail&.dup
|
2019-08-27 15:05:56 +02:00
|
|
|
|
procedure.ask_birthday = false # see issue #4242
|
2017-03-07 18:19:48 +01:00
|
|
|
|
|
2018-04-12 18:35:13 +02:00
|
|
|
|
procedure.cloned_from_library = from_library
|
2018-04-24 15:23:07 +02:00
|
|
|
|
procedure.parent_procedure = self
|
2020-02-25 13:47:44 +01:00
|
|
|
|
procedure.canonical_procedure = nil
|
2018-04-12 18:35:13 +02:00
|
|
|
|
|
2018-06-28 11:33:10 +02:00
|
|
|
|
if from_library
|
|
|
|
|
procedure.service = nil
|
2019-01-10 13:42:40 +01:00
|
|
|
|
elsif self.service.present? && is_different_admin
|
2018-12-19 15:35:07 +01:00
|
|
|
|
procedure.service = self.service.clone_and_assign_to_administrateur(admin)
|
2018-06-28 11:33:10 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-08-21 16:33:26 +02:00
|
|
|
|
procedure.save
|
2020-08-27 19:55:10 +02:00
|
|
|
|
procedure.draft_types_de_champ.update_all(revision_id: procedure.draft_revision.id)
|
|
|
|
|
procedure.draft_types_de_champ_private.update_all(revision_id: procedure.draft_revision.id)
|
2020-06-26 12:00:21 +02:00
|
|
|
|
|
|
|
|
|
if is_different_admin || from_library
|
2020-08-27 19:55:10 +02:00
|
|
|
|
procedure.draft_types_de_champ.each { |tdc| tdc.options&.delete(:old_pj) }
|
2020-06-26 12:00:21 +02:00
|
|
|
|
end
|
|
|
|
|
|
2018-01-08 15:42:38 +01:00
|
|
|
|
procedure
|
2016-06-15 11:34:05 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-03-28 17:17:29 +01:00
|
|
|
|
def clone_attachments(original, kopy)
|
|
|
|
|
if original.is_a?(TypeDeChamp)
|
|
|
|
|
clone_attachment(:piece_justificative_template, original, kopy)
|
|
|
|
|
elsif original.is_a?(Procedure)
|
2019-08-28 13:11:58 +02:00
|
|
|
|
clone_attachment(:logo, original, kopy)
|
2019-03-28 17:17:29 +01:00
|
|
|
|
clone_attachment(:notice, original, kopy)
|
|
|
|
|
clone_attachment(:deliberation, original, kopy)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def clone_attachment(attribute, original, kopy)
|
|
|
|
|
original_attachment = original.send(attribute)
|
|
|
|
|
if original_attachment.attached?
|
|
|
|
|
kopy.send(attribute).attach({
|
|
|
|
|
io: StringIO.new(original_attachment.download),
|
2019-08-20 15:54:28 +02:00
|
|
|
|
filename: original_attachment.filename,
|
|
|
|
|
content_type: original_attachment.content_type,
|
|
|
|
|
# we don't want to run virus scanner on cloned file
|
|
|
|
|
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
|
2019-03-28 17:17:29 +01:00
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-01-10 17:52:43 +01:00
|
|
|
|
def whitelisted?
|
|
|
|
|
whitelisted_at.present?
|
|
|
|
|
end
|
|
|
|
|
|
2016-07-22 15:06:30 +02:00
|
|
|
|
def total_dossier
|
2017-07-10 16:11:12 +02:00
|
|
|
|
self.dossiers.state_not_brouillon.size
|
2016-07-22 15:06:30 +02:00
|
|
|
|
end
|
2017-01-26 12:12:52 +01:00
|
|
|
|
|
2018-11-22 00:14:16 +01:00
|
|
|
|
def export_filename(format)
|
2018-09-13 18:33:35 +02:00
|
|
|
|
procedure_identifier = path || "procedure-#{id}"
|
2018-11-22 00:14:16 +01:00
|
|
|
|
"dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}"
|
2018-03-16 12:00:01 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-06-25 15:46:10 +02:00
|
|
|
|
def export(dossiers)
|
|
|
|
|
ProcedureExportService.new(self, dossiers)
|
2018-11-22 00:14:16 +01:00
|
|
|
|
end
|
2017-04-05 11:34:16 +02:00
|
|
|
|
|
2019-06-25 15:46:10 +02:00
|
|
|
|
def to_csv(dossiers)
|
|
|
|
|
export(dossiers).to_csv
|
2018-11-22 00:14:16 +01:00
|
|
|
|
end
|
2017-04-05 11:34:16 +02:00
|
|
|
|
|
2019-06-25 15:46:10 +02:00
|
|
|
|
def to_xlsx(dossiers)
|
|
|
|
|
export(dossiers).to_xlsx
|
2018-11-22 00:14:16 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-06-25 15:46:10 +02:00
|
|
|
|
def to_ods(dossiers)
|
|
|
|
|
export(dossiers).to_ods
|
2017-04-05 11:34:16 +02:00
|
|
|
|
end
|
|
|
|
|
|
2020-01-15 14:57:40 +01:00
|
|
|
|
def procedure_overview(start_date, groups)
|
|
|
|
|
ProcedureOverview.new(self, start_date, groups)
|
2017-05-12 15:47:05 +02:00
|
|
|
|
end
|
2017-05-26 21:55:19 +02:00
|
|
|
|
|
2017-05-27 01:08:01 +02:00
|
|
|
|
def initiated_mail_template
|
2017-12-22 21:37:08 +01:00
|
|
|
|
initiated_mail || Mails::InitiatedMail.default_for_procedure(self)
|
2017-05-26 21:55:19 +02:00
|
|
|
|
end
|
|
|
|
|
|
2017-05-27 01:08:01 +02:00
|
|
|
|
def received_mail_template
|
2017-12-22 21:37:08 +01:00
|
|
|
|
received_mail || Mails::ReceivedMail.default_for_procedure(self)
|
2017-05-26 21:55:19 +02:00
|
|
|
|
end
|
|
|
|
|
|
2017-05-27 01:08:01 +02:00
|
|
|
|
def closed_mail_template
|
2017-12-22 21:37:08 +01:00
|
|
|
|
closed_mail || Mails::ClosedMail.default_for_procedure(self)
|
2017-05-26 21:55:19 +02:00
|
|
|
|
end
|
|
|
|
|
|
2017-05-27 01:08:01 +02:00
|
|
|
|
def refused_mail_template
|
2017-12-22 21:37:08 +01:00
|
|
|
|
refused_mail || Mails::RefusedMail.default_for_procedure(self)
|
2017-05-26 21:55:19 +02:00
|
|
|
|
end
|
|
|
|
|
|
2017-05-27 01:08:01 +02:00
|
|
|
|
def without_continuation_mail_template
|
2017-12-22 21:37:08 +01:00
|
|
|
|
without_continuation_mail || Mails::WithoutContinuationMail.default_for_procedure(self)
|
2017-05-26 21:55:19 +02:00
|
|
|
|
end
|
2017-10-02 17:03:38 +02:00
|
|
|
|
|
2017-09-27 15:16:07 +02:00
|
|
|
|
def self.default_sort
|
|
|
|
|
{
|
|
|
|
|
'table' => 'self',
|
|
|
|
|
'column' => 'id',
|
|
|
|
|
'order' => 'desc'
|
2018-09-20 17:02:28 +02:00
|
|
|
|
}
|
2017-09-27 15:16:07 +02:00
|
|
|
|
end
|
|
|
|
|
|
2018-01-10 16:46:12 +01:00
|
|
|
|
def whitelist!
|
2018-10-25 15:07:15 +02:00
|
|
|
|
update_attribute('whitelisted_at', Time.zone.now)
|
2018-01-10 16:46:12 +01:00
|
|
|
|
end
|
|
|
|
|
|
2018-04-03 11:33:11 +02:00
|
|
|
|
def closed_mail_template_attestation_inconsistency_state
|
|
|
|
|
# As an optimization, don’t check the predefined templates (they are presumed correct)
|
|
|
|
|
if closed_mail.present?
|
2020-08-25 11:40:24 +02:00
|
|
|
|
tag_present = closed_mail.body.to_s.include?("--lien attestation--")
|
2018-04-03 11:33:11 +02:00
|
|
|
|
if attestation_template&.activated? && !tag_present
|
|
|
|
|
:missing_tag
|
|
|
|
|
elsif !attestation_template&.activated? && tag_present
|
|
|
|
|
:extraneous_tag
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-11-08 17:19:17 +01:00
|
|
|
|
def usual_traitement_time
|
2020-07-13 11:40:15 +02:00
|
|
|
|
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
|
2018-09-18 17:50:05 +02:00
|
|
|
|
end
|
|
|
|
|
|
2018-11-27 14:52:20 +01:00
|
|
|
|
def populate_champ_stable_ids
|
|
|
|
|
TypeDeChamp.where(procedure: self, stable_id: nil).find_each do |type_de_champ|
|
|
|
|
|
type_de_champ.update_column(:stable_id, type_de_champ.id)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-01-04 15:43:02 +01:00
|
|
|
|
def missing_steps
|
|
|
|
|
result = []
|
|
|
|
|
|
|
|
|
|
if service.nil?
|
|
|
|
|
result << :service
|
|
|
|
|
end
|
|
|
|
|
|
2019-08-21 13:53:53 +02:00
|
|
|
|
if missing_instructeurs?
|
2019-01-04 15:43:02 +01:00
|
|
|
|
result << :instructeurs
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
end
|
|
|
|
|
|
2019-05-23 14:28:14 +02:00
|
|
|
|
def process_dossiers!
|
|
|
|
|
case declarative_with_state
|
|
|
|
|
when Procedure.declarative_with_states.fetch(:en_instruction)
|
|
|
|
|
dossiers
|
|
|
|
|
.state_en_construction
|
|
|
|
|
.find_each(&:passer_automatiquement_en_instruction!)
|
|
|
|
|
when Procedure.declarative_with_states.fetch(:accepte)
|
|
|
|
|
dossiers
|
|
|
|
|
.state_en_construction
|
|
|
|
|
.find_each(&:accepter_automatiquement!)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-08-20 11:28:07 +02:00
|
|
|
|
def logo_url
|
2019-08-28 13:11:58 +02:00
|
|
|
|
if logo.attached?
|
|
|
|
|
Rails.application.routes.url_helpers.url_for(logo)
|
2019-08-20 11:28:07 +02:00
|
|
|
|
else
|
2020-04-20 16:27:40 +02:00
|
|
|
|
ActionController::Base.helpers.image_url("republique-francaise-logo.svg")
|
2019-08-20 11:28:07 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-08-21 13:53:53 +02:00
|
|
|
|
def missing_instructeurs?
|
|
|
|
|
!AssignTo.exists?(groupe_instructeur: groupe_instructeurs)
|
|
|
|
|
end
|
|
|
|
|
|
2019-09-18 21:58:23 +02:00
|
|
|
|
def routee?
|
|
|
|
|
groupe_instructeurs.count > 1
|
|
|
|
|
end
|
|
|
|
|
|
2020-03-26 18:49:26 +01:00
|
|
|
|
def can_be_deleted_by_administrateur?
|
|
|
|
|
brouillon? || dossiers.state_instruction_commencee.empty?
|
|
|
|
|
end
|
|
|
|
|
|
2020-03-26 09:08:52 +01:00
|
|
|
|
def can_be_deleted_by_manager?
|
2020-03-26 18:49:26 +01:00
|
|
|
|
kept? && can_be_deleted_by_administrateur?
|
2020-03-26 09:08:52 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def discard_and_keep_track!(author)
|
2020-03-26 18:49:26 +01:00
|
|
|
|
if brouillon?
|
|
|
|
|
reset!
|
|
|
|
|
elsif publiee?
|
|
|
|
|
close!
|
|
|
|
|
end
|
|
|
|
|
|
2020-03-26 09:08:52 +01:00
|
|
|
|
dossiers.each do |dossier|
|
|
|
|
|
dossier.discard_and_keep_track!(author, :procedure_removed)
|
|
|
|
|
end
|
|
|
|
|
|
2020-02-05 16:09:03 +01:00
|
|
|
|
discard!
|
|
|
|
|
end
|
|
|
|
|
|
2020-03-26 17:35:50 +01:00
|
|
|
|
def restore(author)
|
|
|
|
|
if discarded? && undiscard
|
|
|
|
|
dossiers.with_discarded.discarded.find_each do |dossier|
|
|
|
|
|
dossier.restore(author, true)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-03-04 15:51:33 +01:00
|
|
|
|
def flipper_id
|
|
|
|
|
"Procedure;#{id}"
|
|
|
|
|
end
|
|
|
|
|
|
2020-04-28 10:15:08 +02:00
|
|
|
|
def api_entreprise_role?(role)
|
2020-05-05 15:26:08 +02:00
|
|
|
|
ApiEntrepriseToken.new(api_entreprise_token).role?(role)
|
2020-04-29 10:23:35 +02:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def api_entreprise_token
|
|
|
|
|
self[:api_entreprise_token].presence || Rails.application.secrets.api_entreprise[:key]
|
2020-04-28 10:15:08 +02:00
|
|
|
|
end
|
|
|
|
|
|
2020-05-05 15:26:08 +02:00
|
|
|
|
def api_entreprise_token_expired?
|
|
|
|
|
ApiEntrepriseToken.new(api_entreprise_token).expired?
|
|
|
|
|
end
|
|
|
|
|
|
2020-06-26 12:00:21 +02:00
|
|
|
|
def create_new_revision
|
|
|
|
|
draft_revision.deep_clone(include: [:revision_types_de_champ, :revision_types_de_champ_private])
|
|
|
|
|
end
|
|
|
|
|
|
2017-10-02 17:03:38 +02:00
|
|
|
|
private
|
|
|
|
|
|
2019-07-30 16:54:43 +02:00
|
|
|
|
def before_publish
|
2019-12-18 13:28:29 +01:00
|
|
|
|
update!(closed_at: nil, unpublished_at: nil)
|
2018-09-07 18:36:31 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-04 16:58:36 +01:00
|
|
|
|
def after_publish(canonical_procedure = nil)
|
2020-08-27 19:55:10 +02:00
|
|
|
|
update!(published_at: Time.zone.now, canonical_procedure: canonical_procedure, draft_revision: create_new_revision, published_revision: draft_revision)
|
2018-09-07 18:36:31 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-11-14 09:43:45 +01:00
|
|
|
|
def after_close
|
2020-06-26 12:00:21 +02:00
|
|
|
|
update!(closed_at: Time.zone.now)
|
2018-09-07 18:36:31 +02:00
|
|
|
|
end
|
|
|
|
|
|
2019-12-04 15:45:06 +01:00
|
|
|
|
def after_unpublish
|
|
|
|
|
update!(unpublished_at: Time.zone.now)
|
|
|
|
|
end
|
|
|
|
|
|
2018-06-01 11:06:12 +02:00
|
|
|
|
def update_juridique_required
|
|
|
|
|
self.juridique_required ||= (cadre_juridique.present? || deliberation.attached?)
|
|
|
|
|
true
|
|
|
|
|
end
|
|
|
|
|
|
2018-05-31 10:59:38 +02:00
|
|
|
|
def check_juridique
|
2018-06-01 10:51:04 +02:00
|
|
|
|
if juridique_required? && (cadre_juridique.blank? && !deliberation.attached?)
|
2018-05-31 10:59:38 +02:00
|
|
|
|
errors.add(:cadre_juridique, " : veuillez remplir le texte de loi ou la délibération")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-01-09 16:35:06 +01:00
|
|
|
|
def ensure_path_exists
|
2019-09-16 17:00:37 +02:00
|
|
|
|
if self.path.blank?
|
2019-03-06 14:42:27 +01:00
|
|
|
|
self.path = SecureRandom.uuid
|
2019-01-09 16:35:06 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
2019-08-27 15:28:02 +02:00
|
|
|
|
|
|
|
|
|
def ensure_default_groupe_instructeur
|
|
|
|
|
if self.groupe_instructeurs.empty?
|
|
|
|
|
groupe_instructeurs.create(label: GroupeInstructeur::DEFAULT_LABEL)
|
|
|
|
|
end
|
|
|
|
|
end
|
2015-09-21 17:59:03 +02:00
|
|
|
|
end
|