1
0
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:
Sylvain 2020-04-27 12:12:29 +02:00
parent c45c92e86a
commit 5312c13d3f
15 changed files with 89 additions and 63 deletions

View File

@ -604,16 +604,18 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
angular.forEach($scope.events.reserved, function (machineSlot, key) { angular.forEach($scope.events.reserved, function (machineSlot, key) {
machineSlot.is_reserved = true; machineSlot.is_reserved = true;
machineSlot.can_modify = true; machineSlot.can_modify = true;
if ($scope.currentUser.role !== 'admin') { if ($scope.currentUser.role === 'admin' || ($scope.currentUser.role === 'manager' && reservation.user_id !== $scope.currentUser.id)) {
machineSlot.title = _t('app.logged.machines_reserve.i_ve_reserved'); // an admin or a manager booked for someone else
machineSlot.borderColor = BOOKED_SLOT_BORDER_COLOR;
updateMachineSlot(machineSlot, reservation, $scope.currentUser);
} else {
machineSlot.title = _t('app.logged.machines_reserve.not_available'); machineSlot.title = _t('app.logged.machines_reserve.not_available');
machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR; machineSlot.borderColor = UNAVAILABLE_SLOT_BORDER_COLOR;
updateMachineSlot(machineSlot, reservation, $scope.ctrl.member); 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) { if ($scope.selectedPlan) {

View File

@ -10,8 +10,8 @@
* DS102: Remove unnecessary code created because of implicit returns * DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md * 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', 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, helpers, _t, $uibModal) { function ($rootScope, $uibModal, dialogs, growl, Auth, Price, Wallet, CustomAsset, Slot, AuthService, helpers, _t) {
return ({ return ({
restrict: 'E', restrict: 'E',
scope: { scope: {
@ -167,7 +167,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
// first, we check that a user was selected // first, we check that a user was selected
if (Object.keys($scope.user).length > 0) { 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 = []; const slotValidations = [];
let slotNotValid; let slotNotValid;
let slotNotValidError; let slotNotValidError;
@ -195,7 +195,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
}); });
const hasPlanForSlot = slotValidations.every(function (a) { return a; }); const hasPlanForSlot = slotValidations.every(function (a) { return a; });
if (!hasPlanForSlot) { if (!hasPlanForSlot) {
if (!$scope.isAdmin()) { if (!AuthService.isAuthorized(['admin', 'manager'])) {
return growl.error(_t('app.shared.cart.slot_restrict_subscriptions_must_select_plan')); return growl.error(_t('app.shared.cart.slot_restrict_subscriptions_must_select_plan'));
} else { } else {
const modalInstance = $uibModal.open({ const modalInstance = $uibModal.open({
@ -216,7 +216,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
return paySlots(); return paySlots();
} }
} 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 or manager
return growl.error(_t('app.shared.cart.please_select_a_member_first')); 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; 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 */ /* PRIVATE SCOPE */
/** /**
@ -325,11 +319,13 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/** /**
* Callback triggered when the selected slot changed * Callback triggered when the selected slot changed
*/ */
var slotSelectionChanged = function () { const slotSelectionChanged = function () {
if ($scope.slot) { 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) { 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) }); 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.plansGrouped = [];
$scope.slot.group_ids = []; $scope.slot.group_ids = [];
for (let group of Array.from($scope.groups)) { 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 (plan.group_id === group.id) { groupObj.plans.push(plan); }
} }
if (groupObj.plans.length > 0) { 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); $scope.slot.plansGrouped.push(groupObj);
} else if ($scope.user.group_id === groupObj.id) { } else if ($scope.user.group_id === groupObj.id) {
$scope.slot.plansGrouped.push(groupObj); $scope.slot.plansGrouped.push(groupObj);
@ -398,7 +396,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
} }
} }
, function (type) { , function (type) {
// the user has choosen an action, so we proceed // the user has chosen an action, so we proceed
if (type === 'move') { if (type === 'move') {
if (typeof $scope.onSlotStartToModify === 'function') { $scope.onSlotStartToModify(); } if (typeof $scope.onSlotStartToModify === 'function') { $scope.onSlotStartToModify(); }
return $scope.events.modifiable = $scope.slot; 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) * 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.selectedPlan = null;
$scope.coupon.applied = null; $scope.coupon.applied = null;
$scope.events.moved = 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. * Determines if the provided booked slot is able to be modified by the user.
* @param slot {Object} fullCalendar event object * @param slot {Object} fullCalendar event object
*/ */
var slotCanBeModified = function (slot) { const slotCanBeModified = function (slot) {
if ($scope.isAdmin()) { return true; } if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
const slotStart = moment(slot.start); const slotStart = moment(slot.start);
const now = moment(); const now = moment();
return (slot.can_modify && $scope.enableBookingMove && (slotStart.diff(now, 'hours') >= $scope.moveBookingDelay)); 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. * Determines if the provided booked slot is able to be canceled by the user.
* @param slot {Object} fullCalendar event object * @param slot {Object} fullCalendar event object
*/ */
var slotCanBeCanceled = function (slot) { const slotCanBeCanceled = function (slot) {
if ($scope.isAdmin()) { return true; } if (AuthService.isAuthorized(['admin', 'manager'])) { return true; }
const slotStart = moment(slot.start); const slotStart = moment(slot.start);
const now = moment(); const now = moment();
return (slot.can_modify && $scope.enableBookingCancel && (slotStart.diff(now, 'hours') >= $scope.cancelBookingDelay)); 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 * Callback triggered when the selected slot changed
*/ */
var planSelectionChanged = function () { const planSelectionChanged = function () {
if (Auth.isAuthenticated()) { if (Auth.isAuthenticated()) {
if ($scope.selectedPlan !== $scope.plan) { if ($scope.selectedPlan !== $scope.plan) {
$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 * Update the total price of the current selection/reservation
*/ */
var updateCartPrice = function () { const updateCartPrice = function () {
if (Object.keys($scope.user).length > 0) { if (Object.keys($scope.user).length > 0) {
const r = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan); const r = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
return Price.compute(mkRequestParams(r, $scope.coupon.applied), function (res) { 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($scope.events.reserved, function (slot) {
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)) {
@ -518,7 +516,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
* @param coupon {Object} Coupon as returned from the API * @param coupon {Object} Coupon as returned from the API
* @return {{reservation:Object, coupon_code:string}} * @return {{reservation:Object, coupon_code:string}}
*/ */
var mkRequestParams = function (reservation, coupon) { const mkRequestParams = function (reservation, coupon) {
return { return {
reservation, reservation,
coupon_code: ((coupon ? coupon.code : undefined)) 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 * @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}} * @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 = { const reservation = {
user_id: member.id, user_id: member.id,
reservable_id: $scope.reservableId, 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. * 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({ $uibModal.open({
templateUrl: '<%= asset_path "stripe/payment_modal.html" %>', templateUrl: '<%= asset_path "stripe/payment_modal.html" %>',
size: 'md', 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). * 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({ $uibModal.open({
templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>', templateUrl: '<%= asset_path "shared/valid_reservation_modal.html" %>',
size: 'sm', size: 'sm',
@ -681,7 +679,7 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/** /**
* Actions to run after the payment was successful * 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 // we set the cart content as 'paid' to display a summary of the transaction
$scope.events.paid = $scope.events.reserved; $scope.events.paid = $scope.events.reserved;
$scope.amountPaid = $scope.amountTotal; $scope.amountPaid = $scope.amountTotal;
@ -697,19 +695,22 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
/** /**
* Actions to pay slots * Actions to pay slots
*/ */
var paySlots = function() { const paySlots = function() {
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan); const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) { return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount); 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) { if ($rootScope.fablabWithoutOnlinePayment) {
growl.error(_t('app.shared.cart.online_payment_disabled')); growl.error(_t('app.shared.cart.online_payment_disabled'));
} else { } else {
return payByStripe(reservation); return payByStripe(reservation);
} }
} else { } else {
if ($scope.isAdmin() || (amountToPay === 0)) { if (AuthService.isAuthorized(['admin'])
|| (AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id)
|| amountToPay === 0) {
return payOnSite(reservation); return payOnSite(reservation);
} }
} }

View File

@ -45,7 +45,7 @@
</div> </div>
<div class="col-sm-12 col-md-12 col-lg-3"> <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" <a class="btn btn-default export-xls-button"
ng-href="api/availabilities/export_index.xlsx" ng-href="api/availabilities/export_index.xlsx"
target="export-frame" target="export-frame"

View File

@ -25,7 +25,7 @@
<div class="col-sm-12 col-md-12 col-lg-3"> <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> <select-member></select-member>
</div> </div>

View File

@ -16,7 +16,7 @@
<div class="panel-body"> <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="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 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> <label for="offerSlot" class="control-label m-r" translate>{{ 'app.shared.cart.offer_this_slot' }}</label>
<input bs-switch <input bs-switch
ng-model="slot.offered" ng-model="slot.offered"

View File

@ -25,12 +25,13 @@ class API::ReservationsController < API::ApiController
def show; end def show; end
# Admins can create any reservations. Members can directly create reservations if total = 0, # 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 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) 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) @reservation = Reservation.new(reservation_params)
is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id) is_reserve = Reservations::Reserve.new(user_id, current_user.invoicing_profile.id)

View File

@ -112,7 +112,7 @@ class User < ApplicationRecord
end end
def training_machine?(machine) def training_machine?(machine)
return true if admin? return true if admin? || manager?
trainings.map(&:machines).flatten.uniq.include?(machine) trainings.map(&:machines).flatten.uniq.include?(machine)
end end
@ -139,6 +139,14 @@ class User < ApplicationRecord
has_role? :member has_role? :member
end end
def manager?
has_role? :manager
end
def partner?
has_role? :partner
end
def all_projects def all_projects
my_projects.to_a.concat projects my_projects.to_a.concat projects
end end

View File

@ -1,7 +1,10 @@
# frozen_string_literal: true
# Check the access policies for API::AvailabilitiesController
class AvailabilityPolicy < ApplicationPolicy 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 define_method action do
user.admin? user.admin? || user.manager?
end end
end end
end end

View File

@ -3,10 +3,10 @@
# Check the access policies for API::ICalendarController # Check the access policies for API::ICalendarController
class ICalendarPolicy < ApplicationPolicy class ICalendarPolicy < ApplicationPolicy
def create? def create?
user.admin? user.admin? || user.manager?
end end
def destroy? def destroy?
user.admin? user.admin? || user.manager?
end end
end end

View File

@ -2,11 +2,12 @@
# Pundit Additional context to validate the price of a reservation # Pundit Additional context to validate the price of a reservation
class ReservationContext class ReservationContext
attr_reader :reservation, :price attr_reader :reservation, :price, :user_id
def initialize(reservation, price) def initialize(reservation, price, user_id)
@reservation = reservation @reservation = reservation
@price = price @price = price
@user_id = user_id
end end
def policy_class def policy_class

View File

@ -3,10 +3,10 @@
# Check the access policies for API::ReservationsController # Check the access policies for API::ReservationsController
class ReservationPolicy < ApplicationPolicy class ReservationPolicy < ApplicationPolicy
def create? def create?
user.admin? || record.price.zero? user.admin? || (user.manager? && record.user_id != user.id) || record.price.zero?
end end
def update? def update?
user.admin? || record.user == user user.admin? || user.manager? || record.user == user
end end
end end

View File

@ -1,15 +1,18 @@
# frozen_string_literal: true
# Check the access policies for API::SlotsController
class SlotPolicy < ApplicationPolicy class SlotPolicy < ApplicationPolicy
def update? def update?
# check that the update is allowed and the prevention delay has not expired # check that the update is allowed and the prevention delay has not expired
delay = Setting.find_by( name: 'booking_move_delay').value.to_i delay = Setting.find_by(name: 'booking_move_delay').value.to_i
enabled = (Setting.find_by( name: 'booking_move_enable').value == 'true') enabled = (Setting.find_by(name: 'booking_move_enable').value == 'true')
# these condition does not apply to admins # these condition does not apply to admins
user.admin? or user.admin? || user.manager? ||
(record.reservation.user == user and enabled and ((record.start_at - DateTime.current).to_i / 3600 >= delay)) (record.reservation.user == user && enabled && ((record.start_at - DateTime.current).to_i / 3600 >= delay))
end end
def cancel? def cancel?
user.admin? or record.reservation.user == user user.admin? || user.manager? || record.reservation.user == user
end end
end end

View File

@ -1,10 +1,13 @@
# frozen_string_literal: true
# Check the access policies for API::WalletController
class WalletPolicy < ApplicationPolicy class WalletPolicy < ApplicationPolicy
def by_user? def by_user?
user.admin? or user == record.user user.admin? || user.manager? || user == record.user
end end
def transactions? def transactions?
user.admin? or user == record.user user.admin? || user == record.user
end end
def credit? def credit?

View File

@ -50,7 +50,7 @@ class Members::ListService
'SELECT max("created_at") ' \ 'SELECT max("created_at") ' \
'FROM "subscriptions" ' \ 'FROM "subscriptions" ' \
'WHERE "statistic_profile_id" = "statistic_profiles"."id")') '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) .limit(50)
query.downcase.split(' ').each do |word| query.downcase.split(' ').each do |word|
members = members.where('lower(f_unaccent(profiles.first_name)) ~ :search OR ' \ members = members.where('lower(f_unaccent(profiles.first_name)) ~ :search OR ' \

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
json.extract! @machine, :id, :name, :description, :spec, :disabled, :created_at, :updated_at, :slug 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_image @machine.machine_image.attachment.large.url if @machine.machine_image
json.machine_files_attributes @machine.machine_files do |f| json.machine_files_attributes @machine.machine_files do |f|
@ -7,9 +9,11 @@ json.machine_files_attributes @machine.machine_files do |f|
end end
json.trainings @machine.trainings.each, :id, :name, :disabled json.trainings @machine.trainings.each, :id, :name, :disabled
json.current_user_is_training current_user.training_machine?(@machine) if current_user json.current_user_is_training current_user.training_machine?(@machine) if current_user
json.current_user_training_reservation do if current_user && !current_user.training_machine?(@machine) && current_user.training_reservation_by_machine(@machine)
json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(@machine) json.current_user_training_reservation do
end if current_user and !current_user.training_machine?(@machine) and current_user.training_reservation_by_machine(@machine) 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.machine_projects @machine.projects.published.last(10) do |p|
json.id p.id json.id p.id