demarches-normaliennes/app/services/procedure_export_service.rb
2024-11-14 14:45:43 +01:00

158 lines
4.9 KiB
Ruby

# frozen_string_literal: true
class ProcedureExportService
attr_reader :procedure, :dossiers
def initialize(procedure, dossiers, user_profile, export_template)
@procedure = procedure
@dossiers = dossiers
@user_profile = user_profile
@export_template = export_template
end
def to_csv
@dossiers = @dossiers.downloadable_sorted_batch
io = StringIO.new(SpreadsheetArchitect.to_csv(options_for(:dossiers, :csv)))
create_blob(io, :csv)
end
def to_xlsx
@dossiers = @dossiers.downloadable_sorted_batch
tables = [:dossiers, :etablissements, :avis] + champs_repetables_options(format: :xlsx)
# We recursively build multi page spreadsheet
io = tables.reduce(nil) do |package, table|
SpreadsheetArchitect.to_axlsx_package(options_for(table, :xlsx), package)
end.to_stream
create_blob(io, :xlsx)
end
def to_ods
@dossiers = @dossiers.downloadable_sorted_batch
tables = [:dossiers, :etablissements, :avis] + champs_repetables_options(format: :ods)
# We recursively build multi page spreadsheet
io = StringIO.new(tables.reduce(nil) do |spreadsheet, table|
SpreadsheetArchitect.to_rodf_spreadsheet(options_for(table, :ods), spreadsheet)
end.bytes)
create_blob(io, :ods)
end
def to_zip
attachments = ActiveStorage::DownloadableFile.create_list_from_dossiers(dossiers:, user_profile: @user_profile, export_template: @export_template)
DownloadableFileService.download_and_zip(procedure, attachments, base_filename) do |zip_filepath|
ArchiveUploader.new(procedure: procedure, filename: filename(:zip), filepath: zip_filepath).blob
end
end
def to_geo_json
champs_carte = dossiers.flat_map { _1.filled_champs.filter(&:carte?) }
features = GeoArea.where(champ_id: champs_carte).map(&:to_feature)
io = StringIO.new({ type: 'FeatureCollection', features: }.to_json)
create_blob(io, :json)
end
private
def create_blob(io, format)
ActiveStorage::Blob.create_and_upload!(
io: io,
filename: filename(format),
content_type: content_type(format),
identify: false,
# We generate the exports ourselves, so they are safe
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE, identified: true }
)
end
def base_filename
@base_filename ||= "dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}"
end
def filename(format)
"#{base_filename}.#{format}"
end
def procedure_identifier
procedure.path || "procedure-#{procedure.id}"
end
def content_type(format)
case format
when :csv
'text/csv'
when :xlsx
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
when :ods
'application/vnd.oasis.opendocument.spreadsheet'
when :zip
'application/zip'
when :json
'application/json'
end
end
def etablissements
@etablissements ||= dossiers
.flat_map { _1.filled_champs.filter(&:siret?) }
.filter_map(&:etablissement) + dossiers.filter_map(&:etablissement)
end
def avis
@avis ||= dossiers.flat_map(&:avis)
end
def champs_repetables_options(format:)
procedure
.all_revisions_types_de_champ
.repetition
.filter_map do |type_de_champ_repetition|
types_de_champ = procedure.all_revisions_types_de_champ(parent: type_de_champ_repetition).to_a
rows = dossiers.flat_map { _1.repetition_rows_for_export(type_de_champ_repetition) }
if types_de_champ.present? && rows.present?
{
sheet_name: type_de_champ_repetition.libelle_for_export,
instances: rows,
spreadsheet_columns: Proc.new { |instance| instance.spreadsheet_columns(types_de_champ, export_template: @export_template, format:) }
}
end
end
end
DEFAULT_STYLES = {
header_style: { background_color: "000000", color: "FFFFFF", font_size: 12, bold: true },
row_style: { background_color: nil, color: "000000", font_size: 12 }
}
def options_for(table, format)
options = case table
when :dossiers
{ instances: dossiers.to_a, sheet_name: 'Dossiers', spreadsheet_columns: spreadsheet_columns(format) }
when :etablissements
{ instances: etablissements.to_a, sheet_name: 'Etablissements' }
when :avis
{ instances: avis.to_a, sheet_name: 'Avis' }
when Hash
table
end.merge(DEFAULT_STYLES)
# transliterate: convert to ASCII characters
# to ensure truncate respects 30 bytes
# /\*?[] are invalid Excel worksheet characters
options[:sheet_name] = I18n.transliterate(options[:sheet_name], locale: :en)
.delete('/\*?[]')
.truncate(30, omission: '')
options
end
def spreadsheet_columns(format)
types_de_champ = procedure.types_de_champ_for_procedure_export.to_a
Proc.new do |instance|
instance.send(:"spreadsheet_columns_#{format}", types_de_champ: types_de_champ, export_template: @export_template)
end
end
end