2024-04-29 00:17:15 +02:00
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2018-03-06 13:44:29 +01:00
|
|
|
|
class Procedure < ApplicationRecord
|
2024-09-24 17:03:18 +02:00
|
|
|
|
include APIEntrepriseTokenConcern
|
2019-09-17 15:52:38 +02:00
|
|
|
|
include ProcedureStatsConcern
|
2021-07-16 17:03:58 +02:00
|
|
|
|
include EncryptableConcern
|
2023-05-03 15:08:21 +02:00
|
|
|
|
include InitiationProcedureConcern
|
2023-06-14 19:38:19 +02:00
|
|
|
|
include ProcedureGroupeInstructeurAPIHackConcern
|
2023-05-25 19:09:59 +02:00
|
|
|
|
include ProcedureSVASVRConcern
|
2023-08-28 14:25:45 +02:00
|
|
|
|
include ProcedureChorusConcern
|
2024-10-24 11:29:54 +02:00
|
|
|
|
include ProcedurePublishConcern
|
2024-04-26 15:36:30 +02:00
|
|
|
|
include PiecesJointesListConcern
|
2024-08-19 14:34:36 +02:00
|
|
|
|
include ColumnsConcern
|
2019-09-17 15:52:38 +02:00
|
|
|
|
|
2020-02-05 16:09:03 +01:00
|
|
|
|
include Discard::Model
|
|
|
|
|
self.discard_column = :hidden_at
|
2022-10-05 18:01:44 +02:00
|
|
|
|
|
2020-02-05 16:09:03 +01:00
|
|
|
|
default_scope -> { kept }
|
|
|
|
|
|
2022-07-08 17:45:14 +02:00
|
|
|
|
OLD_MAX_DUREE_CONSERVATION = 36
|
2023-11-07 10:45:40 +01:00
|
|
|
|
NEW_MAX_DUREE_CONSERVATION = Expired::DEFAULT_DOSSIER_RENTENTION_IN_MONTH
|
2018-05-24 23:29:33 +02:00
|
|
|
|
|
2021-07-06 15:00:33 +02:00
|
|
|
|
MIN_WEIGHT = 350000
|
2021-07-16 17:03:58 +02:00
|
|
|
|
|
2023-01-06 17:47:28 +01:00
|
|
|
|
DOSSIERS_COUNT_EXPIRING = 1.hour
|
|
|
|
|
|
2021-07-16 17:03:58 +02:00
|
|
|
|
attr_encrypted :api_particulier_token
|
|
|
|
|
|
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
|
|
|
|
|
2022-11-16 12:46:33 +01:00
|
|
|
|
has_many :draft_types_de_champ_public, through: :draft_revision, source: :types_de_champ_public
|
2021-01-20 16:31:38 +01:00
|
|
|
|
has_many :draft_types_de_champ_private, through: :draft_revision, source: :types_de_champ_private
|
2022-11-16 12:46:33 +01:00
|
|
|
|
has_many :published_types_de_champ_public, through: :published_revision, source: :types_de_champ_public
|
|
|
|
|
has_many :published_types_de_champ_private, through: :published_revision, source: :types_de_champ_private
|
|
|
|
|
|
2022-02-04 14:40:16 +01:00
|
|
|
|
has_one :published_dossier_submitted_message, dependent: :destroy, through: :published_revision, source: :dossier_submitted_message
|
|
|
|
|
has_one :draft_dossier_submitted_message, dependent: :destroy, through: :draft_revision, source: :dossier_submitted_message
|
|
|
|
|
has_many :dossier_submitted_messages, through: :revisions, source: :dossier_submitted_message
|
|
|
|
|
|
2021-01-07 15:45:02 +01:00
|
|
|
|
has_many :experts_procedures, dependent: :destroy
|
|
|
|
|
has_many :experts, through: :experts_procedures
|
2022-05-23 18:58:13 +02:00
|
|
|
|
has_many :replaced_procedures, -> { with_discarded }, inverse_of: :replaced_by_procedure, class_name: "Procedure",
|
|
|
|
|
foreign_key: "replaced_by_procedure_id", dependent: :nullify
|
2021-01-07 15:45:02 +01:00
|
|
|
|
|
2016-01-18 16:20:51 +01:00
|
|
|
|
has_one :module_api_carto, dependent: :destroy
|
2024-01-10 13:37:55 +01:00
|
|
|
|
has_many :attestation_templates, dependent: :destroy
|
|
|
|
|
has_one :attestation_template_v1, -> { AttestationTemplate.v1 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure
|
2024-05-28 17:47:05 +02:00
|
|
|
|
has_many :attestation_templates_v2, -> { AttestationTemplate.v2 }, dependent: :destroy, class_name: "AttestationTemplate", inverse_of: :procedure
|
2024-01-10 13:37:55 +01:00
|
|
|
|
|
2024-05-29 11:31:49 +02:00
|
|
|
|
has_one :attestation_template, -> { published }, dependent: :destroy, inverse_of: :procedure
|
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
|
2022-05-23 18:58:13 +02:00
|
|
|
|
belongs_to :replaced_by_procedure, -> { with_discarded }, inverse_of: :replaced_procedures, class_name: "Procedure", optional: true
|
2020-07-20 16:06:03 +02:00
|
|
|
|
belongs_to :service, optional: true
|
2021-12-01 21:05:15 +01:00
|
|
|
|
belongs_to :zone, optional: true
|
2022-08-24 12:39:07 +02:00
|
|
|
|
has_and_belongs_to_many :zones
|
2024-09-29 17:11:10 +02:00
|
|
|
|
has_and_belongs_to_many :procedure_tags
|
2016-01-18 16:20:51 +01:00
|
|
|
|
|
2024-03-05 17:22:15 +01:00
|
|
|
|
has_many :bulk_messages, dependent: :destroy
|
2024-10-29 14:52:14 +01:00
|
|
|
|
has_many :labels, dependent: :destroy
|
2024-03-05 17:22:15 +01:00
|
|
|
|
|
2022-02-04 14:40:16 +01:00
|
|
|
|
def active_dossier_submitted_message
|
|
|
|
|
published_dossier_submitted_message || draft_dossier_submitted_message
|
|
|
|
|
end
|
|
|
|
|
|
2020-06-26 11:37:28 +02:00
|
|
|
|
def active_revision
|
|
|
|
|
brouillon? ? draft_revision : published_revision
|
|
|
|
|
end
|
|
|
|
|
|
2024-10-25 15:09:18 +02:00
|
|
|
|
def all_revisions_types_de_champ(parent: nil, with_header_section: false)
|
|
|
|
|
types_de_champ_scope = with_header_section ? TypeDeChamp.with_header_section : TypeDeChamp.fillable
|
2021-06-16 10:48:15 +02:00
|
|
|
|
if brouillon?
|
2022-05-18 12:37:43 +02:00
|
|
|
|
if parent.nil?
|
2024-10-25 15:09:18 +02:00
|
|
|
|
types_de_champ_scope
|
2022-05-18 12:37:43 +02:00
|
|
|
|
.joins(:revision_types_de_champ)
|
|
|
|
|
.where(revision_types_de_champ: { revision_id: draft_revision_id, parent_id: nil })
|
|
|
|
|
.order(:private, :position)
|
|
|
|
|
else
|
|
|
|
|
draft_revision.children_of(parent)
|
|
|
|
|
end
|
2021-06-16 10:48:15 +02:00
|
|
|
|
else
|
2024-10-25 15:09:18 +02:00
|
|
|
|
cache_key = ['all_revisions_types_de_champ', published_revision, parent, with_header_section].compact
|
|
|
|
|
Rails.cache.fetch(cache_key, expires_in: 1.month) { published_revisions_types_de_champ(parent:, with_header_section:) }
|
2021-06-16 10:48:15 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-08-22 12:37:10 +02:00
|
|
|
|
def types_de_champ_for_procedure_export
|
|
|
|
|
all_revisions_types_de_champ.not_repetition
|
|
|
|
|
end
|
|
|
|
|
|
2021-01-21 14:11:17 +01:00
|
|
|
|
def types_de_champ_for_tags
|
2022-07-07 19:14:27 +02:00
|
|
|
|
TypeDeChamp
|
|
|
|
|
.fillable
|
|
|
|
|
.joins(:revisions)
|
|
|
|
|
.where(procedure_revisions: brouillon? ? { id: draft_revision_id } : { procedure_id: id })
|
|
|
|
|
.where(revision_types_de_champ: { parent_id: nil })
|
|
|
|
|
.order(:created_at)
|
|
|
|
|
.distinct(:id)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def types_de_champ_public_for_tags
|
|
|
|
|
types_de_champ_for_tags.public_only
|
2021-01-21 14:11:17 +01:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def types_de_champ_private_for_tags
|
2022-07-07 19:14:27 +02:00
|
|
|
|
types_de_champ_for_tags.private_only
|
2021-01-21 14:11:17 +01:00
|
|
|
|
end
|
|
|
|
|
|
2023-02-09 14:39:33 +01:00
|
|
|
|
def revisions_with_pending_dossiers
|
|
|
|
|
@revisions_with_pending_dossiers ||= begin
|
|
|
|
|
ids = dossiers
|
|
|
|
|
.where.not(revision_id: [draft_revision_id, published_revision_id].compact)
|
|
|
|
|
.state_en_construction_ou_instruction
|
|
|
|
|
.distinct(:revision_id)
|
|
|
|
|
.pluck(:revision_id)
|
|
|
|
|
ProcedureRevision.includes(revision_types_de_champ: [:type_de_champ]).where(id: ids)
|
|
|
|
|
end
|
2022-11-02 09:24:20 +01:00
|
|
|
|
end
|
|
|
|
|
|
2022-03-02 12:09:35 +01:00
|
|
|
|
has_many :administrateurs_procedures, dependent: :delete_all
|
2024-10-17 12:09:06 +02:00
|
|
|
|
has_many :administrateurs, through: :administrateurs_procedures, before_remove: :check_administrateur_minimal_presence
|
2022-11-08 12:42:16 +01:00
|
|
|
|
has_many :groupe_instructeurs, -> { order(:label) }, inverse_of: :procedure, dependent: :destroy
|
2020-04-09 09:22:37 +02:00
|
|
|
|
has_many :instructeurs, through: :groupe_instructeurs
|
2024-03-02 22:13:09 +01:00
|
|
|
|
has_many :export_templates, through: :groupe_instructeurs
|
2016-05-20 15:39:17 +02:00
|
|
|
|
|
2022-11-23 09:10:39 +01:00
|
|
|
|
has_many :active_groupe_instructeurs, -> { active }, class_name: 'GroupeInstructeur', inverse_of: false
|
|
|
|
|
has_many :closed_groupe_instructeurs, -> { closed }, class_name: 'GroupeInstructeur', inverse_of: false
|
|
|
|
|
|
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
|
2023-11-08 14:41:22 +01:00
|
|
|
|
has_one :re_instructed_mail, class_name: "Mails::ReInstructedMail", dependent: :destroy
|
2017-05-26 21:55:19 +02:00
|
|
|
|
|
2023-04-07 14:31:06 +02:00
|
|
|
|
belongs_to :defaut_groupe_instructeur, class_name: 'GroupeInstructeur', inverse_of: false, optional: true
|
2019-09-16 15:37:12 +02:00
|
|
|
|
|
2024-07-03 18:07:33 +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
|
|
|
|
|
2022-12-19 12:32:09 +01:00
|
|
|
|
scope :brouillons, -> { where(aasm_state: :brouillon) }
|
|
|
|
|
scope :publiees, -> { where(aasm_state: :publiee) }
|
2023-01-03 14:46:10 +01:00
|
|
|
|
scope :publiees_ou_brouillons, -> { where(aasm_state: [:publiee, :brouillon]) }
|
2022-12-19 12:32:09 +01:00
|
|
|
|
scope :closes, -> { where(aasm_state: [:close, :depubliee]) }
|
|
|
|
|
scope :opendata, -> { where(opendata: true) }
|
|
|
|
|
scope :publiees_ou_closes, -> { where(aasm_state: [:publiee, :close, :depubliee]) }
|
2023-02-28 16:25:53 +01:00
|
|
|
|
|
2023-06-29 12:58:38 +02:00
|
|
|
|
scope :with_external_urls, -> { where.not(lien_notice: [nil, '']).or(where.not(lien_dpo: [nil, ''])) }
|
|
|
|
|
|
2023-02-28 16:25:53 +01:00
|
|
|
|
scope :publiques, -> do
|
|
|
|
|
publiees_ou_closes
|
|
|
|
|
.opendata
|
|
|
|
|
.where('estimated_dossiers_count >= ?', 4)
|
|
|
|
|
.where.not('lien_site_web LIKE ?', '%mail%')
|
|
|
|
|
.where.not('lien_site_web LIKE ?', '%intra%')
|
|
|
|
|
end
|
|
|
|
|
|
2022-12-19 12:32:09 +01:00
|
|
|
|
scope :by_libelle, -> { order(libelle: :asc) }
|
|
|
|
|
scope :created_during, -> (range) { where(created_at: range) }
|
|
|
|
|
scope :cloned_from_library, -> { where(cloned_from_library: true) }
|
|
|
|
|
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,
|
2022-04-28 14:25:49 +02:00
|
|
|
|
:types_de_champ_public
|
2020-08-27 19:55:10 +02:00
|
|
|
|
],
|
|
|
|
|
draft_revision: [
|
|
|
|
|
:types_de_champ_private,
|
2022-04-28 14:25:49 +02:00
|
|
|
|
:types_de_champ_public
|
2020-08-27 19:55:10 +02:00
|
|
|
|
]
|
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'
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-15 10:41:58 +01:00
|
|
|
|
enum closing_reason: {
|
|
|
|
|
internal_procedure: 'internal_procedure',
|
|
|
|
|
other: 'other'
|
2024-03-20 13:53:15 +01:00
|
|
|
|
}, _prefix: true
|
2024-02-15 10:41:58 +01:00
|
|
|
|
|
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
|
2023-06-29 12:58:38 +02:00
|
|
|
|
|
2019-07-29 16:01:13 +02:00
|
|
|
|
validates :lien_site_web, presence: true, if: :publiee?
|
2023-06-29 12:58:38 +02:00
|
|
|
|
validates :lien_notice, url: { no_local: true, allow_blank: true }
|
2023-07-18 15:16:31 +02:00
|
|
|
|
validates :lien_dpo, url: { no_local: true, allow_blank: true, accept_email: true }
|
2023-06-29 12:58:38 +02:00
|
|
|
|
|
2022-11-16 12:46:33 +01:00
|
|
|
|
validates :draft_types_de_champ_public,
|
2024-06-03 07:44:27 +02:00
|
|
|
|
'types_de_champ/condition': true,
|
|
|
|
|
'types_de_champ/expression_reguliere': true,
|
|
|
|
|
'types_de_champ/header_section_consistency': true,
|
2022-09-29 17:42:53 +02:00
|
|
|
|
'types_de_champ/no_empty_block': true,
|
2022-01-26 18:06:39 +01:00
|
|
|
|
'types_de_champ/no_empty_drop_down': true,
|
2024-06-03 07:44:27 +02:00
|
|
|
|
on: [:types_de_champ_public_editor, :publication]
|
|
|
|
|
|
2022-01-26 17:52:54 +01:00
|
|
|
|
validates :draft_types_de_champ_private,
|
2024-06-03 07:44:27 +02:00
|
|
|
|
'types_de_champ/condition': true,
|
|
|
|
|
'types_de_champ/header_section_consistency': true,
|
2022-09-29 17:42:53 +02:00
|
|
|
|
'types_de_champ/no_empty_block': true,
|
2022-01-26 18:06:39 +01:00
|
|
|
|
'types_de_champ/no_empty_drop_down': true,
|
2024-06-03 07:44:27 +02:00
|
|
|
|
on: [:types_de_champ_private_editor, :publication]
|
2022-11-22 15:17:37 +01:00
|
|
|
|
|
|
|
|
|
validate :check_juridique, on: [:create, :publication]
|
|
|
|
|
|
2024-03-20 13:53:15 +01:00
|
|
|
|
validates :replaced_by_procedure_id, presence: true, if: :closing_reason_internal_procedure?
|
2024-02-15 10:41:58 +01:00
|
|
|
|
|
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 }
|
2022-07-08 17:45:14 +02:00
|
|
|
|
validates :duree_conservation_dossiers_dans_ds, allow_nil: false,
|
|
|
|
|
numericality: {
|
|
|
|
|
only_integer: true,
|
2022-10-05 17:21:51 +02:00
|
|
|
|
greater_than_or_equal_to: 1,
|
|
|
|
|
less_than_or_equal_to: :max_duree_conservation_dossiers_dans_ds
|
|
|
|
|
}
|
|
|
|
|
validates :max_duree_conservation_dossiers_dans_ds, allow_nil: false,
|
|
|
|
|
numericality: {
|
|
|
|
|
only_integer: true,
|
|
|
|
|
greater_than_or_equal_to: 1,
|
2023-11-07 10:45:40 +01:00
|
|
|
|
less_than_or_equal_to: Expired::MAX_DOSSIER_RENTENTION_IN_MONTH
|
2022-10-05 17:21:51 +02:00
|
|
|
|
}
|
2022-07-08 17:45:14 +02:00
|
|
|
|
|
2019-07-17 17:13:08 +02:00
|
|
|
|
validates_with MonAvisEmbedValidator
|
2021-09-14 18:03:40 +02:00
|
|
|
|
|
2024-06-05 18:08:33 +02:00
|
|
|
|
validate :validates_associated_draft_revision_with_context
|
2022-11-02 10:03:10 +01:00
|
|
|
|
validates_associated :initiated_mail, on: :publication
|
|
|
|
|
validates_associated :received_mail, on: :publication
|
|
|
|
|
validates_associated :closed_mail, on: :publication
|
|
|
|
|
validates_associated :refused_mail, on: :publication
|
|
|
|
|
validates_associated :without_continuation_mail, on: :publication
|
2023-11-08 14:41:22 +01:00
|
|
|
|
validates_associated :re_instructed_mail, on: :publication
|
2022-11-09 12:10:19 +01:00
|
|
|
|
validates_associated :attestation_template, on: :publication, if: -> { attestation_template&.activated? }
|
2022-11-02 10:03:10 +01:00
|
|
|
|
|
2021-09-14 18:03:40 +02:00
|
|
|
|
FILE_MAX_SIZE = 20.megabytes
|
2020-03-16 17:55:16 +01:00
|
|
|
|
validates :notice, content_type: [
|
|
|
|
|
"application/msword",
|
|
|
|
|
"application/pdf",
|
|
|
|
|
"application/vnd.ms-powerpoint",
|
|
|
|
|
"application/vnd.oasis.opendocument.presentation",
|
2021-01-04 15:13:26 +01:00
|
|
|
|
"application/vnd.oasis.opendocument.text",
|
|
|
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
|
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
|
|
|
"image/jpeg",
|
|
|
|
|
"image/jpg",
|
|
|
|
|
"image/png",
|
2020-03-16 17:55:16 +01:00
|
|
|
|
"text/plain"
|
2021-11-24 10:04:20 +01:00
|
|
|
|
], size: { less_than: FILE_MAX_SIZE }, if: -> { new_record? || created_at > Date.new(2020, 2, 28) }
|
2020-03-16 17:55:16 +01:00
|
|
|
|
|
|
|
|
|
validates :deliberation, content_type: [
|
|
|
|
|
"application/msword",
|
|
|
|
|
"application/pdf",
|
2021-01-04 15:13:26 +01:00
|
|
|
|
"application/vnd.oasis.opendocument.text",
|
2020-03-16 17:55:16 +01:00
|
|
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
2021-01-04 15:13:26 +01:00
|
|
|
|
"image/jpeg",
|
|
|
|
|
"image/jpg",
|
|
|
|
|
"image/png",
|
|
|
|
|
"text/plain"
|
2021-11-24 10:04:20 +01:00
|
|
|
|
], size: { less_than: FILE_MAX_SIZE }, if: -> { new_record? || created_at > Date.new(2020, 4, 29) }
|
2021-03-04 16:13:19 +01:00
|
|
|
|
|
2021-09-14 18:03:40 +02:00
|
|
|
|
LOGO_MAX_SIZE = 5.megabytes
|
2021-03-04 16:13:19 +01:00
|
|
|
|
validates :logo, content_type: ['image/png', 'image/jpg', 'image/jpeg'],
|
2021-11-24 10:04:20 +01:00
|
|
|
|
size: { less_than: LOGO_MAX_SIZE },
|
2021-09-20 19:51:20 +02:00
|
|
|
|
if: -> { new_record? || created_at > Date.new(2020, 11, 13) }
|
2020-03-16 17:55:16 +01:00
|
|
|
|
|
2021-09-09 19:48:34 +02:00
|
|
|
|
validates :api_particulier_token, format: { with: /\A[A-Za-z0-9\-_=.]{15,}\z/ }, allow_blank: true
|
2023-06-01 14:47:06 +02:00
|
|
|
|
validate :validate_auto_archive_on_in_the_future, if: :will_save_change_to_auto_archive_on?
|
2020-07-08 17:00:21 +02:00
|
|
|
|
|
2018-06-01 11:06:12 +02:00
|
|
|
|
before_save :update_juridique_required
|
2023-11-09 16:41:32 +01:00
|
|
|
|
after_save :extend_conservation_for_dossiers
|
|
|
|
|
|
2019-09-16 17:00:37 +02:00
|
|
|
|
after_initialize :ensure_path_exists
|
|
|
|
|
before_save :ensure_path_exists
|
2021-03-03 11:33:10 +01:00
|
|
|
|
after_create :ensure_defaut_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
|
|
|
|
|
2021-04-13 18:41:49 +02:00
|
|
|
|
event :publish, before: :before_publish do
|
|
|
|
|
transitions from: :brouillon, to: :publiee, after: :after_publish
|
|
|
|
|
transitions from: :close, to: :publiee, after: :after_republish
|
|
|
|
|
transitions from: :depubliee, to: :publiee, after: :after_republish
|
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
|
|
|
|
|
2024-10-17 12:09:06 +02:00
|
|
|
|
def check_administrateur_minimal_presence(_object)
|
|
|
|
|
if self.administrateurs.count <= 1
|
|
|
|
|
raise ActiveRecord::RecordNotDestroyed.new("Cannot remove the last administrateur of procedure #{self.libelle} (#{self.id})")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-08-16 16:53:41 +02:00
|
|
|
|
def dossiers_close_to_expiration
|
|
|
|
|
dossiers.close_to_expiration.count
|
|
|
|
|
end
|
|
|
|
|
|
2019-07-30 16:54:43 +02:00
|
|
|
|
def suggested_path(administrateur)
|
|
|
|
|
if path_customized?
|
|
|
|
|
return path
|
|
|
|
|
end
|
|
|
|
|
slug = libelle&.parameterize&.first(50)
|
|
|
|
|
suggestion = slug
|
|
|
|
|
counter = 1
|
2024-03-14 17:17:47 +01:00
|
|
|
|
while !path_available?(suggestion)
|
2019-07-30 16:54:43 +02:00
|
|
|
|
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
|
|
|
|
|
|
2024-03-14 17:17:47 +01:00
|
|
|
|
def path_available?(path)
|
|
|
|
|
other_procedure_with_path(path).blank?
|
2020-03-13 15:32:30 +01:00
|
|
|
|
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
|
|
|
|
|
|
2021-01-20 13:21:23 +01:00
|
|
|
|
def draft_changed?
|
2024-01-25 17:00:27 +01:00
|
|
|
|
preload_draft_and_published_revisions
|
2024-06-05 17:30:33 +02:00
|
|
|
|
!brouillon? && (types_de_champ_revision_changes.present? || ineligibilite_rules_revision_changes.present?)
|
2021-01-20 13:21:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2024-06-05 17:30:33 +02:00
|
|
|
|
def types_de_champ_revision_changes
|
|
|
|
|
published_revision.compare_types_de_champ(draft_revision)
|
2021-01-20 13:21:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2024-06-05 17:30:33 +02:00
|
|
|
|
def ineligibilite_rules_revision_changes
|
|
|
|
|
published_revision.compare_ineligibilite_rules(draft_revision)
|
2021-01-20 13:21:23 +01:00
|
|
|
|
end
|
|
|
|
|
|
2024-01-25 17:00:27 +01:00
|
|
|
|
def preload_draft_and_published_revisions
|
2024-01-26 13:43:10 +01:00
|
|
|
|
revisions = []
|
2024-01-25 17:00:27 +01:00
|
|
|
|
if !association(:published_revision).loaded? && published_revision_id.present?
|
2024-01-26 13:43:10 +01:00
|
|
|
|
revisions.push(published_revision)
|
2024-01-25 17:00:27 +01:00
|
|
|
|
end
|
|
|
|
|
if !association(:draft_revision).loaded? && draft_revision_id.present?
|
2024-01-26 13:43:10 +01:00
|
|
|
|
revisions.push(draft_revision)
|
2024-01-25 17:00:27 +01:00
|
|
|
|
end
|
2024-01-26 13:43:10 +01:00
|
|
|
|
ProcedureRevisionPreloader.new(revisions).all if !revisions.empty?
|
2024-01-25 17:00:27 +01: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
|
|
|
|
|
|
2023-04-21 12:18:27 +02:00
|
|
|
|
def replaced_by_procedure?
|
|
|
|
|
replaced_by_procedure_id.present?
|
|
|
|
|
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
|
|
|
|
|
|
2022-12-02 18:15:43 +01:00
|
|
|
|
def declarative_en_instruction?
|
|
|
|
|
declarative_with_state == Procedure.declarative_with_states.fetch(:en_instruction)
|
|
|
|
|
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
|
|
|
|
|
|
2021-03-03 18:20:34 +01:00
|
|
|
|
def feature_enabled?(feature)
|
|
|
|
|
Flipper.enabled?(feature, self)
|
|
|
|
|
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
|
2023-05-12 11:29:11 +02:00
|
|
|
|
include_list = {
|
|
|
|
|
attestation_template: [],
|
|
|
|
|
draft_revision: {
|
|
|
|
|
revision_types_de_champ: [:type_de_champ],
|
|
|
|
|
dossier_submitted_message: []
|
|
|
|
|
}
|
2020-04-06 17:42:09 +02:00
|
|
|
|
}
|
2023-08-29 14:41:28 +02:00
|
|
|
|
include_list[:groupe_instructeurs] = [:instructeurs, :contact_information] if !is_different_admin
|
2021-07-01 13:33:22 +02:00
|
|
|
|
procedure = self.deep_clone(include: include_list) do |original, kopy|
|
2024-02-07 17:29:47 +01:00
|
|
|
|
ClonePiecesJustificativesService.clone_attachments(original, kopy)
|
2021-07-01 13:33:22 +02:00
|
|
|
|
end
|
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
|
2022-04-21 17:39:03 +02:00
|
|
|
|
procedure.auto_archive_on = nil
|
2018-12-06 13:51:41 +01:00
|
|
|
|
procedure.lien_notice = nil
|
2022-08-17 09:07:12 +02:00
|
|
|
|
procedure.duree_conservation_etendue_par_ds = false
|
|
|
|
|
if procedure.duree_conservation_dossiers_dans_ds > NEW_MAX_DUREE_CONSERVATION
|
|
|
|
|
procedure.duree_conservation_dossiers_dans_ds = NEW_MAX_DUREE_CONSERVATION
|
2022-10-06 15:04:59 +02:00
|
|
|
|
procedure.max_duree_conservation_dossiers_dans_ds = NEW_MAX_DUREE_CONSERVATION
|
2022-08-17 09:07:12 +02:00
|
|
|
|
end
|
2023-03-07 18:16:28 +01:00
|
|
|
|
procedure.estimated_dossiers_count = 0
|
2023-05-04 16:15:41 +02:00
|
|
|
|
procedure.published_revision = nil
|
2023-05-12 11:29:11 +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
|
2021-07-16 17:03:58 +02:00
|
|
|
|
procedure.encrypted_api_particulier_token = nil
|
2022-05-27 18:51:06 +02:00
|
|
|
|
procedure.opendata = true
|
2021-09-08 16:29:54 +02:00
|
|
|
|
procedure.api_particulier_scopes = []
|
2023-09-21 15:24:17 +02:00
|
|
|
|
procedure.routing_enabled = false
|
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
|
2023-11-08 14:41:22 +01:00
|
|
|
|
procedure.re_instructed_mail = re_instructed_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
|
2022-05-23 18:58:13 +02:00
|
|
|
|
procedure.replaced_by_procedure = nil
|
2022-08-17 08:47:19 +02:00
|
|
|
|
procedure.service = nil
|
2024-03-22 16:24:29 +01:00
|
|
|
|
procedure.closing_reason = nil
|
|
|
|
|
procedure.closing_details = nil
|
|
|
|
|
procedure.closing_notification_brouillon = false
|
|
|
|
|
procedure.closing_notification_en_cours = false
|
2024-05-06 18:16:24 +02:00
|
|
|
|
procedure.template = false
|
2024-06-10 14:55:51 +02:00
|
|
|
|
procedure.monavis_embed = nil
|
2024-10-29 14:52:14 +01:00
|
|
|
|
procedure.labels = labels.map(&:dup)
|
2018-06-28 11:33:10 +02:00
|
|
|
|
|
2023-05-16 17:54:37 +02:00
|
|
|
|
if !procedure.valid?
|
|
|
|
|
procedure.errors.attribute_names.each do |attribute|
|
|
|
|
|
next if [:notice, :deliberation, :logo].exclude?(attribute)
|
|
|
|
|
procedure.public_send("#{attribute}=", nil)
|
2023-05-16 10:59:17 +02:00
|
|
|
|
end
|
2023-05-16 17:54:37 +02:00
|
|
|
|
end
|
2022-05-24 15:57:31 +02:00
|
|
|
|
|
2023-05-16 17:54:37 +02:00
|
|
|
|
transaction do
|
|
|
|
|
procedure.save!
|
2022-06-01 09:14:24 +02:00
|
|
|
|
move_new_children_to_new_parent_coordinate(procedure.draft_revision)
|
|
|
|
|
end
|
2020-06-26 12:00:21 +02:00
|
|
|
|
|
|
|
|
|
if is_different_admin || from_library
|
2022-11-16 12:46:33 +01:00
|
|
|
|
procedure.draft_revision.types_de_champ_public.each { |tdc| tdc.options&.delete(:old_pj) }
|
2020-06-26 12:00:21 +02:00
|
|
|
|
end
|
|
|
|
|
|
2023-04-26 10:52:31 +02:00
|
|
|
|
new_defaut_groupe = procedure.groupe_instructeurs
|
|
|
|
|
.find_by(label: defaut_groupe_instructeur.label) || procedure.groupe_instructeurs.first
|
2023-04-21 11:19:09 +02:00
|
|
|
|
procedure.update!(defaut_groupe_instructeur: new_defaut_groupe)
|
|
|
|
|
|
2023-11-07 14:53:32 +01:00
|
|
|
|
Flipper.features.each do |feature|
|
2024-03-02 16:38:00 +01:00
|
|
|
|
next if feature.enabled? # don't clone features globally enabled
|
|
|
|
|
next unless feature_enabled?(feature.key)
|
|
|
|
|
|
|
|
|
|
Flipper.enable(feature.key, procedure)
|
2023-11-07 14:53:32 +01:00
|
|
|
|
end
|
|
|
|
|
|
2018-01-08 15:42:38 +01:00
|
|
|
|
procedure
|
2016-06-15 11:34:05 +02:00
|
|
|
|
end
|
|
|
|
|
|
2018-01-10 17:52:43 +01:00
|
|
|
|
def whitelisted?
|
|
|
|
|
whitelisted_at.present?
|
|
|
|
|
end
|
|
|
|
|
|
2023-07-21 16:15:25 +02:00
|
|
|
|
def hidden_as_template?
|
|
|
|
|
hidden_at_as_template.present?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def hide_as_template!
|
|
|
|
|
touch(:hidden_at_as_template)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def unhide_as_template!
|
|
|
|
|
self.hidden_at_as_template = nil
|
|
|
|
|
save
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
2023-11-29 12:59:55 +01:00
|
|
|
|
def passer_en_construction_email_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
|
|
|
|
|
|
2023-11-29 12:59:55 +01:00
|
|
|
|
def passer_en_instruction_email_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
|
|
|
|
|
|
2023-11-29 12:59:55 +01:00
|
|
|
|
def accepter_email_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
|
|
|
|
|
|
2023-11-29 12:59:55 +01:00
|
|
|
|
def refuser_email_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
|
|
|
|
|
|
2023-11-29 12:59:55 +01:00
|
|
|
|
def classer_sans_suite_email_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
|
|
|
|
|
2023-11-29 12:59:55 +01:00
|
|
|
|
def repasser_en_instruction_email_template
|
2023-11-08 14:41:22 +01:00
|
|
|
|
re_instructed_mail || Mails::ReInstructedMail.default_for_procedure(self)
|
|
|
|
|
end
|
|
|
|
|
|
2023-11-29 12:59:55 +01:00
|
|
|
|
def email_template_for(state)
|
|
|
|
|
case state
|
2021-04-29 19:10:22 +02:00
|
|
|
|
when Dossier.states.fetch(:en_construction)
|
2023-11-29 12:59:55 +01:00
|
|
|
|
passer_en_construction_email_template
|
2021-04-29 19:10:22 +02:00
|
|
|
|
when Dossier.states.fetch(:en_instruction)
|
2023-11-29 12:59:55 +01:00
|
|
|
|
passer_en_instruction_email_template
|
|
|
|
|
when DossierOperationLog.operations.fetch(:repasser_en_instruction)
|
|
|
|
|
repasser_en_instruction_email_template
|
2021-04-29 19:10:22 +02:00
|
|
|
|
when Dossier.states.fetch(:accepte)
|
2023-11-29 12:59:55 +01:00
|
|
|
|
accepter_email_template
|
2021-04-29 19:10:22 +02:00
|
|
|
|
when Dossier.states.fetch(:refuse)
|
2023-11-29 12:59:55 +01:00
|
|
|
|
refuser_email_template
|
2021-04-29 19:10:22 +02:00
|
|
|
|
when Dossier.states.fetch(:sans_suite)
|
2023-11-29 12:59:55 +01:00
|
|
|
|
classer_sans_suite_email_template
|
2021-04-29 19:10:22 +02:00
|
|
|
|
else
|
|
|
|
|
raise "Unknown dossier state: #{state}"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-01-10 16:46:12 +01:00
|
|
|
|
def whitelist!
|
2021-05-27 18:50:39 +02:00
|
|
|
|
touch(:whitelisted_at)
|
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--")
|
2022-02-11 08:45:16 +01:00
|
|
|
|
if attestation_template&.activated? && !tag_present
|
2018-04-03 11:33:11 +02:00
|
|
|
|
:missing_tag
|
2022-02-11 08:45:16 +01:00
|
|
|
|
elsif !attestation_template&.activated? && tag_present
|
2018-04-03 11:33:11 +02:00
|
|
|
|
:extraneous_tag
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-09-18 17:50:05 +02:00
|
|
|
|
|
2018-11-27 14:52:20 +01:00
|
|
|
|
def populate_champ_stable_ids
|
2021-02-17 18:33:08 +01:00
|
|
|
|
TypeDeChamp
|
|
|
|
|
.joins(:revisions)
|
|
|
|
|
.where(procedure_revisions: { procedure_id: id }, stable_id: nil)
|
|
|
|
|
.find_each do |type_de_champ|
|
|
|
|
|
type_de_champ.update_column(:stable_id, type_de_champ.id)
|
|
|
|
|
end
|
2018-11-27 14:52:20 +01:00
|
|
|
|
end
|
|
|
|
|
|
2019-01-04 15:43:02 +01:00
|
|
|
|
def missing_steps
|
|
|
|
|
result = []
|
|
|
|
|
|
|
|
|
|
if service.nil?
|
|
|
|
|
result << :service
|
|
|
|
|
end
|
|
|
|
|
|
2024-09-20 15:21:22 +02:00
|
|
|
|
if service_siret_test?
|
2024-09-02 16:47:34 +02:00
|
|
|
|
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
|
|
|
|
|
|
2022-09-27 19:46:50 +02:00
|
|
|
|
if missing_zones?
|
|
|
|
|
result << :zones
|
|
|
|
|
end
|
|
|
|
|
|
2019-01-04 15:43:02 +01:00
|
|
|
|
result
|
|
|
|
|
end
|
|
|
|
|
|
2019-08-20 11:28:07 +02:00
|
|
|
|
def logo_url
|
2019-08-28 13:11:58 +02:00
|
|
|
|
if logo.attached?
|
2024-07-03 18:07:33 +02:00
|
|
|
|
logo_variant = logo.variant(resize_to_limit: [400, 400])
|
|
|
|
|
logo_variant.key.present? ? logo_variant.processed.url : Rails.application.routes.url_helpers.url_for(logo)
|
2019-08-20 11:28:07 +02:00
|
|
|
|
else
|
2020-12-10 21:05:42 +01:00
|
|
|
|
ActionController::Base.helpers.image_url(PROCEDURE_DEFAULT_LOGO_SRC)
|
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
|
|
|
|
|
|
2022-09-27 19:46:50 +02:00
|
|
|
|
def missing_zones?
|
2022-12-20 09:22:28 +01:00
|
|
|
|
if Rails.application.config.ds_zonage_enabled
|
2022-09-27 19:46:50 +02:00
|
|
|
|
zones.empty?
|
|
|
|
|
else
|
|
|
|
|
false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-09-20 15:21:22 +02:00
|
|
|
|
def service_siret_test?
|
2024-09-02 16:47:34 +02:00
|
|
|
|
service&.siret == Service::SIRET_TEST
|
|
|
|
|
end
|
|
|
|
|
|
2022-03-16 16:23:53 +01:00
|
|
|
|
def revised?
|
2022-11-24 15:03:47 +01:00
|
|
|
|
revisions.size > 2
|
2022-03-16 16:23:53 +01:00
|
|
|
|
end
|
|
|
|
|
|
2022-09-06 09:58:09 +02:00
|
|
|
|
def revisions_count
|
|
|
|
|
# We start counting from the first revision after publication and we are not counting the draft (there is always one)
|
|
|
|
|
revisions.size - 2
|
|
|
|
|
end
|
|
|
|
|
|
2023-06-26 18:05:01 +02:00
|
|
|
|
def instructeurs_self_management?
|
2024-02-15 09:30:38 +01:00
|
|
|
|
instructeurs_self_management_enabled?
|
2023-06-26 18:05:01 +02:00
|
|
|
|
end
|
|
|
|
|
|
2023-04-27 10:11:27 +02:00
|
|
|
|
def groupe_instructeurs_but_defaut
|
|
|
|
|
groupe_instructeurs - [defaut_groupe_instructeur]
|
|
|
|
|
end
|
|
|
|
|
|
2023-11-16 15:09:38 +01:00
|
|
|
|
def routing_champs
|
2024-11-04 11:41:24 +01:00
|
|
|
|
active_revision.revision_types_de_champ_public.filter(&:used_by_routing_rules?).map(&:libelle)
|
2023-11-16 15:09:38 +01:00
|
|
|
|
end
|
|
|
|
|
|
2020-03-26 18:49:26 +01:00
|
|
|
|
def can_be_deleted_by_administrateur?
|
2022-01-12 11:31:05 +01:00
|
|
|
|
brouillon? || dossiers.state_en_instruction.empty?
|
2020-03-26 18:49:26 +01:00
|
|
|
|
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
|
|
|
|
|
|
2023-11-10 15:53:29 +01:00
|
|
|
|
dossiers.visible_by_administration.find_each do |dossier|
|
2022-03-09 10:27:43 +01:00
|
|
|
|
dossier.hide_and_keep_track!(author, :procedure_removed)
|
2020-03-26 09:08:52 +01:00
|
|
|
|
end
|
|
|
|
|
|
2020-02-05 16:09:03 +01:00
|
|
|
|
discard!
|
|
|
|
|
end
|
|
|
|
|
|
2022-01-12 11:31:05 +01:00
|
|
|
|
def purge_discarded
|
2022-03-09 10:27:43 +01:00
|
|
|
|
if dossiers.empty?
|
2022-01-12 11:31:05 +01:00
|
|
|
|
destroy
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def self.purge_discarded
|
2024-04-11 10:46:05 +02:00
|
|
|
|
discarded_expired.find_each do |p|
|
|
|
|
|
p.purge_discarded
|
|
|
|
|
rescue StandardError => e
|
|
|
|
|
Sentry.capture_exception(e, extra: { procedure_id: p.id })
|
|
|
|
|
end
|
2022-01-12 11:31:05 +01:00
|
|
|
|
end
|
|
|
|
|
|
2020-03-26 17:35:50 +01:00
|
|
|
|
def restore(author)
|
|
|
|
|
if discarded? && undiscard
|
2021-12-21 12:44:57 +01:00
|
|
|
|
dossiers.hidden_by_administration.find_each do |dossier|
|
2021-12-16 11:25:44 +01:00
|
|
|
|
dossier.restore(author)
|
2020-03-26 17:35:50 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-03-04 15:51:33 +01:00
|
|
|
|
def flipper_id
|
|
|
|
|
"Procedure;#{id}"
|
|
|
|
|
end
|
|
|
|
|
|
2021-05-26 16:09:28 +02:00
|
|
|
|
def average_dossier_weight
|
|
|
|
|
if dossiers.termine.any?
|
2021-06-01 14:25:30 +02:00
|
|
|
|
dossiers_sample = dossiers.termine.limit(100)
|
2021-05-26 16:09:28 +02:00
|
|
|
|
total_size = Champ
|
2022-10-20 10:14:47 +02:00
|
|
|
|
.includes(piece_justificative_file_attachments: :blob)
|
2021-06-04 10:38:53 +02:00
|
|
|
|
.where(type: Champs::PieceJustificativeChamp.to_s, dossier: dossiers_sample)
|
2021-05-26 16:09:28 +02:00
|
|
|
|
.sum('active_storage_blobs.byte_size')
|
|
|
|
|
|
2021-07-06 15:00:33 +02:00
|
|
|
|
MIN_WEIGHT + total_size / dossiers_sample.length
|
2021-05-26 16:09:28 +02:00
|
|
|
|
else
|
|
|
|
|
nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-27 16:18:22 +02:00
|
|
|
|
def cnaf_enabled?
|
|
|
|
|
api_particulier_sources['cnaf'].present?
|
|
|
|
|
end
|
|
|
|
|
|
2021-11-24 17:30:35 +01:00
|
|
|
|
def dgfip_enabled?
|
|
|
|
|
api_particulier_sources['dgfip'].present?
|
|
|
|
|
end
|
|
|
|
|
|
2021-12-01 10:21:30 +01:00
|
|
|
|
def pole_emploi_enabled?
|
|
|
|
|
api_particulier_sources['pole_emploi'].present?
|
|
|
|
|
end
|
|
|
|
|
|
2021-12-15 15:25:02 +01:00
|
|
|
|
def mesri_enabled?
|
|
|
|
|
api_particulier_sources['mesri'].present?
|
|
|
|
|
end
|
|
|
|
|
|
2022-08-12 14:46:54 +02:00
|
|
|
|
def published_or_created_at
|
|
|
|
|
published_at || created_at
|
|
|
|
|
end
|
|
|
|
|
|
2023-02-09 09:44:06 +01:00
|
|
|
|
def publiee_or_close?
|
|
|
|
|
publiee? || close?
|
|
|
|
|
end
|
|
|
|
|
|
2022-10-22 00:49:18 +02:00
|
|
|
|
def self.tags
|
|
|
|
|
unnest = Arel::Nodes::NamedFunction.new('UNNEST', [self.arel_table[:tags]])
|
2023-02-02 07:04:30 +01:00
|
|
|
|
query = self.select(unnest.as('tags')).publiees.distinct.order('tags')
|
2022-10-22 00:49:18 +02:00
|
|
|
|
self.connection.query(query.to_sql).flatten
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-06 17:47:28 +01:00
|
|
|
|
def compute_dossiers_count
|
|
|
|
|
now = Time.zone.now
|
|
|
|
|
if now > (self.dossiers_count_computed_at || self.created_at) + DOSSIERS_COUNT_EXPIRING
|
|
|
|
|
self.update(estimated_dossiers_count: self.dossiers.visible_by_administration.count,
|
|
|
|
|
dossiers_count_computed_at: now)
|
|
|
|
|
end
|
|
|
|
|
end
|
2017-10-02 17:03:38 +02:00
|
|
|
|
|
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
|
|
|
|
|
2023-11-09 16:41:32 +01:00
|
|
|
|
def extend_conservation_for_dossiers
|
2023-11-28 16:59:41 +01:00
|
|
|
|
return if !previous_changes.include?(:duree_conservation_dossiers_dans_ds)
|
2023-11-09 16:41:32 +01:00
|
|
|
|
before, after = duree_conservation_dossiers_dans_ds_previous_change
|
|
|
|
|
return if [before, after].any?(&:nil?)
|
|
|
|
|
return if (after - before).negative?
|
|
|
|
|
|
|
|
|
|
ResetExpiringDossiersJob.perform_later(self)
|
|
|
|
|
end
|
|
|
|
|
|
2021-03-03 11:33:10 +01:00
|
|
|
|
def ensure_defaut_groupe_instructeur
|
2019-08-27 15:28:02 +02:00
|
|
|
|
if self.groupe_instructeurs.empty?
|
2023-04-07 14:30:24 +02:00
|
|
|
|
gi = groupe_instructeurs.create(label: GroupeInstructeur::DEFAUT_LABEL)
|
|
|
|
|
self.update(defaut_groupe_instructeur_id: gi.id)
|
2019-08-27 15:28:02 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
2023-03-31 12:46:28 +02:00
|
|
|
|
|
2024-10-29 14:52:14 +01:00
|
|
|
|
def create_generic_labels
|
|
|
|
|
Label::GENERIC_LABELS.each do |label|
|
|
|
|
|
Label.create(name: label[:name], color: label[:color], procedure_id: self.id)
|
2024-09-25 15:08:10 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-11-04 11:41:24 +01:00
|
|
|
|
def used_by_routing_rules?(type_de_champ)
|
|
|
|
|
type_de_champ.stable_id.in?(stable_ids_used_by_routing_rules)
|
2023-03-31 12:46:28 +02:00
|
|
|
|
end
|
2023-04-19 18:29:18 +02:00
|
|
|
|
|
|
|
|
|
# We need this to unfuck administrate + aasm
|
|
|
|
|
def self.human_attribute_name(attribute, options = {})
|
|
|
|
|
if attribute == :aasm_state
|
|
|
|
|
'Statut'
|
|
|
|
|
else
|
|
|
|
|
super
|
|
|
|
|
end
|
|
|
|
|
end
|
2023-06-01 14:47:06 +02:00
|
|
|
|
|
2023-06-28 11:00:25 +02:00
|
|
|
|
def toggle_routing
|
|
|
|
|
update!(routing_enabled: self.groupe_instructeurs.active.many?)
|
|
|
|
|
end
|
|
|
|
|
|
2023-06-29 12:58:38 +02:00
|
|
|
|
def lien_dpo_email?
|
|
|
|
|
lien_dpo.present? && lien_dpo.match?(/@/)
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-18 12:38:46 +01:00
|
|
|
|
def dossier_for_preview(user)
|
|
|
|
|
# Try to use a preview or a dossier filled by current user
|
2024-07-04 20:59:35 +02:00
|
|
|
|
dossiers.where(for_procedure_preview: true).or(dossiers.visible_by_administration)
|
2024-02-15 16:43:31 +01:00
|
|
|
|
.order(Arel.sql("CASE WHEN user_id = #{user.id} THEN 1 ELSE 0 END DESC,
|
|
|
|
|
CASE WHEN state = 'accepte' THEN 1 ELSE 0 END DESC,
|
2024-07-04 20:59:35 +02:00
|
|
|
|
CASE WHEN state = 'brouillon' THEN 0 ELSE 1 END DESC,
|
|
|
|
|
CASE WHEN for_procedure_preview = True THEN 1 ELSE 0 END DESC,
|
|
|
|
|
id DESC")) \
|
2024-01-18 12:38:46 +01:00
|
|
|
|
.first
|
|
|
|
|
end
|
|
|
|
|
|
2024-02-15 11:09:00 +01:00
|
|
|
|
def reset_closing_params
|
|
|
|
|
update!(closing_reason: nil, closing_details: nil, replaced_by_procedure_id: nil, closing_notification_brouillon: false, closing_notification_en_cours: false)
|
|
|
|
|
end
|
|
|
|
|
|
2024-06-10 11:09:55 +02:00
|
|
|
|
def monavis_embed_html_source(source)
|
|
|
|
|
monavis_embed.gsub('nd_source=button', "nd_source=#{source}").gsub('<a ', '<a target="_blank" rel="noopener noreferrer" ')
|
|
|
|
|
end
|
|
|
|
|
|
2023-06-01 14:47:06 +02:00
|
|
|
|
private
|
|
|
|
|
|
2024-11-04 11:41:24 +01:00
|
|
|
|
def stable_ids_used_by_routing_rules
|
|
|
|
|
@stable_ids_used_by_routing_rules ||= groupe_instructeurs.flat_map { _1.routing_rule&.sources }.compact.uniq
|
|
|
|
|
end
|
|
|
|
|
|
2024-10-25 15:09:18 +02:00
|
|
|
|
def published_revisions_types_de_champ(parent: nil, with_header_section: false)
|
2024-08-22 12:37:10 +02:00
|
|
|
|
# all published revisions
|
|
|
|
|
revision_ids = revisions.ids - [draft_revision_id]
|
|
|
|
|
# fetch all parent types de champ
|
|
|
|
|
parent_ids = if parent.present?
|
|
|
|
|
ProcedureRevisionTypeDeChamp
|
|
|
|
|
.where(revision_id: revision_ids)
|
|
|
|
|
.joins(:type_de_champ)
|
|
|
|
|
.where(type_de_champ: { stable_id: parent.stable_id })
|
|
|
|
|
.ids
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# fetch all type_de_champ.stable_id for all the revisions expect draft
|
|
|
|
|
# and for each stable_id take the bigger (more recent) type_de_champ.id
|
2024-10-25 15:09:18 +02:00
|
|
|
|
types_de_champ_scope = with_header_section ? TypeDeChamp.with_header_section : TypeDeChamp.fillable
|
|
|
|
|
recent_ids = types_de_champ_scope
|
2024-08-22 12:37:10 +02:00
|
|
|
|
.joins(:revision_types_de_champ)
|
|
|
|
|
.where(revision_types_de_champ: { revision_id: revision_ids, parent_id: parent_ids })
|
|
|
|
|
.group(:stable_id).select('MAX(types_de_champ.id)')
|
|
|
|
|
|
|
|
|
|
# fetch the more recent procedure_revision_types_de_champ
|
|
|
|
|
# which includes recents_ids
|
|
|
|
|
recents_prtdc = ProcedureRevisionTypeDeChamp
|
|
|
|
|
.where(type_de_champ_id: recent_ids)
|
|
|
|
|
.where.not(revision_id: draft_revision_id)
|
|
|
|
|
.group(:type_de_champ_id)
|
|
|
|
|
.select('MAX(id)')
|
|
|
|
|
|
|
|
|
|
TypeDeChamp
|
|
|
|
|
.joins(:revision_types_de_champ)
|
|
|
|
|
.where(revision_types_de_champ: { id: recents_prtdc }).then do |relation|
|
|
|
|
|
if feature_enabled?(:export_order_by_revision) # Fonds Verts, en attente d'exports personnalisables
|
|
|
|
|
relation.order(:private, 'revision_types_de_champ.revision_id': :desc, position: :asc)
|
|
|
|
|
else
|
|
|
|
|
relation.order(:private, :position, 'revision_types_de_champ.revision_id': :desc)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-06-05 18:08:33 +02:00
|
|
|
|
def validates_associated_draft_revision_with_context
|
|
|
|
|
return if draft_revision.blank?
|
|
|
|
|
return if draft_revision.validate(validation_context)
|
|
|
|
|
|
|
|
|
|
draft_revision.errors.map { errors.import(_1) }
|
|
|
|
|
end
|
|
|
|
|
|
2023-06-01 14:47:06 +02:00
|
|
|
|
def validate_auto_archive_on_in_the_future
|
|
|
|
|
return if auto_archive_on.nil?
|
|
|
|
|
return if auto_archive_on.future?
|
|
|
|
|
|
|
|
|
|
errors.add(:auto_archive_on, 'doit être dans le futur')
|
|
|
|
|
end
|
2015-09-21 17:59:03 +02:00
|
|
|
|
end
|