mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
[manager] manage agenda + book machines for himself&others
This commit is contained in:
parent
c45c92e86a
commit
5312c13d3f
@ -604,16 +604,18 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
||||
angular.forEach($scope.events.reserved, function (machineSlot, key) {
|
||||
machineSlot.is_reserved = true;
|
||||
machineSlot.can_modify = true;
|
||||
if ($scope.currentUser.role !== 'admin') {
|
||||
machineSlot.title = _t('app.logged.machines_reserve.i_ve_reserved');
|
||||
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR;
|
||||
updateMachineSlot(machineSlot, reservation, $scope.currentUser);
|
||||
} else {
|
||||
if ($scope.currentUser.role === 'admin' || ($scope.currentUser.role === 'manager' && reservation.user_id !== $scope.currentUser.id)) {
|
||||
// an admin or a manager booked for someone else
|
||||
machineSlot.title = _t('app.logged.machines_reserve.not_available');
|
||||
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR;
|
||||
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member);
|
||||
} else {
|
||||
// booked for "myself"
|
||||
machineSlot.title = _t('app.logged.machines_reserve.i_ve_reserved');
|
||||
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR;
|
||||
updateMachineSlot(machineSlot, reservation, $scope.currentUser);
|
||||
}
|
||||
return machineSlot.backgroundColor = 'white';
|
||||
machineSlot.backgroundColor = 'white';
|
||||
});
|
||||
|
||||
if ($scope.selectedPlan) {
|
||||
|
@ -10,8 +10,8 @@
|
||||
* 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', 'helpers', '_t', '$uibModal',
|
||||
function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, helpers, _t, $uibModal) {
|
||||
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) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
@ -167,7 +167,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
// first, we check that a user was selected
|
||||
if (Object.keys($scope.user).length > 0) {
|
||||
|
||||
// check user was selected a plan if slot is restricted for subscriptions
|
||||
// check selected user has a subscription, if any slot is restricted for subscriptions
|
||||
const slotValidations = [];
|
||||
let slotNotValid;
|
||||
let slotNotValidError;
|
||||
@ -195,7 +195,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
});
|
||||
const hasPlanForSlot = slotValidations.every(function (a) { return a; });
|
||||
if (!hasPlanForSlot) {
|
||||
if (!$scope.isAdmin()) {
|
||||
if (!AuthService.isAuthorized(['admin', 'manager'])) {
|
||||
return growl.error(_t('app.shared.cart.slot_restrict_subscriptions_must_select_plan'));
|
||||
} else {
|
||||
const modalInstance = $uibModal.open({
|
||||
@ -216,7 +216,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
return paySlots();
|
||||
}
|
||||
} 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 or manager
|
||||
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
|
||||
}
|
||||
};
|
||||
@ -285,12 +285,6 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the currently logged user has teh 'admin' role?
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.isAdmin = function () { return $rootScope.currentUser && ($rootScope.currentUser.role === 'admin'); };
|
||||
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
/**
|
||||
@ -325,11 +319,13 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Callback triggered when the selected slot changed
|
||||
*/
|
||||
var slotSelectionChanged = function () {
|
||||
const slotSelectionChanged = function () {
|
||||
if ($scope.slot) {
|
||||
// build a list of plans if this slot is restricted for subscriptions
|
||||
// if this slot is restricted for subscribers...
|
||||
if ($scope.slot.plan_ids.length > 0) {
|
||||
// ... we select all the plans matching these restrictions...
|
||||
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 = [];
|
||||
$scope.slot.group_ids = [];
|
||||
for (let group of Array.from($scope.groups)) {
|
||||
@ -338,7 +334,9 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
||||
}
|
||||
if (groupObj.plans.length > 0) {
|
||||
if ($scope.isAdmin()) {
|
||||
// ... 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'])) {
|
||||
$scope.slot.plansGrouped.push(groupObj);
|
||||
} else if ($scope.user.group_id === groupObj.id) {
|
||||
$scope.slot.plansGrouped.push(groupObj);
|
||||
@ -398,7 +396,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
}
|
||||
}
|
||||
, function (type) {
|
||||
// the user has choosen an action, so we proceed
|
||||
// the user has chosen an action, so we proceed
|
||||
if (type === 'move') {
|
||||
if (typeof $scope.onSlotStartToModify === 'function') { $scope.onSlotStartToModify(); }
|
||||
return $scope.events.modifiable = $scope.slot;
|
||||
@ -433,7 +431,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Reset the parameters that may lead to a wrong price but leave the content (events added to cart)
|
||||
*/
|
||||
var resetCartState = function () {
|
||||
const resetCartState = function () {
|
||||
$scope.selectedPlan = null;
|
||||
$scope.coupon.applied = null;
|
||||
$scope.events.moved = null;
|
||||
@ -446,8 +444,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
* Determines if the provided booked slot is able to be modified by the user.
|
||||
* @param slot {Object} fullCalendar event object
|
||||
*/
|
||||
var slotCanBeModified = function (slot) {
|
||||
if ($scope.isAdmin()) { return true; }
|
||||
const slotCanBeModified = function (slot) {
|
||||
if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
|
||||
const slotStart = moment(slot.start);
|
||||
const now = moment();
|
||||
return (slot.can_modify && $scope.enableBookingMove && (slotStart.diff(now, 'hours') >= $scope.moveBookingDelay));
|
||||
@ -457,8 +455,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
* Determines if the provided booked slot is able to be canceled by the user.
|
||||
* @param slot {Object} fullCalendar event object
|
||||
*/
|
||||
var slotCanBeCanceled = function (slot) {
|
||||
if ($scope.isAdmin()) { return true; }
|
||||
const slotCanBeCanceled = function (slot) {
|
||||
if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
|
||||
const slotStart = moment(slot.start);
|
||||
const now = moment();
|
||||
return (slot.can_modify && $scope.enableBookingCancel && (slotStart.diff(now, 'hours') >= $scope.cancelBookingDelay));
|
||||
@ -467,7 +465,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Callback triggered when the selected slot changed
|
||||
*/
|
||||
var planSelectionChanged = function () {
|
||||
const planSelectionChanged = function () {
|
||||
if (Auth.isAuthenticated()) {
|
||||
if ($scope.selectedPlan !== $scope.plan) {
|
||||
$scope.selectedPlan = $scope.plan;
|
||||
@ -486,7 +484,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Update the total price of the current selection/reservation
|
||||
*/
|
||||
var updateCartPrice = function () {
|
||||
const updateCartPrice = function () {
|
||||
if (Object.keys($scope.user).length > 0) {
|
||||
const r = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
|
||||
return Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) {
|
||||
@ -501,7 +499,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
}
|
||||
};
|
||||
|
||||
var setSlotsDetails = function (details) {
|
||||
const setSlotsDetails = function (details) {
|
||||
angular.forEach($scope.events.reserved, function (slot) {
|
||||
angular.forEach(details.slots, function (s) {
|
||||
if (moment(s.start_at).isSame(slot.start)) {
|
||||
@ -518,7 +516,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
* @param coupon {Object} Coupon as returned from the API
|
||||
* @return {{reservation:Object, coupon_code:string}}
|
||||
*/
|
||||
var mkRequestParams = function (reservation, coupon) {
|
||||
const mkRequestParams = function (reservation, coupon) {
|
||||
return {
|
||||
reservation,
|
||||
coupon_code: ((coupon ? coupon.code : undefined))
|
||||
@ -532,7 +530,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
* @param [plan] {Object} Plan as retrieved from the API: plan to buy with the current reservation
|
||||
* @return {{user_id:Number, reservable_id:Number, reservable_type:String, slots_attributes:Array<Object>, plan_id:Number|null}}
|
||||
*/
|
||||
var mkReservation = function (member, slots, plan) {
|
||||
const mkReservation = function (member, slots, plan) {
|
||||
const reservation = {
|
||||
user_id: member.id,
|
||||
reservable_id: $scope.reservableId,
|
||||
@ -555,7 +553,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Open a modal window that allows the user to process a credit card payment for his current shopping cart.
|
||||
*/
|
||||
var payByStripe = function (reservation) {
|
||||
const payByStripe = function (reservation) {
|
||||
$uibModal.open({
|
||||
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>',
|
||||
size: 'md',
|
||||
@ -612,7 +610,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Open a modal window that allows the user to process a local payment for his current shopping cart (admin only).
|
||||
*/
|
||||
var payOnSite = function (reservation) {
|
||||
const payOnSite = function (reservation) {
|
||||
$uibModal.open({
|
||||
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>',
|
||||
size: 'sm',
|
||||
@ -681,7 +679,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Actions to run after the payment was successful
|
||||
*/
|
||||
var afterPayment = function (reservation) {
|
||||
const afterPayment = function (reservation) {
|
||||
// we set the cart content as 'paid' to display a summary of the transaction
|
||||
$scope.events.paid = $scope.events.reserved;
|
||||
$scope.amountPaid = $scope.amountTotal;
|
||||
@ -697,19 +695,22 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
||||
/**
|
||||
* Actions to pay slots
|
||||
*/
|
||||
var paySlots = function() {
|
||||
const paySlots = function() {
|
||||
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 (!$scope.isAdmin() && (amountToPay > 0)) {
|
||||
if ((AuthService.isAuthorized(['member']) && amountToPay > 0)
|
||||
|| (AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) {
|
||||
if ($rootScope.fablabWithoutOnlinePayment) {
|
||||
growl.error(_t('app.shared.cart.online_payment_disabled'));
|
||||
} else {
|
||||
return payByStripe(reservation);
|
||||
}
|
||||
} else {
|
||||
if ($scope.isAdmin() || (amountToPay === 0)) {
|
||||
if (AuthService.isAuthorized(['admin'])
|
||||
|| (AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id)
|
||||
|| amountToPay === 0) {
|
||||
return payOnSite(reservation);
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="m text-center">
|
||||
<div class="m text-center" ng-show="AuthService.isAuthorized('admin')">
|
||||
<a class="btn btn-default export-xls-button"
|
||||
ng-href="api/availabilities/export_index.xlsx"
|
||||
target="export-frame"
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||
|
||||
<div ng-if="currentUser.role === 'admin'">
|
||||
<div ng-if="isAuthorized(['admin', 'manager'])">
|
||||
<select-member></select-member>
|
||||
</div>
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
<div class="panel-body">
|
||||
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(slot.start | amDateFormat:'LLLL'), END_TIME:(slot.end | amDateFormat:'LT') } }}</div>
|
||||
<div class="text-base">{{ 'app.shared.cart.cost_of_TYPE' | translate:{TYPE:reservableType} }} <span ng-class="{'text-blue': !slot.promo, 'red': slot.promo}">{{slot.price | currency}}</span></div>
|
||||
<div ng-show="isAdmin()" class="m-t">
|
||||
<div ng-show="isAuthorized(['admin', 'manager'])" class="m-t">
|
||||
<label for="offerSlot" class="control-label m-r" translate>{{ 'app.shared.cart.offer_this_slot' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="slot.offered"
|
||||
|
@ -25,12 +25,13 @@ class API::ReservationsController < API::ApiController
|
||||
def show; end
|
||||
|
||||
# Admins can create any reservations. Members can directly create reservations if total = 0,
|
||||
# otherwise, they must use payments_controller#confirm_payment
|
||||
# otherwise, they must use payments_controller#confirm_payment.
|
||||
# Managers can create reservations for other users
|
||||
def create
|
||||
user_id = current_user.admin? ? params[:reservation][:user_id] : current_user.id
|
||||
user_id = current_user.admin? || current_user.manager? ? params[:reservation][:user_id] : current_user.id
|
||||
amount = transaction_amount(current_user.admin?, user_id)
|
||||
|
||||
authorize ReservationContext.new(Reservation, amount)
|
||||
authorize ReservationContext.new(Reservation, amount, user_id)
|
||||
|
||||
@reservation = Reservation.new(reservation_params)
|
||||
is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id)
|
||||
|
@ -112,7 +112,7 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def training_machine?(machine)
|
||||
return true if admin?
|
||||
return true if admin? || manager?
|
||||
|
||||
trainings.map(&:machines).flatten.uniq.include?(machine)
|
||||
end
|
||||
@ -139,6 +139,14 @@ class User < ApplicationRecord
|
||||
has_role? :member
|
||||
end
|
||||
|
||||
def manager?
|
||||
has_role? :manager
|
||||
end
|
||||
|
||||
def partner?
|
||||
has_role? :partner
|
||||
end
|
||||
|
||||
def all_projects
|
||||
my_projects.to_a.concat projects
|
||||
end
|
||||
|
@ -1,7 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Check the access policies for API::AvailabilitiesController
|
||||
class AvailabilityPolicy < ApplicationPolicy
|
||||
%w(index? show? create? update? destroy? reservations? export? lock?).each do |action|
|
||||
%w[index? show? create? update? destroy? reservations? export? lock?].each do |action|
|
||||
define_method action do
|
||||
user.admin?
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,10 +3,10 @@
|
||||
# Check the access policies for API::ICalendarController
|
||||
class ICalendarPolicy < ApplicationPolicy
|
||||
def create?
|
||||
user.admin?
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user.admin?
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
end
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
# Pundit Additional context to validate the price of a reservation
|
||||
class ReservationContext
|
||||
attr_reader :reservation, :price
|
||||
attr_reader :reservation, :price, :user_id
|
||||
|
||||
def initialize(reservation, price)
|
||||
def initialize(reservation, price, user_id)
|
||||
@reservation = reservation
|
||||
@price = price
|
||||
@user_id = user_id
|
||||
end
|
||||
|
||||
def policy_class
|
||||
|
@ -3,10 +3,10 @@
|
||||
# Check the access policies for API::ReservationsController
|
||||
class ReservationPolicy < ApplicationPolicy
|
||||
def create?
|
||||
user.admin? || record.price.zero?
|
||||
user.admin? || (user.manager? && record.user_id != user.id) || record.price.zero?
|
||||
end
|
||||
|
||||
def update?
|
||||
user.admin? || record.user == user
|
||||
user.admin? || user.manager? || record.user == user
|
||||
end
|
||||
end
|
||||
|
@ -1,15 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Check the access policies for API::SlotsController
|
||||
class SlotPolicy < ApplicationPolicy
|
||||
def update?
|
||||
# check that the update is allowed and the prevention delay has not expired
|
||||
delay = Setting.find_by( name: 'booking_move_delay').value.to_i
|
||||
enabled = (Setting.find_by( name: 'booking_move_enable').value == 'true')
|
||||
delay = Setting.find_by(name: 'booking_move_delay').value.to_i
|
||||
enabled = (Setting.find_by(name: 'booking_move_enable').value == 'true')
|
||||
|
||||
# these condition does not apply to admins
|
||||
user.admin? or
|
||||
(record.reservation.user == user and enabled and ((record.start_at - DateTime.current).to_i / 3600 >= delay))
|
||||
user.admin? || user.manager? ||
|
||||
(record.reservation.user == user && enabled && ((record.start_at - DateTime.current).to_i / 3600 >= delay))
|
||||
end
|
||||
|
||||
def cancel?
|
||||
user.admin? or record.reservation.user == user
|
||||
user.admin? || user.manager? || record.reservation.user == user
|
||||
end
|
||||
end
|
||||
|
@ -1,10 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Check the access policies for API::WalletController
|
||||
class WalletPolicy < ApplicationPolicy
|
||||
def by_user?
|
||||
user.admin? or user == record.user
|
||||
user.admin? || user.manager? || user == record.user
|
||||
end
|
||||
|
||||
def transactions?
|
||||
user.admin? or user == record.user
|
||||
user.admin? || user == record.user
|
||||
end
|
||||
|
||||
def credit?
|
||||
|
@ -50,7 +50,7 @@ class Members::ListService
|
||||
'SELECT max("created_at") ' \
|
||||
'FROM "subscriptions" ' \
|
||||
'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' OR roles.name = 'manager')")
|
||||
.limit(50)
|
||||
query.downcase.split(' ').each do |word|
|
||||
members = members.where('lower(f_unaccent(profiles.first_name)) ~ :search OR ' \
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.extract! @machine, :id, :name, :description, :spec, :disabled, :created_at, :updated_at, :slug
|
||||
json.machine_image @machine.machine_image.attachment.large.url if @machine.machine_image
|
||||
json.machine_files_attributes @machine.machine_files do |f|
|
||||
@ -7,9 +9,11 @@ json.machine_files_attributes @machine.machine_files do |f|
|
||||
end
|
||||
json.trainings @machine.trainings.each, :id, :name, :disabled
|
||||
json.current_user_is_training current_user.training_machine?(@machine) if current_user
|
||||
json.current_user_training_reservation do
|
||||
json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(@machine)
|
||||
end if current_user and !current_user.training_machine?(@machine) and current_user.training_reservation_by_machine(@machine)
|
||||
if current_user && !current_user.training_machine?(@machine) && current_user.training_reservation_by_machine(@machine)
|
||||
json.current_user_training_reservation do
|
||||
json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(@machine)
|
||||
end
|
||||
end
|
||||
|
||||
json.machine_projects @machine.projects.published.last(10) do |p|
|
||||
json.id p.id
|
||||
|
Loading…
x
Reference in New Issue
Block a user