diff --git a/app/controllers/new_gestionnaire/procedures_controller.rb b/app/controllers/new_gestionnaire/procedures_controller.rb index 4c8ac5d56..f32fb176a 100644 --- a/app/controllers/new_gestionnaire/procedures_controller.rb +++ b/app/controllers/new_gestionnaire/procedures_controller.rb @@ -174,13 +174,21 @@ module NewGestionnaire end def download_dossiers - export = procedure.generate_export - filename = procedure.export_filename + options = params.permit(:limit, :since, tables: []) respond_to do |format| - format.csv { send_data(SpreadsheetArchitect.to_csv(data: export[:data], headers: export[:headers]), filename: "#{filename}.csv") } - format.xlsx { send_data(SpreadsheetArchitect.to_xlsx(data: export[:data], headers: export[:headers]), filename: "#{filename}.xlsx") } - format.ods { send_data(SpreadsheetArchitect.to_ods(data: export[:data], headers: export[:headers]), filename: "#{filename}.ods") } + format.csv do + send_data(procedure.to_csv(options), + filename: procedure.export_filename(:csv)) + end + format.xlsx do + send_data(procedure.to_xlsx(options), + filename: procedure.export_filename(:xlsx)) + end + format.ods do + send_data(procedure.to_ods(options), + filename: procedure.export_filename(:ods)) + end end end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 58ff1328d..fd97c3c59 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -52,13 +52,13 @@ class Dossier < ApplicationRecord 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(:etablissement, :champs, :champs_private, :user, :individual, :followers_gestionnaires).order(en_construction_at: 'asc') } + scope :downloadable_sorted, -> { state_not_brouillon.includes(:etablissement, :user, :individual, :followers_gestionnaires, champs: { etablissement: [], type_de_champ: :drop_down_list }, champs_private: { etablissement: [], type_de_champ: :drop_down_list }).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 :followed_by, -> (gestionnaire) { joins(:follows).where(follows: { gestionnaire: gestionnaire }) } scope :with_champs, -> { includes(champs: :type_de_champ) } scope :nearing_end_of_retention, -> (duration = '1 month') { joins(:procedure).where("en_instruction_at + (duree_conservation_dossiers_dans_ds * interval '1 month') - now() < interval ?", duration) } - + scope :since, -> (since) { where('dossiers.en_construction_at >= ?', since) } scope :for_api, -> { includes(commentaires: [], champs: [ @@ -146,21 +146,6 @@ class Dossier < ApplicationRecord INSTRUCTION_COMMENCEE.include?(state) end - def export_headers - serialized_dossier = DossierTableExportSerializer.new(self) - headers = serialized_dossier.attributes.keys - headers += procedure.types_de_champ.order(:order_place).map { |types_de_champ| types_de_champ.libelle.parameterize.underscore.to_sym } - headers += procedure.types_de_champ_private.order(:order_place).map { |types_de_champ| types_de_champ.libelle.parameterize.underscore.to_sym } - headers += export_etablissement_data.keys - headers - end - - def export_values - sorted_values.map do |value| - serialize_value_for_export(value) - end - end - def reset! etablissement.destroy @@ -361,36 +346,6 @@ class Dossier < ApplicationRecord end end - def serialize_value_for_export(value) - value.nil? || value.kind_of?(Time) ? value : value.to_s - end - - def convert_specific_hash_values_to_string(hash_to_convert) - hash_to_convert.transform_values do |value| - serialize_value_for_export(value) - end - end - - def export_etablissement_data - if etablissement.present? - etablissement_attr = EtablissementCsvSerializer.new(etablissement).attributes.transform_keys { |k| "etablissement.#{k}".parameterize.underscore.to_sym } - entreprise_attr = EntrepriseSerializer.new(etablissement.entreprise).attributes.transform_keys { |k| "entreprise.#{k}".parameterize.underscore.to_sym } - else - etablissement_attr = EtablissementSerializer.new(Etablissement.new).attributes.transform_keys { |k| "etablissement.#{k}".parameterize.underscore.to_sym } - entreprise_attr = EntrepriseSerializer.new(Entreprise.new).attributes.transform_keys { |k| "entreprise.#{k}".parameterize.underscore.to_sym } - end - convert_specific_hash_values_to_string(etablissement_attr.merge(entreprise_attr)) - end - - def sorted_values - serialized_dossier = DossierTableExportSerializer.new(self) - values = serialized_dossier.attributes.values - values += champs.map(&:for_export) - values += champs_private.map(&:for_export) - values += export_etablissement_data.values - values - end - def send_dossier_received if saved_change_to_state? && en_instruction? NotificationMailer.send_dossier_received(self).deliver_later diff --git a/app/models/procedure.rb b/app/models/procedure.rb index 718342060..4512f22da 100644 --- a/app/models/procedure.rb +++ b/app/models/procedure.rb @@ -242,21 +242,25 @@ class Procedure < ApplicationRecord self.dossiers.state_not_brouillon.size end - def export_filename + def export_filename(format) procedure_identifier = path || "procedure-#{id}" - "dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}" + "dossiers_#{procedure_identifier}_#{Time.zone.now.strftime('%Y-%m-%d_%H-%M')}.#{format}" end - def generate_export - exportable_dossiers = dossiers.downloadable_sorted + def export(options = {}) + ProcedureExportService.new(self, **options.to_h.symbolize_keys) + end - headers = exportable_dossiers&.first&.export_headers || [] - data = exportable_dossiers.any? ? exportable_dossiers.map(&:export_values) : [[]] + def to_csv(options = {}) + export(options).to_csv + end - { - headers: headers, - data: data - } + def to_xlsx(options = {}) + export(options).to_xlsx + end + + def to_ods(options = {}) + export(options).to_ods end def procedure_overview(start_date) diff --git a/app/serializers/dossier_table_export_serializer.rb b/app/serializers/dossier_table_export_serializer.rb deleted file mode 100644 index cdc125cca..000000000 --- a/app/serializers/dossier_table_export_serializer.rb +++ /dev/null @@ -1,57 +0,0 @@ -class DossierTableExportSerializer < ActiveModel::Serializer - include DossierHelper - - attributes :id, - :created_at, - :updated_at, - :archived, - :email, - :state, - :initiated_at, - :received_at, - :processed_at, - :motivation - - attribute :emails_instructeurs - - attributes :individual_gender, - :individual_prenom, - :individual_nom, - :individual_birthdate - - def email - object.user&.email - end - - def state - dossier_legacy_state(object) - end - - def initiated_at - object.en_construction_at - end - - def received_at - object.en_instruction_at - end - - def individual_prenom - object.individual&.prenom - end - - def individual_nom - object.individual&.nom - end - - def individual_birthdate - object.individual&.birthdate - end - - def individual_gender - object.individual&.gender - end - - def emails_instructeurs - object.followers_gestionnaires.pluck(:email).join(' ') - end -end diff --git a/app/serializers/etablissement_csv_serializer.rb b/app/serializers/etablissement_csv_serializer.rb deleted file mode 100644 index c53ce3342..000000000 --- a/app/serializers/etablissement_csv_serializer.rb +++ /dev/null @@ -1,5 +0,0 @@ -class EtablissementCsvSerializer < EtablissementSerializer - def adresse - object.adresse.chomp.gsub("\r\n", ' ').delete("\r") - end -end diff --git a/app/services/procedure_export_service.rb b/app/services/procedure_export_service.rb new file mode 100644 index 000000000..152288723 --- /dev/null +++ b/app/services/procedure_export_service.rb @@ -0,0 +1,261 @@ +class ProcedureExportService + include DossierHelper + + ATTRIBUTES = [ + :id, + :created_at, + :updated_at, + :archived, + :email, + :state, + :initiated_at, + :received_at, + :processed_at, + :motivation, + :emails_instructeurs, + :individual_gender, + :individual_prenom, + :individual_nom, + :individual_birthdate + ] + + ETABLISSEMENT_ATTRIBUTES = [ + :siret, + :siege_social, + :naf, + :libelle_naf, + :adresse, + :numero_voie, + :type_voie, + :nom_voie, + :complement_adresse, + :code_postal, + :localite, + :code_insee_localite + ] + + ENTREPRISE_ATTRIBUTES = [ + :siren, + :capital_social, + :numero_tva_intracommunautaire, + :forme_juridique, + :forme_juridique_code, + :nom_commercial, + :raison_sociale, + :siret_siege_social, + :code_effectif_entreprise, + :date_creation, + :nom, + :prenom + ] + + def initialize(procedure, tables: [], ids: nil, since: nil, limit: nil) + @procedure = procedure + @dossiers = procedure.dossiers.downloadable_sorted + if ids + @dossiers = @dossiers.where(id: ids) + end + if since + @dossiers = @dossiers.since(since) + end + if limit + @dossiers = @dossiers.limit(limit) + end + @dossiers = @dossiers.to_a + @tables = tables.map(&:to_sym) + end + + def to_csv + SpreadsheetArchitect.to_csv(to_data(:dossiers)) + end + + def to_xlsx + package = SpreadsheetArchitect.to_axlsx_package(to_data(:dossiers)) + + # Next we recursively build multi page spreadsheet + @tables.reduce(package) do |package, table| + SpreadsheetArchitect.to_axlsx_package(to_data(table), package) + end.to_stream.read + end + + def to_ods + spreadsheet = SpreadsheetArchitect.to_rodf_spreadsheet(to_data(:dossiers)) + + # Next we recursively build multi page spreadsheet + @tables.reduce(spreadsheet) do |spreadsheet, table| + SpreadsheetArchitect.to_rodf_spreadsheet(to_data(table), spreadsheet) + end.bytes + end + + def to_data(table) + case table + when :dossiers + dossiers_table_data + when :etablissements + etablissements_table_data + end + end + + private + + def empty_table_data(sheet_name, headers = []) + { + sheet_name: sheet_name, + headers: headers, + data: [[]] + } + end + + def dossiers_table_data + if @dossiers.any? + { + sheet_name: 'Dossiers', + headers: dossiers_headers, + data: dossiers_data + } + else + empty_table_data('Dossiers', dossiers_headers) + end + end + + def etablissements_table_data + @etablissements = @dossiers.flat_map do |dossier| + dossier.champs.select do |champ| + champ.is_a?(Champs::SiretChamp) + end + dossier.champs_private.select do |champ| + champ.is_a?(Champs::SiretChamp) + end + end.map(&:etablissement).compact + + if @etablissements.any? + { + sheet_name: 'Etablissements', + headers: etablissements_headers, + data: etablissements_data + } + else + empty_table_data('Etablissements', etablissements_headers) + end + end + + def dossiers_headers + headers = ATTRIBUTES.map do |key| + label_for_export(key.to_s) + end + headers += @procedure.types_de_champ.map do |champ| + label_for_export(champ.libelle) + end + headers += @procedure.types_de_champ_private.map do |champ| + label_for_export(champ.libelle) + end + headers += ETABLISSEMENT_ATTRIBUTES.map do |key| + label_for_export("etablissement.#{key}") + end + headers += ENTREPRISE_ATTRIBUTES.map do |key| + label_for_export("entreprise.#{key}") + end + headers + end + + def dossiers_data + @dossiers.map do |dossier| + values = ATTRIBUTES.map do |key| + case key + when :email + dossier.user.email + when :state + dossier_legacy_state(dossier) + when :initiated_at + dossier.en_construction_at + when :received_at + dossier.en_instruction_at + when :individual_prenom + dossier.individual&.prenom + when :individual_nom + dossier.individual&.nom + when :individual_birthdate + dossier.individual&.birthdate + when :individual_gender + dossier.individual&.gender + when :emails_instructeurs + dossier.followers_gestionnaires.map(&:email).join(' ') + else + dossier.read_attribute(key) + end + end + values = normalize_values(values) + values += dossier.champs.map do |champ| + value_for_export(champ) + end + values += dossier.champs_private.map do |champ| + value_for_export(champ) + end + values += etablissement_data(dossier.etablissement) + values + end + end + + def etablissements_headers + headers = [:dossier_id, :libelle] + headers += ETABLISSEMENT_ATTRIBUTES.map do |key| + label_for_export("etablissement.#{key}") + end + headers += ENTREPRISE_ATTRIBUTES.map do |key| + label_for_export("entreprise.#{key}") + end + headers + end + + def etablissements_data + @etablissements.map do |etablissement| + data = [ + etablissement.champ.dossier_id, + label_for_export(etablissement.champ.libelle).to_s + ] + data += etablissement_data(etablissement) + end + end + + def etablissement_data(etablissement) + data = ETABLISSEMENT_ATTRIBUTES.map do |key| + if etablissement.present? + case key + when :adresse + etablissement.adresse&.chomp&.gsub("\r\n", ' ')&.delete("\r") + else + etablissement.read_attribute(key) + end + end + end + data += ENTREPRISE_ATTRIBUTES.map do |key| + if etablissement.present? + case key + when :date_creation + etablissement.entreprise_date_creation&.to_datetime + else + etablissement.read_attribute(:"entreprise_#{key}") + end + end + end + normalize_values(data) + end + + def label_for_export(label) + label.parameterize.underscore.to_sym + end + + def value_for_export(champ) + champ.for_export + end + + def normalize_values(values) + values.map do |value| + case value + when TrueClass, FalseClass + value.to_s + else + value.blank? ? nil : value.to_s + end + end + end +end diff --git a/app/views/new_gestionnaire/procedures/_download_dossiers.html.haml b/app/views/new_gestionnaire/procedures/_download_dossiers.html.haml index 2dd53c586..cf18d2045 100644 --- a/app/views/new_gestionnaire/procedures/_download_dossiers.html.haml +++ b/app/views/new_gestionnaire/procedures/_download_dossiers.html.haml @@ -7,6 +7,6 @@ %li = link_to "Au format .csv", download_dossiers_gestionnaire_procedure_path(format: :csv, procedure_id: procedure.id), target: "_blank" %li - = link_to "Au format .xlsx", download_dossiers_gestionnaire_procedure_path(format: :xlsx, procedure_id: procedure.id), target: "_blank" + = link_to "Au format .xlsx", download_dossiers_gestionnaire_procedure_path(format: :xlsx, procedure_id: procedure.id, tables: [:etablissements]), target: "_blank" %li - = link_to "Au format .ods", download_dossiers_gestionnaire_procedure_path(format: :ods, procedure_id: procedure.id), target: "_blank" + = link_to "Au format .ods", download_dossiers_gestionnaire_procedure_path(format: :ods, procedure_id: procedure.id, tables: [:etablissements]), target: "_blank" diff --git a/spec/controllers/new_gestionnaire/procedures_controller_spec.rb b/spec/controllers/new_gestionnaire/procedures_controller_spec.rb index 324db5d50..d89d73871 100644 --- a/spec/controllers/new_gestionnaire/procedures_controller_spec.rb +++ b/spec/controllers/new_gestionnaire/procedures_controller_spec.rb @@ -307,4 +307,33 @@ describe NewGestionnaire::ProceduresController, type: :controller do end end end + + describe "#download_dossiers" do + let(:gestionnaire) { create(:gestionnaire) } + let!(:procedure) { create(:procedure, gestionnaires: [gestionnaire]) } + + context "when logged in" do + before do + sign_in(gestionnaire) + end + + context "csv" do + before { get :download_dossiers, params: { procedure_id: procedure.id }, format: 'csv' } + + it { expect(response).to have_http_status(:ok) } + end + + context "xlsx" do + before { get :download_dossiers, params: { procedure_id: procedure.id }, format: 'xlsx' } + + it { expect(response).to have_http_status(:ok) } + end + + context "ods" do + before { get :download_dossiers, params: { procedure_id: procedure.id }, format: 'ods' } + + it { expect(response).to have_http_status(:ok) } + end + end + end end diff --git a/spec/factories/champ.rb b/spec/factories/champ.rb index f83c2aff7..f9c345837 100644 --- a/spec/factories/champ.rb +++ b/spec/factories/champ.rb @@ -29,8 +29,28 @@ FactoryBot.define do end end - factory :champ_integer_number, class: 'Champs::IntegerNumberChamp' do - type_de_champ { create(:type_de_champ_integer_number) } + factory :champ_text, class: 'Champs::TextChamp' do + type_de_champ { create(:type_de_champ_text) } + value { 'text' } + end + + factory :champ_textarea, class: 'Champs::TextareaChamp' do + type_de_champ { create(:type_de_champ_textarea) } + value { 'textarea' } + end + + factory :champ_date, class: 'Champs::DateChamp' do + type_de_champ { create(:type_de_champ_date) } + value { 1.day.ago.iso8601 } + end + + factory :champ_datetime, class: 'Champs::DatetimeChamp' do + type_de_champ { create(:type_de_champ_datetime) } + value { 1.day.ago.iso8601 } + end + + factory :champ_number, class: 'Champs::NumberChamp' do + type_de_champ { create(:type_de_champ_number) } value { '42' } end @@ -39,23 +59,89 @@ FactoryBot.define do value { '42.1' } end + factory :champ_integer_number, class: 'Champs::IntegerNumberChamp' do + type_de_champ { create(:type_de_champ_integer_number) } + value { '42' } + end + + factory :champ_checkbox, class: 'Champs::CheckboxChamp' do + type_de_champ { create(:type_de_champ_checkbox) } + value { 'on' } + end + + factory :champ_civilite, class: 'Champs::CiviliteChamp' do + type_de_champ { create(:type_de_champ_civilite) } + value { 'M.' } + end + + factory :champ_email, class: 'Champs::EmailChamp' do + type_de_champ { create(:type_de_champ_email) } + value { 'yoda@beta.gouv.fr' } + end + + factory :champ_phone, class: 'Champs::PhoneChamp' do + type_de_champ { create(:type_de_champ_phone) } + value { '0666666666' } + end + + factory :champ_address, class: 'Champs::AddressChamp' do + type_de_champ { create(:type_de_champ_address) } + value { '2 rue des Démarches' } + end + + factory :champ_yes_no, class: 'Champs::YesNoChamp' do + type_de_champ { create(:type_de_champ_yes_no) } + value { 'true' } + end + + factory :champ_drop_down_list, class: 'Champs::DropDownListChamp' do + type_de_champ { create(:type_de_champ_drop_down_list) } + value { '' } + end + + factory :champ_multiple_drop_down_list, class: 'Champs::MultipleDropDownListChamp' do + type_de_champ { create(:type_de_champ_multiple_drop_down_list) } + value { '' } + end + factory :champ_linked_drop_down_list, class: 'Champs::LinkedDropDownListChamp' do type_de_champ { create(:type_de_champ_linked_drop_down_list) } value { '{}' } end - factory :champ_carte, class: 'Champs::CarteChamp' do - type_de_champ { create(:type_de_champ_carte) } + factory :champ_pays, class: 'Champs::PaysChamp' do + type_de_champ { create(:type_de_champ_pays) } + value { 'France' } end - factory :champ_siret, class: 'Champs::SiretChamp' do - type_de_champ { create(:type_de_champ_siret) } - value { '44011762001530' } - etablissement { create(:etablissement) } + factory :champ_regions, class: 'Champs::RegionChamp' do + type_de_champ { create(:type_de_champ_regions) } + value { '' } + end - before(:create) do |champ, evaluator| - champ.etablissement.signature = champ.etablissement.sign - end + factory :champ_departements, class: 'Champs::DepartementChamp' do + type_de_champ { create(:type_de_champ_departements) } + value { '' } + end + + factory :champ_engagement, class: 'Champs::EngagementChamp' do + type_de_champ { create(:type_de_champ_engagement) } + value { 'true' } + end + + factory :champ_header_section, class: 'Champs::HeaderSectionChamp' do + type_de_champ { create(:type_de_champ_header_section) } + value { 'une section' } + end + + factory :champ_explication, class: 'Champs::ExplicationChamp' do + type_de_champ { create(:type_de_champ_explication) } + value { 'une explication' } + end + + factory :champ_dossier_link, class: 'Champs::DossierLinkChamp' do + type_de_champ { create(:type_de_champ_dossier_link) } + value { create(:dossier).id } end factory :champ_piece_justificative, class: 'Champs::PieceJustificativeChamp' do @@ -65,4 +151,18 @@ FactoryBot.define do champ.piece_justificative_file.attach(io: StringIO.new("toto"), filename: "toto.txt", content_type: "text/plain") end end + + factory :champ_carte, class: 'Champs::CarteChamp' do + type_de_champ { create(:type_de_champ_carte) } + end + + factory :champ_siret, class: 'Champs::SiretChamp' do + association :type_de_champ, factory: [:type_de_champ_siret] + association :etablissement, factory: [:etablissement] + value { '44011762001530' } + + after(:build) do |champ, evaluator| + champ.etablissement.signature = champ.etablissement.sign + end + end end diff --git a/spec/factories/dossier.rb b/spec/factories/dossier.rb index ddcef3b01..41b50f313 100644 --- a/spec/factories/dossier.rb +++ b/spec/factories/dossier.rb @@ -131,5 +131,23 @@ FactoryBot.define do dossier.attestation = dossier.build_attestation end end + + trait :with_all_champs do + after(:create) do |dossier, _evaluator| + dossier.champs = dossier.procedure.types_de_champ.map do |type_de_champ| + build(:"champ_#{type_de_champ.type_champ}", type_de_champ: type_de_champ) + end + dossier.save! + end + end + + trait :with_all_annotations do + after(:create) do |dossier, _evaluator| + dossier.champs = dossier.procedure.types_de_champ.map do |type_de_champ| + build(:"champ_#{type_de_champ.type_champ}", type_de_champ: type_de_champ) + end + dossier.save! + end + end end end diff --git a/spec/factories/procedure.rb b/spec/factories/procedure.rb index 7b38f42d2..c26c4b7ce 100644 --- a/spec/factories/procedure.rb +++ b/spec/factories/procedure.rb @@ -176,28 +176,34 @@ FactoryBot.define do trait :with_all_champs_mandatory do after(:build) do |procedure, _evaluator| - tdcs = [] - tdcs << create(:type_de_champ, mandatory: true, libelle: 'text') - tdcs << create(:type_de_champ_textarea, mandatory: true, libelle: 'textarea') - tdcs << create(:type_de_champ_date, mandatory: true, libelle: 'date') - tdcs << create(:type_de_champ_datetime, mandatory: true, libelle: 'datetime') - tdcs << create(:type_de_champ_number, mandatory: true, libelle: 'number') - tdcs << create(:type_de_champ_checkbox, mandatory: true, libelle: 'checkbox') - tdcs << create(:type_de_champ_civilite, mandatory: true, libelle: 'civilite') - tdcs << create(:type_de_champ_email, mandatory: true, libelle: 'email') - tdcs << create(:type_de_champ_phone, mandatory: true, libelle: 'phone') - tdcs << create(:type_de_champ_yes_no, mandatory: true, libelle: 'yes_no') - tdcs << create(:type_de_champ_drop_down_list, mandatory: true, libelle: 'simple_drop_down_list') - tdcs << create(:type_de_champ_multiple_drop_down_list, mandatory: true, libelle: 'multiple_drop_down_list') - tdcs << create(:type_de_champ_pays, mandatory: true, libelle: 'pays') - tdcs << create(:type_de_champ_regions, mandatory: true, libelle: 'regions') - tdcs << create(:type_de_champ_departements, mandatory: true, libelle: 'departements') - tdcs << create(:type_de_champ_engagement, mandatory: true, libelle: 'engagement') - tdcs << create(:type_de_champ_header_section, mandatory: true, libelle: 'header_section') - tdcs << create(:type_de_champ_explication, mandatory: true, libelle: 'explication') - tdcs << create(:type_de_champ_dossier_link, mandatory: true, libelle: 'dossier_link') - tdcs << create(:type_de_champ_piece_justificative, mandatory: true, libelle: 'piece_justificative') - procedure.types_de_champ = tdcs + procedure.types_de_champ = TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index| + if libelle == 'drop_down_list' + libelle = 'simple_drop_down_list' + end + build(:"type_de_champ_#{type_champ}", mandatory: true, libelle: libelle, order_place: index) + end + end + end + + trait :with_all_champs do + after(:build) do |procedure, _evaluator| + procedure.types_de_champ = TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index| + if libelle == 'drop_down_list' + libelle = 'simple_drop_down_list' + end + build(:"type_de_champ_#{type_champ}", libelle: libelle, order_place: index) + end + end + end + + trait :with_all_annotations do + after(:build) do |procedure, _evaluator| + procedure.types_de_champ_private = TypeDeChamp.type_champs.map.with_index do |(libelle, type_champ), index| + if libelle == 'drop_down_list' + libelle = 'simple_drop_down_list' + end + build(:"type_de_champ_#{type_champ}", private: true, libelle: libelle, order_place: index) + end end end end diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index a27d889eb..36b9d2e95 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -166,60 +166,6 @@ describe Dossier do end end - describe '#convert_specific_hash_values_to_string(hash_to_convert)' do - let(:procedure) { create(:procedure) } - let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } - let(:dossier_serialized_attributes) { DossierSerializer.new(dossier).attributes } - - subject { dossier.send(:convert_specific_hash_values_to_string, dossier_serialized_attributes) } - - it { expect(dossier_serialized_attributes[:id]).to be_an(Integer) } - it { expect(dossier_serialized_attributes[:created_at]).to be_a(Time) } - it { expect(dossier_serialized_attributes[:updated_at]).to be_a(Time) } - it { expect(dossier_serialized_attributes[:archived]).to be_in([true, false]) } - it { expect(dossier_serialized_attributes[:state]).to be_a(String) } - - it { expect(subject[:id]).to be_a(String) } - it { expect(subject[:created_at]).to be_a(Time) } - it { expect(subject[:updated_at]).to be_a(Time) } - it { expect(subject[:archived]).to be_a(String) } - it { expect(subject[:state]).to be_a(String) } - end - - describe '#export_etablissement_data' do - let(:procedure) { create(:procedure) } - let(:dossier) { create(:dossier, :with_entreprise, user: user, procedure: procedure) } - - subject { dossier.send(:export_etablissement_data) } - - it { expect(subject[:etablissement_siret]).to eq('44011762001530') } - it { expect(subject[:etablissement_siege_social]).to eq('true') } - it { expect(subject[:etablissement_naf]).to eq('4950Z') } - it { expect(subject[:etablissement_libelle_naf]).to eq('Transports par conduites') } - it { expect(subject[:etablissement_adresse]).to eq('GRTGAZ IMMEUBLE BORA 6 RUE RAOUL NORDLING 92270 BOIS COLOMBES') } - it { expect(subject[:etablissement_numero_voie]).to eq('6') } - it { expect(subject[:etablissement_type_voie]).to eq('RUE') } - it { expect(subject[:etablissement_nom_voie]).to eq('RAOUL NORDLING') } - it { expect(subject[:etablissement_complement_adresse]).to eq('IMMEUBLE BORA') } - it { expect(subject[:etablissement_code_postal]).to eq('92270') } - it { expect(subject[:etablissement_localite]).to eq('BOIS COLOMBES') } - it { expect(subject[:etablissement_code_insee_localite]).to eq('92009') } - it { expect(subject[:entreprise_siren]).to eq('440117620') } - it { expect(subject[:entreprise_capital_social]).to eq('537100000') } - it { expect(subject[:entreprise_numero_tva_intracommunautaire]).to eq('FR27440117620') } - it { expect(subject[:entreprise_forme_juridique]).to eq("SA à conseil d'administration (s.a.i.)") } - it { expect(subject[:entreprise_forme_juridique_code]).to eq('5599') } - it { expect(subject[:entreprise_nom_commercial]).to eq('GRTGAZ') } - it { expect(subject[:entreprise_raison_sociale]).to eq('GRTGAZ') } - it { expect(subject[:entreprise_siret_siege_social]).to eq('44011762001530') } - it { expect(subject[:entreprise_code_effectif_entreprise]).to eq('51') } - it { expect(subject[:entreprise_date_creation]).to eq('1990-04-24T00:00:00+00:00') } - it { expect(subject[:entreprise_nom]).to be_nil } - it { expect(subject[:entreprise_prenom]).to be_nil } - - it { expect(subject.count).to eq(EntrepriseSerializer.new(Entreprise.new).as_json.count + EtablissementSerializer.new(Etablissement.new).as_json.count) } - end - context 'when dossier is followed' do let(:procedure) { create(:procedure, :with_type_de_champ, :with_type_de_champ_private) } let(:gestionnaire) { create(:gestionnaire) } @@ -236,112 +182,6 @@ describe Dossier do it { expect(subject).to eq [gestionnaire] } it { expect(subject).not_to include(non_following_gestionnaire) } end - - describe '#export_headers' do - subject { dossier.export_headers } - - it { expect(subject).to include(dossier.champs.first.libelle.parameterize.underscore.to_sym) } - it { expect(subject).to include(:individual_gender) } - it { expect(subject).to include(:individual_nom) } - it { expect(subject).to include(:individual_prenom) } - it { expect(subject).to include(:individual_birthdate) } - it do - expect(subject.count).to eq(DossierTableExportSerializer.new(dossier).attributes.count + - dossier.procedure.types_de_champ.count + - dossier.procedure.types_de_champ_private.count + - dossier.send(:export_etablissement_data).count) - end - end - - describe '#sorted_values' do - subject { dossier.send(:sorted_values) } - - it { expect(subject[0]).to be_a_kind_of(Integer) } - it { expect(subject[1]).to be_a_kind_of(Time) } - it { expect(subject[2]).to be_a_kind_of(Time) } - it { expect(subject[3]).to be_in([true, false]) } - it { expect(subject[4]).to eq(dossier.user.email) } - it { expect(subject[5]).to eq(Dossier.states.fetch(:brouillon)) } - it { expect(subject[6]).to eq(date1) } - it { expect(subject[7]).to eq(date2) } - it { expect(subject[8]).to eq(date3) } - it { expect(subject[9]).to be_a_kind_of(String) } - it { expect(subject[10]).to be_a_kind_of(String) } - it { expect(subject[11]).to be_nil } - it { expect(subject[12]).to be_nil } - it { expect(subject[13]).to be_nil } - it { expect(subject[14]).to be_nil } - it { expect(subject[15]).to be_nil } - it do - expect(subject.count).to eq(DossierTableExportSerializer.new(dossier).attributes.count + - dossier.procedure.types_de_champ.count + - dossier.procedure.types_de_champ_private.count + - dossier.send(:export_etablissement_data).count) - end - - context 'dossier for individual' do - let(:dossier_with_individual) { create(:dossier, :for_individual, user: user, procedure: procedure) } - - subject { dossier_with_individual.send(:sorted_values) } - - it { expect(subject[11]).to eq(dossier_with_individual.individual.gender) } - it { expect(subject[12]).to eq(dossier_with_individual.individual.prenom) } - it { expect(subject[13]).to eq(dossier_with_individual.individual.nom) } - it { expect(subject[14]).to eq(dossier_with_individual.individual.birthdate) } - end - end - - describe "#full_data_string" do - let(:expected_string) { - [ - dossier.id.to_s, - dossier.created_at, - dossier.updated_at, - "false", - dossier.user.email, - Dossier.states.fetch(:brouillon), - dossier.en_construction_at, - dossier.en_instruction_at, - dossier.processed_at, - "Motivation", - gestionnaire.email, - nil, - nil, - nil, - nil, - nil, - nil, - "44011762001530", - "true", - "4950Z", - "Transports par conduites", - "GRTGAZ IMMEUBLE BORA 6 RUE RAOUL NORDLING 92270 BOIS COLOMBES", - "6", - "RUE", - "RAOUL NORDLING", - "IMMEUBLE BORA", - "92270", - "BOIS COLOMBES", - "92009", - "440117620", - "537100000", - "FR27440117620", - "SA à conseil d'administration (s.a.i.)", - "5599", - "GRTGAZ", - "GRTGAZ", - "44011762001530", - "51", - "1990-04-24T00:00:00+00:00", - nil, - nil - ] - } - - subject { dossier } - - it { expect(dossier.export_values).to eq(expected_string) } - end end describe '#reset!' do diff --git a/spec/models/procedure_spec.rb b/spec/models/procedure_spec.rb index cb9f92ad8..196fc010d 100644 --- a/spec/models/procedure_spec.rb +++ b/spec/models/procedure_spec.rb @@ -515,67 +515,6 @@ describe Procedure do it { is_expected.to eq 2 } end - describe '#generate_export' do - let(:procedure) { create :procedure } - subject { procedure.generate_export } - - shared_examples "export is empty" do - it { expect(subject[:data]).to eq([[]]) } - it { expect(subject[:headers]).to eq([]) } - end - - context 'when there are no dossiers' do - it_behaves_like "export is empty" - end - - context 'when there are some dossiers' do - let!(:dossier){ create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) } - let!(:dossier2){ create(:dossier, procedure: procedure, state: Dossier.states.fetch(:accepte)) } - - it { expect(subject[:data].size).to eq(2) } - it { expect(subject[:headers]).to eq(dossier.export_headers) } - - context 'with ordered champs' do - let(:tc_2) { create(:type_de_champ, order_place: 2) } - let(:tc_1) { create(:type_de_champ, order_place: 1) } - let(:tcp_2) { create(:type_de_champ, :private, order_place: 2) } - let(:tcp_1) { create(:type_de_champ, :private, order_place: 1) } - - before do - procedure.types_de_champ << tc_2 << tc_1 - procedure.types_de_champ_private << tcp_2 << tcp_1 - - dossier.build_default_champs - dossier.champs.find_by(type_de_champ: tc_1).update(value: "value 1") - dossier.champs.find_by(type_de_champ: tc_2).update(value: "value 2") - dossier.champs_private.find_by(type_de_champ: tcp_1).update(value: "private value 1") - dossier.champs_private.find_by(type_de_champ: tcp_2).update(value: "private value 2") - - dossier2.build_default_champs - dossier2.champs.find_by(type_de_champ: tc_1).update(value: "value 1") - dossier2.champs.find_by(type_de_champ: tc_2).update(value: "value 2") - dossier2.champs_private.find_by(type_de_champ: tcp_1).update(value: "private value 1") - dossier2.champs_private.find_by(type_de_champ: tcp_2).update(value: "private value 2") - end - - it { expect(subject[:headers].index(tc_1.libelle.parameterize.underscore.to_sym)).to be < subject[:headers].index(tc_2.libelle.parameterize.underscore.to_sym) } - it { expect(subject[:headers].index(tcp_1.libelle.parameterize.underscore.to_sym)).to be < subject[:headers].index(tcp_2.libelle.parameterize.underscore.to_sym) } - - it { expect(subject[:data][0].index("value 1")).to be < subject[:data].first.index("value 2") } - it { expect(subject[:data][0].index("private value 1")).to be < subject[:data].first.index("private value 2") } - - it { expect(subject[:data][1].index("value 1")).to be < subject[:data].first.index("value 2") } - it { expect(subject[:data][1].index("private value 1")).to be < subject[:data].first.index("private value 2") } - end - end - - context 'when there is a brouillon dossier' do - let!(:dossier_not_exportable){ create(:dossier, procedure: procedure, state: Dossier.states.fetch(:brouillon)) } - - it_behaves_like "export is empty" - end - end - describe '#default_path' do let(:procedure){ create(:procedure, libelle: 'A long libelle with àccênts, blabla coucou hello un deux trois voila') } @@ -632,18 +571,18 @@ describe Procedure do before { Timecop.freeze(Time.zone.local(2018, 1, 2, 23, 11, 14)) } after { Timecop.return } - subject { procedure.export_filename } + subject { procedure.export_filename(:csv) } context "with a path" do let(:procedure) { create(:procedure, :published) } - it { is_expected.to eq("dossiers_#{procedure.path}_2018-01-02_23-11") } + it { is_expected.to eq("dossiers_#{procedure.path}_2018-01-02_23-11.csv") } end context "without a path" do let(:procedure) { create(:procedure) } - it { is_expected.to eq("dossiers_procedure-#{procedure.id}_2018-01-02_23-11") } + it { is_expected.to eq("dossiers_procedure-#{procedure.id}_2018-01-02_23-11.csv") } end end diff --git a/spec/serializers/dossier_table_export_serializer_spec.rb b/spec/serializers/dossier_table_export_serializer_spec.rb deleted file mode 100644 index a1a9ea2e2..000000000 --- a/spec/serializers/dossier_table_export_serializer_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'spec_helper' - -describe DossierTableExportSerializer do - describe '#attributes' do - subject { DossierTableExportSerializer.new(dossier).serializable_hash } - - context 'when the dossier is en_construction' do - let(:dossier) { create(:dossier, :en_construction) } - - it { is_expected.to include(initiated_at: dossier.en_construction_at) } - it { is_expected.to include(state: 'initiated') } - end - - context 'when the dossier is en instruction' do - let(:dossier) { create(:dossier, :en_instruction) } - - it { is_expected.to include(received_at: dossier.en_instruction_at) } - it { is_expected.to include(state: 'received') } - end - - context 'when the dossier is accepte' do - let(:dossier) { create(:dossier, state: Dossier.states.fetch(:accepte)) } - - it { is_expected.to include(state: 'closed') } - end - - context 'when the dossier is refuse' do - let(:dossier) { create(:dossier, state: Dossier.states.fetch(:refuse)) } - - it { is_expected.to include(state: 'refused') } - end - - context 'when the dossier is sans_suite' do - let(:dossier) { create(:dossier, state: Dossier.states.fetch(:sans_suite)) } - - it { is_expected.to include(state: 'without_continuation') } - end - end - - describe '#emails_instructeurs' do - let(:gestionnaire){ create(:gestionnaire) } - let(:gestionnaire2) { create :gestionnaire } - let(:dossier) { create(:dossier) } - - subject { DossierTableExportSerializer.new(dossier).emails_instructeurs } - - context 'when there is no instructeurs' do - it { is_expected.to eq('') } - end - - context 'when there one instructeur' do - before { gestionnaire.followed_dossiers << dossier } - - it { is_expected.to eq(gestionnaire.email) } - end - - context 'when there is 2 followers' do - before do - gestionnaire.followed_dossiers << dossier - gestionnaire2.followed_dossiers << dossier - end - - it { is_expected.to eq "#{gestionnaire.email} #{gestionnaire2.email}" } - end - end -end diff --git a/spec/services/procedure_export_service_spec.rb b/spec/services/procedure_export_service_spec.rb new file mode 100644 index 000000000..168a387b6 --- /dev/null +++ b/spec/services/procedure_export_service_spec.rb @@ -0,0 +1,261 @@ +require 'spec_helper' + +describe ProcedureExportService do + describe 'to_data' do + let(:procedure) { create(:procedure, :published, :with_all_champs) } + let(:table) { :dossiers } + subject { ProcedureExportService.new(procedure).to_data(table) } + + let(:headers) { subject[:headers] } + let(:data) { subject[:data] } + + context 'dossiers' do + it 'should have headers' do + expect(headers).to eq([ + :id, + :created_at, + :updated_at, + :archived, + :email, + :state, + :initiated_at, + :received_at, + :processed_at, + :motivation, + :emails_instructeurs, + :individual_gender, + :individual_prenom, + :individual_nom, + :individual_birthdate, + :text, + :textarea, + :date, + :datetime, + :number, + :decimal_number, + :integer_number, + :checkbox, + :civilite, + :email, + :phone, + :address, + :yes_no, + :simple_drop_down_list, + :multiple_drop_down_list, + :linked_drop_down_list, + :pays, + :regions, + :departements, + :engagement, + :header_section, + :explication, + :dossier_link, + :piece_justificative, + :siret, + :carte, + :etablissement_siret, + :etablissement_siege_social, + :etablissement_naf, + :etablissement_libelle_naf, + :etablissement_adresse, + :etablissement_numero_voie, + :etablissement_type_voie, + :etablissement_nom_voie, + :etablissement_complement_adresse, + :etablissement_code_postal, + :etablissement_localite, + :etablissement_code_insee_localite, + :entreprise_siren, + :entreprise_capital_social, + :entreprise_numero_tva_intracommunautaire, + :entreprise_forme_juridique, + :entreprise_forme_juridique_code, + :entreprise_nom_commercial, + :entreprise_raison_sociale, + :entreprise_siret_siege_social, + :entreprise_code_effectif_entreprise, + :entreprise_date_creation, + :entreprise_nom, + :entreprise_prenom + ]) + end + + it 'should have empty values' do + expect(data).to eq([[]]) + end + + context 'with dossier' do + let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :for_individual, procedure: procedure) } + + let(:dossier_data) { + [ + dossier.id.to_s, + dossier.created_at.to_s, + dossier.updated_at.to_s, + "false", + dossier.user.email, + "received", + dossier.en_construction_at.to_s, + dossier.en_instruction_at.to_s, + nil, + nil, + nil + ] + individual_data + } + + let(:individual_data) { + [ + "M.", + "Xavier", + "Julien", + "1991-11-01" + ] + } + + let(:champs_data) { + dossier.champs.map(&:for_export) + } + + let(:etablissement_data) { + Array.new(24) + } + + it 'should have values' do + expect(data.first[0..14]).to eq(dossier_data) + expect(data.first[15..40]).to eq(champs_data) + expect(data.first[41..64]).to eq(etablissement_data) + + expect(data).to eq([ + dossier_data + champs_data + etablissement_data + ]) + end + + context 'and etablissement' do + let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, :with_entreprise, procedure: procedure) } + + let(:etablissement_data) { + [ + dossier.etablissement.siret, + dossier.etablissement.siege_social.to_s, + dossier.etablissement.naf, + dossier.etablissement.libelle_naf, + dossier.etablissement.adresse&.chomp&.gsub("\r\n", ' ')&.delete("\r"), + dossier.etablissement.numero_voie, + dossier.etablissement.type_voie, + dossier.etablissement.nom_voie, + dossier.etablissement.complement_adresse, + dossier.etablissement.code_postal, + dossier.etablissement.localite, + dossier.etablissement.code_insee_localite, + dossier.etablissement.entreprise_siren, + dossier.etablissement.entreprise_capital_social.to_s, + dossier.etablissement.entreprise_numero_tva_intracommunautaire, + dossier.etablissement.entreprise_forme_juridique, + dossier.etablissement.entreprise_forme_juridique_code, + dossier.etablissement.entreprise_nom_commercial, + dossier.etablissement.entreprise_raison_sociale, + dossier.etablissement.entreprise_siret_siege_social, + dossier.etablissement.entreprise_code_effectif_entreprise, + dossier.etablissement.entreprise_date_creation.to_datetime.to_s, + dossier.etablissement.entreprise_nom, + dossier.etablissement.entreprise_prenom + ] + } + + let(:individual_data) { + Array.new(4) + } + + it 'should have values' do + expect(data.first[0..14]).to eq(dossier_data) + expect(data.first[15..40]).to eq(champs_data) + expect(data.first[41..64]).to eq(etablissement_data) + + expect(data).to eq([ + dossier_data + champs_data + etablissement_data + ]) + end + end + end + end + + context 'etablissements' do + let(:table) { :etablissements } + + it 'should have headers' do + expect(headers).to eq([ + :dossier_id, + :libelle, + :etablissement_siret, + :etablissement_siege_social, + :etablissement_naf, + :etablissement_libelle_naf, + :etablissement_adresse, + :etablissement_numero_voie, + :etablissement_type_voie, + :etablissement_nom_voie, + :etablissement_complement_adresse, + :etablissement_code_postal, + :etablissement_localite, + :etablissement_code_insee_localite, + :entreprise_siren, + :entreprise_capital_social, + :entreprise_numero_tva_intracommunautaire, + :entreprise_forme_juridique, + :entreprise_forme_juridique_code, + :entreprise_nom_commercial, + :entreprise_raison_sociale, + :entreprise_siret_siege_social, + :entreprise_code_effectif_entreprise, + :entreprise_date_creation, + :entreprise_nom, + :entreprise_prenom + ]) + end + + it 'should have empty values' do + expect(data).to eq([[]]) + end + + context 'with dossier containing champ siret' do + let!(:dossier) { create(:dossier, :en_instruction, :with_all_champs, procedure: procedure) } + let(:etablissement) { dossier.champs.find { |champ| champ.type_champ == 'siret' }.etablissement } + + let(:etablissement_data) { + [ + dossier.id, + 'siret', + etablissement.siret, + etablissement.siege_social.to_s, + etablissement.naf, + etablissement.libelle_naf, + etablissement.adresse&.chomp&.gsub("\r\n", ' ')&.delete("\r"), + etablissement.numero_voie, + etablissement.type_voie, + etablissement.nom_voie, + etablissement.complement_adresse, + etablissement.code_postal, + etablissement.localite, + etablissement.code_insee_localite, + etablissement.entreprise_siren, + etablissement.entreprise_capital_social.to_s, + etablissement.entreprise_numero_tva_intracommunautaire, + etablissement.entreprise_forme_juridique, + etablissement.entreprise_forme_juridique_code, + etablissement.entreprise_nom_commercial, + etablissement.entreprise_raison_sociale, + etablissement.entreprise_siret_siege_social, + etablissement.entreprise_code_effectif_entreprise, + etablissement.entreprise_date_creation.to_datetime.to_s, + etablissement.entreprise_nom, + etablissement.entreprise_prenom + ] + } + + it 'should have values' do + expect(data.first).to eq(etablissement_data) + end + end + end + end +end