feat(groupe instructeur): allow import of many groups when procedure is not routed yet

This commit is contained in:
Eric Leroy-Terquem 2023-03-02 18:07:05 +01:00
parent 126819939b
commit 9f0f5b0dc5
5 changed files with 178 additions and 189 deletions

View file

@ -204,35 +204,24 @@ module Administrateurs
file = csv_file.read file = csv_file.read
base_encoding = CharlockHolmes::EncodingDetector.detect(file) base_encoding = CharlockHolmes::EncodingDetector.detect(file)
if params[:group_csv_file] csv_content = ACSV::CSV.new_for_ruby3(file.encode("UTF-8", base_encoding[:encoding], invalid: :replace, replace: ""), headers: true, header_converters: :downcase).map(&:to_h)
groupes_emails = ACSV::CSV.new_for_ruby3(file.encode("UTF-8", base_encoding[:encoding], invalid: :replace, replace: ""), headers: true, header_converters: :downcase)
.map { |r| r.to_h.slice('groupe', 'email') }
groupes_emails_has_keys = groupes_emails.first.has_key?("groupe") && groupes_emails.first.has_key?("email") if csv_content.first.has_key?("groupe") && csv_content.first.has_key?("email")
groupes_emails = csv_content.map { |r| r.to_h.slice('groupe', 'email') }
if groupes_emails_has_keys.blank?
flash[:alert] = "Importation impossible, veuillez importer un csv #{view_context.link_to('suivant ce modèle', "/csv/#{I18n.locale}/import-groupe-test.csv")}"
else
added_instructeurs_by_group, invalid_emails = InstructeursImportService.import_groupes(procedure, groupes_emails) added_instructeurs_by_group, invalid_emails = InstructeursImportService.import_groupes(procedure, groupes_emails)
added_instructeurs_by_group.each do |groupe, added_instructeurs| added_instructeurs_by_group.each do |groupe, added_instructeurs|
GroupeInstructeurMailer GroupeInstructeurMailer
.notify_added_instructeurs(groupe, added_instructeurs, current_administrateur.email) .notify_added_instructeurs(groupe, added_instructeurs, current_administrateur.email)
.deliver_later .deliver_later
end
flash_message_for_import(invalid_emails) flash_message_for_import(invalid_emails)
end end
elsif params[:instructeurs_csv_file] elsif csv_content.first.has_key?("email") && !csv_content.map(&:to_h).first.keys.many? && procedure.groupe_instructeurs.one?
instructors_emails = ACSV::CSV.new_for_ruby3(file.encode("UTF-8", base_encoding[:encoding], invalid: :replace, replace: ""), headers: true, header_converters: :downcase) instructors_emails = csv_content.map(&:to_h)
.map(&:to_h)
instructors_emails_has_key = instructors_emails.first.has_key?("email") && !instructors_emails.first.keys.many?
if instructors_emails_has_key.blank?
flash[:alert] = "Importation impossible, veuillez importer un csv #{view_context.link_to('suivant ce modèle', "/csv/import-instructeurs-test.csv")}"
else
added_instructeurs, invalid_emails = InstructeursImportService.import_instructeurs(procedure, instructors_emails) added_instructeurs, invalid_emails = InstructeursImportService.import_instructeurs(procedure, instructors_emails)
GroupeInstructeurMailer GroupeInstructeurMailer
@ -240,7 +229,8 @@ module Administrateurs
.deliver_later .deliver_later
flash_message_for_import(invalid_emails) flash_message_for_import(invalid_emails)
end else
flash_message_for_invalid_csv
end end
redirect_to admin_procedure_groupe_instructeurs_path(procedure) redirect_to admin_procedure_groupe_instructeurs_path(procedure)
end end
@ -316,7 +306,7 @@ module Administrateurs
end end
def csv_file def csv_file
params[:group_csv_file] || params[:instructeurs_csv_file] params[:csv_file]
end end
def marcel_content_type def marcel_content_type
@ -338,5 +328,9 @@ module Administrateurs
flash[:alert] = "Import terminé. Cependant les emails suivants ne sont pas pris en compte: #{result.join(', ')}" flash[:alert] = "Import terminé. Cependant les emails suivants ne sont pas pris en compte: #{result.join(', ')}"
end end
end end
def flash_message_for_invalid_csv
flash[:alert] = "Importation impossible, veuillez importer un csv suivant #{view_context.link_to('ce modèle', "/csv/import-instructeurs-test.csv")} pour une procédure sans routage ou #{view_context.link_to('celui-ci', "/csv/#{I18n.locale}/import-groupe-test.csv")} pour une procédure routée"
end
end end
end end

View file

