diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cd0c27d..ae1660c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Interface to manage partners - Ability to define, per availability, a custom duration for the reservation slots - Ability to promote a user to a higher role (member > manager > admin) +- Ask for confirmation before booking a slot for a member without the required tag - Corrected the documentation about BOOK_SLOT_AT_SAME_TIME - Auto-adjusts text colors based on the selected theme colors - Fix a bug: unable to change group if the previous was deactivated diff --git a/app/assets/javascripts/directives/cart.js.erb b/app/assets/javascripts/directives/cart.js.erb index 8836bb53e..eb927e229 100644 --- a/app/assets/javascripts/directives/cart.js.erb +++ b/app/assets/javascripts/directives/cart.js.erb @@ -74,38 +74,12 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', * @param slot {Object} fullCalendar event object */ $scope.validateSlot = function (slot) { - let sameTimeReservations = [ - 'training_reservations', - 'machine_reservations', - 'space_reservations', - 'events_reservations' - ].map(function (k) { - return _.filter($scope.user[k], function(r) { - return slot.start.isSame(r.start_at) || - (slot.end.isAfter(r.start_at) && slot.end.isBefore(r.end_at)) || - (slot.start.isAfter(r.start_at) && slot.start.isBefore(r.end_at)) || - (slot.start.isBefore(r.start_at) && slot.end.isAfter(r.end_at)); - }) - }); - sameTimeReservations = _.union.apply(null, sameTimeReservations); - if (sameTimeReservations.length > 0) { - const modalInstance = $uibModal.open({ - animation: true, - templateUrl: '<%= asset_path "shared/_reserve_slot_same_time.html" %>', - size: 'md', - controller: 'ReserveSlotSameTimeController', - resolve: { - sameTimeReservations: function() { return sameTimeReservations; } - } - }); - modalInstance.result.then(function(res) { + validateTags(slot, function () { + validateSameTimeReservations(slot, function () { slot.isValid = true; - return updateCartPrice(); - }); - } else { - slot.isValid = true; - return updateCartPrice(); - } + updateCartPrice(); + }) + }) }; /** @@ -330,6 +304,74 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', }); }; + /** + * Validates that the current slot is reserved by a member with an authorized tag. Admin and managers can overpass + * the mismatch. + * @param slot {Object} fullCalendar event object. + * @param callback {function} + */ + const validateTags = function (slot, callback) { + const interTags = _.intersection.apply(null, [slot.tag_ids, $scope.user.tag_ids]); + if (slot.tag_ids.length === 0 || interTags.length > 0) { + if (typeof callback === 'function') callback(); + } else { + // ask confirmation + const modalInstance = $uibModal.open({ + animation: true, + templateUrl: '<%= asset_path "shared/_reserve_slot_tags_mismatch.html" %>', + size: 'md', + controller: 'ReserveSlotTagsMismatchController', + resolve: { + slotTags: function() { return slot.tags; }, + userTags: function () { return $scope.user.tags; }, + userName: function () { return $scope.user.name; } + } + }); + modalInstance.result.then(function(res) { + if (typeof callback === 'function') callback(res); + }); + } + } + + /** + * Validates that no other reservations were made that conflict the current slot and alert the user about the conflict. + * If the user is an administrator or a manager, he can overpass the conflict. + * @param slot {Object} fullCalendar event object. + * @param callback {function} + */ + const validateSameTimeReservations = function (slot, callback) { + let sameTimeReservations = [ + 'training_reservations', + 'machine_reservations', + 'space_reservations', + 'events_reservations' + ].map(function (k) { + return _.filter($scope.user[k], function(r) { + return slot.start.isSame(r.start_at) || + (slot.end.isAfter(r.start_at) && slot.end.isBefore(r.end_at)) || + (slot.start.isAfter(r.start_at) && slot.start.isBefore(r.end_at)) || + (slot.start.isBefore(r.start_at) && slot.end.isAfter(r.end_at)); + }) + }); + sameTimeReservations = _.union.apply(null, sameTimeReservations); + if (sameTimeReservations.length > 0) { + const modalInstance = $uibModal.open({ + animation: true, + templateUrl: '<%= asset_path "shared/_reserve_slot_same_time.html" %>', + size: 'md', + controller: 'ReserveSlotSameTimeController', + resolve: { + sameTimeReservations: function() { return sameTimeReservations; } + } + }); + modalInstance.result.then(function(res) { + if (typeof callback === 'function') callback(res); + }); + } else { + if (typeof callback === 'function') callback(); + } + } + /** * Callback triggered when the selected slot changed */ @@ -504,12 +546,12 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', return Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) { $scope.amountTotal = res.price; $scope.totalNoCoupon = res.price_without_coupon; - return setSlotsDetails(res.details); + setSlotsDetails(res.details); }); } else { // otherwise we alert, this error musn't occur when the current user is not admin growl.warning(_t('app.shared.cart.please_select_a_member_first')); - return $scope.amountTotal = null; + $scope.amountTotal = null; } }; @@ -518,7 +560,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', angular.forEach(details.slots, function (s) { if (moment(s.start_at).isSame(slot.start)) { slot.promo = s.promo; - return slot.price = s.price; + slot.price = s.price; } }); }); @@ -739,10 +781,10 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', ]); /** - * Controller of modal for show reservations the same date at the same time + * Controller of the modal showing the reservations the same date at the same time */ -Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'AuthService', 'sameTimeReservations', 'growl', '_t', - function ($scope, $uibModalInstance, AuthService, sameTimeReservations, growl, _t) { +Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'AuthService', 'sameTimeReservations', + function ($scope, $uibModalInstance, AuthService, sameTimeReservations) { $scope.sameTimeReservations = sameTimeReservations; $scope.bookSlotAtSameTime = Fablab.bookSlotAtSameTime; $scope.isAuthorized = AuthService.isAuthorized; @@ -761,6 +803,31 @@ Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', ' } ]); + +/** + * Controller of the modal showing the slot tags + */ +Application.Controllers.controller('ReserveSlotTagsMismatchController', ['$scope', '$uibModalInstance', 'AuthService', 'slotTags', 'userTags', 'userName', + function ($scope, $uibModalInstance, AuthService, slotTags, userTags, userName) { + $scope.slotTags = slotTags; + $scope.userTags = userTags; + $scope.userName = userName; + $scope.isAuthorized = AuthService.isAuthorized; + /** + * Confirmation callback + */ + $scope.ok = function () { + $uibModalInstance.close({}); + } + /** + * Cancellation callback + */ + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + } + } +]); + /** * Controller used to alert admin reserve slot without plan */ diff --git a/app/assets/templates/shared/_reserve_slot_tags_mismatch.html.erb b/app/assets/templates/shared/_reserve_slot_tags_mismatch.html.erb new file mode 100644 index 000000000..9e1cebd2f --- /dev/null +++ b/app/assets/templates/shared/_reserve_slot_tags_mismatch.html.erb @@ -0,0 +1,26 @@ +
{{ 'app.shared.cart.confirm_book_slot_tags_mismatch' }}
+{{ 'app.shared.cart.unable_to_book_slot_tags_mismatch' }}
+