demarches-normaliennes/app/models/dossier.rb
gregoirenovel fd08bb8c57 Eager-load data for downloadable dossiers
So that the export is faster
2017-10-11 12:38:00 +02:00

383 lines
12 KiB
Ruby

class Dossier < ActiveRecord::Base
enum state: {
draft: 'draft',
initiated: 'initiated',
received: 'received',
closed: 'closed',
refused: 'refused',
without_continuation: 'without_continuation'
}
BROUILLON = %w(draft)
NOUVEAUX = %w(initiated)
EN_CONSTRUCTION = %w(initiated)
EN_INSTRUCTION = %w(received)
EN_CONSTRUCTION_OU_INSTRUCTION = EN_CONSTRUCTION + EN_INSTRUCTION
TERMINE = %w(closed refused without_continuation)
has_one :etablissement, dependent: :destroy
has_one :entreprise, dependent: :destroy
has_one :individual, dependent: :destroy
has_one :attestation
has_many :cerfa, dependent: :destroy
has_many :pieces_justificatives, dependent: :destroy
has_many :champs, class_name: 'ChampPublic', dependent: :destroy
has_many :champs_private, class_name: 'ChampPrivate', dependent: :destroy
has_many :quartier_prioritaires, dependent: :destroy
has_many :cadastres, dependent: :destroy
has_many :commentaires, dependent: :destroy
has_many :invites, dependent: :destroy
has_many :invites_user, class_name: 'InviteUser', dependent: :destroy
has_many :invites_gestionnaires, class_name: 'InviteGestionnaire', dependent: :destroy
has_many :follows
has_many :notifications, dependent: :destroy
has_many :avis, dependent: :destroy
belongs_to :procedure
belongs_to :user
accepts_nested_attributes_for :champs
accepts_nested_attributes_for :champs_private
default_scope { where(hidden_at: nil) }
scope :state_brouillon, -> { where(state: BROUILLON) }
scope :state_not_brouillon, -> { where.not(state: BROUILLON) }
scope :state_nouveaux, -> { where(state: NOUVEAUX) }
scope :state_en_construction, -> { where(state: EN_CONSTRUCTION) }
scope :state_en_instruction, -> { where(state: EN_INSTRUCTION) }
scope :state_en_construction_ou_instruction, -> { where(state: EN_CONSTRUCTION_OU_INSTRUCTION) }
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 :all_state, -> { not_archived.state_not_brouillon }
scope :nouveaux, -> { not_archived.state_nouveaux }
scope :en_instruction, -> { not_archived.state_en_instruction }
scope :termine, -> { not_archived.state_termine }
scope :downloadable, -> { state_not_brouillon.includes(:entreprise, :etablissement, :champs, :champs_private) }
scope :en_cours, -> { not_archived.state_en_construction_ou_instruction }
scope :without_followers, -> { includes(:follows).where(follows: { id: nil }) }
scope :with_unread_notifications, -> { where(notifications: { already_read: false }) }
accepts_nested_attributes_for :individual
delegate :siren, to: :entreprise
delegate :siret, to: :etablissement, allow_nil: true
delegate :types_de_piece_justificative, to: :procedure
delegate :types_de_champ, to: :procedure
delegate :france_connect_information, to: :user
before_validation :update_state_dates, if: -> { state_changed? }
after_save :build_default_champs, if: Proc.new { procedure_id_changed? }
after_save :build_default_individual, if: Proc.new { procedure.for_individual? }
after_save :send_notification_email
validates :user, presence: true
def unreaded_notifications
@unreaded_notif ||= notifications.where(already_read: false)
end
def first_unread_notification
unreaded_notifications.order("created_at ASC").first
end
def was_piece_justificative_uploaded_for_type_id?(type_id)
pieces_justificatives.where(type_de_piece_justificative_id: type_id).count > 0
end
def notifications_summary
unread_notifications = notifications.unread
{
demande: unread_notifications.select(&:demande?).present?,
avis: unread_notifications.select(&:avis?).present?,
messagerie: unread_notifications.select(&:messagerie?).present?,
annotations_privees: unread_notifications.select(&:annotations_privees?).present?
}
end
def retrieve_last_piece_justificative_by_type(type)
pieces_justificatives.where(type_de_piece_justificative_id: type).last
end
def retrieve_all_piece_justificative_by_type(type)
pieces_justificatives.where(type_de_piece_justificative_id: type).order(created_at: :DESC)
end
def build_default_champs
procedure.types_de_champ.all.each do |type_de_champ|
ChampPublic.create(type_de_champ: type_de_champ, dossier: self)
end
procedure.types_de_champ_private.all.each do |type_de_champ|
ChampPrivate.create(type_de_champ: type_de_champ, dossier: self)
end
end
def build_default_individual
if Individual.where(dossier_id: self.id).count == 0
Individual.create(dossier: self)
end
end
def ordered_champs
# TODO: use the line below when the procedure preview does not leak champ with dossier_id == 0
# champs.joins(:type_de_champ).order('types_de_champ.order_place')
champs.joins(', types_de_champ').where("champs.type_de_champ_id = types_de_champ.id AND types_de_champ.procedure_id = #{procedure.id}").order('order_place')
end
def ordered_champs_private
# TODO: use the line below when the procedure preview does not leak champ with dossier_id == 0
# champs_private.includes(:type_de_champ).order('types_de_champ.order_place')
champs_private.joins(', types_de_champ').where("champs.type_de_champ_id = types_de_champ.id AND types_de_champ.procedure_id = #{procedure.id}").order('order_place')
end
def ordered_pieces_justificatives
champs.joins(', types_de_piece_justificative').where("pieces_justificatives.type_de_piece_justificative_id = types_de_piece_justificative.id AND types_de_piece_justificative.procedure_id = #{procedure.id}").order('order_place ASC')
end
def next_step! role, action, motivation = nil
unless %w(initiate follow update comment receive refuse without_continuation close).include?(action)
fail 'action is not valid'
end
unless %w(user gestionnaire).include?(role)
fail 'role is not valid'
end
case role
when 'user'
case action
when 'initiate'
if draft?
initiated!
end
end
when 'gestionnaire'
case action
when 'close'
if received?
self.attestation = build_attestation
save
closed!
if motivation
self.motivation = motivation
save
end
end
when 'refuse'
if received?
refused!
if motivation
self.motivation = motivation
save
end
end
when 'without_continuation'
if received?
without_continuation!
if motivation
self.motivation = motivation
save
end
end
end
end
state
end
def brouillon?
BROUILLON.include?(state)
end
def en_construction?
EN_CONSTRUCTION.include?(state)
end
def en_instruction?
EN_INSTRUCTION.include?(state)
end
def en_construction_ou_instruction?
EN_CONSTRUCTION_OU_INSTRUCTION.include?(state)
end
def termine?
TERMINE.include?(state)
end
def cerfa_available?
procedure.cerfa_flag? && cerfa.size != 0
end
def convert_specific_hash_values_to_string(hash_to_convert)
hash = {}
hash_to_convert.each do |key, value|
value = serialize_value_for_export(value)
hash.store(key, value)
end
return hash
end
def full_data_strings_array
data_with_champs.map do |value|
serialize_value_for_export(value)
end
end
def export_entreprise_data
unless entreprise.nil?
etablissement_attr = EtablissementCsvSerializer.new(self.etablissement).attributes.map { |k, v| ["etablissement.#{k}".parameterize.underscore.to_sym, v] }.to_h
entreprise_attr = EntrepriseSerializer.new(self.entreprise).attributes.map { |k, v| ["entreprise.#{k}".parameterize.underscore.to_sym, v] }.to_h
else
etablissement_attr = EtablissementSerializer.new(Etablissement.new).attributes.map { |k, v| ["etablissement.#{k}".parameterize.underscore.to_sym, v] }.to_h
entreprise_attr = EntrepriseSerializer.new(Entreprise.new).attributes.map { |k, v| ["entreprise.#{k}".parameterize.underscore.to_sym, v] }.to_h
end
return convert_specific_hash_values_to_string(etablissement_attr.merge(entreprise_attr))
end
def data_with_champs
serialized_dossier = DossierTableExportSerializer.new(self)
data = serialized_dossier.attributes.values
data += self.ordered_champs.map(&:value)
data += self.ordered_champs_private.map(&:value)
data += self.export_entreprise_data.values
return data
end
def export_headers
serialized_dossier = DossierTableExportSerializer.new(self)
headers = serialized_dossier.attributes.keys
headers += self.procedure.types_de_champ.order(:order_place).map { |types_de_champ| types_de_champ.libelle.parameterize.underscore.to_sym }
headers += self.procedure.types_de_champ_private.order(:order_place).map { |types_de_champ| types_de_champ.libelle.parameterize.underscore.to_sym }
headers += self.export_entreprise_data.keys
return headers
end
def followers_gestionnaires
follows.includes(:gestionnaire).map(&:gestionnaire)
end
def reset!
etablissement.destroy
entreprise.destroy
update_attributes(autorisation_donnees: false)
end
def total_follow
follows.size
end
def read_only?
received? || closed? || refused? || without_continuation?
end
def owner? email
user.email == email
end
def invite_by_user? email
(invites_user.pluck :email).include? email
end
def can_be_initiated?
!(procedure.archivee? && draft?)
end
def text_summary
if brouillon?
parts = [
"Dossier en brouillon répondant à la procédure ",
procedure.libelle,
" gérée par l'organisme ",
procedure.organisation
]
else
parts = [
"Dossier déposé le ",
initiated_at.localtime.strftime("%d/%m/%Y"),
" sur la procédure ",
procedure.libelle,
" gérée par l'organisme ",
procedure.organisation
]
end
parts.join
end
def avis_for(gestionnaire)
if gestionnaire.dossiers.include?(self)
avis.order(created_at: :asc)
else
avis
.where(confidentiel: false)
.or(avis.where(claimant: gestionnaire))
.or(avis.where(gestionnaire: gestionnaire))
.order(created_at: :asc)
end
end
def get_value(table, column)
case table
when 'self'
self.send(column)
when 'user'
self.user.send(column)
when 'france_connect_information'
self.user.france_connect_information&.send(column)
when 'entreprise'
self.entreprise&.send(column)
when 'etablissement'
self.etablissement&.send(column)
when 'type_de_champ'
self.champs.find {|c| c.type_de_champ_id == column.to_i }.value
when 'type_de_champ_private'
self.champs_private.find {|c| c.type_de_champ_id == column.to_i }.value
end
end
def self.sanitize_for_order(order)
sanitize_sql_for_order(order)
end
private
def build_attestation
if procedure.attestation_template.present? && procedure.attestation_template.activated?
procedure.attestation_template.attestation_for(self)
end
end
def update_state_dates
if initiated? && !self.initiated_at
self.initiated_at = DateTime.now
elsif received? && !self.received_at
self.received_at = DateTime.now
elsif TERMINE.include?(state)
self.processed_at = DateTime.now
end
end
def serialize_value_for_export(value)
value.nil? || value.kind_of?(Time) ? value : value.to_s
end
def send_notification_email
if state_changed? && EN_INSTRUCTION.include?(state)
NotificationMailer.send_notification(self, procedure.received_mail_template).deliver_now!
end
end
end