1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-12 23:09:03 +01:00
fab-manager/app/assets/javascripts/directives/cart.js.erb

852 lines
36 KiB
Plaintext
Raw Normal View History

/* eslint-disable
no-return-assign,
no-undef,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs', 'growl', 'Auth', 'Price', 'Wallet', 'CustomAsset', 'Slot', 'AuthService', 'helpers', '_t',
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, AuthService, helpers, _t) {
2018-11-20 13:44:53 +01:00
return ({
restrict: 'E',
scope: {
slot: '=',
slotSelectionTime: '=',
events: '=',
user: '=',
modePlans: '=',
plan: '=',
planSelectionTime: '=',
settings: '=',
plans: '=',
groups: '=',
onSlotAddedToCart: '=',
onSlotRemovedFromCart: '=',
onSlotStartToModify: '=',
onSlotModifyDestination: '=',
onSlotModifySuccess: '=',
onSlotModifyCancel: '=',
onSlotModifyUnselect: '=',
onSlotCancelSuccess: '=',
afterPayment: '=',
reservableId: '@',
reservableType: '@',
reservableName: '@',
limitToOneSlot: '@'
},
templateUrl: '<%= asset_path "shared/_cart.html" %>',
link ($scope, element, attributes) {
2018-11-19 16:17:49 +01:00
// will store the user's plan if he choosed to buy one
2018-11-21 11:08:53 +01:00
$scope.selectedPlan = null;
2018-11-19 16:17:49 +01:00
// total amount of the bill to pay
2018-11-21 11:08:53 +01:00
$scope.amountTotal = 0;
2018-11-19 16:17:49 +01:00
// total amount of the elements in the cart, without considering any coupon
2018-11-21 11:08:53 +01:00
$scope.totalNoCoupon = 0;
// once the cart was paid, retain the total amount paid by the customer
$scope.amountPaid = 0;
2018-11-19 16:17:49 +01:00
// Discount coupon to apply to the basket, if any
2018-11-21 11:08:53 +01:00
$scope.coupon = { applied: null };
2018-11-19 16:17:49 +01:00
// Global config: is the user authorized to change his bookings slots?
2018-11-21 11:08:53 +01:00
$scope.enableBookingMove = ($scope.settings.booking_move_enable === 'true');
2018-11-19 16:17:49 +01:00
// Global config: delay in hours before a booking while changing the booking slot is forbidden
2018-11-21 11:08:53 +01:00
$scope.moveBookingDelay = parseInt($scope.settings.booking_move_delay);
2018-11-19 16:17:49 +01:00
// Global config: is the user authorized to cancel his bookings?
2018-11-21 11:08:53 +01:00
$scope.enableBookingCancel = ($scope.settings.booking_cancel_enable === 'true');
2018-11-19 16:17:49 +01:00
// Global config: delay in hours before a booking while the cancellation is forbidden
2018-11-21 11:08:53 +01:00
$scope.cancelBookingDelay = parseInt($scope.settings.booking_cancel_delay);
2018-11-19 16:17:49 +01:00
/**
* Add the provided slot to the shopping cart (state transition from free to 'about to be reserved')
* and increment the total amount of the cart if needed.
* @param slot {Object} fullCalendar event object
*/
$scope.validateSlot = function (slot) {
validateTags(slot, function () {
validateSameTimeReservations(slot, function () {
slot.isValid = true;
updateCartPrice();
})
})
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Remove the provided slot from the shopping cart (state transition from 'about to be reserved' to free)
* and decrement the total amount of the cart if needed.
* @param slot {Object} fullCalendar event object
* @param index {number} index of the slot in the reservation array
* @param [event] {Object} see https://docs.angularjs.org/guide/expression#-event-
*/
$scope.removeSlot = function (slot, index, event) {
2018-11-21 11:08:53 +01:00
if (event) { event.preventDefault(); }
$scope.events.reserved.splice(index, 1);
// if is was the last slot, we remove any plan from the cart
if ($scope.events.reserved.length === 0) {
2018-11-21 11:08:53 +01:00
$scope.selectedPlan = null;
$scope.plan = null;
$scope.modePlans = false;
}
2018-11-21 11:08:53 +01:00
if (typeof $scope.onSlotRemovedFromCart === 'function') { $scope.onSlotRemovedFromCart(slot); }
return updateCartPrice();
};
2018-11-19 16:17:49 +01:00
/**
* Checks that every selected slots were added to the shopping cart. Ie. will return false if
* any checked slot was not validated by the user.
*/
$scope.isSlotsValid = function () {
2018-11-21 11:08:53 +01:00
let isValid = true;
angular.forEach($scope.events.reserved, function (m) {
2018-11-21 11:08:53 +01:00
if (!m.isValid) { return isValid = false; }
});
return isValid;
};
2018-11-19 16:17:49 +01:00
/**
* Switch the user's view from the reservation agenda to the plan subscription
*/
$scope.showPlans = function () {
// first, we ensure that a user was selected (admin/manager) or logged (member)
const isSelectedUser = Object.keys($scope.user).length > 0;
// all slots are in future
const areFutureSlots = _.every($scope.events.reserved, function(s) {
return s.start.isAfter();
});
if (isSelectedUser && areFutureSlots) {
2018-11-21 11:08:53 +01:00
return $scope.modePlans = true;
} else if (!isSelectedUser){
// otherwise we alert, this error musn't occur when the current user hasn't the admin role
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
} else if (!areFutureSlots){
2020-01-21 11:16:49 +01:00
return growl.error(_t('app.shared.cart.unable_to_select_plan_if_slots_in_the_past'));
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Validates the shopping chart and redirect the user to the payment step
*/
$scope.payCart = function () {
// first, we check that a user was selected
if (Object.keys($scope.user).length > 0) {
// check selected user has a subscription, if any slot is restricted for subscriptions
2020-02-12 12:58:17 +01:00
const slotValidations = [];
let slotNotValid;
let slotNotValidError;
2020-02-11 11:46:40 +01:00
$scope.events.reserved.forEach(function (slot) {
if (slot.plan_ids.length > 0) {
if (
($scope.selectedPlan && _.include(slot.plan_ids, $scope.selectedPlan.id)) ||
($scope.user.subscribed_plan && _.include(slot.plan_ids, $scope.user.subscribed_plan.id))
) {
2020-02-12 12:58:17 +01:00
slotValidations.push(true);
} else {
2020-02-12 12:58:17 +01:00
slotNotValid = slot;
if ($scope.selectedPlan && !_.include(slot.plan_ids, $scope.selectedPlan.id)) {
slotNotValidError = 'selectedPlanError';
}
if ($scope.user.subscribed_plan && !_.include(slot.plan_ids, $scope.user.subscribed_plan.id)) {
slotNotValidError = 'userPlanError';
}
if (!$scope.selectedPlan || !$scope.user.subscribed_plan) {
slotNotValidError = 'noPlanError';
}
slotValidations.push(false);
}
}
2018-11-21 11:08:53 +01:00
});
2020-02-12 12:58:17 +01:00
const hasPlanForSlot = slotValidations.every(function (a) { return a; });
if (!hasPlanForSlot) {
if (!AuthService.isAuthorized(['admin', 'manager'])) {
2020-02-12 12:58:17 +01:00
return growl.error(_t('app.shared.cart.slot_restrict_subscriptions_must_select_plan'));
} else {
2020-02-12 12:58:17 +01:00
const modalInstance = $uibModal.open({
animation: true,
templateUrl: '<%= asset_path "shared/_reserve_slot_without_plan.html" %>',
size: 'md',
controller: 'ReserveSlotWithoutPlanController',
resolve: {
slot: function() { return slotNotValid; },
slotNotValidError: function() { return slotNotValidError; },
}
});
modalInstance.result.then(function(res) {
return paySlots();
});
}
2020-02-12 12:58:17 +01:00
} else {
return paySlots();
}
} else {
// otherwise we alert, this error musn't occur when the current user is not admin or manager
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* When modifying an already booked reservation, confirm the modification.
*/
2018-11-20 13:44:53 +01:00
$scope.modifySlot = function () {
Slot.update({ id: $scope.events.modifiable.id }, {
slot: {
start_at: $scope.events.placable.start,
end_at: $scope.events.placable.end,
availability_id: $scope.events.placable.availability_id
}
}
, function () { // success
2018-11-20 13:44:53 +01:00
// -> run the callback
2018-11-21 11:08:53 +01:00
if (typeof $scope.onSlotModifySuccess === 'function') { $scope.onSlotModifySuccess(); }
// -> set the events as successfully moved (to display a summary)
$scope.events.moved = {
newSlot: $scope.events.placable,
oldSlot: $scope.events.modifiable
2018-11-21 11:08:53 +01:00
};
// -> reset the 'moving' status
2018-11-21 11:08:53 +01:00
$scope.events.placable = null;
return $scope.events.modifiable = null;
}
, function (err) { // failure
growl.error(_t('app.shared.cart.unable_to_change_the_reservation'));
2018-11-21 11:08:53 +01:00
return console.error(err);
});
};
2018-11-19 16:17:49 +01:00
/**
* Cancel the current booking modification, reseting the whole process
* @param event {Object} see https://docs.angularjs.org/guide/expression#-event-
*/
$scope.cancelModifySlot = function (event) {
2018-11-21 11:08:53 +01:00
if (event) { event.preventDefault(); }
if (typeof $scope.onSlotModifyCancel === 'function') { $scope.onSlotModifyCancel(); }
$scope.events.placable = null;
return $scope.events.modifiable = null;
};
2018-11-19 16:17:49 +01:00
/**
* When modifying an already booked reservation, cancel the choice of the new slot
* @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
*/
$scope.removeSlotToPlace = function (e) {
2018-11-21 11:08:53 +01:00
e.preventDefault();
if (typeof $scope.onSlotModifyUnselect === 'function') { $scope.onSlotModifyUnselect(); }
return $scope.events.placable = null;
};
2018-11-19 16:17:49 +01:00
/**
* Checks if $scope.events.modifiable and $scope.events.placable have tag incompatibilities
* @returns {boolean} true in case of incompatibility
*/
$scope.tagMissmatch = function () {
2018-11-21 11:08:53 +01:00
if ($scope.events.placable.tag_ids.length === 0) { return false; }
for (let tag of Array.from($scope.events.modifiable.tags)) {
if (!Array.from($scope.events.placable.tag_ids).includes(tag.id)) {
2018-11-21 11:08:53 +01:00
return true;
}
}
2018-11-21 11:08:53 +01:00
return false;
};
/**
* Check if the currently logged user has the 'admin' role OR the 'manager' role, but is not taking reseravtion for himself
* @returns {boolean}
*/
$scope.isAuthorized = function () {
if (AuthService.isAuthorized('admin')) return true;
if (AuthService.isAuthorized('manager')) {
return ($rootScope.currentUser.id !== $scope.user.id);
}
return false;
}
/* PRIVATE SCOPE */
2018-11-19 16:17:49 +01:00
/**
* Kind of constructor: these actions will be realized first when the directive is loaded
*/
const initialize = function () {
2018-11-20 13:44:53 +01:00
// What the bound slot
$scope.$watch('slotSelectionTime', function (newValue, oldValue) {
if (newValue !== oldValue) {
2018-11-21 11:08:53 +01:00
return slotSelectionChanged();
}
2018-11-21 11:08:53 +01:00
});
$scope.$watch('user', function (newValue, oldValue) {
if (newValue !== oldValue) {
2018-11-21 11:08:53 +01:00
resetCartState();
return updateCartPrice();
}
2018-11-21 11:08:53 +01:00
});
$scope.$watch('planSelectionTime', function (newValue, oldValue) {
if (newValue !== oldValue) {
2018-11-21 11:08:53 +01:00
return planSelectionChanged();
}
2018-11-21 11:08:53 +01:00
});
// watch when a coupon is applied to re-compute the total price
$scope.$watch('coupon.applied', function (newValue, oldValue) {
if (newValue !== oldValue) {
2018-11-21 11:08:53 +01:00
return updateCartPrice();
}
2018-11-21 11:08:53 +01:00
});
};
/**
* 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();
}
}
2018-11-19 16:17:49 +01:00
/**
* Callback triggered when the selected slot changed
*/
const slotSelectionChanged = function () {
if ($scope.slot) {
// if this slot is restricted for subscribers...
if ($scope.slot.plan_ids.length > 0) {
// ... we select all the plans matching these restrictions...
2020-02-11 11:46:40 +01:00
const _plans = _.filter($scope.plans, function (p) { return _.include($scope.slot.plan_ids, p.id) });
// ... and we group these plans, by Group...
$scope.slot.plansGrouped = [];
2020-02-12 12:58:17 +01:00
$scope.slot.group_ids = [];
for (let group of Array.from($scope.groups)) {
const groupObj = { id: group.id, name: group.name, plans: [] };
for (let plan of Array.from(_plans)) {
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
}
if (groupObj.plans.length > 0) {
// ... Finally, we only keep the plans matching the group of the current user
// OR all plans if the current user is admin or manager
if (AuthService.isAuthorized(['admin', 'manager'])) {
2020-02-12 12:58:17 +01:00
$scope.slot.plansGrouped.push(groupObj);
} else if ($scope.user.group_id === groupObj.id) {
$scope.slot.plansGrouped.push(groupObj);
}
}
}
2020-02-12 12:58:17 +01:00
$scope.slot.group_ids = $scope.slot.plansGrouped.map(function(g) { return g.id; });
}
if (!$scope.slot.is_reserved && !$scope.events.modifiable && !$scope.slot.is_completed) {
2018-11-20 13:44:53 +01:00
// slot is not reserved and we are not currently modifying a slot
// -> can be added to cart or removed if already present
2018-11-21 11:08:53 +01:00
const index = $scope.events.reserved.indexOf($scope.slot);
if (index === -1) {
if (($scope.limitToOneSlot === 'true') && $scope.events.reserved[0]) {
// if we limit the number of slots in the cart to 1, and there is already
// a slot in the cart, we remove it before adding the new one
2018-11-21 11:08:53 +01:00
$scope.removeSlot($scope.events.reserved[0], 0);
}
// slot is not in the cart, so we add it
2018-11-21 11:08:53 +01:00
$scope.events.reserved.push($scope.slot);
if (typeof $scope.onSlotAddedToCart === 'function') { $scope.onSlotAddedToCart(); }
} else {
2018-11-20 13:44:53 +01:00
// slot is in the cart, remove it
2018-11-21 11:08:53 +01:00
$scope.removeSlot($scope.slot, index);
}
// in every cases, because a new reservation has started, we reset the cart content
2018-11-21 11:08:53 +01:00
resetCartState();
// finally, we update the prices
2018-11-21 11:08:53 +01:00
return updateCartPrice();
} else if (!$scope.slot.is_reserved && !$scope.slot.is_completed && $scope.events.modifiable) {
2018-11-20 13:44:53 +01:00
// slot is not reserved but we are currently modifying a slot
// -> we request the calender to change the rendering
2018-11-21 11:08:53 +01:00
if (typeof $scope.onSlotModifyUnselect === 'function') { $scope.onSlotModifyUnselect(); }
// -> then, we re-affect the destination slot
if (!$scope.events.placable || ($scope.events.placable._id !== $scope.slot._id)) {
2018-11-21 11:08:53 +01:00
return $scope.events.placable = $scope.slot;
} else {
2018-11-21 11:08:53 +01:00
return $scope.events.placable = null;
}
} else if ($scope.slot.is_reserved && $scope.events.modifiable && ($scope.slot.is_reserved._id === $scope.events.modifiable._id)) {
2018-11-20 13:44:53 +01:00
// slot is reserved and currently modified
// -> we cancel the modification
2018-11-21 11:08:53 +01:00
return $scope.cancelModifySlot();
} else if ($scope.slot.is_reserved && (slotCanBeModified($scope.slot) || slotCanBeCanceled($scope.slot)) && !$scope.events.modifiable && ($scope.events.reserved.length === 0)) {
2018-11-20 13:44:53 +01:00
// slot is reserved and is ok to be modified or cancelled
// but we are not currently running a modification or having any slots in the cart
// -> first the affect the modification/cancellation rights attributes to the current slot
2018-11-21 11:08:53 +01:00
resetCartState();
$scope.slot.movable = slotCanBeModified($scope.slot);
$scope.slot.cancelable = slotCanBeCanceled($scope.slot);
// -> then, we open a dialog to ask to the user to choose an action
return dialogs.confirm({
templateUrl: '<%= asset_path "shared/confirm_modify_slot_modal.html" %>',
resolve: {
2018-11-21 11:08:53 +01:00
object () { return $scope.slot; }
}
}
, function (type) {
// the user has chosen an action, so we proceed
if (type === 'move') {
2018-11-21 11:08:53 +01:00
if (typeof $scope.onSlotStartToModify === 'function') { $scope.onSlotStartToModify(); }
return $scope.events.modifiable = $scope.slot;
} else if (type === 'cancel') {
2018-11-20 13:44:53 +01:00
return dialogs.confirm(
{
resolve: {
object () {
return {
title: _t('app.shared.cart.confirmation_required'),
msg: _t('app.shared.cart.do_you_really_want_to_cancel_this_reservation')
2018-11-21 11:08:53 +01:00
};
}
}
2018-11-20 13:44:53 +01:00
},
function () { // cancel confirmed
Slot.cancel({ id: $scope.slot.id }, function () { // successfully canceled
growl.success(_t('app.shared.cart.reservation_was_cancelled_successfully'));
2018-11-21 11:08:53 +01:00
if (typeof $scope.onSlotCancelSuccess === 'function') { return $scope.onSlotCancelSuccess(); }
2018-11-20 13:44:53 +01:00
}
2018-11-21 10:59:07 +01:00
, function () { // error while canceling
growl.error(_t('app.shared.cart.cancellation_failed'));
2018-11-21 11:08:53 +01:00
});
}
2018-11-21 11:08:53 +01:00
);
}
2018-11-21 11:08:53 +01:00
});
}
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Reset the parameters that may lead to a wrong price but leave the content (events added to cart)
*/
const resetCartState = function () {
2018-11-21 11:08:53 +01:00
$scope.selectedPlan = null;
$scope.coupon.applied = null;
$scope.events.moved = null;
$scope.events.paid = [];
$scope.events.modifiable = null;
return $scope.events.placable = null;
};
2018-11-19 16:17:49 +01:00
/**
* Determines if the provided booked slot is able to be modified by the user.
* @param slot {Object} fullCalendar event object
*/
const slotCanBeModified = function (slot) {
if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
2018-11-21 11:08:53 +01:00
const slotStart = moment(slot.start);
const now = moment();
return (slot.can_modify && $scope.enableBookingMove && (slotStart.diff(now, 'hours') >= $scope.moveBookingDelay));
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Determines if the provided booked slot is able to be canceled by the user.
* @param slot {Object} fullCalendar event object
*/
const slotCanBeCanceled = function (slot) {
if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
2018-11-21 11:08:53 +01:00
const slotStart = moment(slot.start);
const now = moment();
return (slot.can_modify && $scope.enableBookingCancel && (slotStart.diff(now, 'hours') >= $scope.cancelBookingDelay));
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Callback triggered when the selected slot changed
*/
const planSelectionChanged = function () {
if (Auth.isAuthenticated()) {
if ($scope.selectedPlan !== $scope.plan) {
2018-11-21 11:08:53 +01:00
$scope.selectedPlan = $scope.plan;
} else {
2018-11-21 11:08:53 +01:00
$scope.selectedPlan = null;
}
2018-11-21 11:08:53 +01:00
return updateCartPrice();
} else {
return $rootScope.login(null, function () {
2018-11-21 11:08:53 +01:00
$scope.selectedPlan = $scope.plan;
return updateCartPrice();
});
}
2018-11-21 11:08:53 +01:00
};
2018-11-19 16:17:49 +01:00
/**
* Update the total price of the current selection/reservation
*/
const updateCartPrice = function () {
if (Object.keys($scope.user).length > 0) {
2018-11-21 11:08:53 +01:00
const r = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
return Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) {
2018-11-21 11:08:53 +01:00
$scope.amountTotal = res.price;
$scope.totalNoCoupon = res.price_without_coupon;
setSlotsDetails(res.details);
2018-11-21 11:08:53 +01:00
});
} else {
2018-11-20 13:44:53 +01:00
// 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'));
$scope.amountTotal = null;
}
2018-11-21 11:08:53 +01:00
};
const setSlotsDetails = function (details) {
2018-11-20 13:44:53 +01:00
angular.forEach($scope.events.reserved, function (slot) {
angular.forEach(details.slots, function (s) {
if (moment(s.start_at).isSame(slot.start)) {
2018-11-21 11:08:53 +01:00
slot.promo = s.promo;
slot.price = s.price;
}
2018-11-21 11:08:53 +01:00
});
});
};
2018-11-19 16:17:49 +01:00
/**
* Format the parameters expected by /api/prices/compute or /api/reservations and return the resulting object
* @param reservation {Object} as returned by mkReservation()
* @param coupon {Object} Coupon as returned from the API
* @return {{reservation:Object, coupon_code:string}}
*/
const mkRequestParams = function (reservation, coupon) {
2018-11-20 13:44:53 +01:00
return {
reservation,
coupon_code: ((coupon ? coupon.code : undefined))
2018-11-21 11:08:53 +01:00
};
};
2018-11-19 16:17:49 +01:00
/**
* Create an hash map implementing the Reservation specs
2018-11-20 13:44:53 +01:00
* @param member {Object} User as retrieved from the API: current user / selected user if current is admin
2018-11-19 16:17:49 +01:00
* @param slots {Array<Object>} Array of fullCalendar events: slots selected on the calendar
2018-11-20 13:44:53 +01:00
* @param [plan] {Object} Plan as retrieved from the API: plan to buy with the current reservation
2018-11-19 16:17:49 +01:00
* @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
*/
const mkReservation = function (member, slots, plan) {
const reservation = {
user_id: member.id,
reservable_id: $scope.reservableId,
reservable_type: $scope.reservableType,
slots_attributes: [],
plan_id: ((plan ? plan.id : undefined))
2018-11-21 11:08:53 +01:00
};
angular.forEach(slots, function (slot) {
reservation.slots_attributes.push({
start_at: slot.start,
end_at: slot.end,
availability_id: slot.availability_id,
offered: slot.offered || false
2018-11-21 11:08:53 +01:00
});
});
2018-11-21 11:08:53 +01:00
return reservation;
};
2018-11-19 16:17:49 +01:00
/**
* Open a modal window that allows the user to process a credit card payment for his current shopping cart.
*/
const payByStripe = function (reservation) {
$uibModal.open({
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>',
size: 'md',
resolve: {
reservation () {
2018-11-21 11:08:53 +01:00
return reservation;
},
price () {
2018-11-21 11:08:53 +01:00
return Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise;
},
wallet () {
2018-11-21 11:08:53 +01:00
return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
},
cgv () {
2018-11-21 11:08:53 +01:00
return CustomAsset.get({ name: 'cgv-file' }).$promise;
},
coupon () {
2018-11-21 11:08:53 +01:00
return $scope.coupon.applied;
2019-09-09 17:37:54 +02:00
},
cartItems () {
return mkRequestParams(reservation, $scope.coupon.applied);
}
},
2019-09-09 17:37:54 +02:00
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'cgv', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon', 'cartItems',
function ($scope, $uibModalInstance, $state, reservation, price, cgv, Auth, Reservation, wallet, helpers, $filter, coupon, cartItems) {
2018-11-20 13:44:53 +01:00
// user wallet amount
2018-11-21 11:08:53 +01:00
$scope.walletAmount = wallet.amount;
// Price
2018-11-21 11:08:53 +01:00
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
// Cart items
2019-09-09 17:37:54 +02:00
$scope.cartItems = cartItems;
// CGV
2018-11-21 11:08:53 +01:00
$scope.cgv = cgv.custom_asset;
// Reservation
2018-11-21 11:08:53 +01:00
$scope.reservation = reservation;
// Used in wallet info template to interpolate some translations
2018-11-21 11:08:53 +01:00
$scope.numberFilter = $filter('number');
2018-11-19 16:17:49 +01:00
/**
* Callback to handle the post-payment and reservation
2018-11-19 16:17:49 +01:00
*/
2019-09-10 12:46:02 +02:00
$scope.onPaymentSuccess = function (response) {
$uibModalInstance.close(response);
2018-11-21 11:08:53 +01:00
};
}
2018-11-20 13:44:53 +01:00
]
2018-11-21 11:08:53 +01:00
}).result['finally'](null).then(function (reservation) { afterPayment(reservation); });
};
2018-11-19 16:17:49 +01:00
/**
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
*/
const payOnSite = function (reservation) {
$uibModal.open({
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>',
size: 'sm',
resolve: {
reservation () {
2018-11-21 11:08:53 +01:00
return reservation;
},
price () {
2018-11-21 11:08:53 +01:00
return Price.compute(mkRequestParams(reservation, $scope.coupon.applied)).$promise;
},
wallet () {
2018-11-21 11:08:53 +01:00
return Wallet.getWalletByUser({ user_id: reservation.user_id }).$promise;
},
coupon () {
2018-11-21 11:08:53 +01:00
return $scope.coupon.applied;
}
},
controller: ['$scope', '$uibModalInstance', '$state', 'reservation', 'price', 'Auth', 'Reservation', 'wallet', 'helpers', '$filter', 'coupon',
function ($scope, $uibModalInstance, $state, reservation, price, Auth, Reservation, wallet, helpers, $filter, coupon) {
// user wallet amount
2018-11-21 11:08:53 +01:00
$scope.walletAmount = wallet.amount;
2018-11-21 10:59:07 +01:00
// Global price (total of all items)
2018-11-21 11:08:53 +01:00
$scope.price = price.price;
2018-11-21 10:59:07 +01:00
// Price to pay (wallet deducted)
2018-11-21 11:08:53 +01:00
$scope.amount = helpers.getAmountToPay(price.price, wallet.amount);
2018-11-21 10:59:07 +01:00
// Reservation
2018-11-21 11:08:53 +01:00
$scope.reservation = reservation;
2018-11-21 10:59:07 +01:00
// Used in wallet info template to interpolate some translations
2018-11-21 11:08:53 +01:00
$scope.numberFilter = $filter('number');
// Button label
if ($scope.amount > 0) {
$scope.validButtonName = _t('app.shared.cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')($scope.amount) });
} else {
if ((price.price > 0) && ($scope.walletAmount === 0)) {
$scope.validButtonName = _t('app.shared.cart.confirm_payment_of_html', { ROLE: $rootScope.currentUser.role, AMOUNT: $filter('currency')(price.price) });
} else {
$scope.validButtonName = _t('app.shared.buttons.confirm');
}
}
2018-11-19 16:17:49 +01:00
/**
* Callback to process the local payment, triggered on button click
*/
$scope.ok = function () {
2018-11-21 11:08:53 +01:00
$scope.attempting = true;
return Reservation.save(mkRequestParams($scope.reservation, coupon), function (reservation) {
2018-11-21 11:08:53 +01:00
$uibModalInstance.close(reservation);
return $scope.attempting = true;
}
, function (response) {
2018-11-21 11:08:53 +01:00
$scope.alerts = [];
$scope.alerts.push({ msg: _t('app.shared.cart.a_problem_occurred_during_the_payment_process_please_try_again_later'), type: 'danger' });
2018-11-21 11:08:53 +01:00
return $scope.attempting = false;
});
};
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
}
2018-11-20 13:44:53 +01:00
]
2018-11-21 11:08:53 +01:00
}).result['finally'](null).then(function (reservation) { afterPayment(reservation); });
};
2018-11-19 16:17:49 +01:00
/**
* Actions to run after the payment was successful
2018-11-19 16:17:49 +01:00
*/
const afterPayment = function (reservation) {
2018-11-20 13:44:53 +01:00
// we set the cart content as 'paid' to display a summary of the transaction
2018-11-21 11:08:53 +01:00
$scope.events.paid = $scope.events.reserved;
$scope.amountPaid = $scope.amountTotal;
// we call the external callback if present
2018-11-21 11:08:53 +01:00
if (typeof $scope.afterPayment === 'function') { $scope.afterPayment(reservation); }
// we reset the coupon and the cart content and we unselect the slot
2018-11-21 11:08:53 +01:00
$scope.events.reserved = [];
$scope.coupon.applied = null;
$scope.slot = null;
return $scope.selectedPlan = null;
};
2017-02-16 17:57:14 +01:00
2020-02-12 12:58:17 +01:00
/**
* Actions to pay slots
*/
const paySlots = function() {
2020-02-12 12:58:17 +01:00
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
if ((AuthService.isAuthorized(['member']) && amountToPay > 0)
|| (AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) {
2020-02-12 12:58:17 +01:00
if ($rootScope.fablabWithoutOnlinePayment) {
growl.error(_t('app.shared.cart.online_payment_disabled'));
} else {
return payByStripe(reservation);
}
} else {
if (AuthService.isAuthorized(['admin'])
|| (AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id)
|| amountToPay === 0) {
2020-02-12 12:58:17 +01:00
return payOnSite(reservation);
}
}
});
};
2018-11-19 16:17:49 +01:00
// !!! MUST BE CALLED AT THE END of the directive
2018-11-21 11:08:53 +01:00
return initialize();
}
2018-11-21 11:08:53 +01:00
});
2018-11-20 13:44:53 +01:00
}
2018-11-21 11:08:53 +01:00
]);
/**
* Controller of the modal showing the reservations the same date at the same time
*/
Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '$uibModalInstance', 'AuthService', 'sameTimeReservations',
function ($scope, $uibModalInstance, AuthService, sameTimeReservations) {
$scope.sameTimeReservations = sameTimeReservations;
$scope.bookSlotAtSameTime = Fablab.bookSlotAtSameTime;
2020-04-27 16:44:56 +02:00
$scope.isAuthorized = AuthService.isAuthorized;
/**
* Confirmation callback
*/
$scope.ok = function () {
$uibModalInstance.close({});
}
/**
* Cancellation callback
*/
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
}
}
]);
/**
* 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');
}
}
]);
2020-02-12 12:58:17 +01:00
/**
* Controller used to alert admin reserve slot without plan
*/
Application.Controllers.controller('ReserveSlotWithoutPlanController', ['$scope', '$uibModalInstance', 'slot', 'slotNotValidError', 'growl', '_t',
function ($scope, $uibModalInstance, slot, slotNotValidError, growl, _t) {
$scope.slot = slot;
$scope.slotNotValidError = slotNotValidError;
/**
* Confirmation callback
*/
$scope.ok = function () {
$uibModalInstance.close({});
}
/**
* Cancellation callback
*/
$scope.cancel = function () {
$uibModalInstance.dismiss('cancel');
}
}
]);