1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-18 07:52:23 +01:00

Merge branch 'statisticprofile' into dev

This commit is contained in:
Sylvain 2019-06-05 16:10:22 +02:00
commit 971102ec1d
79 changed files with 653 additions and 373 deletions

View File

@ -1,3 +1,3 @@
web: bundle exec rails server puma -p $PORT -b0.0.0.0 #web: bundle exec rails server puma -p $PORT -b0.0.0.0
worker: bundle exec sidekiq -C ./config/sidekiq.yml worker: bundle exec sidekiq -C ./config/sidekiq.yml
mail: bundle exec mailcatcher --foreground mail: bundle exec mailcatcher --foreground

View File

@ -264,8 +264,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
* @return {string} 'male' or 'female' * @return {string} 'male' or 'female'
*/ */
var getGender = function (user) { var getGender = function (user) {
if (user.profile) { if (user.statistic_profile) {
if (user.profile.gender === 'true') { return 'male'; } else { return 'female'; } if (user.statistic_profile.gender === 'true') { return 'male'; } else { return 'female'; }
} else { return 'other'; } } else { return 'other'; }
}; };

View File

@ -225,8 +225,11 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
* Callback when the search field content changes: reload the search results * Callback when the search field content changes: reload the search results
*/ */
$scope.updateTextSearch = function () { $scope.updateTextSearch = function () {
if (searchTimeout) clearTimeout(searchTimeout);
searchTimeout = setTimeout(function() {
resetSearchMember(); resetSearchMember();
return memberSearch(); memberSearch();
}, 300);
}; };
/** /**
@ -252,6 +255,11 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
} }
}; };
/**
* Will temporize the search query to prevent overloading the API
*/
var searchTimeout = null;
/** /**
* Iterate through the provided array and return the index of the requested admin * Iterate through the provided array and return the index of the requested admin
* @param admins {Array} full list of users with role 'admin' * @param admins {Array} full list of users with role 'admin'
@ -267,13 +275,13 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
*/ */
var resetSearchMember = function () { var resetSearchMember = function () {
$scope.member.noMore = false; $scope.member.noMore = false;
return $scope.member.page = 1; $scope.member.page = 1;
}; };
/** /**
* Run a search query with the current parameters set ($scope.member[searchText,order,page]) * Run a search query with the current parameters set ($scope.member[searchText,order,page])
* and affect or append the result in $scope.members, depending on the concat parameter * and affect or append the result in $scope.members, depending on the concat parameter
* @param concat {boolean} if true, the result will be append to $scope.members instead of being affected * @param [concat] {boolean} if true, the result will be append to $scope.members instead of being affected
*/ */
var memberSearch = function (concat) { var memberSearch = function (concat) {
Member.list({ Member.list({
@ -536,7 +544,7 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
CSRF.setMetaTags(); CSRF.setMetaTags();
// init the birth date to JS object // init the birth date to JS object
$scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate(); $scope.user.statistic_profile.birthday = moment($scope.user.statistic_profile.birthday).toDate();
// the user subscription // the user subscription
if (($scope.user.subscribed_plan != null) && ($scope.user.subscription != null)) { if (($scope.user.subscribed_plan != null) && ($scope.user.subscription != null)) {
@ -579,7 +587,11 @@ Application.Controllers.controller('NewMemberController', ['$scope', '$state', '
$scope.password = { change: false }; $scope.password = { change: false };
// Default member's profile parameters // Default member's profile parameters
$scope.user = { plan_interval: '' }; $scope.user = {
plan_interval: '',
invoicing_profile: {},
statistic_profile: {}
};
// Callback when the admin check/uncheck the box telling that the new user is an organization. // Callback when the admin check/uncheck the box telling that the new user is an organization.
// Disable or enable the organization fields in the form, accordingly // Disable or enable the organization fields in the form, accordingly
@ -604,9 +616,10 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
// default admin profile // default admin profile
let getGender; let getGender;
$scope.admin = { $scope.admin = {
profile_attributes: { statistic_profile_attributes: {
gender: true gender: true
}, },
profile_attributes: {},
invoicing_profile_attributes: {} invoicing_profile_attributes: {}
}; };
@ -650,8 +663,8 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A
* @return {string} 'male' or 'female' * @return {string} 'male' or 'female'
*/ */
return getGender = function (user) { return getGender = function (user) {
if (user.profile_attributes) { if (user.statistic_profile_attributes) {
if (user.profile_attributes.gender) { return 'male'; } else { return 'female'; } if (user.statistic_profile_attributes.gender) { return 'male'; } else { return 'female'; }
} else { return 'other'; } } else { return 'other'; }
}; };
} }

View File

@ -86,7 +86,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
return $uibModal.open({ return $uibModal.open({
templateUrl: '<%= asset_path "shared/signupModal.html" %>', templateUrl: '<%= asset_path "shared/signupModal.html" %>',
size: 'md', size: 'md',
controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', function ($scope, $uibModalInstance, Group, CustomAsset) { controller: ['$scope', '$uibModalInstance', 'Group', 'CustomAsset', 'growl', '_t', function ($scope, $uibModalInstance, Group, CustomAsset, growl, _t) {
// default parameters for the date picker in the account creation modal // default parameters for the date picker in the account creation modal
$scope.datePicker = { $scope.datePicker = {
format: Fablab.uibDateFormat, format: Fablab.uibDateFormat,
@ -134,8 +134,13 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
delete $scope.user.organization; delete $scope.user.organization;
// register on server // register on server
return Auth.register($scope.user).then(function (user) { return Auth.register($scope.user).then(function (user) {
if (user.id) {
// creation successful // creation successful
$uibModalInstance.close(user); $uibModalInstance.close(user);
} else {
// the user was not saved in database, something wrong occurred
growl.error(_t('unexpected_error_occurred'));
}
}, function (error) { }, function (error) {
// creation failed... // creation failed...
// restore organization param // restore organization param

View File

@ -258,7 +258,7 @@ Application.Controllers.controller('EditProfileController', ['$scope', '$rootSco
CSRF.setMetaTags(); CSRF.setMetaTags();
// init the birth date to JS object // init the birth date to JS object
$scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate(); $scope.user.statistic_profile.birthday = moment($scope.user.statistic_profile.birthday).toDate();
if ($scope.activeProvider.providable_type !== 'DatabaseProvider') { if ($scope.activeProvider.providable_type !== 'DatabaseProvider') {
$scope.preventPassword = true; $scope.preventPassword = true;

View File

@ -150,8 +150,8 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop
* @return {string} 'male' or 'female' * @return {string} 'male' or 'female'
*/ */
$scope.getGender = function (user) { $scope.getGender = function (user) {
if (user && user.profile) { if (user && user.statistic_profile) {
if (user.profile.gender === 'true') { return 'male'; } else { return 'female'; } if (user.statistic_profile.gender === 'true') { return 'male'; } else { return 'female'; }
} else { return 'other'; } } else { return 'other'; }
}; };

View File

@ -206,7 +206,7 @@ Application.Controllers.controller('CompleteProfileController', ['$scope', '$roo
CSRF.setMetaTags(); CSRF.setMetaTags();
// init the birth date to JS object // init the birth date to JS object
$scope.user.profile.birthday = moment($scope.user.profile.birthday).toDate(); $scope.user.statistic_profile.birthday = moment($scope.user.statistic_profile.birthday).toDate();
// bind fields protection with sso fields // bind fields protection with sso fields
angular.forEach(activeProviderPromise.mapping, function (map) { $scope.preventField[map] = true; }); angular.forEach(activeProviderPromise.mapping, function (map) { $scope.preventField[map] = true; });

View File

@ -1,18 +1,18 @@
<div class="row m-t"> <div class="row m-t">
<div class="col-sm-offset-3 col-sm-6"> <div class="col-sm-offset-3 col-sm-6">
<div class="form-group" ng-class="{'has-error': adminForm['admin[profile_attributes][gender]'].$dirty && adminForm['admin[profile_attributes][gender]'].$invalid}"> <div class="form-group" ng-class="{'has-error': adminForm['admin[statistic_profile_attributes][gender]'].$dirty && adminForm['admin[statistic_profile_attributes][gender]'].$invalid}">
<label class="checkbox-inline btn btn-default"> <label class="checkbox-inline btn btn-default">
<input type="radio" <input type="radio"
name="admin[profile_attributes][gender]" name="admin[statistic_profile_attributes][gender]"
ng-model="admin.profile_attributes.gender" ng-model="admin.statistic_profile_attributes.gender"
ng-value="true" ng-value="true"
required/> required/>
<i class="fa fa-male m-l-sm"></i> {{ 'man' | translate }} <i class="fa fa-male m-l-sm"></i> {{ 'man' | translate }}
</label> </label>
<label class="checkbox-inline btn btn-default"> <label class="checkbox-inline btn btn-default">
<input type="radio" <input type="radio"
name="admin[profile_attributes][gender]" name="admin[statistic_profile_attributes][gender]"
ng-model="admin.profile_attributes.gender" ng-model="admin.statistic_profile_attributes.gender"
ng-value="false"/> ng-value="false"/>
<i class="fa fa-female m-l-sm"></i> {{ 'woman' | translate }} <i class="fa fa-female m-l-sm"></i> {{ 'woman' | translate }}
</label> </label>
@ -73,13 +73,13 @@
<span class="help-block" ng-show="adminForm['admin[email]'].$dirty && adminForm['admin[email]'].$error.required" translate>{{ 'email_is_required' }}</span> <span class="help-block" ng-show="adminForm['admin[email]'].$dirty && adminForm['admin[email]'].$error.required" translate>{{ 'email_is_required' }}</span>
</div> </div>
<div class="form-group" ng-class="{'has-error': adminForm['admin[profile_attributes][birthday]'].$dirty && adminForm['admin[profile_attributes][birthday]'].$invalid}"> <div class="form-group" ng-class="{'has-error': adminForm['admin[statistic_profile_attributes][birthday]'].$dirty && adminForm['admin[statistic_profile_attributes][birthday]'].$invalid}">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"><i class="fa fa-calendar-o"></i> </span> <span class="input-group-addon"><i class="fa fa-calendar-o"></i> </span>
<input type="text" <input type="text"
id="user_birthday" id="user_birthday"
class="form-control" class="form-control"
ng-model="admin.profile_attributes.birthday" ng-model="admin.statistic_profile_attributes.birthday"
uib-datepicker-popup="{{datePicker.format}}" uib-datepicker-popup="{{datePicker.format}}"
datepicker-options="datePicker.options" datepicker-options="datePicker.options"
is-open="datePicker.opened" is-open="datePicker.opened"
@ -87,8 +87,8 @@
ng-click="openDatePicker($event)" ng-click="openDatePicker($event)"
/> />
<input type="hidden" <input type="hidden"
name="admin[profile_attributes][birthday]" name="admin[statistic_profile_attributes][birthday]"
value="{{admin.profile_attributes.birthday | toIsoDate}}" /> value="{{admin.statistic_profile_attributes.birthday | toIsoDate}}" />
</div> </div>
</div> </div>

View File

@ -3,6 +3,7 @@
<input name="_method" type="hidden" ng-value="method"> <input name="_method" type="hidden" ng-value="method">
<input name="user[profile_attributes][id]" type="hidden" ng-value="user.profile.id"> <input name="user[profile_attributes][id]" type="hidden" ng-value="user.profile.id">
<input name="user[invoicing_profile_attributes][id]" type="hidden" ng-value="user.invoicing_profile.id"> <input name="user[invoicing_profile_attributes][id]" type="hidden" ng-value="user.invoicing_profile.id">
<input name="user[statistic_profile_attributes][id]" type="hidden" ng-value="user.statistic_profile.id">
<div class="row m-t"> <div class="row m-t">
<div class="col-sm-3 col-sm-offset-1"> <div class="col-sm-3 col-sm-offset-1">
@ -38,27 +39,27 @@
</div> </div>
<div class="col-sm-offset-1 col-sm-6"> <div class="col-sm-offset-1 col-sm-6">
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$invalid}"> <div class="form-group" ng-class="{'has-error': userForm['user[statistic_profile_attributes][gender]'].$dirty && userForm['user[statistic_profile_attributes][gender]'].$invalid}">
<label class="checkbox-inline btn btn-default"> <label class="checkbox-inline btn btn-default">
<input type="radio" <input type="radio"
name="user[profile_attributes][gender]" name="user[statistic_profile_attributes][gender]"
ng-model="user.profile.gender" ng-model="user.statistic_profile.gender"
value="true" value="true"
ng-disabled="preventField['profile.gender'] && user.profile.gender && !userForm['user[profile_attributes][gender]'].$dirty" ng-disabled="preventField['profile.gender'] && user.statistic_profile.gender && !userForm['user[statistic_profile_attributes][gender]'].$dirty"
required/> required/>
<i class="fa fa-male m-l-sm"></i> {{ 'man' | translate }} <i class="fa fa-male m-l-sm"></i> {{ 'man' | translate }}
</label> </label>
<label class="checkbox-inline btn btn-default"> <label class="checkbox-inline btn btn-default">
<input type="radio" <input type="radio"
name="user[profile_attributes][gender]" name="user[statistic_profile_attributes][gender]"
ng-model="user.profile.gender" ng-model="user.statistic_profile.gender"
value="false" value="false"
ng-disabled="preventField['profile.gender'] && user.profile.gender && !userForm['user[profile_attributes][gender]'].$dirty"/> ng-disabled="preventField['profile.gender'] && user.statistic_profile.gender && !userForm['user[statistic_profile_attributes][gender]'].$dirty"/>
<i class="fa fa-female m-l-sm"></i> {{ 'woman' | translate }} <i class="fa fa-female m-l-sm"></i> {{ 'woman' | translate }}
</label> </label>
<span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span> <span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
<span class="help-block" ng-show="userForm['user[profile_attributes][gender]'].$dirty && userForm['user[profile_attributes][gender]'].$error.required" translate>{{ 'gender_is_required' }}</span> <span class="help-block" ng-show="userForm['user[statistic_profile_attributes][gender]'].$dirty && userForm['user[statistic_profile_attributes][gender]'].$error.required" translate>{{ 'gender_is_required' }}</span>
</div> </div>
@ -200,25 +201,25 @@
<span class="help-block" ng-show="userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$error.required" translate>{{ 'organization_address_is_required' }}</span> <span class="help-block" ng-show="userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$dirty && userForm['user[invoicing_profile_attributes][organization_attributes][address_attributes][address]'].$error.required" translate>{{ 'organization_address_is_required' }}</span>
</div> </div>
<div class="form-group" ng-class="{'has-error': userForm['user[profile_attributes][birthday]'].$dirty && userForm['user[profile_attributes][birthday]'].$invalid}"> <div class="form-group" ng-class="{'has-error': userForm['user[statistic_profile_attributes][birthday]'].$dirty && userForm['user[statistic_profile_attributes][birthday]'].$invalid}">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"><i class="fa fa-calendar-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span> <span class="input-group-addon"><i class="fa fa-calendar-o"></i> <span class="exponent"><i class="fa fa-asterisk" aria-hidden="true"></i></span></span>
<input type="text" <input type="text"
id="user_birthday" id="user_birthday"
class="form-control" class="form-control"
ng-model="user.profile.birthday" ng-model="user.statistic_profile.birthday"
uib-datepicker-popup="{{datePicker.format}}" uib-datepicker-popup="{{datePicker.format}}"
datepicker-options="datePicker.options" datepicker-options="datePicker.options"
is-open="datePicker.opened" is-open="datePicker.opened"
placeholder="{{ 'date_of_birth' | translate }}" placeholder="{{ 'date_of_birth' | translate }}"
ng-click="openDatePicker($event)" ng-click="openDatePicker($event)"
ng-disabled="preventField['profile.birthday'] && user.profile.birthday && !userForm['user[profile_attributes][birthday]'].$dirty" ng-disabled="preventField['profile.birthday'] && user.statistic_profile.birthday && !userForm['user[statistic_profile_attributes][birthday]'].$dirty"
required/> required/>
<input type="hidden" <input type="hidden"
name="user[profile_attributes][birthday]" name="user[statistic_profile_attributes][birthday]"
value="{{user.profile.birthday | toIsoDate}}" /> value="{{user.statistic_profile.birthday | toIsoDate}}" />
</div> </div>
<span class="help-block" ng-show="userForm['user[profile_attributes][birthday]'].$dirty && userForm['user[profile_attributes][birthday]'].$error.required" translate>{{ 'date_of_birth_is_required' }}</span> <span class="help-block" ng-show="userForm['user[statistic_profile_attributes][birthday]'].$dirty && userForm['user[statistic_profile_attributes][birthday]'].$error.required" translate>{{ 'date_of_birth_is_required' }}</span>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -12,14 +12,14 @@
<label class="checkbox-inline"> <label class="checkbox-inline">
<input type="radio" <input type="radio"
name="gender" name="gender"
ng-model="user.profile_attributes.gender" ng-model="user.statistic_profile_attributes.gender"
value="true" value="true"
required/> {{ 'man' | translate }} required/> {{ 'man' | translate }}
</label> </label>
<label class="checkbox-inline"> <label class="checkbox-inline">
<input type="radio" <input type="radio"
name="gender" name="gender"
ng-model="user.profile_attributes.gender" ng-model="user.statistic_profile_attributes.gender"
value="false"/> {{ 'woman' | translate }} value="false"/> {{ 'woman' | translate }}
</label> </label>
<span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span> <span class="exponent m-l-xs"><i class="fa fa-asterisk" aria-hidden="true"></i></span>
@ -182,7 +182,7 @@
<input type="text" <input type="text"
class="form-control" class="form-control"
name="birthday" name="birthday"
ng-model="user.profile_attributes.birthday" ng-model="user.statistic_profile_attributes.birthday"
uib-datepicker-popup="{{datePicker.format}}" uib-datepicker-popup="{{datePicker.format}}"
datepicker-options="datePicker.options" datepicker-options="datePicker.options"
is-open="datePicker.opened" is-open="datePicker.opened"

View File

@ -11,24 +11,13 @@ class API::AdminsController < API::ApiController
def create def create
authorize :admin authorize :admin
generated_password = Devise.friendly_token.first(8) res = UserService.create_admin(admin_params)
@admin = User.new(admin_params.merge(password: generated_password))
@admin.send :set_slug
# we associate the admin group to prevent linking any other 'normal' group (which won't be deletable afterwards) if res[:saved]
@admin.group = Group.find_by(slug: 'admins') @admin = res[:user]
# if the authentication is made through an SSO, generate a migration token
@admin.generate_auth_migration_token unless AuthProvider.active.providable_type == DatabaseProvider.name
if @admin.save(validate: false)
@admin.send_confirmation_instructions
@admin.add_role(:admin)
@admin.remove_role(:member)
UsersMailer.delay.notify_user_account_created(@admin, generated_password)
render :create, status: :created render :create, status: :created
else else
render json: @admin.errors.full_messages, status: :unprocessable_entity render json: res[:user].errors.full_messages, status: :unprocessable_entity
end end
end end
@ -47,8 +36,9 @@ class API::AdminsController < API::ApiController
def admin_params def admin_params
params.require(:admin).permit( params.require(:admin).permit(
:username, :email, :username, :email,
profile_attributes: %i[first_name last_name gender birthday phone], profile_attributes: %i[first_name last_name phone],
invoicing_profile_attributes: [address_attributes: [:address]] invoicing_profile_attributes: [address_attributes: [:address]],
statistic_profile_attributes: %i[gender birthday]
) )
end end
end end

View File

@ -189,32 +189,31 @@ class API::MembersController < API::ApiController
def user_params def user_params
if current_user.id == params[:id].to_i if current_user.id == params[:id].to_i
params.require(:user).permit(:username, :email, :password, :password_confirmation, :group_id, :is_allow_contact, params.require(:user).permit(:username, :email, :password, :password_confirmation, :group_id, :is_allow_contact, :is_allow_newsletter,
:is_allow_newsletter, profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job,
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest, :facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
:software_mastered, :website, :job, :facebook, :twitter,
:google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
user_avatar_attributes: %i[id attachment destroy]],
invoicing_profile_attributes: [
address_attributes: %i[id address],
organization_attributes: [:id, :name, address_attributes: %i[id address]]
])
elsif current_user.admin?
params.require(:user).permit(:username, :email, :password, :password_confirmation,
:is_allow_contact, :is_allow_newsletter, :group_id,
training_ids: [], tag_ids: [],
profile_attributes: [:id, :first_name, :last_name, :gender, :birthday, :phone, :interest,
:software_mastered, :website, :job, :facebook, :twitter,
:google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr, :dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
user_avatar_attributes: %i[id attachment destroy]], user_avatar_attributes: %i[id attachment destroy]],
invoicing_profile_attributes: [ invoicing_profile_attributes: [
:id, :id,
address_attributes: %i[id address], address_attributes: %i[id address],
organization_attributes: [:id, :name, address_attributes: %i[id address]] organization_attributes: [:id, :name, address_attributes: %i[id address]]
]) ],
statistic_profile_attributes: %i[id gender birthday])
elsif current_user.admin?
params.require(:user).permit(:username, :email, :password, :password_confirmation, :is_allow_contact, :is_allow_newsletter, :group_id,
training_ids: [], tag_ids: [],
profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job,
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
user_avatar_attributes: %i[id attachment destroy]],
invoicing_profile_attributes: [
:id,
address_attributes: %i[id address],
organization_attributes: [:id, :name, address_attributes: %i[id address]]
],
statistic_profile_attributes: %i[id gender birthday])
end end
end end

View File

@ -23,7 +23,7 @@ class API::ReservationsController < API::ApiController
def create def create
method = current_user.admin? ? :local : :stripe method = current_user.admin? ? :local : :stripe
user_id = current_user.admin? ? reservation_params[:user_id] : current_user.id user_id = current_user.admin? ? params[:reservation][:user_id] : current_user.id
@reservation = Reservation.new(reservation_params) @reservation = Reservation.new(reservation_params)
is_reserve = Reservations::Reserve.new(user_id, current_user.id) is_reserve = Reservations::Reserve.new(user_id, current_user.id)
@ -56,8 +56,7 @@ class API::ReservationsController < API::ApiController
end end
def reservation_params def reservation_params
params.require(:reservation).permit(:user_id, :message, :reservable_id, :reservable_type, :card_token, :plan_id, params.require(:reservation).permit(:message, :reservable_id, :reservable_type, :card_token, :plan_id, :nb_reserve_places,
:nb_reserve_places,
tickets_attributes: %i[event_price_category_id booked], tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered]) slots_attributes: %i[id start_at end_at availability_id offered])
end end

View File

@ -16,10 +16,10 @@ class API::SubscriptionsController < API::ApiController
head 403 head 403
else else
method = current_user.admin? ? :local : :stripe method = current_user.admin? ? :local : :stripe
user_id = current_user.admin? ? subscription_params[:user_id] : current_user.id user_id = current_user.admin? ? params[:subscription][:user_id] : current_user.id
@subscription = Subscription.new(subscription_params) @subscription = Subscription.new(subscription_params)
is_subscribe = Subscriptions::Subscribe.new(user_id, current_user.id) is_subscribe = Subscriptions::Subscribe.new(current_user.id, user_id)
.pay_and_save(@subscription, method, coupon_params[:coupon_code], true) .pay_and_save(@subscription, method, coupon_params[:coupon_code], true)
if is_subscribe if is_subscribe
@ -35,7 +35,7 @@ class API::SubscriptionsController < API::ApiController
free_days = params[:subscription][:free] || false free_days = params[:subscription][:free] || false
res = Subscriptions::Subscribe.new(@subscription.user_id, current_user.id) res = Subscriptions::Subscribe.new(current_user.id)
.extend_subscription(@subscription, subscription_update_params[:expired_at], free_days) .extend_subscription(@subscription, subscription_update_params[:expired_at], free_days)
if res.is_a?(Subscription) if res.is_a?(Subscription)
@subscription = res @subscription = res
@ -56,7 +56,7 @@ class API::SubscriptionsController < API::ApiController
# Never trust parameters from the scary internet, only allow the white list through. # Never trust parameters from the scary internet, only allow the white list through.
def subscription_params def subscription_params
params.require(:subscription).permit(:plan_id, :user_id, :card_token) params.require(:subscription).permit(:plan_id, :card_token)
end end
def coupon_params def coupon_params

View File

@ -14,24 +14,13 @@ class API::UsersController < API::ApiController
def create def create
if current_user.admin? if current_user.admin?
generated_password = Devise.friendly_token.first(8) res = UserService.create_partner(partner_params)
@user = User.new(email: partner_params[:email],
username: "#{partner_params[:first_name]}#{partner_params[:last_name]}",
password: generated_password,
password_confirmation: generated_password,
group_id: Group.first.id)
@user.build_profile(first_name: partner_params[:first_name],
last_name: partner_params[:last_name],
gender: true,
birthday: Time.now,
phone: '0000000000')
if @user.save if res[:saved]
@user.remove_role :member @user = res[:user]
@user.add_role :partner
render status: :created render status: :created
else else
render json: @user.errors.full_messages, status: :unprocessable_entity render json: res[:user].errors.full_messages, status: :unprocessable_entity
end end
else else
head 403 head 403

View File

@ -33,10 +33,11 @@ class ApplicationController < ActionController::Base
devise_parameter_sanitizer.permit(:sign_up, devise_parameter_sanitizer.permit(:sign_up,
keys: [ keys: [
{ {
profile_attributes: %i[phone last_name first_name gender birthday interest software_mastered], profile_attributes: %i[phone last_name first_name interest software_mastered],
invoicing_profile_attributes: [ invoicing_profile_attributes: [
organization_attributes: [:name, address_attributes: [:address]] organization_attributes: [:name, address_attributes: [:address]]
] ],
statistic_profile_attributes: %i[gender birthday]
}, },
:username, :is_allow_contact, :is_allow_newsletter, :cgu, :group_id :username, :is_allow_contact, :is_allow_newsletter, :cgu, :group_id
]) ])

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# Handling a new user registration through the sign-up modal
class RegistrationsController < Devise::RegistrationsController class RegistrationsController < Devise::RegistrationsController
# POST /resource # POST /users.json
def create def create
build_resource(sign_up_params) build_resource(sign_up_params)
@ -24,5 +27,4 @@ class RegistrationsController < Devise::RegistrationsController
respond_with resource respond_with resource
end end
end end
end end

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
# Devise controller for handling client sessions
class SessionsController < Devise::SessionsController class SessionsController < Devise::SessionsController
#before_action :set_csrf_headers, only: [:create, :destroy]
def new def new
active_provider = AuthProvider.active active_provider = AuthProvider.active
@ -9,9 +11,4 @@ class SessionsController < Devise::SessionsController
super super
end end
end end
protected
def set_csrf_headers
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end
end end

View File

@ -0,0 +1,3 @@
# Raised when an expected profile (statistic, invoicing or normal) was not found on an user
class NoProfileError < StandardError
end

View File

@ -1,6 +1,7 @@
class Group < ActiveRecord::Base class Group < ActiveRecord::Base
has_many :plans has_many :plans
has_many :users has_many :users
has_many :statistic_profiles
has_many :trainings_pricings, dependent: :destroy has_many :trainings_pricings, dependent: :destroy
has_many :machines_prices, -> { where(priceable_type: 'Machine') }, class_name: 'Price', dependent: :destroy has_many :machines_prices, -> { where(priceable_type: 'Machine') }, class_name: 'Price', dependent: :destroy
has_many :spaces_prices, -> { where(priceable_type: 'Space') }, class_name: 'Price', dependent: :destroy has_many :spaces_prices, -> { where(priceable_type: 'Space') }, class_name: 'Price', dependent: :destroy

View File

@ -1,3 +1,8 @@
# frozen_string_literal: true
# This table will save the user's profile data needed for legal accounting (invoices, wallet, etc.)
# Legal accounting must be kept for 10 years but GDPR requires that an user can delete his account at any time.
# The data will be kept even if the user is deleted, but it will be unlinked from the user's account.
class InvoicingProfile < ActiveRecord::Base class InvoicingProfile < ActiveRecord::Base
belongs_to :user belongs_to :user
has_one :address, as: :placeable, dependent: :destroy has_one :address, as: :placeable, dependent: :destroy
@ -11,17 +16,8 @@ class InvoicingProfile < ActiveRecord::Base
has_many :history_values, dependent: :nullify has_many :history_values, dependent: :nullify
after_create :create_a_wallet
def full_name def full_name
# if first_name or last_name is nil, the empty string will be used as a temporary replacement # if first_name or last_name is nil, the empty string will be used as a temporary replacement
(first_name || '').humanize.titleize + ' ' + (last_name || '').humanize.titleize (first_name || '').humanize.titleize + ' ' + (last_name || '').humanize.titleize
end end
private
def create_a_wallet
create_wallet
end
end end

View File

@ -10,11 +10,9 @@ class Profile < ActiveRecord::Base
validates :first_name, presence: true, length: { maximum: 30 } validates :first_name, presence: true, length: { maximum: 30 }
validates :last_name, presence: true, length: { maximum: 30 } validates :last_name, presence: true, length: { maximum: 30 }
validates :gender, inclusion: { in: [true, false] }
validates :birthday, presence: true
validates_numericality_of :phone, only_integer: true, allow_blank: false validates_numericality_of :phone, only_integer: true, allow_blank: false
after_save :update_invoicing_profile after_commit :update_invoicing_profile, if: :invoicing_data_was_modified?, on: [:update]
def full_name def full_name
# if first_name or last_name is nil, the empty string will be used as a temporary replacement # if first_name or last_name is nil, the empty string will be used as a temporary replacement
@ -25,19 +23,6 @@ class Profile < ActiveRecord::Base
full_name full_name
end end
def age
if birthday.present?
now = Time.now.utc.to_date
(now - birthday).to_f / 365.2425
else
''
end
end
def str_gender
gender ? 'male' : 'female'
end
def self.mapping def self.mapping
# we protect some fields as they are designed to be managed by the system and must not be updated externally # we protect some fields as they are designed to be managed by the system and must not be updated externally
blacklist = %w[id user_id created_at updated_at] blacklist = %w[id user_id created_at updated_at]
@ -51,19 +36,17 @@ class Profile < ActiveRecord::Base
private private
def invoicing_data_was_modified?
first_name_changed? or last_name_changed? or new_record?
end
def update_invoicing_profile def update_invoicing_profile
if user.invoicing_profile.nil? raise NoProfileError if user.invoicing_profile.nil?
InvoicingProfile.create!(
user: user,
first_name: first_name,
last_name: last_name
)
else
user.invoicing_profile.update_attributes( user.invoicing_profile.update_attributes(
first_name: first_name, first_name: first_name,
last_name: last_name last_name: last_name
) )
end end
end
end end

View File

@ -1,7 +1,7 @@
class Reservation < ActiveRecord::Base class Reservation < ActiveRecord::Base
include NotifyWith::NotificationAttachedObject include NotifyWith::NotificationAttachedObject
belongs_to :user belongs_to :statistic_profile
has_many :slots_reservations, dependent: :destroy has_many :slots_reservations, dependent: :destroy
has_many :slots, through: :slots_reservations has_many :slots, through: :slots_reservations
@ -240,8 +240,8 @@ class Reservation < ActiveRecord::Base
# TODO: refactoring # TODO: refactoring
customer = Stripe::Customer.retrieve(user.stp_customer_id) customer = Stripe::Customer.retrieve(user.stp_customer_id)
if plan_id if plan_id
self.subscription = Subscription.find_or_initialize_by(user_id: user.id) self.subscription = Subscription.find_or_initialize_by(statistic_profile_id: statistic_profile_id)
subscription.attributes = { plan_id: plan_id, user_id: user.id, card_token: card_token, expiration_date: nil } subscription.attributes = { plan_id: plan_id, statistic_profile_id: statistic_profile_id, card_token: card_token, expiration_date: nil }
if subscription.save_with_payment(operator_id, false) if subscription.save_with_payment(operator_id, false)
self.stp_invoice_id = invoice_items.first.refresh.invoice self.stp_invoice_id = invoice_items.first.refresh.invoice
invoice.stp_invoice_id = invoice_items.first.refresh.invoice invoice.stp_invoice_id = invoice_items.first.refresh.invoice
@ -338,8 +338,8 @@ class Reservation < ActiveRecord::Base
end end
# check reservation amount total and strip invoice total to pay is equal # check reservation amount total and strip invoice total to pay is equal
# @params stp_invoice[Stripe::Invoice] # @param stp_invoice[Stripe::Invoice]
# @params coupon_code[String] # @param coupon_code[String]
# return Boolean # return Boolean
def is_equal_reservation_total_and_stp_invoice_total(stp_invoice, coupon_code = nil) def is_equal_reservation_total_and_stp_invoice_total(stp_invoice, coupon_code = nil)
compute_amount_total_to_pay(coupon_code) == stp_invoice.total compute_amount_total_to_pay(coupon_code) == stp_invoice.total
@ -375,8 +375,8 @@ class Reservation < ActiveRecord::Base
return false unless valid? return false unless valid?
if plan_id if plan_id
self.subscription = Subscription.find_or_initialize_by(user_id: user.id) self.subscription = Subscription.find_or_initialize_by(statistic_profile_id: statistic_profile_id)
subscription.attributes = { plan_id: plan_id, user_id: user.id, expiration_date: nil } subscription.attributes = { plan_id: plan_id, statistic_profile_id: statistic_profile_id, expiration_date: nil }
if subscription.save_with_local_payment(operator_id, false) if subscription.save_with_local_payment(operator_id, false)
invoice.invoice_items.push InvoiceItem.new( invoice.invoice_items.push InvoiceItem.new(
amount: subscription.plan.amount, amount: subscription.plan.amount,
@ -405,6 +405,10 @@ class Reservation < ActiveRecord::Base
total total
end end
def user
statistic_profile.user
end
private private
def machine_not_already_reserved def machine_not_already_reserved

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
# This table will save the user's profile data needed for statistical purposes.
# GDPR requires that an user can delete his account at any time but we need to keep the statistics original data to being able to
# rebuild them at any time.
# The data will be kept even if the user is deleted, but it will be unlinked from the user's account (ie. anonymized)
class StatisticProfile < ActiveRecord::Base
belongs_to :user
belongs_to :group
# relations to reservations, trainings, subscriptions
has_many :subscriptions, dependent: :destroy
accepts_nested_attributes_for :subscriptions, allow_destroy: false
has_many :reservations, dependent: :destroy
accepts_nested_attributes_for :reservations, allow_destroy: false
def str_gender
gender ? 'male' : 'female'
end
def age
if birthday.present?
now = Time.now.utc.to_date
(now - birthday).to_f / 365.2425
else
''
end
end
end

View File

@ -2,7 +2,7 @@ class Subscription < ActiveRecord::Base
include NotifyWith::NotificationAttachedObject include NotifyWith::NotificationAttachedObject
belongs_to :plan belongs_to :plan
belongs_to :user belongs_to :statistic_profile
has_many :invoices, as: :invoiced, dependent: :destroy has_many :invoices, as: :invoiced, dependent: :destroy
has_many :offer_days, dependent: :destroy has_many :offer_days, dependent: :destroy
@ -223,6 +223,10 @@ class Subscription < ActiveRecord::Base
false false
end end
def user
statistic_profile.user
end
private private
def notify_member_subscribed_plan def notify_member_subscribed_plan

View File

@ -24,22 +24,19 @@ class User < ActiveRecord::Base
has_one :invoicing_profile, dependent: :nullify has_one :invoicing_profile, dependent: :nullify
accepts_nested_attributes_for :invoicing_profile accepts_nested_attributes_for :invoicing_profile
has_one :statistic_profile, dependent: :nullify
accepts_nested_attributes_for :statistic_profile
has_many :my_projects, foreign_key: :author_id, class_name: 'Project', dependent: :destroy has_many :my_projects, foreign_key: :author_id, class_name: 'Project', dependent: :destroy
has_many :project_users, dependent: :destroy has_many :project_users, dependent: :destroy
has_many :projects, through: :project_users has_many :projects, through: :project_users
has_many :reservations, dependent: :destroy
accepts_nested_attributes_for :reservations, allow_destroy: true
# Trainings that were already passed # Trainings that were already passed
has_many :user_trainings, dependent: :destroy has_many :user_trainings, dependent: :destroy
has_many :trainings, through: :user_trainings has_many :trainings, through: :user_trainings
belongs_to :group belongs_to :group
has_many :subscriptions, dependent: :destroy
accepts_nested_attributes_for :subscriptions, allow_destroy: true
has_many :users_credits, dependent: :destroy has_many :users_credits, dependent: :destroy
has_many :credits, through: :users_credits has_many :credits, through: :users_credits
@ -62,20 +59,27 @@ class User < ActiveRecord::Base
before_create :assign_default_role before_create :assign_default_role
after_commit :create_stripe_customer, on: [:create] after_commit :create_stripe_customer, on: [:create]
after_commit :notify_admin_when_user_is_created, on: :create after_commit :notify_admin_when_user_is_created, on: :create
after_create :init_dependencies
after_update :notify_group_changed, if: :group_id_changed? after_update :notify_group_changed, if: :group_id_changed?
after_save :update_invoicing_profile after_update :update_invoicing_profile, if: :invoicing_data_was_modified?
after_update :update_statistic_profile, if: :statistic_data_was_modified?
attr_accessor :cgu attr_accessor :cgu
delegate :first_name, to: :profile delegate :first_name, to: :profile
delegate :last_name, to: :profile delegate :last_name, to: :profile
delegate :subscriptions, to: :statistic_profile
delegate :reservations, to: :statistic_profile
delegate :wallet, to: :invoicing_profile
delegate :wallet_transactions, to: :invoicing_profile
delegate :invoices, to: :invoicing_profile
validate :cgu_must_accept, if: :new_record? validate :cgu_must_accept, if: :new_record?
validates :username, presence: true, uniqueness: true, length: { maximum: 30 } validates :username, presence: true, uniqueness: true, length: { maximum: 30 }
scope :active, -> { where(is_active: true) } scope :active, -> { where(is_active: true) }
scope :without_subscription, -> { includes(:subscriptions).where(subscriptions: { user_id: nil }) } scope :without_subscription, -> { includes(statistic_profile: [:subscriptions]).where(subscriptions: { statistic_profile_id: nil }) }
scope :with_subscription, -> { joins(:subscriptions) } scope :with_subscription, -> { joins(statistic_profile: [:subscriptions]) }
def to_json(*) def to_json(*)
ApplicationController.new.view_context.render( ApplicationController.new.view_context.render(
@ -128,18 +132,6 @@ class User < ActiveRecord::Base
my_projects.to_a.concat projects my_projects.to_a.concat projects
end end
def invoices
invoicing_profile.invoices
end
def wallet
invoicing_profile.wallet
end
def wallet_transactions
invoicing_profile.wallet_transactions
end
def generate_subscription_invoice(operator_id) def generate_subscription_invoice(operator_id)
return unless subscription return unless subscription
@ -166,9 +158,7 @@ class User < ActiveRecord::Base
def self.from_omniauth(auth) def self.from_omniauth(auth)
active_provider = AuthProvider.active active_provider = AuthProvider.active
if active_provider.strategy_name != auth.provider raise SecurityError, 'The identity provider does not match the activated one' if active_provider.strategy_name != auth.provider
raise SecurityError, 'The identity provider does not match the activated one'
end
where(provider: auth.provider, uid: auth.uid).first_or_create.tap do |user| where(provider: auth.provider, uid: auth.uid).first_or_create.tap do |user|
# execute this regardless of whether record exists or not (-> User#tap) # execute this regardless of whether record exists or not (-> User#tap)
@ -182,8 +172,8 @@ class User < ActiveRecord::Base
end end
def need_completion? def need_completion?
profile.gender.nil? || profile.first_name.blank? || profile.last_name.blank? || username.blank? || statistic_profile.gender.nil? || profile.first_name.blank? || profile.last_name.blank? || username.blank? ||
email.blank? || encrypted_password.blank? || group_id.nil? || profile.birthday.blank? || profile.phone.blank? email.blank? || encrypted_password.blank? || group_id.nil? || statistic_profile.birthday.blank? || profile.phone.blank?
end end
## Retrieve the requested data in the User and user's Profile tables ## Retrieve the requested data in the User and user's Profile tables
@ -244,9 +234,7 @@ class User < ActiveRecord::Base
## and remove the auth_token to mark his account as "migrated" ## and remove the auth_token to mark his account as "migrated"
def link_with_omniauth_provider(auth) def link_with_omniauth_provider(auth)
active_provider = AuthProvider.active active_provider = AuthProvider.active
if active_provider.strategy_name != auth.provider raise SecurityError, 'The identity provider does not match the activated one' if active_provider.strategy_name != auth.provider
raise SecurityError, 'The identity provider does not match the activated one'
end
if User.where(provider: auth.provider, uid: auth.uid).size.positive? if User.where(provider: auth.provider, uid: auth.uid).size.positive?
raise DuplicateIndexError, "This #{active_provider.name} account is already linked to an existing user" raise DuplicateIndexError, "This #{active_provider.name} account is already linked to an existing user"
@ -362,16 +350,52 @@ class User < ActiveRecord::Base
attached_object: self attached_object: self
end end
def update_invoicing_profile def invoicing_data_was_modified?
email_changed?
end
def statistic_data_was_modified?
group_id_changed?
end
def init_dependencies
if invoicing_profile.nil? if invoicing_profile.nil?
InvoicingProfile.create!( ip = InvoicingProfile.create!(
user: user, user: self,
email: email email: email,
first_name: first_name,
last_name: last_name
)
end
if wallet.nil?
ip ||= invoicing_profile
Wallet.create!(
invoicing_profile: ip
)
end
if statistic_profile.nil?
StatisticProfile.create!(
user: self,
group_id: group_id
) )
else else
update_statistic_profile
end
end
def update_invoicing_profile
raise NoProfileError if invoicing_profile.nil?
invoicing_profile.update_attributes( invoicing_profile.update_attributes(
email: email email: email
) )
end end
def update_statistic_profile
raise NoProfileError if statistic_profile.nil?
statistic_profile.update_attributes(
group_id: group_id
)
end end
end end

View File

@ -2,7 +2,7 @@ class UserPolicy < ApplicationPolicy
class Scope < Scope class Scope < Scope
def resolve def resolve
if user.admin? if user.admin?
scope.includes(:group, :training_credits, :machine_credits, subscriptions: [plan: [:credits]], profile: [:user_avatar]) scope.includes(:group, :training_credits, :machine_credits, statistic_profile: [subscriptions: [plan: [:credits]]], profile: [:user_avatar])
.joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc') .joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc')
else else
scope.includes(profile: [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'") scope.includes(profile: [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'")

View File

@ -93,7 +93,7 @@ class Availabilities::AvailabilitiesService
def reservations(reservable) def reservations(reservable)
Reservation.where('reservable_type = ? and reservable_id = ?', reservable.class.name, reservable.id) Reservation.where('reservable_type = ? and reservable_id = ?', reservable.class.name, reservable.id)
.includes(:slots, user: [:profile]) .includes(:slots, statistic_profile: [user: [:profile]])
.references(:slots, :user) .references(:slots, :user)
.where('slots.start_at > ?', Time.now) .where('slots.start_at > ?', Time.now)
end end

View File

@ -4,16 +4,25 @@
class Members::ListService class Members::ListService
class << self class << self
def list(params) def list(params)
@query = User.includes(:profile, :group, :subscriptions) @query = User.includes(:profile, :group, :statistic_profile)
.joins(:profile, .joins(:profile,
:statistic_profile,
:group, :group,
:roles, :roles,
'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id" ' \ 'LEFT JOIN (
SELECT *
FROM "subscriptions" AS s1
INNER JOIN (
SELECT MAX("created_at") AS "s2_created_at", "statistic_profile_id" AS "s2_statistic_profile_id"
FROM "subscriptions"
GROUP BY "statistic_profile_id"
) As s2
ON "s1"."statistic_profile_id" = "s2"."s2_statistic_profile_id"
WHERE "s1"."expiration_date" > now()::date
) AS "subscriptions" ON "subscriptions"."statistic_profile_id" = "statistic_profiles"."id" ' \
'LEFT JOIN "plans" ON "plans"."id" = "subscriptions"."plan_id"') 'LEFT JOIN "plans" ON "plans"."id" = "subscriptions"."plan_id"')
.where("users.is_active = 'true' AND roles.name = 'member'") .where("users.is_active = 'true' AND roles.name = 'member'")
.order(list_order(params)) .order(list_order(params))
.page(params[:page])
.per(params[:size])
# ILIKE => PostgreSQL case-insensitive LIKE # ILIKE => PostgreSQL case-insensitive LIKE
if params[:search].size.positive? if params[:search].size.positive?
@ -31,12 +40,13 @@ class Members::ListService
def search(current_user, query, subscription) def search(current_user, query, subscription)
members = User.includes(:profile) members = User.includes(:profile)
.joins(:profile, .joins(:profile,
:statistic_profile,
:roles, :roles,
'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id" AND ' \ 'LEFT JOIN "subscriptions" ON "subscriptions"."statistic_profile_id" = "statistic_profiles"."id" AND ' \
'"subscriptions"."created_at" = ( ' \ '"subscriptions"."created_at" = ( ' \
'SELECT max("created_at") ' \ 'SELECT max("created_at") ' \
'FROM "subscriptions" ' \ 'FROM "subscriptions" ' \
'WHERE "user_id" = "users"."id")') 'WHERE "statistic_profile_id" = "statistic_profiles"."id")')
.where("users.is_active = 'true' AND roles.name = 'member'") .where("users.is_active = 'true' AND roles.name = 'member'")
.limit(50) .limit(50)
query.downcase.split(' ').each do |word| query.downcase.split(' ').each do |word|
@ -64,6 +74,8 @@ class Members::ListService
def list_order(params) def list_order(params)
direction = (params[:order_by][0] == '-' ? 'DESC' : 'ASC') direction = (params[:order_by][0] == '-' ? 'DESC' : 'ASC')
order_key = (params[:order_by][0] == '-' ? params[:order_by][1, params[:order_by].size] : params[:order_by]) order_key = (params[:order_by][0] == '-' ? params[:order_by][1, params[:order_by].size] : params[:order_by])
limit = params[:size]
offset = (params[:page]&.to_i || 1) - 1
order_key = case order_key order_key = case order_key
when 'last_name' when 'last_name'
@ -82,7 +94,7 @@ class Members::ListService
'users.id' 'users.id'
end end
"#{order_key} #{direction}" "#{order_key} #{direction} LIMIT #{limit} OFFSET #{offset}"
end end
end end
end end

View File

@ -10,7 +10,7 @@ class Reservations::Reserve
end end
def pay_and_save(reservation, payment_method, coupon) def pay_and_save(reservation, payment_method, coupon)
reservation.user_id = user_id reservation.statistic_profile_id = User.find(user_id).statistic_profile.id
if payment_method == :local if payment_method == :local
reservation.save_with_local_payment(operator_id, coupon) reservation.save_with_local_payment(operator_id, coupon)
elsif payment_method == :stripe elsif payment_method == :stripe

View File

@ -356,8 +356,8 @@ class StatisticService
def user_info(user) def user_info(user)
{ {
user_id: user.id, user_id: user.id,
gender: user.profile.str_gender, gender: user.statistic_profile.str_gender,
age: user.profile.age, age: user.statistic_profile.age,
group: user.group ? user.group.slug : nil, group: user.group ? user.group.slug : nil,
email: user.email email: user.email
} }

View File

@ -4,13 +4,15 @@
class Subscriptions::Subscribe class Subscriptions::Subscribe
attr_accessor :user_id, :operator_id attr_accessor :user_id, :operator_id
def initialize(user_id, operator_id) def initialize(operator_id, user_id = nil)
@user_id = user_id @user_id = user_id
@operator_id = operator_id @operator_id = operator_id
end end
def pay_and_save(subscription, payment_method, coupon, invoice) def pay_and_save(subscription, payment_method, coupon, invoice)
subscription.user_id = user_id return false if user_id.nil?
subscription.statistic_profile_id = User.find(user_id).statistic_profile.id
if payment_method == :local if payment_method == :local
subscription.save_with_local_payment(operator_id, invoice, coupon) subscription.save_with_local_payment(operator_id, invoice, coupon)
elsif payment_method == :stripe elsif payment_method == :stripe
@ -23,7 +25,7 @@ class Subscriptions::Subscribe
new_sub = Subscription.create( new_sub = Subscription.create(
plan_id: subscription.plan_id, plan_id: subscription.plan_id,
user_id: subscription.user_id, statistic_profile_id: subscription.statistic_profile_id,
expiration_date: new_expiration_date expiration_date: new_expiration_date
) )
if new_sub.save if new_sub.save

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
# helpers for managing users with special roles
class UserService
def self.create_partner(params)
generated_password = Devise.friendly_token.first(8)
group_id = Group.first.id
user = User.new(
email: params[:email],
username: "#{params[:first_name]}#{params[:last_name]}".parameterize,
password: generated_password,
password_confirmation: generated_password,
group_id: group_id
)
user.build_profile(
first_name: params[:first_name],
last_name: params[:last_name],
phone: '0000000000'
)
user.build_statistic_profile(
gender: true,
birthday: Time.now
)
saved = user.save
if saved
user.remove_role :member
user.add_role :partner
end
{ saved: saved, user: user }
end
def self.create_admin(params)
generated_password = Devise.friendly_token.first(8)
admin = User.new(params.merge(password: generated_password))
admin.send :set_slug
# we associate the admin group to prevent linking any other 'normal' group (which won't be deletable afterwards)
admin.group = Group.find_by(slug: 'admins')
# if the authentication is made through an SSO, generate a migration token
admin.generate_auth_migration_token unless AuthProvider.active.providable_type == DatabaseProvider.name
saved = admin.save(validate: false)
if saved
admin.send_confirmation_instructions
admin.add_role(:admin)
admin.remove_role(:member)
UsersMailer.delay.notify_user_account_created(admin, generated_password)
end
{ saved: saved, user: admin }
end
end

View File

@ -1,7 +1,7 @@
class SubscriptionGroupValidator < ActiveModel::Validator class SubscriptionGroupValidator < ActiveModel::Validator
def validate(record) def validate(record)
if record.user.group != record.plan.group return if record.statistic_profile.group_id == record.plan.group_id
record.errors[:plan_id] << "This plan is not compatible with the current user's group" record.errors[:plan_id] << "This plan is not compatible with the current user's group"
end end
end
end end

View File

@ -3,8 +3,8 @@ json.profile_attributes do
json.id admin.profile.id json.id admin.profile.id
json.first_name admin.profile.first_name json.first_name admin.profile.first_name
json.last_name admin.profile.last_name json.last_name admin.profile.last_name
json.gender admin.profile.gender json.gender admin.statistic_profile.gender
json.birthday admin.profile.birthday if admin.profile.birthday json.birthday admin.statistic_profile.birthday if admin.statistic_profile.birthday
json.phone admin.profile.phone json.phone admin.profile.phone
if admin.profile.user_avatar if admin.profile.user_avatar
json.user_avatar do json.user_avatar do

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
json.extract! member, :id, :username, :email, :group_id json.extract! member, :id, :username, :email, :group_id
json.role member.roles.first.name json.role member.roles.first.name
json.name member.profile.full_name json.name member.profile.full_name
@ -13,8 +15,6 @@ json.profile do
end end
json.first_name member.profile.first_name json.first_name member.profile.first_name
json.last_name member.profile.last_name json.last_name member.profile.last_name
json.gender member.profile.gender.to_s
json.birthday member.profile.birthday.to_date.iso8601 if member.profile.birthday
json.interest member.profile.interest json.interest member.profile.interest
json.software_mastered member.profile.software_mastered json.software_mastered member.profile.software_mastered
json.phone member.profile.phone json.phone member.profile.phone
@ -46,6 +46,12 @@ json.invoicing_profile do
end end
end end
json.statistic_profile do
json.id member.statistic_profile.id
json.gender member.statistic_profile.gender.to_s
json.birthday member.statistic_profile&.birthday&.to_date&.iso8601
end
if member.subscribed_plan if member.subscribed_plan
json.subscribed_plan do json.subscribed_plan do
json.partial! 'api/shared/plan', plan: member.subscribed_plan json.partial! 'api/shared/plan', plan: member.subscribed_plan

View File

@ -1,4 +1,6 @@
user_is_admin = (current_user and current_user.admin?) # frozen_string_literal: true
user_is_admin = current_user&.admin?
max_members = @query.except(:offset, :limit, :order).count max_members = @query.except(:offset, :limit, :order).count
json.array!(@members) do |member| json.array!(@members) do |member|
@ -10,34 +12,48 @@ json.array!(@members) do |member|
json.email member.email if current_user json.email member.email if current_user
json.first_name member.profile.first_name json.first_name member.profile.first_name
json.last_name member.profile.last_name json.last_name member.profile.last_name
json.need_completion member.need_completion?
json.group_id member.group_id
if attribute_requested?(@requested_attributes, 'profile')
json.profile do json.profile do
if member.profile.user_avatar
json.user_avatar do json.user_avatar do
json.id member.profile.user_avatar.id json.id member.profile.user_avatar.id
json.attachment_url member.profile.user_avatar.attachment_url json.attachment_url member.profile.user_avatar.attachment_url
end if member.profile.user_avatar end
end
json.first_name member.profile.first_name json.first_name member.profile.first_name
json.last_name member.profile.last_name json.last_name member.profile.last_name
json.gender member.profile.gender.to_s
if user_is_admin
json.phone member.profile.phone json.phone member.profile.phone
json.birthday member.profile.birthday.iso8601 if member.profile.birthday
end end
end if attribute_requested?(@requested_attributes, 'profile') if user_is_admin
json.need_completion member.need_completion? json.statistic_profile do
json.group_id member.group_id json.gender member.statistic_profile.gender.to_s
json.birthday member.statistic_profile&.birthday&.iso8601
end
end
end
if attribute_requested?(@requested_attributes, 'group') && member.group
json.group do json.group do
json.id member.group.id json.id member.group.id
json.name member.group.name json.name member.group.name
end if attribute_requested?(@requested_attributes, 'group') and member.group end
end
if attribute_requested?(@requested_attributes, 'subscription')
if user_is_admin if user_is_admin
if member.subscribed_plan
json.subscribed_plan do json.subscribed_plan do
json.partial! 'api/shared/plan', plan: member.subscribed_plan json.partial! 'api/shared/plan', plan: member.subscribed_plan
end if member.subscribed_plan end
end
if member.subscription
json.subscription do json.subscription do
json.id member.subscription.id json.id member.subscription.id
json.expired_at member.subscription.expired_at.iso8601 json.expired_at member.subscription.expired_at.iso8601
json.canceled_at member.subscription.canceled_at.iso8601 if member.subscription.canceled_at json.canceled_at member.subscription&.canceled_at&.iso8601
json.stripe member.subscription.stp_subscription_id.present? json.stripe member.subscription.stp_subscription_id.present?
json.plan do json.plan do
json.id member.subscription.plan.id json.id member.subscription.plan.id
@ -45,20 +61,28 @@ json.array!(@members) do |member|
json.interval member.subscription.plan.interval json.interval member.subscription.plan.interval
json.amount member.subscription.plan.amount ? (member.subscription.plan.amount / 100.0) : 0 json.amount member.subscription.plan.amount ? (member.subscription.plan.amount / 100.0) : 0
end end
end if member.subscription end
end if attribute_requested?(@requested_attributes, 'subscription') end
end
end
if attribute_requested?(@requested_attributes, 'credits') || attribute_requested?(@requested_attributes, 'training_credits')
json.training_credits member.training_credits do |tc| json.training_credits member.training_credits do |tc|
json.training_id tc.creditable_id json.training_id tc.creditable_id
end if attribute_requested?(@requested_attributes, 'credits') or attribute_requested?(@requested_attributes, 'training_credits') end
end
if attribute_requested?(@requested_attributes, 'credits') || attribute_requested?(@requested_attributes, 'machine_credits')
json.machine_credits member.machine_credits do |mc| json.machine_credits member.machine_credits do |mc|
json.machine_id mc.creditable_id json.machine_id mc.creditable_id
json.hours_used mc.users_credits.find_by(user_id: member.id).hours_used json.hours_used mc.users_credits.find_by(user_id: member.id).hours_used
end if attribute_requested?(@requested_attributes, 'credits') or attribute_requested?(@requested_attributes, 'machine_credits') end
end
if attribute_requested?(@requested_attributes, 'tags')
json.tags member.tags do |t| json.tags member.tags do |t|
json.id t.id json.id t.id
json.name t.name json.name t.name
end if attribute_requested?(@requested_attributes, 'tags') end
end
end end

View File

@ -39,13 +39,15 @@ json.all_projects @member.all_projects do |project|
json.first_name pu.user.profile.first_name json.first_name pu.user.profile.first_name
json.last_name pu.user.profile.last_name json.last_name pu.user.profile.last_name
json.full_name pu.user.profile.full_name json.full_name pu.user.profile.full_name
if pu.user.profile.user_avatar
json.user_avatar do json.user_avatar do
json.id pu.user.profile.user_avatar.id json.id pu.user.profile.user_avatar.id
json.attachment_url pu.user.profile.user_avatar.attachment_url json.attachment_url pu.user.profile.user_avatar.attachment_url
end if pu.user.profile.user_avatar end
end
json.username pu.user.username json.username pu.user.username
json.is_valid pu.is_valid json.is_valid pu.is_valid
json.valid_token pu.valid_token if !pu.is_valid and @member == pu.user json.valid_token pu.valid_token if !pu.is_valid && @member == pu.user
end end
end end
end end

View File

@ -2,6 +2,6 @@ json.title notification.notification_type
json.description _t('.user_NAME_changed_his_group_html', json.description _t('.user_NAME_changed_his_group_html',
{ {
NAME: notification.attached_object.profile.full_name, NAME: notification.attached_object.profile.full_name,
GENDER: bool_to_sym(notification.attached_object.profile.gender) GENDER: bool_to_sym(notification.attached_object.statistic_profile.gender)
}) # messageFormat }) # messageFormat
json.url notification_url(notification, format: :json) json.url notification_url(notification, format: :json)

View File

@ -2,7 +2,7 @@ json.title notification.notification_type
json.description _t('.user_NAME_has_merged_his_account_with_the_one_imported_from_PROVIDER_UID_html', json.description _t('.user_NAME_has_merged_his_account_with_the_one_imported_from_PROVIDER_UID_html',
{ {
NAME: notification.attached_object.profile.full_name, NAME: notification.attached_object.profile.full_name,
GENDER: bool_to_sym(notification.attached_object.profile.gender), GENDER: bool_to_sym(notification.attached_object.statistic_profile.gender),
PROVIDER: notification.attached_object.provider, PROVIDER: notification.attached_object.provider,
UID: notification.attached_object.uid UID: notification.attached_object.uid
}) # messageFormat }) # messageFormat

View File

@ -1,5 +1,5 @@
json.id reservation.id json.id reservation.id
json.user_id reservation.user_id json.user_id reservation.statistic_profile.user_id
json.user_full_name reservation.user.profile.full_name json.user_full_name reservation.user.profile.full_name
json.message reservation.message json.message reservation.message
json.slots reservation.slots do |s| json.slots reservation.slots do |s|

View File

@ -1,16 +1,18 @@
json.id @reservation.id json.id @reservation.id
json.user_id @reservation.user_id json.user_id @reservation.statistic_profile.user_id
json.user do json.user do
json.id @reservation.user.id json.id @reservation.user.id
if @reservation.user.subscribed_plan
json.subscribed_plan do json.subscribed_plan do
json.partial! 'api/shared/plan', plan: @reservation.user.subscribed_plan json.partial! 'api/shared/plan', plan: @reservation.user.subscribed_plan
end if @reservation.user.subscribed_plan end
end
json.training_credits @reservation.user.training_credits do |tc| json.training_credits @reservation.user.training_credits do |tc|
json.training_id tc.creditable_id json.training_id tc.creditable_id
end end
json.machine_credits @reservation.user.machine_credits do |mc| json.machine_credits @reservation.user.machine_credits do |mc|
json.machine_id mc.creditable_id json.machine_id mc.creditable_id
json.hours_used mc.users_credits.find_by(user_id: @reservation.user_id).hours_used json.hours_used mc.users_credits.find_by(user_id: @reservation.statistic_profile.user_id).hours_used
end end
end end
json.message @reservation.message json.message @reservation.message

View File

@ -1,2 +1,4 @@
json.extract! @user, :id, :email, :first_name, :last_name # frozen_string_literal: true
json.name "#{@user.first_name} #{@user.last_name}"
json.extract! @user, :id, :email, :first_name, :last_name
json.name @user.profile.full_name

View File

@ -1,4 +1,4 @@
json.users @users do |user| json.users @users do |user|
json.extract! user, :id, :email, :first_name, :last_name json.extract! user, :id, :email, :first_name, :last_name
json.name "#{user.first_name} #{user.last_name}" json.name user.profile.full_name
end end

View File

@ -14,10 +14,14 @@ json.invoices do
end end
end end
json.user do json.user do
json.extract! invoice[:invoice].user, :id, :email, :created_at json.extract! invoice[:invoice].invoicing_profile, :user_id, :email, :first_name, :last_name
json.profile do json.address invoice[:invoice].invoicing_profile&.address&.address
json.extract! invoice[:invoice].user.profile, :id, :first_name, :last_name, :birthday, :phone json.invoicing_profile_id invoice[:invoice].invoicing_profile.id
json.gender invoice[:invoice].user.profile.gender ? 'male' : 'female' if invoice[:invoice].invoicing_profile.organization
json.organization do
json.extract! invoice[:invoice].invoicing_profile.organization, :name, :id
json.address invoice[:invoice].invoicing_profile.organization&.address&.address
end
end end
end end
json.invoice_items invoice[:invoice].invoice_items do |item| json.invoice_items invoice[:invoice].invoice_items do |item|

View File

@ -42,7 +42,7 @@ wb.add_worksheet(name: t('export_members.members')) do |sheet|
member.profile.first_name, member.profile.first_name,
member.email, member.email,
member.is_allow_newsletter, member.is_allow_newsletter,
member.profile.gender ? t('export_members.man') : t('export_members.woman'), member.statistic_profile.gender ? t('export_members.man') : t('export_members.woman'),
member.profile.age, member.profile.age,
member.invoicing_profile.address ? member.invoicing_profile.address.address : '', member.invoicing_profile.address ? member.invoicing_profile.address.address : '',
member.profile.phone, member.profile.phone,

View File

@ -86,6 +86,7 @@ en:
i_ve_read_and_i_accept_: "I've read and I accept" i_ve_read_and_i_accept_: "I've read and I accept"
_the_fablab_policy: "the FabLab policy" _the_fablab_policy: "the FabLab policy"
field_required: "Field required" field_required: "Field required"
unexpected_error_occurred: "An unexpected error occurred. Please try again later."
# password modification modal # password modification modal
change_your_password: "Change your password" change_your_password: "Change your password"

View File

@ -84,7 +84,8 @@ es:
i_accept_to_receive_information_from_the_fablab: "Acepto recibir información del FabLab" i_accept_to_receive_information_from_the_fablab: "Acepto recibir información del FabLab"
i_ve_read_and_i_accept_: "He leido y acepto" i_ve_read_and_i_accept_: "He leido y acepto"
_the_fablab_policy: "la política de FabLab" _the_fablab_policy: "la política de FabLab"
field_required: "Field required" #translation_missing field_required: "Field required" # translation missing
unexpected_error_occurred: "An unexpected error occurred. Please try again later." # translation missing
# password modification modal # password modification modal
change_your_password: "Cambiar contraseña" change_your_password: "Cambiar contraseña"

View File

@ -86,6 +86,7 @@ fr:
i_ve_read_and_i_accept_: "J'ai lu et j'accepte" i_ve_read_and_i_accept_: "J'ai lu et j'accepte"
_the_fablab_policy: "la charte d'utilisation du Fab Lab" _the_fablab_policy: "la charte d'utilisation du Fab Lab"
field_required: "Champ requis" field_required: "Champ requis"
unexpected_error_occurred: "Une erreur inattendue s'est produite. Veuillez réessayer ultérieurement."
# fenêtre de changement de mot de passe # fenêtre de changement de mot de passe
change_your_password: "Modifier votre mot de passe" change_your_password: "Modifier votre mot de passe"

View File

@ -85,7 +85,8 @@ pt:
i_accept_to_receive_information_from_the_fablab: "Eu aceito receber informações do FabLab" i_accept_to_receive_information_from_the_fablab: "Eu aceito receber informações do FabLab"
i_ve_read_and_i_accept_: "Eu li e aceito" i_ve_read_and_i_accept_: "Eu li e aceito"
_the_fablab_policy: "a política do FabLab" _the_fablab_policy: "a política do FabLab"
field_required: "Field required" #translation_missing field_required: "Field required" # translation missing
unexpected_error_occurred: "An unexpected error occurred. Please try again later." # translation missing
# password modification modal # password modification modal
change_your_password: "Mudar sua senha" change_your_password: "Mudar sua senha"

View File

@ -7,12 +7,14 @@ Rails.application.routes.draw do
if AuthProvider.active.providable_type == DatabaseProvider.name if AuthProvider.active.providable_type == DatabaseProvider.name
# with local authentification we do not use omniAuth so we must differentiate the config # with local authentification we do not use omniAuth so we must differentiate the config
devise_for :users, controllers: {registrations: 'registrations', sessions: 'sessions', devise_for :users, controllers: {
confirmations: 'confirmations', passwords: 'passwords'} registrations: 'registrations', sessions: 'sessions', confirmations: 'confirmations', passwords: 'passwords'
}
else else
devise_for :users, controllers: {registrations: 'registrations', sessions: 'sessions', devise_for :users, controllers: {
confirmations: 'confirmations', passwords: 'passwords', registrations: 'registrations', sessions: 'sessions', confirmations: 'confirmations', passwords: 'passwords',
:omniauth_callbacks => 'users/omniauth_callbacks'} omniauth_callbacks: 'users/omniauth_callbacks'
}
end end

View File

@ -11,5 +11,8 @@ class CreateInvoicingProfiles < ActiveRecord::Migration
add_reference :organizations, :invoicing_profile, index: true, foreign_key: true add_reference :organizations, :invoicing_profile, index: true, foreign_key: true
add_reference :invoices, :invoicing_profile, index: true, foreign_key: true add_reference :invoices, :invoicing_profile, index: true, foreign_key: true
add_reference :wallets, :invoicing_profile, index: true, foreign_key: true
add_reference :wallet_transactions, :invoicing_profile, index: true, foreign_key: true
add_reference :history_values, :invoicing_profile, index: true, foreign_key: true
end end
end end

View File

@ -0,0 +1,9 @@
class RemoveUserIdColumns < ActiveRecord::Migration
def change
remove_column :invoices, :user_id, :integer
remove_reference :organizations, :profile, index: true, foreign_key: true
remove_reference :wallets, :user, index: true, foreign_key: true
remove_reference :wallet_transactions, :user, index: true, foreign_key: true
remove_reference :history_values, :user, index: true, foreign_key: true
end
end

View File

@ -1,5 +0,0 @@
class RemoveUserIdFromInvoice < ActiveRecord::Migration
def change
remove_column :invoices, :user_id, :integer
end
end

View File

@ -1,5 +0,0 @@
class RemoveProfileFromOrganization < ActiveRecord::Migration
def change
remove_reference :organizations, :profile, index: true, foreign_key: true
end
end

View File

@ -1,6 +0,0 @@
class AddInvoicingProfileToWallet < ActiveRecord::Migration
def change
add_reference :wallets, :invoicing_profile, index: true, foreign_key: true
add_reference :wallet_transactions, :invoicing_profile, index: true, foreign_key: true
end
end

View File

@ -1,6 +0,0 @@
class RemoveUserIdFromWallet < ActiveRecord::Migration
def change
remove_reference :wallets, :user, index: true, foreign_key: true
remove_reference :wallet_transactions, :user, index: true, foreign_key: true
end
end

View File

@ -1,5 +0,0 @@
class AddInvoicingProfileToHistoryValue < ActiveRecord::Migration
def change
add_reference :history_values, :invoicing_profile, index: true, foreign_key: true
end
end

View File

@ -1,5 +0,0 @@
class RemoveUserIdFromHistoryValue < ActiveRecord::Migration
def change
remove_reference :history_values, :user, index: true, foreign_key: true
end
end

View File

@ -0,0 +1,13 @@
class CreateStatisticProfile < ActiveRecord::Migration
def change
create_table :statistic_profiles do |t|
t.boolean :gender
t.date :birthday
t.belongs_to :group, index: true, foreign_key: true
t.belongs_to :user, index: true, foreign_key: true
end
add_reference :reservations, :statistic_profile, index: true, foreign_key: true
add_reference :subscriptions, :statistic_profile, index: true, foreign_key: true
end
end

View File

@ -0,0 +1,27 @@
class MigrateProfileToStatisticProfile < ActiveRecord::Migration
def up
User.all.each do |u|
p = u.profile
puts "WARNING: User #{u.id} has no profile" and next unless p
StatisticProfile.create!(
user: u,
group: u.group,
gender: p.gender,
birthday: p.birthday
)
end
end
def down
StatisticProfile.all.each do |sp|
p = sp.user.profile
puts "WARNING: User #{sp.user_id} has no profile" and next unless p
p.update_attributes(
gender: sp.gender,
birthday: sp.birthday
)
end
end
end

View File

@ -0,0 +1,19 @@
class MigrateReservationToStatisticProfile < ActiveRecord::Migration
def up
Reservation.all.each do |r|
user = User.find(r.user_id)
r.update_column(
'statistic_profile_id', user.statistic_profile.id
)
end
end
def down
Reservation.all.each do |r|
statistic_profile = User.find(r.statistic_profile_id)
r.update_column(
'user_id', statistic_profile.user_id
)
end
end
end

View File

@ -0,0 +1,19 @@
class MigrateSubscriptionToStatisticProfile < ActiveRecord::Migration
def up
Subscription.all.each do |s|
user = User.find(s.user_id)
s.update_column(
'statistic_profile_id', user.statistic_profile.id
)
end
end
def down
Subscription.all.each do |s|
statistic_profile = User.find(s.statistic_profile_id)
s.update_column(
'user_id', statistic_profile.user_id
)
end
end
end

View File

@ -0,0 +1,8 @@
class RemoveStatisticColumns < ActiveRecord::Migration
def change
remove_column :profiles, :gender, :boolean
remove_column :profiles, :birthday, :date
remove_column :reservations, :user_id
remove_column :subscriptions, :user_id
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20190603141109) do ActiveRecord::Schema.define(version: 20190604075717) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -447,8 +447,6 @@ ActiveRecord::Schema.define(version: 20190603141109) do
t.integer "user_id" t.integer "user_id"
t.string "first_name", limit: 255 t.string "first_name", limit: 255
t.string "last_name", limit: 255 t.string "last_name", limit: 255
t.boolean "gender"
t.date "birthday"
t.string "phone", limit: 255 t.string "phone", limit: 255
t.text "interest" t.text "interest"
t.text "software_mastered" t.text "software_mastered"
@ -545,7 +543,6 @@ ActiveRecord::Schema.define(version: 20190603141109) do
add_index "projects_themes", ["theme_id"], name: "index_projects_themes_on_theme_id", using: :btree add_index "projects_themes", ["theme_id"], name: "index_projects_themes_on_theme_id", using: :btree
create_table "reservations", force: :cascade do |t| create_table "reservations", force: :cascade do |t|
t.integer "user_id"
t.text "message" t.text "message"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
@ -553,11 +550,12 @@ ActiveRecord::Schema.define(version: 20190603141109) do
t.string "reservable_type", limit: 255 t.string "reservable_type", limit: 255
t.string "stp_invoice_id", limit: 255 t.string "stp_invoice_id", limit: 255
t.integer "nb_reserve_places" t.integer "nb_reserve_places"
t.integer "statistic_profile_id"
end end
add_index "reservations", ["reservable_id", "reservable_type"], name: "index_reservations_on_reservable_id_and_reservable_type", using: :btree add_index "reservations", ["reservable_id", "reservable_type"], name: "index_reservations_on_reservable_id_and_reservable_type", using: :btree
add_index "reservations", ["statistic_profile_id"], name: "index_reservations_on_statistic_profile_id", using: :btree
add_index "reservations", ["stp_invoice_id"], name: "index_reservations_on_stp_invoice_id", using: :btree add_index "reservations", ["stp_invoice_id"], name: "index_reservations_on_stp_invoice_id", using: :btree
add_index "reservations", ["user_id"], name: "index_reservations_on_user_id", using: :btree
create_table "roles", force: :cascade do |t| create_table "roles", force: :cascade do |t|
t.string "name", limit: 255 t.string "name", limit: 255
@ -664,6 +662,16 @@ ActiveRecord::Schema.define(version: 20190603141109) do
t.boolean "ca", default: true t.boolean "ca", default: true
end end
create_table "statistic_profiles", force: :cascade do |t|
t.boolean "gender"
t.date "birthday"
t.integer "group_id"
t.integer "user_id"
end
add_index "statistic_profiles", ["group_id"], name: "index_statistic_profiles_on_group_id", using: :btree
add_index "statistic_profiles", ["user_id"], name: "index_statistic_profiles_on_user_id", using: :btree
create_table "statistic_sub_types", force: :cascade do |t| create_table "statistic_sub_types", force: :cascade do |t|
t.string "key", limit: 255 t.string "key", limit: 255
t.string "label", limit: 255 t.string "label", limit: 255
@ -701,16 +709,16 @@ ActiveRecord::Schema.define(version: 20190603141109) do
create_table "subscriptions", force: :cascade do |t| create_table "subscriptions", force: :cascade do |t|
t.integer "plan_id" t.integer "plan_id"
t.integer "user_id"
t.string "stp_subscription_id", limit: 255 t.string "stp_subscription_id", limit: 255
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.datetime "expiration_date" t.datetime "expiration_date"
t.datetime "canceled_at" t.datetime "canceled_at"
t.integer "statistic_profile_id"
end end
add_index "subscriptions", ["plan_id"], name: "index_subscriptions_on_plan_id", using: :btree add_index "subscriptions", ["plan_id"], name: "index_subscriptions_on_plan_id", using: :btree
add_index "subscriptions", ["user_id"], name: "index_subscriptions_on_user_id", using: :btree add_index "subscriptions", ["statistic_profile_id"], name: "index_subscriptions_on_statistic_profile_id", using: :btree
create_table "tags", force: :cascade do |t| create_table "tags", force: :cascade do |t|
t.string "name" t.string "name"
@ -906,11 +914,15 @@ ActiveRecord::Schema.define(version: 20190603141109) do
add_foreign_key "prices", "plans" add_foreign_key "prices", "plans"
add_foreign_key "projects_spaces", "projects" add_foreign_key "projects_spaces", "projects"
add_foreign_key "projects_spaces", "spaces" add_foreign_key "projects_spaces", "spaces"
add_foreign_key "reservations", "statistic_profiles"
add_foreign_key "slots_reservations", "reservations" add_foreign_key "slots_reservations", "reservations"
add_foreign_key "slots_reservations", "slots" add_foreign_key "slots_reservations", "slots"
add_foreign_key "spaces_availabilities", "availabilities" add_foreign_key "spaces_availabilities", "availabilities"
add_foreign_key "spaces_availabilities", "spaces" add_foreign_key "spaces_availabilities", "spaces"
add_foreign_key "statistic_custom_aggregations", "statistic_types" add_foreign_key "statistic_custom_aggregations", "statistic_types"
add_foreign_key "statistic_profiles", "groups"
add_foreign_key "statistic_profiles", "users"
add_foreign_key "subscriptions", "statistic_profiles"
add_foreign_key "tickets", "event_price_categories" add_foreign_key "tickets", "event_price_categories"
add_foreign_key "tickets", "reservations" add_foreign_key "tickets", "reservations"
add_foreign_key "user_tags", "tags" add_foreign_key "user_tags", "tags"

View File

@ -89,7 +89,8 @@ Group.create! name: I18n.t('group.admins'), slug: 'admins' unless Group.find_by(
if Role.where(name: 'admin').joins(:users).count.zero? if Role.where(name: 'admin').joins(:users).count.zero?
admin = User.new(username: 'admin', email: ENV['ADMIN_EMAIL'], password: ENV['ADMIN_PASSWORD'], admin = User.new(username: 'admin', email: ENV['ADMIN_EMAIL'], password: ENV['ADMIN_PASSWORD'],
password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id, password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id,
profile_attributes: { first_name: 'admin', last_name: 'admin', gender: true, phone: '0123456789', birthday: Time.now }) profile_attributes: { first_name: 'admin', last_name: 'admin', phone: '0123456789' },
statistic_profile_attributes: { gender: true, birthday: Time.now })
admin.add_role 'admin' admin.add_role 'admin'
admin.save! admin.save!
end end

View File

@ -42,8 +42,8 @@ This can be achieved doing the following:
## Using another DBMS ## Using another DBMS
Some users may want to use another DBMS than PostgreSQL. Some users may want to use another DBMS than PostgreSQL.
This is currently not supported, because of some PostgreSQL specific instructions that cannot be efficiently handled with the ActiveRecord ORM: This is currently not supported, because of some PostgreSQL specific instructions that cannot be efficiently handled with the ActiveRecord ORM:
- `app/controllers/api/members_controllers.rb@list` is using `ILIKE` - `app/services/members/list_service.rb@list` is using `ILIKE`, `now()::date` and `OFFSET`.
- `app/controllers/api/invoices_controllers.rb@list` is using `ILIKE` and `date_trunc()` - `app/services/invoices_service.rb@list` is using `ILIKE` and `date_trunc()`
- `db/migrate/20160613093842_create_unaccent_function.rb` is using [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html) modules and defines a PL/pgSQL function (`f_unaccent()`) - `db/migrate/20160613093842_create_unaccent_function.rb` is using [unaccent](https://www.postgresql.org/docs/current/static/unaccent.html) and [trigram](https://www.postgresql.org/docs/current/static/pgtrgm.html) modules and defines a PL/pgSQL function (`f_unaccent()`)
- `app/controllers/api/members_controllers.rb@search` is using `f_unaccent()` (see above) and `regexp_replace()` - `app/controllers/api/members_controllers.rb@search` is using `f_unaccent()` (see above) and `regexp_replace()`
- `db/migrate/20150604131525_add_meta_data_to_notifications.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype. - `db/migrate/20150604131525_add_meta_data_to_notifications.rb` is using [jsonb](https://www.postgresql.org/docs/9.4/static/datatype-json.html), a PostgreSQL 9.4+ datatype.

View File

@ -4,8 +4,6 @@ profile_1:
user_id: 1 user_id: 1
first_name: admin first_name: admin
last_name: admin last_name: admin
gender: true
birthday: 2016-04-04
phone: 0123456789 phone: 0123456789
interest: interest:
software_mastered: software_mastered:
@ -17,8 +15,6 @@ profile_2:
user_id: 2 user_id: 2
first_name: Jean first_name: Jean
last_name: Dupont last_name: Dupont
gender: true
birthday: 1975-06-07
phone: '0214321420' phone: '0214321420'
interest: 3D printers interest: 3D printers
software_mastered: autocad software_mastered: autocad
@ -30,8 +26,6 @@ profile_4:
user_id: 4 user_id: 4
first_name: Kevin first_name: Kevin
last_name: Dumas last_name: Dumas
gender: true
birthday: 1998-06-05
phone: 0446124793 phone: 0446124793
interest: '' interest: ''
software_mastered: solidworks software_mastered: solidworks
@ -43,8 +37,6 @@ profile_5:
user_id: 5 user_id: 5
first_name: Vanessa first_name: Vanessa
last_name: Lonchamp last_name: Lonchamp
gender: false
birthday: 1994-05-03
phone: 0233412482 phone: 0233412482
interest: vélo, natation interest: vélo, natation
software_mastered: '' software_mastered: ''
@ -56,8 +48,6 @@ profile_6:
user_id: 6 user_id: 6
first_name: Gilbert first_name: Gilbert
last_name: Partenaire last_name: Partenaire
gender: true
birthday: 2016-04-04
phone: '0000000000' phone: '0000000000'
interest: interest:
software_mastered: software_mastered:
@ -69,8 +59,6 @@ profile_3:
user_id: 3 user_id: 3
first_name: Paulette first_name: Paulette
last_name: Durand last_name: Durand
gender: false
birthday: 1949-06-18
phone: '0474264261' phone: '0474264261'
interest: '' interest: ''
software_mastered: '' software_mastered: ''
@ -82,8 +70,6 @@ profile_7:
user_id: 7 user_id: 7
first_name: Lucile first_name: Lucile
last_name: Seguin last_name: Seguin
gender: false
birthday: 1969-02-03
phone: '0241853679' phone: '0241853679'
interest: interest:
software_mastered: software_mastered:

View File

@ -1,7 +1,7 @@
reservation_1: reservation_1:
id: 1 id: 1
user_id: 7 statistic_profile_id: 7
message: message:
created_at: 2012-03-12 11:03:31.651441000 Z created_at: 2012-03-12 11:03:31.651441000 Z
updated_at: 2012-03-12 11:03:31.651441000 Z updated_at: 2012-03-12 11:03:31.651441000 Z
@ -12,7 +12,7 @@ reservation_1:
reservation_2: reservation_2:
id: 2 id: 2
user_id: 3 statistic_profile_id: 3
message: message:
created_at: 2015-06-10 11:20:01.341130000 Z created_at: 2015-06-10 11:20:01.341130000 Z
updated_at: 2015-06-10 11:20:01.341130000 Z updated_at: 2015-06-10 11:20:01.341130000 Z

48
test/fixtures/statistic_profiles.yml vendored Normal file
View File

@ -0,0 +1,48 @@
admin:
id: 1
user_id: 1
gender: true
birthday: 2016-04-04
group_id: 3
jdupont:
id: 2
user_id: 2
gender: true
birthday: 1975-06-07
group_id: 1
kdumas:
id: 4
user_id: 4
gender: true
birthday: 1998-06-05
group_id: 2
vlonchamp:
id: 5
user_id: 5
gender: false
birthday: 1994-05-03
group_id: 2
gpartenaire:
id: 6
user_id: 6
gender: true
birthday: 2016-04-04
group_id: 1
pdurand:
id: 3
user_id: 3
gender: false
birthday: 1949-06-18
group_id: 1
lseguin:
id: 7
user_id: 7
gender: false
birthday: 1969-02-03
group_id: 1

View File

@ -2,7 +2,7 @@
subscription_1: subscription_1:
id: 1 id: 1
plan_id: 2 plan_id: 2
user_id: 3 statistic_profile_id: 3
stp_subscription_id: sub_8DGB4ErIc2asOv stp_subscription_id: sub_8DGB4ErIc2asOv
created_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> created_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
updated_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> updated_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
@ -12,7 +12,7 @@ subscription_1:
subscription_2: subscription_2:
id: 2 id: 2
plan_id: 3 plan_id: 3
user_id: 4 statistic_profile_id: 4
stp_subscription_id: stp_subscription_id:
created_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> created_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
updated_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %> updated_at: <%= 10.days.ago.utc.strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
@ -23,7 +23,7 @@ subscription_2:
subscription_3: subscription_3:
id: 3 id: 3
plan_id: 1 plan_id: 1
user_id: 7 statistic_profile_id: 7
stp_subscription_id: stp_subscription_id:
created_at: 2012-03-12 11:03:31.651441000 Z created_at: 2012-03-12 11:03:31.651441000 Z
updated_at: 2012-03-12 11:03:31.651441000 Z updated_at: 2012-03-12 11:03:31.651441000 Z

View File

@ -23,14 +23,16 @@ class AdminsTest < ActionDispatch::IntegrationTest
profile_attributes: { profile_attributes: {
first_name: 'Gérard', first_name: 'Gérard',
last_name: 'Lepower', last_name: 'Lepower',
gender: true, phone: '0547124852'
birthday: '1999-09-19',
phone: '0547124852',
}, },
invoicing_profile_attributes: { invoicing_profile_attributes: {
address_attributes: { address_attributes: {
address: '6 Avenue Henri de Bournazel, 19000 Tulle' address: '6 Avenue Henri de Bournazel, 19000 Tulle'
} }
},
statistic_profile_attributes: {
gender: true,
birthday: '1999-09-19'
} }
} }
}.to_json, }.to_json,

View File

@ -20,16 +20,18 @@ class MembersTest < ActionDispatch::IntegrationTest
email: email, email: email,
group_id: group_id, group_id: group_id,
profile_attributes: { profile_attributes: {
gender: true,
last_name: 'Dubois', last_name: 'Dubois',
first_name: 'Robert', first_name: 'Robert',
birthday: '2018-02-08',
phone: '0485232145' phone: '0485232145'
}, },
invoicing_profile_attributes: { invoicing_profile_attributes: {
address_attributes: { address_attributes: {
address: '21 grand rue, 73110 Bourget-en-Huile' address: '21 grand rue, 73110 Bourget-en-Huile'
} }
},
statistic_profile_attributes: {
gender: true,
birthday: '2018-02-08'
} }
} }.to_json, default_headers } }.to_json, default_headers
end end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
module Subscriptions module Subscriptions
class CreateAsAdminTest < ActionDispatch::IntegrationTest class CreateAsAdminTest < ActionDispatch::IntegrationTest
@ -7,11 +9,11 @@ module Subscriptions
login_as(@admin, scope: :user) login_as(@admin, scope: :user)
end end
test "admin successfully takes a subscription for a user" do test 'admin successfully takes a subscription for a user' do
user = User.find_by(username: 'jdupond') user = User.find_by(username: 'jdupond')
plan = Plan.find_by(group_id: user.group.id, type: 'Plan', base_name: 'Mensuel') plan = Plan.find_by(group_id: user.group.id, type: 'Plan', base_name: 'Mensuel')
VCR.use_cassette("subscriptions_admin_create_success") do VCR.use_cassette('subscriptions_admin_create_success') do
post '/api/subscriptions', post '/api/subscriptions',
{ {
subscription: { subscription: {

View File

@ -1,9 +1,15 @@
# frozen_string_literal: true
require 'test_helper' require 'test_helper'
class UserTest < ActiveSupport::TestCase class UserTest < ActiveSupport::TestCase
test "must create a wallet after create user" do test 'must create wallet and profiles after create user' do
u = User.create(username: 'user', email: 'userwallet@fabmanager.com', password: 'testpassword', password_confirmation: 'testpassword', u = User.create(username: 'user', email: 'userwallet@fabmanager.com', password: 'testpassword', password_confirmation: 'testpassword',
profile_attributes: {first_name: 'user', last_name: 'wallet', gender: true, birthday: 18.years.ago, phone: '0123456789'} ) profile_attributes: { first_name: 'user', last_name: 'wallet', phone: '0123456789' },
statistic_profile_attributes: { gender: true, birthday: 18.years.ago })
assert u.wallet.present? assert u.wallet.present?
assert u.profile.present?
assert u.invoicing_profile.present?
assert u.statistic_profile.present?
end end
end end

View File

@ -8,14 +8,14 @@ class SubscriptionExtensionAfterReservationTest < ActiveSupport::TestCase
@plan = Plan.find(3) @plan = Plan.find(3)
@plan.update!(is_rolling: true) @plan.update!(is_rolling: true)
@user = User.joins(:subscriptions).find_by(subscriptions: { plan: @plan }) @user = User.joins(statistic_profile: [:subscriptions]).find_by(subscriptions: { plan_id: @plan.id })
@user.reservations.destroy_all # ensure no reservations @user.reservations.destroy_all # ensure no reservations
@availability = @machine.availabilities.first @availability = @machine.availabilities.first
slot = Slot.new(start_at: @availability.start_at, end_at: @availability.end_at, availability_id: @availability.id) slot = Slot.new(start_at: @availability.start_at, end_at: @availability.end_at, availability_id: @availability.id)
@reservation_machine = Reservation.new(user: @user, reservable: @machine, slots: [slot]) @reservation_machine = Reservation.new(statistic_profile: @user.statistic_profile, reservable: @machine, slots: [slot])
@reservation_training = Reservation.new(user: @user, reservable: @training, slots: [slot]) @reservation_training = Reservation.new(statistic_profile: @user.statistic_profile, reservable: @training, slots: [slot])
@reservation_training.save! @reservation_training.save!
end end

View File

@ -5,11 +5,11 @@ class UsersCreditsManagerTest < ActiveSupport::TestCase
@machine = Machine.find(6) @machine = Machine.find(6)
@training = Training.find(2) @training = Training.find(2)
@plan = Plan.find(3) @plan = Plan.find(3)
@user = User.joins(:subscriptions).find_by(subscriptions: { plan: @plan }) @user = User.joins(statistic_profile: [:subscriptions]).find_by(subscriptions: { plan_id: @plan.id })
@user.users_credits.destroy_all @user.users_credits.destroy_all
@availability = @machine.availabilities.first @availability = @machine.availabilities.first
@reservation_machine = Reservation.new(user: @user, reservable: @machine) @reservation_machine = Reservation.new(statistic_profile: @user.statistic_profile, reservable: @machine)
@reservation_training = Reservation.new(user: @user, reservable: @training) @reservation_training = Reservation.new(statistic_profile: @user.statistic_profile, reservable: @training)
end end
## context machine reservation ## context machine reservation