1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-26 20:54:21 +01:00

Ask for confirmation before booking a slot for a member without the required tag

This commit is contained in:
Sylvain 2020-05-06 15:32:40 +02:00
parent 47a0fca481
commit 5f7287cec7
4 changed files with 137 additions and 37 deletions

View File

@ -5,6 +5,7 @@
- Interface to manage partners - Interface to manage partners
- Ability to define, per availability, a custom duration for the reservation slots - Ability to define, per availability, a custom duration for the reservation slots
- Ability to promote a user to a higher role (member > manager > admin) - 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 - Corrected the documentation about BOOK_SLOT_AT_SAME_TIME
- Auto-adjusts text colors based on the selected theme colors - Auto-adjusts text colors based on the selected theme colors
- Fix a bug: unable to change group if the previous was deactivated - Fix a bug: unable to change group if the previous was deactivated

View File

@ -74,38 +74,12 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
* @param slot {Object} fullCalendar event object * @param slot {Object} fullCalendar event object
*/ */
$scope.validateSlot = function (slot) { $scope.validateSlot = function (slot) {
let sameTimeReservations = [ validateTags(slot, function () {
'training_reservations', validateSameTimeReservations(slot, function () {
'machine_reservations', slot.isValid = true;
'space_reservations', updateCartPrice();
'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) {
slot.isValid = true;
return updateCartPrice();
});
} else {
slot.isValid = true;
return 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 * 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) { return Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) {
$scope.amountTotal = res.price; $scope.amountTotal = res.price;
$scope.totalNoCoupon = res.price_without_coupon; $scope.totalNoCoupon = res.price_without_coupon;
return setSlotsDetails(res.details); setSlotsDetails(res.details);
}); });
} else { } else {
// otherwise we alert, this error musn't occur when the current user is not admin // 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')); 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) { angular.forEach(details.slots, function (s) {
if (moment(s.start_at).isSame(slot.start)) { if (moment(s.start_at).isSame(slot.start)) {
slot.promo = s.promo; 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', Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'AuthService', 'sameTimeReservations',
function ($scope, $uibModalInstance, AuthService, sameTimeReservations, growl, _t) { function ($scope, $uibModalInstance, AuthService, sameTimeReservations) {
$scope.sameTimeReservations = sameTimeReservations; $scope.sameTimeReservations = sameTimeReservations;
$scope.bookSlotAtSameTime = Fablab.bookSlotAtSameTime; $scope.bookSlotAtSameTime = Fablab.bookSlotAtSameTime;
$scope.isAuthorized = AuthService.isAuthorized; $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 * Controller used to alert admin reserve slot without plan
*/ */

View File

@ -0,0 +1,26 @@
<div class="modal-header">
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
<h1 translate>{{ 'app.shared.cart.tags_mismatch' }}</h1>
</div>
<div class="modal-body">
<p ng-show="isAuthorized(['admin', 'manager'])" translate translate-values="{USER: userName}">{{ 'app.shared.cart.confirm_book_slot_tags_mismatch' }}</p>
<p ng-hide="isAuthorized(['admin', 'manager'])" translate>{{ 'app.shared.cart.unable_to_book_slot_tags_mismatch' }}</p>
<h3 translate>{{ 'app.shared.cart.slot_tags' }}</h3>
<ul class="list-unstyled" ng-show="slotTags.length > 0">
<li ng-repeat="t in slotTags">
<span class="label label-default">{{t.name}}</span>
</li>
</ul>
<span ng-hide="slotTags.length > 0" translate>{{ 'app.shared.cart.no_tags' }}</span>
<h3 translate>{{ 'app.shared.cart.user_tags' }}</h3>
<ul class="list-unstyled">
<li ng-repeat="t in userTags" ng-show="userTags.length > 0">
<span class="label label-default">{{t.name}}</span>
</li>
</ul>
<span ng-hide="userTags.length > 0" translate>{{ 'app.shared.cart.no_tags' }}</span>
</div>
<div class="modal-footer">
<button ng-if="isAuthorized(['admin', 'manager'])" class="btn btn-info" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>

View File

@ -425,6 +425,12 @@ en:
slot_at_same_time: "Conflict with others reservations" slot_at_same_time: "Conflict with others reservations"
do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time" do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time"
unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time." unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time."
tags_mismatch: "Tags mismatch"
confirm_book_slot_tags_mismatch: "Do you really want to book this slot? {USER} does not have any of the required tags."
unable_to_book_slot_tags_mismatch: "Unable to book this slot because you don't have any of the required tags."
slot_tags: "Slot tags"
user_tags: "User tags"
no_tags: "No tags"
# feature-tour modal # feature-tour modal
tour: tour:
previous: "Previous" previous: "Previous"