817 lines
28 KiB
Ruby
817 lines
28 KiB
Ruby
class Dossier < ApplicationRecord
|
|
self.ignored_columns = ['procedure_id']
|
|
include DossierFilteringConcern
|
|
|
|
include Discard::Model
|
|
self.discard_column = :hidden_at
|
|
default_scope -> { kept }
|
|
|
|
enum state: {
|
|
brouillon: 'brouillon',
|
|
en_construction: 'en_construction',
|
|
en_instruction: 'en_instruction',
|
|
accepte: 'accepte',
|
|
refuse: 'refuse',
|
|
sans_suite: 'sans_suite'
|
|
}
|
|
|
|
EN_CONSTRUCTION_OU_INSTRUCTION = [states.fetch(:en_construction), states.fetch(:en_instruction)]
|
|
TERMINE = [states.fetch(:accepte), states.fetch(:refuse), states.fetch(:sans_suite)]
|
|
INSTRUCTION_COMMENCEE = TERMINE + [states.fetch(:en_instruction)]
|
|
SOUMIS = EN_CONSTRUCTION_OU_INSTRUCTION + TERMINE
|
|
|
|
TAILLE_MAX_ZIP = 100.megabytes
|
|
|
|
REMAINING_DAYS_BEFORE_CLOSING = 2
|
|
INTERVAL_BEFORE_CLOSING = "#{REMAINING_DAYS_BEFORE_CLOSING} days"
|
|
INTERVAL_BEFORE_EXPIRATION = '1 month'
|
|
INTERVAL_EXPIRATION = '1 month 5 days'
|
|
|
|
has_one :etablissement, dependent: :destroy
|
|
has_one :individual, validate: false, dependent: :destroy
|
|
has_one :attestation, dependent: :destroy
|
|
|
|
has_one_attached :justificatif_motivation
|
|
|
|
has_many :champs, -> { root.public_only.ordered }, inverse_of: :dossier, dependent: :destroy
|
|
has_many :champs_private, -> { root.private_only.ordered }, class_name: 'Champ', inverse_of: :dossier, dependent: :destroy
|
|
has_many :commentaires, inverse_of: :dossier, dependent: :destroy
|
|
has_many :invites, dependent: :destroy
|
|
has_many :follows, -> { active }, inverse_of: :dossier
|
|
has_many :previous_follows, -> { inactive }, class_name: 'Follow', inverse_of: :dossier
|
|
has_many :followers_instructeurs, through: :follows, source: :instructeur
|
|
has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur
|
|
has_many :avis, inverse_of: :dossier, dependent: :destroy
|
|
|
|
has_many :dossier_operation_logs, -> { order(:created_at) }, dependent: :nullify, inverse_of: :dossier
|
|
|
|
belongs_to :groupe_instructeur
|
|
has_one :procedure, through: :groupe_instructeur
|
|
belongs_to :user
|
|
|
|
accepts_nested_attributes_for :champs
|
|
accepts_nested_attributes_for :champs_private
|
|
|
|
include AASM
|
|
|
|
aasm whiny_persistence: true, column: :state, enum: true do
|
|
state :brouillon, initial: true
|
|
state :en_construction
|
|
state :en_instruction
|
|
state :accepte
|
|
state :refuse
|
|
state :sans_suite
|
|
|
|
event :passer_en_construction, after: :after_passer_en_construction do
|
|
transitions from: :brouillon, to: :en_construction
|
|
end
|
|
|
|
event :passer_en_instruction, after: :after_passer_en_instruction do
|
|
transitions from: :en_construction, to: :en_instruction
|
|
end
|
|
|
|
event :passer_automatiquement_en_instruction, after: :after_passer_automatiquement_en_instruction do
|
|
transitions from: :en_construction, to: :en_instruction
|
|
end
|
|
|
|
event :repasser_en_construction, after: :after_repasser_en_construction do
|
|
transitions from: :en_instruction, to: :en_construction
|
|
end
|
|
|
|
event :accepter, after: :after_accepter do
|
|
transitions from: :en_instruction, to: :accepte
|
|
end
|
|
|
|
event :accepter_automatiquement, after: :after_accepter_automatiquement do
|
|
transitions from: :en_construction, to: :accepte
|
|
end
|
|
|
|
event :refuser, after: :after_refuser do
|
|
transitions from: :en_instruction, to: :refuse
|
|
end
|
|
|
|
event :classer_sans_suite, after: :after_classer_sans_suite do
|
|
transitions from: :en_instruction, to: :sans_suite
|
|
end
|
|
|
|
event :repasser_en_instruction, after: :after_repasser_en_instruction do
|
|
transitions from: :refuse, to: :en_instruction
|
|
transitions from: :sans_suite, to: :en_instruction
|
|
transitions from: :accepte, to: :en_instruction
|
|
end
|
|
end
|
|
|
|
scope :state_brouillon, -> { where(state: states.fetch(:brouillon)) }
|
|
scope :state_not_brouillon, -> { where.not(state: states.fetch(:brouillon)) }
|
|
scope :state_en_construction, -> { where(state: states.fetch(:en_construction)) }
|
|
scope :state_en_instruction, -> { where(state: states.fetch(:en_instruction)) }
|
|
scope :state_en_construction_ou_instruction, -> { where(state: EN_CONSTRUCTION_OU_INSTRUCTION) }
|
|
scope :state_instruction_commencee, -> { where(state: INSTRUCTION_COMMENCEE) }
|
|
scope :state_termine, -> { where(state: TERMINE) }
|
|
|
|
scope :archived, -> { where(archived: true) }
|
|
scope :not_archived, -> { where(archived: false) }
|
|
|
|
scope :order_by_updated_at, -> (order = :desc) { order(updated_at: order) }
|
|
scope :order_by_created_at, -> (order = :asc) { order(en_construction_at: order, created_at: order, id: order) }
|
|
scope :updated_since, -> (since) { where('dossiers.updated_at >= ?', since) }
|
|
scope :created_since, -> (since) { where('dossiers.en_construction_at >= ?', since) }
|
|
|
|
scope :all_state, -> { not_archived.state_not_brouillon }
|
|
scope :en_construction, -> { not_archived.state_en_construction }
|
|
scope :en_instruction, -> { not_archived.state_en_instruction }
|
|
scope :termine, -> { not_archived.state_termine }
|
|
scope :downloadable_sorted, -> {
|
|
state_not_brouillon
|
|
.includes(
|
|
:user,
|
|
:individual,
|
|
:followers_instructeurs,
|
|
:avis,
|
|
etablissement: :champ,
|
|
champs: {
|
|
etablissement: :champ,
|
|
type_de_champ: :drop_down_list,
|
|
piece_justificative_file_attachment: :blob,
|
|
champs: [
|
|
piece_justificative_file_attachment: :blob
|
|
]
|
|
},
|
|
champs_private: {
|
|
etablissement: :champ,
|
|
type_de_champ: :drop_down_list,
|
|
piece_justificative_file_attachment: :blob,
|
|
champs: [
|
|
piece_justificative_file_attachment: :blob
|
|
]
|
|
},
|
|
procedure: :groupe_instructeurs
|
|
).order(en_construction_at: 'asc')
|
|
}
|
|
scope :en_cours, -> { not_archived.state_en_construction_ou_instruction }
|
|
scope :without_followers, -> { left_outer_joins(:follows).where(follows: { id: nil }) }
|
|
scope :with_champs, -> { includes(champs: :type_de_champ) }
|
|
scope :for_api, -> {
|
|
includes(commentaires: { piece_jointe_attachment: :blob },
|
|
champs: [
|
|
:geo_areas,
|
|
:etablissement,
|
|
piece_justificative_file_attachment: :blob,
|
|
champs: [
|
|
piece_justificative_file_attachment: :blob
|
|
]
|
|
],
|
|
champs_private: [
|
|
:geo_areas,
|
|
:etablissement,
|
|
piece_justificative_file_attachment: :blob,
|
|
champs: [
|
|
piece_justificative_file_attachment: :blob
|
|
]
|
|
],
|
|
justificatif_motivation_attachment: :blob,
|
|
attestation: [],
|
|
avis: { piece_justificative_file_attachment: :blob },
|
|
etablissement: [],
|
|
individual: [],
|
|
user: [])
|
|
}
|
|
|
|
scope :with_notifiable_procedure, -> (notify_on_closed: false) do
|
|
states = notify_on_closed ? [:publiee, :close, :depubliee] : [:publiee, :depubliee]
|
|
joins(:procedure)
|
|
.where(procedures: { aasm_state: states })
|
|
end
|
|
|
|
scope :brouillon_close_to_expiration, -> do
|
|
state_brouillon
|
|
.joins(:procedure)
|
|
.where("dossiers.created_at + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
|
|
end
|
|
scope :en_construction_close_to_expiration, -> do
|
|
state_en_construction
|
|
.joins(:procedure)
|
|
.where("dossiers.en_construction_at + dossiers.en_construction_conservation_extension + (duree_conservation_dossiers_dans_ds * INTERVAL '1 month') - INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_BEFORE_EXPIRATION })
|
|
end
|
|
scope :en_instruction_close_to_expiration, -> do
|
|
state_en_instruction
|
|
.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 })
|
|
end
|
|
scope :termine_close_to_expiration, -> do
|
|
state_termine
|
|
.joins(:procedure)
|
|
.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
|
|
|
|
scope :brouillon_expired, -> do
|
|
state_brouillon
|
|
.where("brouillon_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
|
|
end
|
|
scope :en_construction_expired, -> do
|
|
state_en_construction
|
|
.where("en_construction_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
|
|
end
|
|
scope :termine_expired, -> do
|
|
state_termine
|
|
.where("termine_close_to_expiration_notice_sent_at + INTERVAL :expires_in < :now", { now: Time.zone.now, expires_in: INTERVAL_EXPIRATION })
|
|
end
|
|
|
|
scope :without_brouillon_expiration_notice_sent, -> { where(brouillon_close_to_expiration_notice_sent_at: nil) }
|
|
scope :without_en_construction_expiration_notice_sent, -> { where(en_construction_close_to_expiration_notice_sent_at: nil) }
|
|
scope :without_termine_expiration_notice_sent, -> { where(termine_close_to_expiration_notice_sent_at: nil) }
|
|
|
|
scope :discarded_brouillon_expired, -> do
|
|
with_discarded
|
|
.discarded
|
|
.state_brouillon
|
|
.where('hidden_at < ?', 1.month.ago)
|
|
end
|
|
scope :discarded_en_construction_expired, -> do
|
|
with_discarded
|
|
.discarded
|
|
.state_en_construction
|
|
.where('dossiers.hidden_at < ?', 1.month.ago)
|
|
end
|
|
|
|
scope :brouillon_near_procedure_closing_date, -> do
|
|
# select users who have submitted dossier for the given 'procedures.id'
|
|
users_who_submitted =
|
|
state_not_brouillon
|
|
.joins(:groupe_instructeur)
|
|
.where("groupe_instructeurs.procedure_id = procedures.id")
|
|
.select(:user_id)
|
|
# select dossier in brouillon where procedure closes in two days and for which the user has not submitted a Dossier
|
|
state_brouillon
|
|
.with_notifiable_procedure
|
|
.where("procedures.auto_archive_on - INTERVAL :before_closing = :now", { now: Time.zone.today, before_closing: INTERVAL_BEFORE_CLOSING })
|
|
.where.not(user: users_who_submitted)
|
|
end
|
|
|
|
scope :for_procedure, -> (procedure) { includes(:user, :groupe_instructeur).where(groupe_instructeurs: { procedure: procedure }) }
|
|
scope :for_api_v2, -> { includes(procedure: [:administrateurs], etablissement: [], individual: []) }
|
|
|
|
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;
|
|
# it will fail otherwise
|
|
joined_dossiers = joins('LEFT OUTER JOIN "champs" ON "champs" . "dossier_id" = "dossiers" . "id" AND "champs" . "parent_id" IS NULL AND "champs" . "private" = FALSE AND "champs"."updated_at" > "follows"."demande_seen_at"')
|
|
.joins('LEFT OUTER JOIN "champs" "champs_privates_dossiers" ON "champs_privates_dossiers" . "dossier_id" = "dossiers" . "id" AND "champs_privates_dossiers" . "parent_id" IS NULL AND "champs_privates_dossiers" . "private" = TRUE AND "champs_privates_dossiers"."updated_at" > "follows"."annotations_privees_seen_at"')
|
|
.joins('LEFT OUTER JOIN "avis" ON "avis" . "dossier_id" = "dossiers" . "id" AND avis.updated_at > follows.avis_seen_at')
|
|
.joins('LEFT OUTER JOIN "commentaires" ON "commentaires" . "dossier_id" = "dossiers" . "id" and commentaires.updated_at > follows.messagerie_seen_at and "commentaires"."email" != \'contact@tps.apientreprise.fr\' AND "commentaires"."email" != \'contact@demarches-simplifiees.fr\'')
|
|
|
|
updated_demandes = joined_dossiers
|
|
.where('champs.updated_at > follows.demande_seen_at OR groupe_instructeur_updated_at > follows.demande_seen_at')
|
|
|
|
updated_annotations = joined_dossiers
|
|
.where('champs_privates_dossiers.updated_at > follows.annotations_privees_seen_at')
|
|
|
|
updated_avis = joined_dossiers
|
|
.where('avis.updated_at > follows.avis_seen_at')
|
|
|
|
updated_messagerie = joined_dossiers
|
|
.where('commentaires.updated_at > follows.messagerie_seen_at')
|
|
.where.not(commentaires: { email: OLD_CONTACT_EMAIL })
|
|
.where.not(commentaires: { email: CONTACT_EMAIL })
|
|
|
|
updated_demandes.or(updated_annotations).or(updated_avis).or(updated_messagerie).distinct
|
|
end
|
|
|
|
accepts_nested_attributes_for :individual
|
|
|
|
delegate :siret, :siren, to: :etablissement, allow_nil: true
|
|
delegate :types_de_champ, to: :procedure
|
|
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 :update_search_terms
|
|
|
|
after_save :send_dossier_received
|
|
after_save :send_web_hook
|
|
after_create :send_draft_notification_email
|
|
|
|
validates :user, presence: true
|
|
validates :individual, presence: true, if: -> { procedure.for_individual? }
|
|
validates :groupe_instructeur, presence: true
|
|
|
|
def update_search_terms
|
|
self.search_terms = [
|
|
user&.email,
|
|
*champs.flat_map(&:search_terms),
|
|
*etablissement&.search_terms,
|
|
individual&.nom,
|
|
individual&.prenom
|
|
].compact.join(' ')
|
|
self.private_search_terms = champs_private.flat_map(&:search_terms).compact.join(' ')
|
|
end
|
|
|
|
def build_default_champs
|
|
procedure.build_champs.each do |champ|
|
|
champs << champ
|
|
end
|
|
procedure.build_champs_private.each do |champ|
|
|
champs_private << champ
|
|
end
|
|
end
|
|
|
|
def build_default_individual
|
|
if procedure.for_individual? && individual.blank?
|
|
self.individual = if france_connect_information.present?
|
|
Individual.from_france_connect(france_connect_information)
|
|
else
|
|
Individual.new
|
|
end
|
|
end
|
|
end
|
|
|
|
def en_construction_ou_instruction?
|
|
EN_CONSTRUCTION_OU_INSTRUCTION.include?(state)
|
|
end
|
|
|
|
def termine?
|
|
TERMINE.include?(state)
|
|
end
|
|
|
|
def instruction_commencee?
|
|
INSTRUCTION_COMMENCEE.include?(state)
|
|
end
|
|
|
|
def reset!
|
|
etablissement.destroy
|
|
|
|
update_columns(autorisation_donnees: false)
|
|
end
|
|
|
|
def read_only?
|
|
en_instruction? || accepte? || refuse? || sans_suite?
|
|
end
|
|
|
|
def can_transition_to_en_construction?
|
|
brouillon? && procedure.dossier_can_transition_to_en_construction?
|
|
end
|
|
|
|
def can_be_updated_by_user?
|
|
brouillon? || en_construction?
|
|
end
|
|
|
|
def can_be_deleted_by_user?
|
|
brouillon? || en_construction?
|
|
end
|
|
|
|
def can_be_deleted_by_manager?
|
|
kept? && can_be_deleted_by_user?
|
|
end
|
|
|
|
def messagerie_available?
|
|
!brouillon? && !archived
|
|
end
|
|
|
|
def en_construction_close_to_expiration?
|
|
Dossier.en_construction_close_to_expiration.where(id: self).present?
|
|
end
|
|
|
|
def assign_to_groupe_instructeur(groupe_instructeur, author = nil)
|
|
if groupe_instructeur.procedure == procedure && groupe_instructeur != self.groupe_instructeur
|
|
if update(groupe_instructeur: groupe_instructeur, groupe_instructeur_updated_at: Time.zone.now)
|
|
unfollow_stale_instructeurs
|
|
|
|
if author.present?
|
|
log_dossier_operation(author, :changer_groupe_instructeur, self)
|
|
end
|
|
|
|
true
|
|
end
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def text_summary
|
|
if brouillon?
|
|
parts = [
|
|
"Dossier en brouillon répondant à la démarche ",
|
|
procedure.libelle,
|
|
" gérée par l'organisme ",
|
|
procedure.organisation_name
|
|
]
|
|
else
|
|
parts = [
|
|
"Dossier déposé le ",
|
|
en_construction_at.strftime("%d/%m/%Y"),
|
|
" sur la démarche ",
|
|
procedure.libelle,
|
|
" gérée par l'organisme ",
|
|
procedure.organisation_name
|
|
]
|
|
end
|
|
|
|
parts.join
|
|
end
|
|
|
|
def avis_for(instructeur)
|
|
if instructeur.dossiers.include?(self)
|
|
avis.order(created_at: :asc)
|
|
else
|
|
avis
|
|
.where(confidentiel: false)
|
|
.or(avis.where(claimant: instructeur))
|
|
.or(avis.where(instructeur: instructeur))
|
|
.order(created_at: :asc)
|
|
end
|
|
end
|
|
|
|
def owner_name
|
|
if etablissement.present?
|
|
etablissement.entreprise_raison_sociale
|
|
elsif individual.present?
|
|
"#{individual.nom} #{individual.prenom}"
|
|
end
|
|
end
|
|
|
|
def log_operations?
|
|
!procedure.brouillon?
|
|
end
|
|
|
|
def keep_track_on_deletion?
|
|
!procedure.brouillon?
|
|
end
|
|
|
|
def expose_legacy_carto_api?
|
|
procedure.expose_legacy_carto_api?
|
|
end
|
|
|
|
def geo_position
|
|
if etablissement.present?
|
|
point = Geocoder.search(etablissement.geo_adresse).first
|
|
end
|
|
|
|
lon = Champs::CarteChamp::DEFAULT_LON.to_s
|
|
lat = Champs::CarteChamp::DEFAULT_LAT.to_s
|
|
zoom = "13"
|
|
|
|
if point.present?
|
|
lat, lon = point.coordinates.map(&:to_s)
|
|
end
|
|
|
|
{ lon: lon, lat: lat, zoom: zoom }
|
|
end
|
|
|
|
def unspecified_attestation_champs
|
|
attestation_template = procedure.attestation_template
|
|
|
|
if attestation_template&.activated?
|
|
attestation_template.unspecified_champs_for_dossier(self)
|
|
else
|
|
[]
|
|
end
|
|
end
|
|
|
|
def build_attestation
|
|
if procedure.attestation_template&.activated?
|
|
procedure.attestation_template.attestation_for(self)
|
|
end
|
|
end
|
|
|
|
def expired_keep_track!
|
|
if keep_track_on_deletion?
|
|
DeletedDossier.create_from_dossier(self, :expired)
|
|
log_automatic_dossier_operation(:supprimer, self)
|
|
end
|
|
end
|
|
|
|
def discard_and_keep_track!(author, reason)
|
|
if keep_track_on_deletion? && en_construction?
|
|
deleted_dossier = DeletedDossier.create_from_dossier(self, reason)
|
|
|
|
administration_emails = followers_instructeurs.present? ? followers_instructeurs.map(&:email) : procedure.administrateurs.map(&:email)
|
|
administration_emails.each do |email|
|
|
DossierMailer.notify_deletion_to_administration(deleted_dossier, email).deliver_later
|
|
end
|
|
DossierMailer.notify_deletion_to_user(deleted_dossier, user.email).deliver_later
|
|
|
|
log_dossier_operation(author, :supprimer, self)
|
|
end
|
|
|
|
discard!
|
|
end
|
|
|
|
def restore(author, only_discarded_with_procedure = false)
|
|
if discarded?
|
|
deleted_dossier = DeletedDossier.find_by(dossier_id: id)
|
|
|
|
if !only_discarded_with_procedure || deleted_dossier&.procedure_removed?
|
|
if undiscard && keep_track_on_deletion? && en_construction?
|
|
deleted_dossier&.destroy
|
|
log_dossier_operation(author, :restaurer, self)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def after_passer_en_instruction(instructeur)
|
|
instructeur.follow(self)
|
|
|
|
log_dossier_operation(instructeur, :passer_en_instruction)
|
|
end
|
|
|
|
def after_passer_automatiquement_en_instruction
|
|
log_automatic_dossier_operation(:passer_en_instruction)
|
|
end
|
|
|
|
def after_repasser_en_construction(instructeur)
|
|
self.en_instruction_at = nil
|
|
|
|
save!
|
|
log_dossier_operation(instructeur, :repasser_en_construction)
|
|
end
|
|
|
|
def after_repasser_en_instruction(instructeur)
|
|
self.archived = false
|
|
self.processed_at = nil
|
|
self.motivation = nil
|
|
attestation&.destroy
|
|
|
|
save!
|
|
DossierMailer.notify_revert_to_instruction(self).deliver_later
|
|
log_dossier_operation(instructeur, :repasser_en_instruction)
|
|
end
|
|
|
|
def after_accepter(instructeur, motivation, justificatif = nil)
|
|
self.motivation = motivation
|
|
|
|
if justificatif
|
|
self.justificatif_motivation.attach(justificatif)
|
|
end
|
|
|
|
if attestation.nil?
|
|
self.attestation = build_attestation
|
|
end
|
|
|
|
save!
|
|
NotificationMailer.send_closed_notification(self).deliver_later
|
|
log_dossier_operation(instructeur, :accepter, self)
|
|
end
|
|
|
|
def after_accepter_automatiquement
|
|
self.en_instruction_at ||= Time.zone.now
|
|
|
|
if attestation.nil?
|
|
self.attestation = build_attestation
|
|
end
|
|
|
|
save!
|
|
NotificationMailer.send_closed_notification(self).deliver_later
|
|
log_automatic_dossier_operation(:accepter, self)
|
|
end
|
|
|
|
def after_refuser(instructeur, motivation, justificatif = nil)
|
|
self.motivation = motivation
|
|
|
|
if justificatif
|
|
self.justificatif_motivation.attach(justificatif)
|
|
end
|
|
|
|
save!
|
|
NotificationMailer.send_refused_notification(self).deliver_later
|
|
log_dossier_operation(instructeur, :refuser, self)
|
|
end
|
|
|
|
def after_classer_sans_suite(instructeur, motivation, justificatif = nil)
|
|
self.motivation = motivation
|
|
|
|
if justificatif
|
|
self.justificatif_motivation.attach(justificatif)
|
|
end
|
|
|
|
save!
|
|
NotificationMailer.send_without_continuation_notification(self).deliver_later
|
|
log_dossier_operation(instructeur, :classer_sans_suite, self)
|
|
end
|
|
|
|
def check_mandatory_champs
|
|
(champs + champs.filter(&:repetition?).flat_map(&:champs))
|
|
.filter(&:mandatory_and_blank?)
|
|
.map do |champ|
|
|
"Le champ #{champ.libelle.truncate(200)} doit être rempli."
|
|
end
|
|
end
|
|
|
|
def modifier_annotations!(instructeur)
|
|
champs_private.filter(&:value_previously_changed?).each do |champ|
|
|
log_dossier_operation(instructeur, :modifier_annotation, champ)
|
|
end
|
|
end
|
|
|
|
def demander_un_avis!(avis)
|
|
log_dossier_operation(avis.claimant, :demander_un_avis, avis)
|
|
end
|
|
|
|
def spreadsheet_columns_csv
|
|
spreadsheet_columns(with_etablissement: true)
|
|
end
|
|
|
|
def spreadsheet_columns_xlsx
|
|
spreadsheet_columns
|
|
end
|
|
|
|
def spreadsheet_columns_ods
|
|
spreadsheet_columns
|
|
end
|
|
|
|
def spreadsheet_columns(with_etablissement: false)
|
|
columns = [
|
|
['ID', id.to_s],
|
|
['Email', user.email]
|
|
]
|
|
|
|
if procedure.for_individual?
|
|
columns += [
|
|
['Civilité', individual&.gender],
|
|
['Nom', individual&.nom],
|
|
['Prénom', individual&.prenom]
|
|
]
|
|
if procedure.ask_birthday
|
|
columns += [['Date de naissance', individual&.birthdate]]
|
|
end
|
|
elsif with_etablissement
|
|
columns += [
|
|
['Établissement SIRET', etablissement&.siret],
|
|
['Établissement siège social', etablissement&.siege_social],
|
|
['Établissement NAF', etablissement&.naf],
|
|
['Établissement libellé NAF', etablissement&.libelle_naf],
|
|
['Établissement Adresse', etablissement&.adresse],
|
|
['Établissement numero voie', etablissement&.numero_voie],
|
|
['Établissement type voie', etablissement&.type_voie],
|
|
['Établissement nom voie', etablissement&.nom_voie],
|
|
['Établissement complément adresse', etablissement&.complement_adresse],
|
|
['Établissement code postal', etablissement&.code_postal],
|
|
['Établissement localité', etablissement&.localite],
|
|
['Établissement code INSEE localité', etablissement&.code_insee_localite],
|
|
['Entreprise SIREN', etablissement&.entreprise_siren],
|
|
['Entreprise capital social', etablissement&.entreprise_capital_social],
|
|
['Entreprise numero TVA intracommunautaire', etablissement&.entreprise_numero_tva_intracommunautaire],
|
|
['Entreprise forme juridique', etablissement&.entreprise_forme_juridique],
|
|
['Entreprise forme juridique code', etablissement&.entreprise_forme_juridique_code],
|
|
['Entreprise nom commercial', etablissement&.entreprise_nom_commercial],
|
|
['Entreprise raison sociale', etablissement&.entreprise_raison_sociale],
|
|
['Entreprise SIRET siège social', etablissement&.entreprise_siret_siege_social],
|
|
['Entreprise code effectif entreprise', etablissement&.entreprise_code_effectif_entreprise],
|
|
['Entreprise date de création', etablissement&.entreprise_date_creation],
|
|
['Entreprise nom', etablissement&.entreprise_nom],
|
|
['Entreprise prénom', etablissement&.entreprise_prenom],
|
|
['Association RNA', etablissement&.association_rna],
|
|
['Association titre', etablissement&.association_titre],
|
|
['Association objet', etablissement&.association_objet],
|
|
['Association date de création', etablissement&.association_date_creation],
|
|
['Association date de déclaration', etablissement&.association_date_declaration],
|
|
['Association date de publication', etablissement&.association_date_publication]
|
|
]
|
|
else
|
|
columns << ['Entreprise raison sociale', etablissement&.entreprise_raison_sociale]
|
|
end
|
|
|
|
columns += [
|
|
['Archivé', :archived],
|
|
['État du dossier', I18n.t(state, scope: [:activerecord, :attributes, :dossier, :state])],
|
|
['Dernière mise à jour le', :updated_at],
|
|
['Déposé le', :en_construction_at],
|
|
['Passé en instruction le', :en_instruction_at],
|
|
['Traité le', :processed_at],
|
|
['Motivation de la décision', :motivation],
|
|
['Instructeurs', followers_instructeurs.map(&:email).join(' ')]
|
|
]
|
|
|
|
if procedure.routee?
|
|
columns << ['Groupe instructeur', groupe_instructeur.label]
|
|
end
|
|
|
|
columns + champs_for_export + annotations_for_export
|
|
end
|
|
|
|
def champs_for_export
|
|
champs.reject(&:exclude_from_export?).map do |champ|
|
|
[champ.libelle, champ.for_export]
|
|
end
|
|
end
|
|
|
|
def annotations_for_export
|
|
champs_private.reject(&:exclude_from_export?).map do |champ|
|
|
[champ.libelle, champ.for_export]
|
|
end
|
|
end
|
|
|
|
def attachments_downloadable?
|
|
!PiecesJustificativesService.liste_pieces_justificatives(self).empty? && PiecesJustificativesService.pieces_justificatives_total_size(self) < Dossier::TAILLE_MAX_ZIP
|
|
end
|
|
|
|
def linked_dossiers_for(instructeur)
|
|
dossier_ids = champs.filter(&:dossier_link?).map(&:value).compact
|
|
(instructeur.dossiers.where(id: dossier_ids) + instructeur.dossiers_from_avis.where(id: dossier_ids)).uniq
|
|
end
|
|
|
|
def hash_for_deletion_mail
|
|
{ id: self.id, procedure_libelle: self.procedure.libelle }
|
|
end
|
|
|
|
def geo_data?
|
|
geo_areas.present?
|
|
end
|
|
|
|
def to_feature_collection
|
|
{
|
|
type: 'FeatureCollection',
|
|
id: id,
|
|
bbox: bounding_box,
|
|
features: geo_areas.map(&:to_feature)
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def geo_areas
|
|
champs.includes(:geo_areas).flat_map(&:geo_areas) + champs_private.includes(:geo_areas).flat_map(&:geo_areas)
|
|
end
|
|
|
|
def bounding_box
|
|
factory = RGeo::Geographic.simple_mercator_factory
|
|
bounding_box = RGeo::Cartesian::BoundingBox.new(factory)
|
|
|
|
geo_areas.map(&:rgeo_geometry).compact.each do |geometry|
|
|
bounding_box.add(geometry)
|
|
end
|
|
|
|
[bounding_box.max_point, bounding_box.min_point].compact.flat_map(&:coordinates)
|
|
end
|
|
|
|
def log_dossier_operation(author, operation, subject = nil)
|
|
if log_operations?
|
|
DossierOperationLog.create_and_serialize(
|
|
dossier: self,
|
|
operation: DossierOperationLog.operations.fetch(operation),
|
|
author: author,
|
|
subject: subject
|
|
)
|
|
end
|
|
end
|
|
|
|
def log_automatic_dossier_operation(operation, subject = nil)
|
|
if log_operations?
|
|
DossierOperationLog.create_and_serialize(
|
|
dossier: self,
|
|
operation: DossierOperationLog.operations.fetch(operation),
|
|
automatic_operation: true,
|
|
subject: subject
|
|
)
|
|
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
|
|
if saved_change_to_state? && en_instruction? && !procedure.declarative_accepte?
|
|
NotificationMailer.send_dossier_received(self).deliver_later
|
|
end
|
|
end
|
|
|
|
def send_draft_notification_email
|
|
if brouillon? && !procedure.declarative?
|
|
DossierMailer.notify_new_draft(self).deliver_later
|
|
end
|
|
end
|
|
|
|
def send_web_hook
|
|
if saved_change_to_state? && !brouillon? && procedure.web_hook_url
|
|
WebHookJob.perform_later(
|
|
procedure,
|
|
self
|
|
)
|
|
end
|
|
end
|
|
|
|
def unfollow_stale_instructeurs
|
|
followers_instructeurs.each do |instructeur|
|
|
if instructeur.groupe_instructeurs.exclude?(groupe_instructeur)
|
|
instructeur.unfollow(self)
|
|
if kept?
|
|
DossierMailer.notify_groupe_instructeur_changed(instructeur, self).deliver_later
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.notify_draft_not_submitted
|
|
brouillon_near_procedure_closing_date
|
|
.includes(:user)
|
|
.find_each do |dossier|
|
|
DossierMailer.notify_brouillon_not_submitted(dossier).deliver_later
|
|
end
|
|
end
|
|
end
|