demarches-normaliennes/app/models/export.rb
2023-05-03 11:35:28 +02:00

211 lines
6.2 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# == Schema Information
#
# Table name: exports
#
# id :bigint not null, primary key
# format :string not null
# job_status :string default("pending"), not null
# key :text not null
# procedure_presentation_snapshot :jsonb
# statut :string default("tous")
# time_span_type :string default("everything"), not null
# created_at :datetime not null
# updated_at :datetime not null
# procedure_presentation_id :bigint
#
class Export < ApplicationRecord
include TransientModelsWithPurgeableJobConcern
MAX_DUREE_CONSERVATION_EXPORT = 32.hours
MAX_DUREE_GENERATION = 16.hours
enum format: {
csv: 'csv',
ods: 'ods',
xlsx: 'xlsx',
zip: 'zip',
json: 'json'
}, _prefix: true
enum time_span_type: {
everything: 'everything',
monthly: 'monthly'
}
enum statut: {
'a-suivre': 'a-suivre',
suivis: 'suivis',
traites: 'traites',
tous: 'tous',
supprimes_recemment: 'supprimes_recemment',
archives: 'archives',
expirant: 'expirant'
}
has_and_belongs_to_many :groupe_instructeurs
belongs_to :procedure_presentation, optional: true
has_one_attached :file
validates :format, :groupe_instructeurs, :key, presence: true
after_create_commit :compute_async
FORMATS_WITH_TIME_SPAN = [:xlsx, :ods, :csv].flat_map do |format|
[{ format: format, time_span_type: 'everything' }]
end
FORMATS = [:xlsx, :ods, :csv, :zip, :json].map do |format|
{ format: format }
end
def compute_async
ExportJob.perform_later(self)
end
def compute
load_snapshot!
file.attach(blob)
end
def since
time_span_type == Export.time_span_types.fetch(:monthly) ? 30.days.ago : nil
end
def old?
updated_at < 10.minutes.ago || filters_changed?
end
def filters_changed?
procedure_presentation&.snapshot != procedure_presentation_snapshot
end
def filtered?
procedure_presentation_id.present?
end
def flash_message
if available?
"Lexport au format \"#{format}\" est prêt. Vous pouvez le <a href=\"#{file.url}\">télécharger</a>"
else
"Nous générons cet export. Veuillez revenir dans quelques minutes pour le télécharger."
end
end
def self.find_or_create_export(format, groupe_instructeurs, time_span_type: time_span_types.fetch(:everything), statut: statuts.fetch(:tous), procedure_presentation: nil, force: false)
export = create_or_find_export(format, groupe_instructeurs, time_span_type: time_span_type, statut: statut, procedure_presentation: procedure_presentation)
if export.available? && export.old? && force
export.destroy
create_or_find_export(format, groupe_instructeurs, time_span_type: time_span_type, statut: statut, procedure_presentation: procedure_presentation)
else
export
end
end
def self.find_for_groupe_instructeurs(groupe_instructeurs_ids, procedure_presentation)
exports = if procedure_presentation.present?
where(key: generate_cache_key(groupe_instructeurs_ids, procedure_presentation))
.or(where(key: generate_cache_key(groupe_instructeurs_ids)))
else
where(key: generate_cache_key(groupe_instructeurs_ids))
end
filtered, not_filtered = exports.partition(&:filtered?)
{
xlsx: {
time_span_type: not_filtered.filter(&:format_xlsx?).index_by(&:time_span_type),
statut: filtered.filter(&:format_xlsx?).index_by(&:statut)
},
ods: {
time_span_type: not_filtered.filter(&:format_ods?).index_by(&:time_span_type),
statut: filtered.filter(&:format_ods?).index_by(&:statut)
},
csv: {
time_span_type: not_filtered.filter(&:format_csv?).index_by(&:time_span_type),
statut: filtered.filter(&:format_csv?).index_by(&:statut)
},
zip: {
time_span_type: {},
statut: filtered.filter(&:format_zip?).index_by(&:statut)
},
json: {
time_span_type: {},
statut: filtered.filter(&:format_json?).index_by(&:statut)
}
}
end
def self.create_or_find_export(format, groupe_instructeurs, time_span_type:, statut:, procedure_presentation:)
create_with(groupe_instructeurs: groupe_instructeurs, procedure_presentation: procedure_presentation, procedure_presentation_snapshot: procedure_presentation&.snapshot)
.includes(:procedure_presentation)
.create_or_find_by(format: format,
time_span_type: time_span_type,
statut: statut,
key: generate_cache_key(groupe_instructeurs.map(&:id), procedure_presentation))
end
def self.generate_cache_key(groupe_instructeurs_ids, procedure_presentation = nil)
if procedure_presentation.present?
[
groupe_instructeurs_ids.sort.join('-'),
procedure_presentation.id,
Digest::MD5.hexdigest(procedure_presentation.snapshot.slice(:filters, :sort).to_s)
].join('--')
else
groupe_instructeurs_ids.sort.join('-')
end
end
def count
if procedure_presentation_id.present?
dossiers_for_export.size
end
end
def procedure
groupe_instructeurs.first.procedure
end
private
def load_snapshot!
if procedure_presentation_snapshot.present?
procedure_presentation.attributes = procedure_presentation_snapshot
end
end
def dossiers_for_export
@dossiers_for_export ||= begin
dossiers = Dossier.where(groupe_instructeur: groupe_instructeurs)
if since.present?
dossiers.visible_by_administration.where('dossiers.depose_at > ?', since)
elsif procedure_presentation.present?
filtered_sorted_ids = procedure_presentation
.filtered_sorted_ids(dossiers, statut)
dossiers.where(id: filtered_sorted_ids)
else
dossiers.visible_by_administration
end
end
end
def blob
service = ProcedureExportService.new(procedure, dossiers_for_export)
case format.to_sym
when :csv
service.to_csv
when :xlsx
service.to_xlsx
when :ods
service.to_ods
when :zip
service.to_zip
when :json
service.to_geo_json
end
end
end