diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index a70b9f6a6..023f53dfe 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -635,7 +635,7 @@ Application.Controllers.controller('ImportMembersController', ['$scope', '$state if (content.id) { $state.go('app.admin.members_import_result', { id: content.id }); } else { - growl.error(content); + growl.error(JSON.stringify(content)); } } @@ -647,17 +647,40 @@ Application.Controllers.controller('ImportMembersController', ['$scope', '$state /** * Controller used in the member's import results page (admin view) */ -Application.Controllers.controller('ImportMembersResultController', ['$scope', '$state', 'importItem', - function ($scope, $state, importItem) { +Application.Controllers.controller('ImportMembersResultController', ['$scope', '$state', 'Import', 'importItem', + function ($scope, $state, Import, importItem) { /* PUBLIC SCOPE */ // Current import as saved in database $scope.import = importItem; + // Current import results + $scope.results = null; + /** * Changes the admin's view to the members import page */ $scope.cancel = function () { $state.go('app.admin.members_import'); }; + + /* PRIVATE SCOPE */ + + /** + * Kind of constructor: these actions will be realized first when the controller is loaded + */ + const initialize = function () { + $scope.results = JSON.parse($scope.import.results); + if (!$scope.results) { + setTimeout(function() { + Import.get({ id: $scope.import.id }, function(data) { + $scope.import = data; + initialize(); + }); + }, 5000); + } + }; + + // !!! MUST BE CALLED AT THE END of the controller + initialize(); } ]); diff --git a/app/assets/templates/admin/members/import_result.html b/app/assets/templates/admin/members/import_result.html index 7802a834e..447444d06 100644 --- a/app/assets/templates/admin/members/import_result.html +++ b/app/assets/templates/admin/members/import_result.html @@ -20,9 +20,39 @@
-
+
- {{import}} +

{{ 'members_import_result.import_details' | translate:{DATE:(import.created_at | amDateFormat:'L'), USER:import.user.full_name, ID:import.id} }}

+ +

{{ 'members_import_result.pending' }}

+
+

{{ 'members_import_result.results' }}

+ +
+
+ + + + + + + +
{{key}}
{{value}}
+
+
+ + {{ 'members_import_result.status_' + resultRow.status | translate:{ID:resultRow.user} }} + + + + + +
+
+ {{ 'members_import_result.error_details' }}{{resultRow}} +
+
+
diff --git a/app/models/import.rb b/app/models/import.rb index b5034d057..eab704afc 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -10,10 +10,14 @@ class Import < ActiveRecord::Base belongs_to :user validates :attachment, file_size: { maximum: Rails.application.secrets.max_import_size&.to_i || 5.megabytes.to_i } - validates :attachment, file_mime_type: { content_type: ['text/csv'] } + validates :attachment, file_mime_type: { content_type: %w[text/csv text/comma-separated-values application/vnd.ms-excel] } after_commit :proceed_import, on: [:create] + def results_hash + YAML.safe_load(results, [Symbol]) if results + end + private def proceed_import diff --git a/app/services/members/import_service.rb b/app/services/members/import_service.rb index 7f3086b2e..970e64e56 100644 --- a/app/services/members/import_service.rb +++ b/app/services/members/import_service.rb @@ -5,40 +5,75 @@ class Members::ImportService class << self def import(import) require 'csv' + log = [] CSV.foreach(import.attachment.url, headers: true, col_sep: ';') do |row| - # try to find member based on import.update_field - user = User.find_by(import.update_field.to_sym => import.update_field) - if user - service = Members::MembersService.new(user) - service.update(row_to_params(row)) - else - user = User.new(row) - service = Members::MembersService.new(user) - service.create(import.user, row_to_params(row)) + begin + log << { row: row.to_hash } + + # try to find member based on import.update_field + user = User.find_by(import.update_field.to_sym => row[import.update_field]) + params = row_to_params(row, user) + if user + service = Members::MembersService.new(user) + res = service.update(params) + log << { user: user.id, status: 'update', result: res } + else + user = User.new(params) + service = Members::MembersService.new(user) + res = service.create(import.user, params) + log << { user: nil, status: 'create', result: res } + end + log << user.errors.to_hash unless user.errors.to_hash.empty? + rescue StandardError => e + log << e.to_s + puts e + puts e.backtrace end end + log end private - def row_to_params(row) - { + def row_to_params(row, user) + res = { + id: row['id'], username: row['username'], email: row['email'], password: row['password'], password_confirmation: row['password'], - is_allow_contact: row['allow_contact'], - is_allow_newsletter: row['allow_newsletter'], - group_id: Group.friendly.find(row['group'])&.id, - tag_ids: Tag.where(id: row['tags'].split(',')).map(&:id), - profile_attributes: profile_attributes(row), - invoicing_profile_attributes: invoicing_profile_attributes(row), - statistic_profile_attributes: statistic_profile_attributes(row) + is_allow_contact: row['allow_contact'] == 'yes', + is_allow_newsletter: row['allow_newsletter'] == 'yes', + group_id: group_id(row), + tag_ids: tag_ids(row) } + + profile_attributes = profile(row, user) + res[:profile_attributes] = profile_attributes if profile_attributes + + invoicing_profile_attributes = invoicing_profile(row, user) + res[:invoicing_profile_attributes] = invoicing_profile_attributes if invoicing_profile_attributes + + statistic_profile_attributes = statistic_profile(row, user) + res[:statistic_profile_attributes] = statistic_profile_attributes if statistic_profile_attributes + + res end - def profile_attributes(row) - { + def group_id(row) + return unless row['group'] + + Group.friendly.find(row['group'])&.id + end + + def tag_ids(row) + return unless row['tags'] + + Tag.where(id: row['tags'].split(',')).map(&:id) + end + + def profile(row, user) + res = { first_name: row['first_name'], last_name: row['last_name'], phone: row['phone'], @@ -61,28 +96,79 @@ class Members::ImportService lastfm: row['lastfm'], flickr: row['flickr'] } + + res[:id] = user.profile.id if user&.profile + + res end - def invoicing_profile_attributes(row) - { - address_attributes: { - address: row['address'] - }, - organization_attributes: { - name: row['organization_name'], - address_attributes: { - address: row['organization_address'] - } - } - } + def invoicing_profile(row, user) + res = {} + + res[:id] = user.invoicing_profile.id if user&.invoicing_profile + + address_attributes = address(row, user) + res[:address_attributes] = address_attributes if address_attributes + + organization_attributes = organization(row, user) + res[:organization_attributes] = organization_attributes if organization_attributes + + res end - def statistic_profile_attributes(row) - { + def statistic_profile(row, user) + res = { gender: row['gender'] == 'male', - birthday: row['birthdate'], - training_ids: Training.where(id: row['trainings'].split(',')).map(&:id) + birthday: row['birthdate'] } + + res[:id] = user.statistic_profile.id if user&.statistic_profile + + training_ids = training_ids(row) + res[:training_ids] = training_ids if training_ids + + res + end + + def address(row, user) + return unless row['address'] + + res = { address: row['address'] } + + res[:id] = user.invoicing_profile.address.id if user&.invoicing_profile&.address + + res + end + + def organization(row, user) + return unless row['organization_name'] + + res = { + name: row['organization_name'] + } + + res[:id] = user.invoicing_profile.organization.id if user&.invoicing_profile&.organization + + address_attributes = organization_address(row, user) + res[:address_attributes] = address_attributes if address_attributes + + res + end + + def organization_address(row, user) + return unless row['organization_address'] + + res = { address: row['organization_address'] } + + res[:id] = user.invoicing_profile.organization.address.id if user&.invoicing_profile&.organization&.address + + res + end + + def training_ids(row) + return unless row['trainings'] + + Training.where(id: row['trainings'].split(',')).map(&:id) end end end diff --git a/app/views/api/imports/show.json.jbuilder b/app/views/api/imports/show.json.jbuilder index 8303a79de..8dfd650b7 100644 --- a/app/views/api/imports/show.json.jbuilder +++ b/app/views/api/imports/show.json.jbuilder @@ -1,6 +1,7 @@ # frozen_string_literal: true -json.extract! @import, :id, :category, :user_id, :update_field, :created_at, :updated_at, :results +json.extract! @import, :id, :category, :user_id, :update_field, :created_at, :updated_at +json.results @import.results_hash.to_json json.user do json.full_name @import.user&.profile&.full_name end diff --git a/app/workers/members_import_worker.rb b/app/workers/members_import_worker.rb index 533094c40..81fdb3853 100644 --- a/app/workers/members_import_worker.rb +++ b/app/workers/members_import_worker.rb @@ -11,7 +11,10 @@ class MembersImportWorker raise SecurityError, 'Not allowed to import' unless import.user.admin? raise KeyError, 'Wrong worker called' unless import.category == 'members' - Members::ImportService.import(import) + res = Members::ImportService.import(import) + + import.results = res.to_yaml + import.save! NotificationCenter.call type: :notify_admin_import_complete, receiver: import.user, diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index bb3a62d1c..cec1cb810 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf # Mime::Type.register_alias "text/html", :iphone -Mime::Type.register "application/vnd.ms-excel", :xls \ No newline at end of file +Mime::Type.register 'application/vnd.ms-excel', :xls \ No newline at end of file diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 605c5bf62..a09a6abe0 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -600,6 +600,12 @@ en: # import results members_import_result: import_results: "Import results" + import_details: "Import #{{ID}}, of {{DATE}}, initiated by {{USER}}" # angular interpolation + results: "Results" + pending: "Pending..." + status_create: "Creating a new user" + status_update: "Updating user {{ID}}" # angular interpolation + error_details: "Error's details:" members_edit: # edit a member diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 837a4fb53..61193f92b 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -600,6 +600,12 @@ es: # import results members_import_result: import_results: "Import results" # translation_missing + import_details: "Import #{{ID}}, of {{DATE}}, initiated by {{USER}}" # angular interpolation # translation_missing + results: "Results" # translation_missing + pending: "Pending..." # translation_missing + status_create: "Creating a new user" # translation_missing + status_update: "Updating user {{ID}}" # angular interpolation # translation_missing + error_details: "Error's details:" # translation_missing members_edit: # edit a member diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 460d995be..5b29a9c1a 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -600,6 +600,12 @@ fr: # résultats de l'import members_import_result: import_results: "Résultats de l'import" + import_details: "Import n°{{ID}}, du {{DATE}}, initié par {{USER}}" # angular interpolation + results: "Résultats" + pending: "En cours..." + status_create: "Création d'un nouvel utilisateur" + status_update: "Mise à jour de l'utilisateur {{ID}}" # angular interpolation + error_details: "Détails de l'erreur :" members_edit: # modifier un membre diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index fd4520abb..ae2107ae8 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -600,6 +600,12 @@ pt: # import results members_import_result: import_results: "Import results" # translation_missing + import_details: "Import #{{ID}}, of {{DATE}}, initiated by {{USER}}" # angular interpolation # translation_missing + results: "Results" # translation_missing + pending: "Pending..." # translation_missing + status_create: "Creating a new user" # translation_missing + status_update: "Updating user {{ID}}" # angular interpolation # translation_missing + error_details: "Error's details:" # translation_missing members_edit: # edit a member