diff --git a/CHANGELOG.md b/CHANGELOG.md index 11277b0b4..8396dda38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Next release - improvement: add loader for create/delete availability slot +- improvement: allow admin configure memeber's profile gender/birthday as required - Fix a bug: unable to update a space with a deleted machine - Fix a bug: unable to get invoice payment details if the account code is same for card/transfer payment method diff --git a/app/frontend/src/javascript/components/user/user-profile-form.tsx b/app/frontend/src/javascript/components/user/user-profile-form.tsx index eb7911b93..a2f5f2c9e 100644 --- a/app/frontend/src/javascript/components/user/user-profile-form.tsx +++ b/app/frontend/src/javascript/components/user/user-profile-form.tsx @@ -93,7 +93,7 @@ export const UserProfileForm: React.FC = ({ action, size, }); setValue('invoicing_profile_attributes.user_profile_custom_fields_attributes', userProfileCustomFields); }).catch(error => onError(error)); - SettingAPI.query(['phone_required', 'address_required', 'external_id']) + SettingAPI.query(['phone_required', 'address_required', 'external_id', 'gender_required', 'birthday_required']) .then(settings => setFieldsSettings(settings)) .catch(error => onError(error)); }, []); @@ -185,7 +185,7 @@ export const UserProfileForm: React.FC = ({ action, size,

{t('app.shared.user_profile_form.personal_data')}

