mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
[ongoing] import members from csv
This commit is contained in:
parent
a532efd198
commit
4deaf1f75a
@ -612,29 +612,55 @@ Application.Controllers.controller('NewMemberController', ['$scope', '$state', '
|
|||||||
/**
|
/**
|
||||||
* Controller used in the member's import page: import from CSV (admin view)
|
* Controller used in the member's import page: import from CSV (admin view)
|
||||||
*/
|
*/
|
||||||
Application.Controllers.controller('ImportMembersController', ['$scope', '$state', 'Group', 'Training', 'CSRF', 'plans', 'tags',
|
Application.Controllers.controller('ImportMembersController', ['$scope', '$state', 'Group', 'Training', 'CSRF', 'tags', 'growl',
|
||||||
function($scope, $state, Group, Training, CSRF, plans, tags) {
|
function($scope, $state, Group, Training, CSRF, tags, growl) {
|
||||||
CSRF.setMetaTags();
|
CSRF.setMetaTags();
|
||||||
|
|
||||||
/* PUBLIC SCOPE */
|
/* PUBLIC SCOPE */
|
||||||
|
|
||||||
// API URL where the form will be posted
|
// API URL where the form will be posted
|
||||||
$scope.actionUrl = '/api/members/import';
|
$scope.actionUrl = '/api/imports/members';
|
||||||
|
|
||||||
// Form action on the above URL
|
// Form action on the above URL
|
||||||
$scope.method = 'post';
|
$scope.method = 'post';
|
||||||
|
|
||||||
// List of all plans
|
|
||||||
$scope.plans = plans;
|
|
||||||
|
|
||||||
// List of all tags
|
// List of all tags
|
||||||
$scope.tags = tags
|
$scope.tags = tags
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback run after the form was submitted
|
||||||
|
* @param content {*} The result provided by the server, may be an Import object or an error message
|
||||||
|
*/
|
||||||
|
$scope.onImportResult = function(content) {
|
||||||
|
if (content.id) {
|
||||||
|
$state.go('app.admin.members_import_result', { id: content.id });
|
||||||
|
} else {
|
||||||
|
growl.error(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Using the MembersController
|
// Using the MembersController
|
||||||
return new MembersController($scope, $state, Group, Training);
|
return new MembersController($scope, $state, Group, Training);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller used in the member's import results page (admin view)
|
||||||
|
*/
|
||||||
|
Application.Controllers.controller('ImportMembersResultController', ['$scope', '$state', 'importItem',
|
||||||
|
function ($scope, $state, importItem) {
|
||||||
|
/* PUBLIC SCOPE */
|
||||||
|
|
||||||
|
// Current import as saved in database
|
||||||
|
$scope.import = importItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the admin's view to the members import page
|
||||||
|
*/
|
||||||
|
$scope.cancel = function () { $state.go('app.admin.members_import'); };
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller used in the admin's creation page (admin view)
|
* Controller used in the admin's creation page (admin view)
|
||||||
*/
|
*/
|
||||||
|
@ -969,10 +969,22 @@ angular.module('application.router', ['ui.router'])
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.members_import', 'app.shared.user', 'app.shared.user_admin']).$promise; }],
|
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.members_import', 'app.shared.user', 'app.shared.user_admin']).$promise; }],
|
||||||
plans: ['Plan', function(Plan) { return Plan.query().$promise }],
|
|
||||||
tags: ['Tag', function(Tag) { return Tag.query().$promise }]
|
tags: ['Tag', function(Tag) { return Tag.query().$promise }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.state('app.admin.members_import_result', {
|
||||||
|
url: '/admin/members/import/:id/results',
|
||||||
|
views: {
|
||||||
|
'main@': {
|
||||||
|
templateUrl: '<%= asset_path "admin/members/import_result.html" %>',
|
||||||
|
controller: 'ImportMembersResultController'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
translations: ['Translations', function (Translations) { return Translations.query(['app.admin.members_import_result', 'app.shared.user', 'app.shared.user_admin']).$promise; }],
|
||||||
|
importItem: ['Import', '$stateParams', function(Import, $stateParams) { return Import.get({ id: $stateParams.id }).$promise }]
|
||||||
|
}
|
||||||
|
})
|
||||||
.state('app.admin.members_edit', {
|
.state('app.admin.members_edit', {
|
||||||
url: '/admin/members/:id/edit',
|
url: '/admin/members/:id/edit',
|
||||||
views: {
|
views: {
|
||||||
|
7
app/assets/javascripts/services/import.js
Normal file
7
app/assets/javascripts/services/import.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
Application.Services.factory('Import', ['$resource', function ($resource) {
|
||||||
|
return $resource('/api/imports/:id',
|
||||||
|
{ id: '@id' }
|
||||||
|
);
|
||||||
|
}]);
|
@ -26,15 +26,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="row p-sm">
|
<div class="row p-sm">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<p class="alert alert-info" translate>
|
<p class="alert alert-info" translate>
|
||||||
{{ 'members_import.info' }}
|
{{ 'members_import.info' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row m-h-sm">
|
<div class="row m-h-sm">
|
||||||
|
|
||||||
<div class="col-md-6 p-h-s">
|
<div class="col-md-6 p-h-s">
|
||||||
<h3 translate>{{ 'members_import.groups' }}</h3>
|
<h3 translate>{{ 'members_import.groups' }}</h3>
|
||||||
@ -80,33 +80,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row m-h-sm" ng-hide="Fablab.withoutPlans || plans.length == 0">
|
|
||||||
|
|
||||||
<div class="col-md-6 p-h-s">
|
|
||||||
<h3 translate>{{ 'members_import.plans' }}</h3>
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th translate>{{ 'members_import.plan_name' }}</th>
|
|
||||||
<th translate>{{ 'members_import.plan_identifier' }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="plan in plans">
|
|
||||||
<td>
|
|
||||||
{{ plan.name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ plan.slug }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row m-h-sm">
|
||||||
|
|
||||||
<div class="col-md-6 p-h-s" ng-hide="tags.length == 0">
|
<div class="col-md-6 p-h-s" ng-hide="tags.length == 0">
|
||||||
<h3 translate>{{ 'members_import.tags' }}</h3>
|
<h3 translate>{{ 'members_import.tags' }}</h3>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@ -134,11 +112,12 @@
|
|||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-sm-12 col-md-12 b-r nopadding">
|
<div class="col-sm-12 col-md-12 b-r nopadding">
|
||||||
|
|
||||||
<form role="form" name="importForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="submited(content)" upload-options-enable-rails-csrf="true">
|
<form role="form" name="importForm" class="form-horizontal" novalidate action="{{ actionUrl }}" ng-upload="onImportResult(content)" upload-options-enable-rails-csrf="true">
|
||||||
<section class="panel panel-default bg-light m-lg">
|
<section class="panel panel-default bg-light m-lg">
|
||||||
|
|
||||||
<div class="panel-body m-r">
|
<div class="panel-body m-r">
|
||||||
<div class="row m-t">
|
|
||||||
|
<div class="m-t">
|
||||||
<p class="alert alert-warning m-h" translate>
|
<p class="alert alert-warning m-h" translate>
|
||||||
{{ 'members_import.required_fields' }}
|
{{ 'members_import.required_fields' }}
|
||||||
</p>
|
</p>
|
||||||
@ -155,10 +134,33 @@
|
|||||||
<span class="fileinput-exists" translate>{{ 'change' }}</span>
|
<span class="fileinput-exists" translate>{{ 'change' }}</span>
|
||||||
<input type="file"
|
<input type="file"
|
||||||
name="import_members"
|
name="import_members"
|
||||||
accept="text/csv"></span>
|
accept="text/csv"
|
||||||
|
required></span>
|
||||||
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
|
<a class="input-group-addon btn btn-danger fileinput-exists" data-dismiss="fileinput" ng-click="deleteFile(file)"><i class="fa fa-trash-o"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="m-h">
|
||||||
|
<span translate>{{ 'members_import.update_field' }}</span>
|
||||||
|
<div class="radio m-l-md">
|
||||||
|
<label class="control-label">
|
||||||
|
<input type="radio" id="update_field" name="update_field" value="id" checked>
|
||||||
|
<span translate>{{ 'members_import.update_on_id' }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio m-l-md">
|
||||||
|
<label class="control-label">
|
||||||
|
<input type="radio" id="update_field" name="update_field" value="username">
|
||||||
|
<span translate>{{ 'members_import.update_on_username' }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio m-l-md">
|
||||||
|
<label class="control-label">
|
||||||
|
<input type="radio" id="update_field" name="update_field" value="email">
|
||||||
|
<span translate>{{ 'members_import.update_on_email' }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div> <!-- ./panel-body -->
|
</div> <!-- ./panel-body -->
|
||||||
|
|
||||||
|
|
||||||
|
30
app/assets/templates/admin/members/import_result.html
Normal file
30
app/assets/templates/admin/members/import_result.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<div>
|
||||||
|
|
||||||
|
<section class="heading b-b">
|
||||||
|
<div class="row no-gutter">
|
||||||
|
|
||||||
|
<div class="col-md-1 hidden-xs">
|
||||||
|
<section class="heading-btn">
|
||||||
|
<a ng-click="cancel()"><i class="fa fa-long-arrow-left"></i></a>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-8 b-l">
|
||||||
|
<section class="heading-title">
|
||||||
|
<h1 translate>{{ 'members_import_result.import_results' }}</h1>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row no-gutter">
|
||||||
|
<div class="col-sm-12 col-md-12 b-r nopadding">
|
||||||
|
|
||||||
|
<span>{{import}}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
34
app/controllers/api/imports_controller.rb
Normal file
34
app/controllers/api/imports_controller.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# API Controller for resources of type Import
|
||||||
|
class API::ImportsController < API::ApiController
|
||||||
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize Import
|
||||||
|
|
||||||
|
@import = Import.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def members
|
||||||
|
authorize Import
|
||||||
|
|
||||||
|
@import = Import.new(
|
||||||
|
attachment: import_params,
|
||||||
|
user: current_user,
|
||||||
|
update_field: params[:update_field],
|
||||||
|
category: 'members'
|
||||||
|
)
|
||||||
|
if @import.save
|
||||||
|
render json: { id: @import.id }, status: :created
|
||||||
|
else
|
||||||
|
render json: @import.errors, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def import_params
|
||||||
|
params.require(:import_members)
|
||||||
|
end
|
||||||
|
end
|
@ -181,18 +181,6 @@ class API::MembersController < API::ApiController
|
|||||||
@members = User.includes(:profile)
|
@members = User.includes(:profile)
|
||||||
end
|
end
|
||||||
|
|
||||||
def import
|
|
||||||
authorize User
|
|
||||||
|
|
||||||
@import = Import.new(attachment: import_params, author: current_user)
|
|
||||||
if @import.save
|
|
||||||
Members::ImportService.import(@import)
|
|
||||||
render json: @import, status: :created
|
|
||||||
else
|
|
||||||
render json: @import.errors, status: :unprocessable_entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_member
|
def set_member
|
||||||
@ -237,8 +225,4 @@ class API::MembersController < API::ApiController
|
|||||||
def query_params
|
def query_params
|
||||||
params.require(:query).permit(:search, :order_by, :page, :size)
|
params.require(:query).permit(:search, :order_by, :page, :size)
|
||||||
end
|
end
|
||||||
|
|
||||||
def import_params
|
|
||||||
params.require(:import_members)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -13,7 +13,7 @@ class API::UsersController < API::ApiController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
if current_user.admin?
|
authorize User
|
||||||
res = UserService.create_partner(partner_params)
|
res = UserService.create_partner(partner_params)
|
||||||
|
|
||||||
if res[:saved]
|
if res[:saved]
|
||||||
@ -22,9 +22,6 @@ class API::UsersController < API::ApiController
|
|||||||
else
|
else
|
||||||
render json: res[:user].errors.full_messages, status: :unprocessable_entity
|
render json: res[:user].errors.full_messages, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
else
|
|
||||||
head 403
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -7,8 +7,21 @@ require 'file_size_validator'
|
|||||||
class Import < ActiveRecord::Base
|
class Import < ActiveRecord::Base
|
||||||
mount_uploader :attachment, ImportUploader
|
mount_uploader :attachment, ImportUploader
|
||||||
|
|
||||||
belongs_to :author, foreign_key: :author_id, class_name: 'User'
|
belongs_to :user
|
||||||
|
|
||||||
validates :attachment, file_size: { maximum: Rails.application.secrets.max_import_size&.to_i || 5.megabytes.to_i }
|
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: ['text/csv'] }
|
||||||
|
|
||||||
|
after_commit :proceed_import, on: [:create]
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def proceed_import
|
||||||
|
case category
|
||||||
|
when 'members'
|
||||||
|
MembersImportWorker.perform_async(id)
|
||||||
|
else
|
||||||
|
raise NoMethodError, "Unknown import service for #{category}"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -46,6 +46,7 @@ class NotificationType
|
|||||||
notify_admin_close_period_reminder
|
notify_admin_close_period_reminder
|
||||||
notify_admin_archive_complete
|
notify_admin_archive_complete
|
||||||
notify_privacy_policy_changed
|
notify_privacy_policy_changed
|
||||||
|
notify_admin_import_complete
|
||||||
]
|
]
|
||||||
# deprecated:
|
# deprecated:
|
||||||
# - notify_member_subscribed_plan_is_changed
|
# - notify_member_subscribed_plan_is_changed
|
||||||
|
@ -36,5 +36,4 @@ class StatisticProfile < ActiveRecord::Base
|
|||||||
''
|
''
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -43,6 +43,7 @@ class User < ActiveRecord::Base
|
|||||||
accepts_nested_attributes_for :tags, allow_destroy: true
|
accepts_nested_attributes_for :tags, allow_destroy: true
|
||||||
|
|
||||||
has_many :exports, dependent: :destroy
|
has_many :exports, dependent: :destroy
|
||||||
|
has_many :imports, dependent: :nullify
|
||||||
|
|
||||||
# fix for create admin user
|
# fix for create admin user
|
||||||
before_save do
|
before_save do
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check the access policies for API::CouponsController
|
||||||
class CouponPolicy < ApplicationPolicy
|
class CouponPolicy < ApplicationPolicy
|
||||||
%w(index show create update destroy send_to).each do |action|
|
%w[index show create update destroy send_to].each do |action|
|
||||||
define_method "#{action}?" do
|
define_method "#{action}?" do
|
||||||
user.admin?
|
user.admin?
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check the access policies for API::EventsController
|
||||||
class EventPolicy < ApplicationPolicy
|
class EventPolicy < ApplicationPolicy
|
||||||
|
# Defines the scope of the events index, depending on the role of the current user
|
||||||
class Scope < Scope
|
class Scope < Scope
|
||||||
def resolve
|
def resolve
|
||||||
if user.nil? or (user and !user.admin?)
|
if user.nil? || (user && !user.admin?)
|
||||||
scope.includes(:event_image, :event_files, :availability, :category)
|
scope.includes(:event_image, :event_files, :availability, :category)
|
||||||
.where('availabilities.start_at >= ?', Time.now)
|
.where('availabilities.start_at >= ?', Time.now)
|
||||||
.order('availabilities.start_at ASC')
|
.order('availabilities.start_at ASC')
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
class ExportPolicy < Struct.new(:user, :export)
|
# frozen_string_literal: true
|
||||||
%w(export_reservations export_members export_subscriptions export_availabilities download status).each do |action|
|
|
||||||
|
# Check the access policies for API::ExportsController
|
||||||
|
class ExportPolicy < ApplicationPolicy
|
||||||
|
%w[export_reservations export_members export_subscriptions export_availabilities download status].each do |action|
|
||||||
define_method "#{action}?" do
|
define_method "#{action}?" do
|
||||||
user.admin?
|
user.admin?
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check the access policies for API::GroupsController
|
||||||
class GroupPolicy < ApplicationPolicy
|
class GroupPolicy < ApplicationPolicy
|
||||||
def create?
|
def create?
|
||||||
user.admin?
|
user.admin?
|
||||||
|
12
app/policies/import_policy.rb
Normal file
12
app/policies/import_policy.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check the access policies for API::ImportsController
|
||||||
|
class ImportPolicy < ApplicationPolicy
|
||||||
|
def show?
|
||||||
|
user.admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def members?
|
||||||
|
user.admin?
|
||||||
|
end
|
||||||
|
end
|
@ -1,4 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check the access policies for API::MembersController and API::UsersController
|
||||||
class UserPolicy < ApplicationPolicy
|
class UserPolicy < ApplicationPolicy
|
||||||
|
# Defines the scope of the users index, depending on the role of the current user
|
||||||
class Scope < Scope
|
class Scope < Scope
|
||||||
def resolve
|
def resolve
|
||||||
if user.admin?
|
if user.admin?
|
||||||
@ -27,7 +31,7 @@ class UserPolicy < ApplicationPolicy
|
|||||||
user.id == record.id
|
user.id == record.id
|
||||||
end
|
end
|
||||||
|
|
||||||
%w[list create mapping import].each do |action|
|
%w[list create mapping].each do |action|
|
||||||
define_method "#{action}?" do
|
define_method "#{action}?" do
|
||||||
user.admin?
|
user.admin?
|
||||||
end
|
end
|
||||||
|
@ -4,7 +4,85 @@
|
|||||||
class Members::ImportService
|
class Members::ImportService
|
||||||
class << self
|
class << self
|
||||||
def import(import)
|
def import(import)
|
||||||
puts import
|
require 'csv'
|
||||||
|
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))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def row_to_params(row)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def profile_attributes(row)
|
||||||
|
{
|
||||||
|
first_name: row['first_name'],
|
||||||
|
last_name: row['last_name'],
|
||||||
|
phone: row['phone'],
|
||||||
|
interest: row['interests'],
|
||||||
|
software_mastered: row['softwares'],
|
||||||
|
website: row['website'],
|
||||||
|
job: row['job'],
|
||||||
|
facebook: row['facebook'],
|
||||||
|
twitter: row['twitter'],
|
||||||
|
google_plus: row['googleplus'],
|
||||||
|
viadeo: row['viadeo'],
|
||||||
|
linkedin: row['linkedin'],
|
||||||
|
instagram: row['instagram'],
|
||||||
|
youtube: row['youtube'],
|
||||||
|
vimeo: row['vimeo'],
|
||||||
|
dailymotion: row['dailymotion'],
|
||||||
|
github: row['github'],
|
||||||
|
echosciences: row['echosciences'],
|
||||||
|
pinterest: row['pinterest'],
|
||||||
|
lastfm: row['lastfm'],
|
||||||
|
flickr: row['flickr']
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def invoicing_profile_attributes(row)
|
||||||
|
{
|
||||||
|
address_attributes: {
|
||||||
|
address: row['address']
|
||||||
|
},
|
||||||
|
organization_attributes: {
|
||||||
|
name: row['organization_name'],
|
||||||
|
address_attributes: {
|
||||||
|
address: row['organization_address']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def statistic_profile_attributes(row)
|
||||||
|
{
|
||||||
|
gender: row['gender'] == 'male',
|
||||||
|
birthday: row['birthdate'],
|
||||||
|
training_ids: Training.where(id: row['trainings'].split(',')).map(&:id)
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
6
app/views/api/imports/show.json.jbuilder
Normal file
6
app/views/api/imports/show.json.jbuilder
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
json.extract! @import, :id, :category, :user_id, :update_field, :created_at, :updated_at, :results
|
||||||
|
json.user do
|
||||||
|
json.full_name @import.user&.profile&.full_name
|
||||||
|
end
|
@ -0,0 +1,6 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
json.title notification.notification_type
|
||||||
|
json.description t('.import_over', CATEGORY: t(".#{notification.attached_object.category}")) +
|
||||||
|
link_to(t('.view_results'), "#!/admin/members/import/#{notification.attached_object.id}/results")
|
||||||
|
json.url notification_url(notification, format: :json)
|
@ -0,0 +1,8 @@
|
|||||||
|
<%= render 'notifications_mailer/shared/hello', recipient: @recipient %>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= t('.body.you_made_an_import', CATEGORY: t(".body.category_#{@attached_object.category}")) %>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<%=link_to( t('.body.click_to_view_results'), "#{root_url}#!/admin/members/import/#{@attached_object.id}/results", target: "_blank" )%>
|
||||||
|
</p>
|
20
app/workers/members_import_worker.rb
Normal file
20
app/workers/members_import_worker.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Will parse the uploaded CSV file and save or update the members described in that file.
|
||||||
|
# This import will be asynchronously proceed by sidekiq and a notification will be sent to the requesting user when it's done.
|
||||||
|
class MembersImportWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
def perform(import_id)
|
||||||
|
import = Import.find(import_id)
|
||||||
|
|
||||||
|
raise SecurityError, 'Not allowed to import' unless import.user.admin?
|
||||||
|
raise KeyError, 'Wrong worker called' unless import.category == 'members'
|
||||||
|
|
||||||
|
Members::ImportService.import(import)
|
||||||
|
|
||||||
|
NotificationCenter.call type: :notify_admin_import_complete,
|
||||||
|
receiver: import.user,
|
||||||
|
attached_object: import
|
||||||
|
end
|
||||||
|
end
|
@ -1,15 +1,9 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require File.expand_path('../boot', __FILE__)
|
require File.expand_path('../boot', __FILE__)
|
||||||
|
|
||||||
# Pick the frameworks you want:
|
|
||||||
#require "active_model/railtie"
|
|
||||||
#require "active_record/railtie"
|
|
||||||
#require "action_controller/railtie"
|
|
||||||
#require "action_mailer/railtie"
|
|
||||||
#require "action_view/railtie"
|
|
||||||
#require "sprockets/railtie"
|
|
||||||
#require "rails/test_unit/railtie"
|
|
||||||
require 'csv'
|
require 'csv'
|
||||||
require "rails/all"
|
require 'rails/all'
|
||||||
require 'elasticsearch/rails/instrumentation'
|
require 'elasticsearch/rails/instrumentation'
|
||||||
require 'elasticsearch/persistence/model'
|
require 'elasticsearch/persistence/model'
|
||||||
|
|
||||||
@ -43,7 +37,7 @@ module Fablab
|
|||||||
config.active_record.raise_in_transactional_callbacks = true
|
config.active_record.raise_in_transactional_callbacks = true
|
||||||
|
|
||||||
config.to_prepare do
|
config.to_prepare do
|
||||||
Devise::Mailer.layout "notifications_mailer"
|
Devise::Mailer.layout 'notifications_mailer'
|
||||||
end
|
end
|
||||||
|
|
||||||
# allow use rails helpers in angular templates
|
# allow use rails helpers in angular templates
|
||||||
@ -60,8 +54,8 @@ module Fablab
|
|||||||
|
|
||||||
if Rails.env.development?
|
if Rails.env.development?
|
||||||
config.web_console.whitelisted_ips << '192.168.0.0/16'
|
config.web_console.whitelisted_ips << '192.168.0.0/16'
|
||||||
config.web_console.whitelisted_ips << '192.168.99.0/16' #docker
|
config.web_console.whitelisted_ips << '192.168.99.0/16' # docker
|
||||||
config.web_console.whitelisted_ips << '10.0.2.2' #vagrant
|
config.web_console.whitelisted_ips << '10.0.2.2' # vagrant
|
||||||
end
|
end
|
||||||
|
|
||||||
# load locales for subdirectories
|
# load locales for subdirectories
|
||||||
@ -78,9 +72,8 @@ module Fablab
|
|||||||
FabManager.activate_plugins!
|
FabManager.activate_plugins!
|
||||||
|
|
||||||
config.after_initialize do
|
config.after_initialize do
|
||||||
if plugins = FabManager.plugins
|
plugins = FabManager.plugins
|
||||||
plugins.each { |plugin| plugin.notify_after_initialize }
|
plugins&.each(&:notify_after_initialize)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -573,7 +573,7 @@ en:
|
|||||||
# members bulk import
|
# members bulk import
|
||||||
members_import:
|
members_import:
|
||||||
import_members: "Import members"
|
import_members: "Import members"
|
||||||
info: "you can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, trainings, suscription and tags of the members."
|
info: "You can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, the trainings and the tags of the members."
|
||||||
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is."
|
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is."
|
||||||
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding."
|
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding."
|
||||||
groups: "Groups"
|
groups: "Groups"
|
||||||
@ -591,6 +591,15 @@ en:
|
|||||||
download_example: "Download the exemple file"
|
download_example: "Download the exemple file"
|
||||||
select_file: "Choose a file"
|
select_file: "Choose a file"
|
||||||
import: "Import"
|
import: "Import"
|
||||||
|
update_field: "Reference field for users to update"
|
||||||
|
update_on_id: "ID"
|
||||||
|
update_on_username: "Username"
|
||||||
|
update_on_email: "Email address"
|
||||||
|
|
||||||
|
members_import_result:
|
||||||
|
# import results
|
||||||
|
members_import_result:
|
||||||
|
import_results: "Import results"
|
||||||
|
|
||||||
members_edit:
|
members_edit:
|
||||||
# edit a member
|
# edit a member
|
||||||
|
@ -573,7 +573,7 @@ es:
|
|||||||
# members bulk import
|
# members bulk import
|
||||||
members_import:
|
members_import:
|
||||||
import_members: "Import members" # translation_missing
|
import_members: "Import members" # translation_missing
|
||||||
info: "you can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, trainings, suscription and tags of the members." # translation_missing
|
info: "You can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, the trainings and the tags of the members." # translation_missing
|
||||||
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is." # translation_missing
|
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is." # translation_missing
|
||||||
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding." # translation_missing
|
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding." # translation_missing
|
||||||
groups: "Groups" # translation_missing
|
groups: "Groups" # translation_missing
|
||||||
@ -591,6 +591,15 @@ es:
|
|||||||
download_example: "Download the exemple file" # translation_missing
|
download_example: "Download the exemple file" # translation_missing
|
||||||
select_file: "Choose a file" # translation_missing
|
select_file: "Choose a file" # translation_missing
|
||||||
import: "Import" # translation_missing
|
import: "Import" # translation_missing
|
||||||
|
update_field: "Reference field for users to update" # translation_missing
|
||||||
|
update_on_id: "ID" # translation_missing
|
||||||
|
update_on_username: "Username" # translation_missing
|
||||||
|
update_on_email: "Email address" # translation_missing
|
||||||
|
|
||||||
|
members_import_result:
|
||||||
|
# import results
|
||||||
|
members_import_result:
|
||||||
|
import_results: "Import results" # translation_missing
|
||||||
|
|
||||||
members_edit:
|
members_edit:
|
||||||
# edit a member
|
# edit a member
|
||||||
|
@ -573,7 +573,7 @@ fr:
|
|||||||
# import massif de members
|
# import massif de members
|
||||||
members_import:
|
members_import:
|
||||||
import_members: "Importer des membres"
|
import_members: "Importer des membres"
|
||||||
info: "Vous pouvez téléverser un fichier CVS afin de créer des nouveaux membres ou de mettre à jour les existants. Votre fichier doit utiliser les identifiants ci-dessous pour spécifier les groupe, formations, abonnement et étiquettes des membres."
|
info: "Vous pouvez téléverser un fichier CVS afin de créer des nouveaux membres ou de mettre à jour les existants. Votre fichier doit utiliser les identifiants ci-dessous pour spécifier le groupe, les formations et les étiquettes des membres."
|
||||||
required_fields: "Votre fichier doit obligatoirement comporter, au minimum, les informations suivantes pour chaque utilisateur à créer : courriel, nom, prénom et groupe. Si le mot passe n'est pas rempli, il sera généré automatiquement. Lors d'une mise à jour, les champs non remplis seront gardés tel quels."
|
required_fields: "Votre fichier doit obligatoirement comporter, au minimum, les informations suivantes pour chaque utilisateur à créer : courriel, nom, prénom et groupe. Si le mot passe n'est pas rempli, il sera généré automatiquement. Lors d'une mise à jour, les champs non remplis seront gardés tel quels."
|
||||||
about_example: "Merci de vous référer au fichier d'exemple fourni pour générer un fichier CSV au bon format. Attention à l'utiliser l'encodage Unicode UTF-8"
|
about_example: "Merci de vous référer au fichier d'exemple fourni pour générer un fichier CSV au bon format. Attention à l'utiliser l'encodage Unicode UTF-8"
|
||||||
groups: "Groupes"
|
groups: "Groupes"
|
||||||
@ -591,6 +591,15 @@ fr:
|
|||||||
download_example: "Télécharger le fichier d'exemple"
|
download_example: "Télécharger le fichier d'exemple"
|
||||||
select_file: "Choisissez un fichier"
|
select_file: "Choisissez un fichier"
|
||||||
import: "Importer"
|
import: "Importer"
|
||||||
|
update_field: "Champ de référence pour les utilisateurs à mettre à jour"
|
||||||
|
update_on_id: "ID"
|
||||||
|
update_on_username: "Pseudonyme"
|
||||||
|
update_on_email: "Adresse de courriel"
|
||||||
|
|
||||||
|
members_import_result:
|
||||||
|
# résultats de l'import
|
||||||
|
members_import_result:
|
||||||
|
import_results: "Résultats de l'import"
|
||||||
|
|
||||||
members_edit:
|
members_edit:
|
||||||
# modifier un membre
|
# modifier un membre
|
||||||
|
@ -573,7 +573,7 @@ pt:
|
|||||||
# members bulk import
|
# members bulk import
|
||||||
members_import:
|
members_import:
|
||||||
import_members: "Import members" # translation_missing
|
import_members: "Import members" # translation_missing
|
||||||
info: "you can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, trainings, suscription and tags of the members." # translation_missing
|
info: "You can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, the trainings and the tags of the members." # translation_missing
|
||||||
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is." # translation_missing
|
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is." # translation_missing
|
||||||
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding." # translation_missing
|
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding." # translation_missing
|
||||||
groups: "Groups" # translation_missing
|
groups: "Groups" # translation_missing
|
||||||
@ -591,6 +591,15 @@ pt:
|
|||||||
download_example: "Download the exemple file" # translation_missing
|
download_example: "Download the exemple file" # translation_missing
|
||||||
select_file: "Choose a file" # translation_missing
|
select_file: "Choose a file" # translation_missing
|
||||||
import: "Import" # translation_missing
|
import: "Import" # translation_missing
|
||||||
|
update_field: "Reference field for users to update" # translation_missing
|
||||||
|
update_on_id: "ID" # translation_missing
|
||||||
|
update_on_username: "Username" # translation_missing
|
||||||
|
update_on_email: "Email address" # translation_missing
|
||||||
|
|
||||||
|
members_import_result:
|
||||||
|
# import results
|
||||||
|
members_import_result:
|
||||||
|
import_results: "Import results" # translation_missing
|
||||||
|
|
||||||
members_edit:
|
members_edit:
|
||||||
# edit a member
|
# edit a member
|
||||||
|
@ -330,6 +330,10 @@ en:
|
|||||||
accounting_acd: "of the accounting data to ACD"
|
accounting_acd: "of the accounting data to ACD"
|
||||||
is_over: "is over."
|
is_over: "is over."
|
||||||
download_here: "Download here"
|
download_here: "Download here"
|
||||||
|
notify_admin_import_complete:
|
||||||
|
import_over: "%{CATEGORY} import is over. "
|
||||||
|
members: "Members"
|
||||||
|
view_results: "View results."
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
enjoy_a_discount_of_PERCENT_with_code_CODE: "Enjoy a discount of %{PERCENT}% with code %{CODE}"
|
enjoy_a_discount_of_PERCENT_with_code_CODE: "Enjoy a discount of %{PERCENT}% with code %{CODE}"
|
||||||
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Enjoy a discount of %{AMOUNT} with code %{CODE}"
|
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Enjoy a discount of %{AMOUNT} with code %{CODE}"
|
||||||
|
@ -330,6 +330,10 @@ es:
|
|||||||
accounting_acd: "de los datos contables para ACD"
|
accounting_acd: "de los datos contables para ACD"
|
||||||
is_over: "se ha acabado."
|
is_over: "se ha acabado."
|
||||||
download_here: "Descargar aquí"
|
download_here: "Descargar aquí"
|
||||||
|
notify_admin_import_complete:
|
||||||
|
import_over: "%{CATEGORY} import is over. " # missing translation
|
||||||
|
members: "Members" # missing translation
|
||||||
|
view_results: "View results." # missing translation
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
enjoy_a_discount_of_PERCENT_with_code_CODE: "Disfruta de un descuento de %{PERCENT}% con el código %{CODE}"
|
enjoy_a_discount_of_PERCENT_with_code_CODE: "Disfruta de un descuento de %{PERCENT}% con el código %{CODE}"
|
||||||
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Disfruta de un descuento de %{AMOUNT} con el código %{CODE}"
|
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Disfruta de un descuento de %{AMOUNT} con el código %{CODE}"
|
||||||
|
@ -330,6 +330,10 @@ fr:
|
|||||||
accounting_acd: "des données comptables pour ACD"
|
accounting_acd: "des données comptables pour ACD"
|
||||||
is_over: "est terminé."
|
is_over: "est terminé."
|
||||||
download_here: "Téléchargez ici"
|
download_here: "Téléchargez ici"
|
||||||
|
notify_admin_import_complete:
|
||||||
|
import_over: "L'import %{CATEGORY} est terminé. "
|
||||||
|
members: "des membres"
|
||||||
|
view_results: "Voir les résultats."
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
enjoy_a_discount_of_PERCENT_with_code_CODE: "Bénéficiez d'une remise de %{PERCENT} % avec le code %{CODE}"
|
enjoy_a_discount_of_PERCENT_with_code_CODE: "Bénéficiez d'une remise de %{PERCENT} % avec le code %{CODE}"
|
||||||
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Bénéficiez d'une remise de %{AMOUNT} avec le code %{CODE}"
|
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Bénéficiez d'une remise de %{AMOUNT} avec le code %{CODE}"
|
||||||
|
@ -274,6 +274,13 @@ en:
|
|||||||
xlsx: "Excel"
|
xlsx: "Excel"
|
||||||
csv: "CSV"
|
csv: "CSV"
|
||||||
|
|
||||||
|
notify_admin_import_complete:
|
||||||
|
subject: "Import completed"
|
||||||
|
body:
|
||||||
|
you_made_an_import: "You have initiated an import %{CATEGORY}"
|
||||||
|
category_members: "of the members"
|
||||||
|
click_to_view_results: "Click here to view results"
|
||||||
|
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
subject: "Coupon"
|
subject: "Coupon"
|
||||||
body:
|
body:
|
||||||
|
@ -273,6 +273,13 @@ es:
|
|||||||
xlsx: "Excel"
|
xlsx: "Excel"
|
||||||
csv: "CSV"
|
csv: "CSV"
|
||||||
|
|
||||||
|
notify_admin_import_complete: #translation_missing
|
||||||
|
subject: "Import completed"
|
||||||
|
body:
|
||||||
|
you_made_an_import: "You have initiated an import %{CATEGORY}"
|
||||||
|
category_members: "of the members"
|
||||||
|
click_to_view_results: "Click here to view results"
|
||||||
|
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
subject: "Cupón"
|
subject: "Cupón"
|
||||||
body:
|
body:
|
||||||
|
@ -274,6 +274,13 @@ fr:
|
|||||||
xlsx: "Excel"
|
xlsx: "Excel"
|
||||||
csv: "CSV"
|
csv: "CSV"
|
||||||
|
|
||||||
|
notify_admin_import_complete:
|
||||||
|
subject: "Import terminé"
|
||||||
|
body:
|
||||||
|
you_made_an_import: "Vous avez initié un import %{CATEGORY}"
|
||||||
|
category_members: "des membres"
|
||||||
|
click_to_view_results: "Cliquez ici pour voir les résultats"
|
||||||
|
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
subject: "Code promo"
|
subject: "Code promo"
|
||||||
body:
|
body:
|
||||||
|
@ -274,6 +274,13 @@ pt:
|
|||||||
xlsx: "Excel"
|
xlsx: "Excel"
|
||||||
csv: "CSV"
|
csv: "CSV"
|
||||||
|
|
||||||
|
notify_admin_import_complete: #translation_missing
|
||||||
|
subject: "Import completed"
|
||||||
|
body:
|
||||||
|
you_made_an_import: "You have initiated an import %{CATEGORY}"
|
||||||
|
category_members: "of the members"
|
||||||
|
click_to_view_results: "Click here to view results"
|
||||||
|
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
subject: "Cupom"
|
subject: "Cupom"
|
||||||
body:
|
body:
|
||||||
|
@ -330,6 +330,10 @@ pt:
|
|||||||
accounting_acd: "de dados contábeis para ACD"
|
accounting_acd: "de dados contábeis para ACD"
|
||||||
is_over: "está finalizado."
|
is_over: "está finalizado."
|
||||||
download_here: "Baixe aqui"
|
download_here: "Baixe aqui"
|
||||||
|
notify_admin_import_complete:
|
||||||
|
import_over: "%{CATEGORY} import is over. " # missing translation
|
||||||
|
members: "Members" # missing translation
|
||||||
|
view_results: "View results." # missing translation
|
||||||
notify_member_about_coupon:
|
notify_member_about_coupon:
|
||||||
enjoy_a_discount_of_PERCENT_with_code_CODE: "Desfrute de um desconto de %{PERCENT}% com o código %{CODE}"
|
enjoy_a_discount_of_PERCENT_with_code_CODE: "Desfrute de um desconto de %{PERCENT}% com o código %{CODE}"
|
||||||
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Desfrute de um desconto de %{AMOUNT} com o código %{CODE}"
|
enjoy_a_discount_of_AMOUNT_with_code_CODE: "Desfrute de um desconto de %{AMOUNT} com o código %{CODE}"
|
||||||
|
@ -55,7 +55,6 @@ Rails.application.routes.draw do
|
|||||||
post 'list', action: 'list', on: :collection
|
post 'list', action: 'list', on: :collection
|
||||||
get 'search/:query', action: 'search', on: :collection
|
get 'search/:query', action: 'search', on: :collection
|
||||||
get 'mapping', action: 'mapping', on: :collection
|
get 'mapping', action: 'mapping', on: :collection
|
||||||
post 'import', action: 'import', on: :collection
|
|
||||||
end
|
end
|
||||||
resources :reservations, only: %i[show create index update]
|
resources :reservations, only: %i[show create index update]
|
||||||
resources :notifications, only: %i[index show update] do
|
resources :notifications, only: %i[index show update] do
|
||||||
@ -150,6 +149,11 @@ Rails.application.routes.draw do
|
|||||||
get 'exports/:id/download' => 'exports#download'
|
get 'exports/:id/download' => 'exports#download'
|
||||||
post 'exports/status' => 'exports#status'
|
post 'exports/status' => 'exports#status'
|
||||||
|
|
||||||
|
# Members CSV import
|
||||||
|
resources :imports, only: [:show] do
|
||||||
|
post 'members', action: 'members', on: :collection
|
||||||
|
end
|
||||||
|
|
||||||
# Fab-manager's version
|
# Fab-manager's version
|
||||||
get 'version' => 'version#show'
|
get 'version' => 'version#show'
|
||||||
|
|
||||||
|
@ -5,8 +5,11 @@
|
|||||||
class CreateImports < ActiveRecord::Migration
|
class CreateImports < ActiveRecord::Migration
|
||||||
def change
|
def change
|
||||||
create_table :imports do |t|
|
create_table :imports do |t|
|
||||||
t.integer :author_id
|
t.integer :user_id
|
||||||
t.string :attachment
|
t.string :attachment
|
||||||
|
t.string :update_field
|
||||||
|
t.string :category
|
||||||
|
t.text :results
|
||||||
|
|
||||||
t.timestamps null: false
|
t.timestamps null: false
|
||||||
end
|
end
|
||||||
|
@ -247,8 +247,11 @@ ActiveRecord::Schema.define(version: 20190924140726) do
|
|||||||
add_index "history_values", ["setting_id"], name: "index_history_values_on_setting_id", using: :btree
|
add_index "history_values", ["setting_id"], name: "index_history_values_on_setting_id", using: :btree
|
||||||
|
|
||||||
create_table "imports", force: :cascade do |t|
|
create_table "imports", force: :cascade do |t|
|
||||||
t.integer "author_id"
|
t.integer "user_id"
|
||||||
t.string "attachment"
|
t.string "attachment"
|
||||||
|
t.string "update_field"
|
||||||
|
t.string "category"
|
||||||
|
t.text "results"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
id;gender;first_name;last_name;username;email;password;birthdate;address;phone;group;subscription;tags;trainings;website;job;interests;softwares;allow_contact;allow_newsletter;facebook;twitter;googleplus;viadeo;linkedin;instagram;youtube;vimeo;dailymotion;github;echosciences;pinterest;lastfm;flickr
|
id;gender;first_name;last_name;username;email;password;birthdate;address;phone;group;tags;trainings;website;job;interests;softwares;allow_contact;allow_newsletter;organization_name;organization_address;facebook;twitter;googleplus;viadeo;linkedin;instagram;youtube;vimeo;dailymotion;github;echosciences;pinterest;lastfm;flickr
|
||||||
;male;jean;dupont;jdupont;jean.dupont@gmail.com;;1970-01-01;12 bvd Libération - 75000 Paris;0123456789;standard;;1,2;1;http://www.example.com;Charpentier;Ping-pong;AutoCAD;yes;no;http://www.facebook.com/jdupont;;;;;;;;;http://github.com/example;;;;
|
;male;jean;dupont;jdupont;jean.dupont@gmail.com;;1970-01-01;12 bvd Libération - 75000 Paris;0123456789;standard;1,2;1;http://www.example.com;Charpentier;Ping-pong;AutoCAD;yes;no;;;http://www.facebook.com/jdupont;;;;;;;;;http://github.com/example;;;;
|
||||||
43;;;;;;newpassword
|
43;;;;;;newpassword
|
||||||
|
|
1
test/fixtures/imports.yml
vendored
1
test/fixtures/imports.yml
vendored
@ -3,6 +3,7 @@
|
|||||||
one:
|
one:
|
||||||
author_id: 1
|
author_id: 1
|
||||||
attachment: 'users.csv'
|
attachment: 'users.csv'
|
||||||
|
update_field: 'id'
|
||||||
created_at: 2019-09-24 15:06:22.151882000 Z
|
created_at: 2019-09-24 15:06:22.151882000 Z
|
||||||
updated_at: 2019-09-24 15:06:22.151882000 Z
|
updated_at: 2019-09-24 15:06:22.151882000 Z
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user