@ -28,13 +28,10 @@
= form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do = form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do
= label_tag t('.csv_import.title') = label_tag t('.csv_import.title')
%p.notice %p.notice
= procedure.routing_enabled? ? t('.csv_import.routing_enabled.notice_1') : t('.csv_import.routing_disabled.notice_1') = t('.csv_import.notice_1_html')
%p.notice %p.notice
= t('.csv_import.notice_2', csv_max_size: number_to_human_size(csv_max_size)) = t('.csv_import.notice_2', csv_max_size: number_to_human_size(csv_max_size))
- sample_file_path = procedure.routing_enabled? ? "/csv/#{I18n.locale}/import-groupe-test.csv" : "/csv/import-instructeurs-test.csv" = file_field_tag :csv_file, required: true, accept: 'text/csv', size: "1"
%p.mt-2.mb-2= link_to t('.csv_import.download_exemple'), sample_file_path
- csv_params = procedure.routing_enabled? ? :group_csv_file : :instructeurs_csv_file
= file_field_tag csv_params, required: true, accept: 'text/csv', size: "1"
= submit_tag t('.csv_import.import_file'), class: 'button primary send', data: { disable_with: "Envoi...", confirm: t('.csv_import.import_file_alert') } = submit_tag t('.csv_import.import_file'), class: 'button primary send', data: { disable_with: "Envoi...", confirm: t('.csv_import.import_file_alert') }
- else - else
%p.mt-4.form.font-weight-bold.mb-2.text-lg %p.mt-4.form.font-weight-bold.mb-2.text-lg

View file

@ -41,12 +41,8 @@ en:
notice: This group will be a choice from the list "%{routing_criteria_name}" notice: This group will be a choice from the list "%{routing_criteria_name}"
csv_import: csv_import:
title: CSV Import title: CSV Import
routing_enabled: notice_1_html: The csv file must have 1 column with instructors emails. For routed procedures, the csv file must have 2 columns (Group, Email) and be separated by commas. The import does not overwrite existing groups and instructors.
notice_1: The csv file must have 2 columns (Group, Email) and be separated by commas. The import does not overwrite existing groups and instructors.
routing_disabled:
notice_1: The csv file must have 1 column with instructors emails.
notice_2: The size of the file must be less than %{csv_max_size}. notice_2: The size of the file must be less than %{csv_max_size}.
download_exemple: Download sample CSV file
import_file: Import file import_file: Import file
import_file_procedure_not_published: The import of instructors by CSV file is available once the process has been published import_file_procedure_not_published: The import of instructors by CSV file is available once the process has been published
import_file_alert: Instructors added to the procedure will receive an email. Are you sure you want to continue ?" import_file_alert: Instructors added to the procedure will receive an email. Are you sure you want to continue ?"

View file