- +
= ({ action, size, register={register} label={t('app.shared.user_profile_form.date_of_birth')} disabled={isDisabled} - rules={{ required: true }} + rules={{ required: fieldsSettings.get('birthday_required') === 'true' }} formState={formState} type="date" nullable /> diff --git a/app/frontend/src/javascript/controllers/admin/members.js b/app/frontend/src/javascript/controllers/admin/members.js index 860cb71aa..cf72738f8 100644 --- a/app/frontend/src/javascript/controllers/admin/members.js +++ b/app/frontend/src/javascript/controllers/admin/members.js @@ -720,6 +720,12 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state', // is the address required in _member_form? $scope.addressRequired = (settingsPromise.address_required === 'true'); + // is the gender number required in _member_form? + $scope.genderRequired = (settingsPromise.gender_required === 'true'); + + // is the birthday required in _member_form? + $scope.birthdayRequired = (settingsPromise.birthday_required === 'true'); + // is user validation required $scope.enableUserValidationRequired = (settingsPromise.user_validation_required === 'true'); @@ -1060,6 +1066,12 @@ Application.Controllers.controller('NewMemberController', ['$scope', '$state', ' // is the address required to sign-up? $scope.addressRequired = (settingsPromise.address_required === 'true'); + // is the gender number required in _member_form? + $scope.genderRequired = (settingsPromise.gender_required === 'true'); + + // is the birthday required in _member_form? + $scope.birthdayRequired = (settingsPromise.birthday_required === 'true'); + // Default member's profile parameters $scope.user = { plan_interval: '', @@ -1205,6 +1217,12 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A // is the address required in _admin_form? $scope.addressRequired = (settingsPromise.address_required === 'true'); + // is the gender number required in _admin_form? + $scope.genderRequired = (settingsPromise.gender_required === 'true'); + + // is the birthday required in _admin_form? + $scope.birthdayRequired = (settingsPromise.birthday_required === 'true'); + // all available groups $scope.groups = groupsPromise; @@ -1276,6 +1294,12 @@ Application.Controllers.controller('NewManagerController', ['$state', '$scope', // is the address required in _admin_form? $scope.addressRequired = (settingsPromise.address_required === 'true'); + // is the gender number required in _admin_form? + $scope.genderRequired = (settingsPromise.gender_required === 'true'); + + // is the birthday required in _admin_form? + $scope.birthdayRequired = (settingsPromise.birthday_required === 'true'); + // list of all groups $scope.groups = groupsPromise.filter(function (g) { return !g.disabled; }); diff --git a/app/frontend/src/javascript/controllers/application.js b/app/frontend/src/javascript/controllers/application.js index 846c5eb40..c081a7bd3 100644 --- a/app/frontend/src/javascript/controllers/application.js +++ b/app/frontend/src/javascript/controllers/application.js @@ -103,6 +103,12 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco // is the address required to sign-up? $scope.addressRequired = (settingsPromise.address_required === 'true'); + // is the gender required to sign-up? + $scope.genderRequired = (settingsPromise.gender_required === 'true'); + + // is the birthday required to sign-up? + $scope.birthdayRequired = (settingsPromise.birthday_required === 'true'); + // reCaptcha v2 site key (or undefined) $scope.recaptchaSiteKey = settingsPromise.recaptcha_site_key; diff --git a/app/frontend/src/javascript/models/setting.ts b/app/frontend/src/javascript/models/setting.ts index 604a58a28..315e74d2e 100644 --- a/app/frontend/src/javascript/models/setting.ts +++ b/app/frontend/src/javascript/models/setting.ts @@ -186,7 +186,9 @@ export const accountSettings = [ 'user_change_group', 'user_validation_required', 'user_validation_required_list', - 'family_account' + 'family_account', + 'gender_required', + 'birthday_required' ] as const; export const analyticsSettings = [ diff --git a/app/frontend/src/javascript/router.js b/app/frontend/src/javascript/router.js index 74d83f59e..610758d88 100644 --- a/app/frontend/src/javascript/router.js +++ b/app/frontend/src/javascript/router.js @@ -127,7 +127,7 @@ angular.module('application.router', ['ui.router']) } }, resolve: { - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['fablab_name', 'name_genre', 'phone_required', 'address_required']" }).$promise; }], + settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['fablab_name', 'name_genre', 'phone_required', 'address_required', 'gender_required', 'birthday_required']" }).$promise; }], activeProviderPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.active().$promise; }], groupsPromise: ['Group', function (Group) { return Group.query().$promise; }], cguFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'cgu-file' }).$promise; }], @@ -174,7 +174,7 @@ angular.module('application.router', ['ui.router']) resolve: { groups: ['Group', function (Group) { return Group.query().$promise; }], activeProviderPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.active().$promise; }], - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }] + settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required', 'gender_required', 'birthday_required']" }).$promise; }] } }) .state('app.logged.dashboard.supporting_document_files', { @@ -1049,7 +1049,7 @@ angular.module('application.router', ['ui.router']) } }, resolve: { - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }] + settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required', 'gender_required', 'birthday_required']" }).$promise; }] } }) .state('app.admin.members_import', { @@ -1090,7 +1090,7 @@ angular.module('application.router', ['ui.router']) walletPromise: ['Wallet', '$transition$', function (Wallet, $transition$) { return Wallet.getWalletByUser({ user_id: $transition$.params().id }).$promise; }], transactionsPromise: ['Wallet', 'walletPromise', function (Wallet, walletPromise) { return Wallet.transactions({ id: walletPromise.id }).$promise; }], tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }], - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required', 'user_validation_required']" }).$promise; }] + settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required', 'gender_required', 'birthday_required', 'user_validation_required']" }).$promise; }] } }) .state('app.admin.admins_new', { @@ -1102,7 +1102,7 @@ angular.module('application.router', ['ui.router']) } }, resolve: { - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }], + settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required', 'gender_required', 'birthday_required']" }).$promise; }], groupsPromise: ['Group', function (Group) { return Group.query({ disabled: false }).$promise; }] } }) @@ -1117,7 +1117,7 @@ angular.module('application.router', ['ui.router']) resolve: { groupsPromise: ['Group', function (Group) { return Group.query().$promise; }], tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }], - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required']" }).$promise; }] + settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['phone_required', 'address_required', 'gender_required', 'birthday_required']" }).$promise; }] } }) @@ -1200,7 +1200,7 @@ angular.module('application.router', ['ui.router']) "'extended_prices_in_same_day', 'recaptcha_site_key', 'recaptcha_secret_key', 'user_validation_required', " + "'user_validation_required_list', 'machines_module', 'user_change_group', " + "'store_module', 'machine_reservation_deadline', 'training_reservation_deadline', 'event_reservation_deadline', " + - "'space_reservation_deadline', 'reservation_context_feature']" + "'space_reservation_deadline', 'reservation_context_feature', 'gender_required', 'birthday_required']" }).$promise; }], privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }], diff --git a/app/frontend/templates/admin/settings/compte.html b/app/frontend/templates/admin/settings/compte.html index 44f0f14a9..536d5a627 100644 --- a/app/frontend/templates/admin/settings/compte.html +++ b/app/frontend/templates/admin/settings/compte.html @@ -110,6 +110,32 @@ {{ 'app.admin.settings.account.customize_account_settings' }}
+
+

{{ 'app.admin.settings.gender' }}

+

+ {{ 'app.admin.settings.gender_required_info' }} +

+
+ + +
+
+
+

{{ 'app.admin.settings.birthday' }}

+

+ {{ 'app.admin.settings.birthday_required_info' }} +

+
+ + +
+

{{ 'app.admin.settings.phone' }}

diff --git a/app/frontend/templates/shared/_admin_form.html b/app/frontend/templates/shared/_admin_form.html index 5f90dc4c6..2b33f38c7 100644 --- a/app/frontend/templates/shared/_admin_form.html +++ b/app/frontend/templates/shared/_admin_form.html @@ -6,7 +6,7 @@ name="admin[statistic_profile_attributes][gender]" ng-model="admin.statistic_profile_attributes.gender" ng-value="true" - required/> + ng-required="genderRequired"/> {{ 'app.admin.admins_new.man' | translate }} - +

@@ -76,7 +76,9 @@
- + + + + ng-required="genderRequired"/> {{ 'app.admin.manager_new.man' | translate }} - +
@@ -76,7 +76,9 @@
- + + + {{ 'app.public.common.man' | translate }} + ng-required="genderRequired"/> {{ 'app.public.common.man' | translate }}
- + {{ 'app.public.common.gender_is_required'}}
@@ -222,9 +222,9 @@ is-open="datePicker.opened" placeholder="{{ 'app.public.common.birth_date' | translate }}" ng-click="openDatePicker($event)" - required/> + ng-required="birthdayRequired"/>
- + {{ 'app.public.common.birth_date_is_required' }}
diff --git a/app/helpers/excel_helper.rb b/app/helpers/excel_helper.rb index 9693c9262..391dc8113 100644 --- a/app/helpers/excel_helper.rb +++ b/app/helpers/excel_helper.rb @@ -31,7 +31,7 @@ module ExcelHelper user&.profile&.full_name || t('export.deleted_user'), user&.email || '', user&.profile&.phone || '', - t("export.#{hit['_source']['gender']}"), + hit['_source']['gender'].present? ? t("export.#{hit['_source']['gender']}") : '', hit['_source']['age'], subtype.nil? ? '' : subtype.label ] diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 79885b5b1..ec52ec881 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -208,6 +208,8 @@ module SettingsHelper project_categories_filter_placeholder project_categories_wording reservation_context_feature + gender_required + birthday_required ].freeze end # rubocop:enable Metrics/ModuleLength diff --git a/app/models/statistic_profile.rb b/app/models/statistic_profile.rb index e0120c65e..4bd910163 100644 --- a/app/models/statistic_profile.rb +++ b/app/models/statistic_profile.rb @@ -31,6 +31,8 @@ class StatisticProfile < ApplicationRecord validate :check_birthday_in_past def str_gender + return '' if gender.blank? + gender ? 'male' : 'female' end diff --git a/app/models/user.rb b/app/models/user.rb index e714b15d7..11edbf2f3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -115,8 +115,10 @@ class User < ApplicationRecord end def need_completion? - statistic_profile.gender.nil? || profile.first_name.blank? || profile.last_name.blank? || username.blank? || - email.blank? || encrypted_password.blank? || group_id.nil? || statistic_profile.birthday.blank? || + (Setting.get('gender_required') && statistic_profile.gender.blank?) || + profile.first_name.blank? || profile.last_name.blank? || username.blank? || + email.blank? || encrypted_password.blank? || group_id.nil? || + (Setting.get('birthday_required') && statistic_profile.birthday.blank?) || (Setting.get('phone_required') && profile.phone.blank?) || (Setting.get('address_required') && invoicing_profile&.address&.address&.blank?) end diff --git a/app/policies/setting_policy.rb b/app/policies/setting_policy.rb index 5e3a620b6..be9fb64dd 100644 --- a/app/policies/setting_policy.rb +++ b/app/policies/setting_policy.rb @@ -47,7 +47,8 @@ class SettingPolicy < ApplicationPolicy machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label events_banner_cta_url projects_list_member_filter_presence projects_list_date_filters_presence advanced_accounting - project_categories_filter_placeholder project_categories_wording reservation_context_feature family_account child_validation_required] + project_categories_filter_placeholder project_categories_wording reservation_context_feature family_account child_validation_required + gender_required birthday_required] end ## diff --git a/app/views/exports/users_members.xlsx.axlsx b/app/views/exports/users_members.xlsx.axlsx index f4866e267..e05916abe 100644 --- a/app/views/exports/users_members.xlsx.axlsx +++ b/app/views/exports/users_members.xlsx.axlsx @@ -49,7 +49,7 @@ wb.add_worksheet(name: ExcelService.name_safe(t('export_members.members'))) do | member.email, member.is_allow_newsletter, member.last_sign_in_at&.to_date, - member.statistic_profile.gender ? t('export_members.man') : t('export_members.woman'), + member.statistic_profile.gender.present? ? (member.statistic_profile.gender ? t('export_members.man') : t('export_members.woman')) : '', member.statistic_profile.age, member.invoicing_profile&.address&.address || '', member.profile.phone, diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index ed86a193e..9bf556b83 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -1839,6 +1839,12 @@ en: address: "Address" address_required_info_html: "You can define if the address should be required to register a new user on Fab-manager.
Please note that, depending on your country, the regulations may requires addresses for the invoices to be valid." address_is_required: "Address is required" + gender: "Gender" + gender_is_required: "Gender required" + gender_required_info: "You can define if the gender should be required to register a new user on Fab-manager." + birthday: "Date of birth" + birthday_is_required: "Date of birth required" + birthday_required_info: "You can define if the date of birth number should be required to register a new user on Fab-manager." external_id: "External identifier" external_id_info_html: "You can set up an external identifier for your users, which cannot be modified by the user himself." enable_external_id: "Enable the external ID" diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 2b62e06a9..574f6ae57 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -742,3 +742,8 @@ Setting.set('reservation_context_feature', false) unless Setting.find_by(name: ' Setting.set('family_account', false) unless Setting.find_by(name: 'family_account').try(:value) Setting.set('child_validation_required', false) unless Setting.find_by(name: 'child_validation_required').try(:value) + +Setting.set('phone_required', false) unless Setting.find_by(name: 'phone_required').try(:value) +Setting.set('address_required', false) unless Setting.find_by(name: 'address_required').try(:value) +Setting.set('gender_required', true) unless Setting.find_by(name: 'gender_required').try(:value) +Setting.set('birthday_required', true) unless Setting.find_by(name: 'birthday_required').try(:value) diff --git a/test/integration/exports/statistics_export_test.rb b/test/integration/exports/statistics_export_test.rb index 0169aec0c..3ed494cb2 100644 --- a/test/integration/exports/statistics_export_test.rb +++ b/test/integration/exports/statistics_export_test.rb @@ -59,7 +59,8 @@ class Exports::StatisticsExportTest < ActionDispatch::IntegrationTest assert_equal reservation.user.profile.full_name, wb.sheet_data[5][1].value assert_equal reservation.user.email, wb.sheet_data[5][2].value assert_equal reservation.user.profile.phone, wb.sheet_data[5][3].value - assert_equal I18n.t("export.#{reservation.user.statistic_profile.str_gender}"), wb.sheet_data[5][4].value + assert_equal reservation.user.statistic_profile.str_gender.present? ? I18n.t("export.#{reservation.user.statistic_profile.str_gender}") : '', + wb.sheet_data[5][4].value assert_equal reservation.user.statistic_profile.age.to_i, wb.sheet_data[5][5].value assert_equal reservation.reservable.name, wb.sheet_data[5][6].value assert_equal reservation.invoice_items.first.invoice.total / 100.0, wb.sheet_data[5][7].value