@ -47,12 +47,10 @@ fr:
notice: Ce groupe sera un choix de la liste "%{routing_criteria_name}" notice: Ce groupe sera un choix de la liste "%{routing_criteria_name}"
csv_import: csv_import:
title: Importer par CSV title: Importer par CSV
routing_enabled: notice_1_html: |
notice_1: Le fichier csv doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules. Pour une procédure standard, le fichier csv doit comporter 1 seule colonne (Email) avec une adresse email d'instructeur par ligne (<a href=\"/csv/import-instructeurs-test.csv\">exemple de fichier</a>).
routing_disabled: Pour une procédure routée, le fichier doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules (<a href=\"/csv/fr/import-groupe-test.csv\">exemple de fichier</a>).
notice_1: Le fichier csv doit comporter 1 seule colonne (Email) avec une adresse email d'instructeur par ligne. notice_2: Limport nécrase pas les instructeurs existants. Il permet uniquement d'en ajouter. Pour supprimer un instructeur, cliquez sur le bouton « retirer ». Le poids du fichier doit être inférieur %{csv_max_size}.
notice_2: Limport nécrase pas les groupes et les instructeurs existants. La modification du fichier csv ne sopère que pour lajout de nouveaux instructeurs. La suppression dun instructeur sopère manuellement en cliquant sur le bouton « retirer ». Le poids du fichier doit être inférieur à %{csv_max_size}.
download_exemple: Télécharger lexemple de fichier CSV
import_file: Importer le fichier import_file: Importer le fichier
import_file_procedure_not_published: Limport dinstructeurs par fichier CSV est disponible une fois la démarche publiée import_file_procedure_not_published: Limport dinstructeurs par fichier CSV est disponible une fois la démarche publiée
import_file_alert: Tous les instructeurs ajoutés à la procédure vont être notifiés par email. Voulez-vous continuer ? import_file_alert: Tous les instructeurs ajoutés à la procédure vont être notifiés par email. Voulez-vous continuer ?

View file

@ -389,11 +389,14 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
end end
end end
describe '#add_groupe_instructeurs_via_csv_file' do describe '#import' do
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
subject do subject do
post :import, params: { procedure_id: procedure.id, group_csv_file: csv_file } post :import, params: { procedure_id: procedure.id, csv_file: csv_file }
end end
context 'routed procedures' do
context 'when the csv file is less than 1 mo and content type text/csv' do context 'when the csv file is less than 1 mo and content type text/csv' do
let(:csv_file) { fixture_file_upload('spec/fixtures/files/groupe-instructeur.csv', 'text/csv') } let(:csv_file) { fixture_file_upload('spec/fixtures/files/groupe-instructeur.csv', 'text/csv') }
@ -413,7 +416,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
it { expect { subject }.not_to raise_error } it { expect { subject }.not_to raise_error }
it { expect(response.status).to eq(302) } it { expect(response.status).to eq(302) }
it { expect(flash.alert).to be_present } it { expect(flash.alert).to be_present }
it { expect(flash.alert).to eq("Importation impossible, veuillez importer un csv <a href=\"/csv/#{I18n.locale}/import-groupe-test.csv\">suivant ce modèle</a>") } it { expect(flash.alert).to eq("Importation impossible, veuillez importer un csv suivant <a href=\"/csv/import-instructeurs-test.csv\">ce modèle</a> pour une procédure classique ou <a href=\"/csv/fr/import-groupe-test.csv\">celui-ci</a> pour une procédure routée") }
end end
context 'when the file content type is application/vnd.ms-excel' do context 'when the file content type is application/vnd.ms-excel' do
@ -434,7 +437,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
subject subject
end end
it { expect(procedure.groupe_instructeurs.pluck(:label)).to match_array(["Auvergne-Rhône-Alpes", "Vendée", "défaut"]) } it { expect(procedure.groupe_instructeurs.pluck(:label)).to match_array(["Auvergne-Rhône-Alpes", "Vendée", "défaut", "groupe instructeur 2"]) }
it { expect(flash.notice).to be_present } it { expect(flash.notice).to be_present }
it { expect(flash.notice).to eq("La liste des instructeurs a été importée avec succès") } it { expect(flash.notice).to eq("La liste des instructeurs a été importée avec succès") }
it { expect(GroupeInstructeurMailer).to have_received(:notify_added_instructeurs).twice } it { expect(GroupeInstructeurMailer).to have_received(:notify_added_instructeurs).twice }
@ -467,7 +470,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
before { subject } before { subject }
it { expect(flash.alert).to be_present } it { expect(flash.alert).to be_present }
it { expect(flash.alert).to eq("Importation impossible, veuillez importer un csv <a href=\"/csv/#{I18n.locale}/import-groupe-test.csv\">suivant ce modèle</a>") } it { expect(flash.alert).to eq("Importation impossible, veuillez importer un csv suivant <a href=\"/csv/import-instructeurs-test.csv\">ce modèle</a> pour une procédure classique ou <a href=\"/csv/fr/import-groupe-test.csv\">celui-ci</a> pour une procédure routée") }
end end
context 'when procedure is closed' do context 'when procedure is closed' do
@ -481,11 +484,11 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
end end
end end
describe '#add_instructeurs_via_csv_file' do context 'unrouted procedures' do
let(:procedure_non_routee) { create(:procedure, :published, :for_individual, administrateurs: [admin]) } let(:procedure_non_routee) { create(:procedure, :published, :for_individual, administrateurs: [admin]) }
subject do subject do
post :import, params: { procedure_id: procedure_non_routee.id, instructeurs_csv_file: csv_file } post :import, params: { procedure_id: procedure_non_routee.id, csv_file: csv_file }
end end
context 'when the csv file is less than 1 mo and content type text/csv' do context 'when the csv file is less than 1 mo and content type text/csv' do
@ -517,7 +520,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
it { expect(response.status).to eq(302) } it { expect(response.status).to eq(302) }
it { expect(flash.alert).to be_present } it { expect(flash.alert).to be_present }
it { expect(flash.alert).to eq("Importation impossible, veuillez importer un csv <a href=\"/csv/import-instructeurs-test.csv\">suivant ce modèle</a>") } it { expect(flash.alert).to eq("Import terminé. Cependant les emails suivants ne sont pas pris en compte: kara") }
end end
context 'when the file content type is application/vnd.ms-excel' do context 'when the file content type is application/vnd.ms-excel' do
@ -550,6 +553,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
it { expect(flash.alert).to eq("Importation impossible : veuillez importer un fichier CSV") } it { expect(flash.alert).to eq("Importation impossible : veuillez importer un fichier CSV") }
end end
end end
end
describe '#export_groupe_instructeurs' do describe '#export_groupe_instructeurs' do
let(:procedure) { create(:procedure, :published) } let(:procedure) { create(:procedure, :published) }