1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-29 10:24:20 +01:00

[feature] ability to block new reservations (#78)

On a machine, space or training availability slot, if there's no current reservation or if all reservations were cancelled, the slot can be locked to prevent any new reservations on it. Once locked it will disapear from the members calendars. The admins can also to unlock these slots later.
This commit is contained in:
Sylvain 2017-09-07 10:48:21 +02:00
commit f4ddd92b40
19 changed files with 217 additions and 40 deletions

View File

@ -132,6 +132,40 @@ Application.Controllers.controller "AdminCalendarController", ["$scope", "$state
##
# Mark the selected slot as unavailable for new reservations or allow reservations again on it
##
$scope.toggleLockReservations = ->
# first, define a shortcut to the lock property
locked = $scope.availability.lock
# then check if we'll allow reservations locking
prevent = !locked # if currently locked, allow unlock anyway
if (!locked)
prevent = false
angular.forEach $scope.reservations, (r) ->
if r.canceled_at == null
prevent = true # if currently unlocked and has any non-cancelled reservation, disallow locking
if (!prevent)
# open a confirmation dialog
dialogs.confirm
resolve:
object: ->
title: _t('admin_calendar.confirmation_required')
msg: if locked then _t("admin_calendar.do_you_really_want_to_allow_reservations") else _t("admin_calendar.do_you_really_want_to_block_this_slot")
, ->
# the admin has confirmed, lock/unlock the slot
Availability.lock {id: $scope.availability.id}, {lock: !locked}
, (data) -> # success
$scope.availability = data
growl.success(if locked then _t('admin_calendar.unlocking_success') else _t('admin_calendar.locking_success') )
uiCalendarConfig.calendars.calendar.fullCalendar 'refetchEvents'
, (error) -> # failed
growl.error(if locked then _t('admin_calendar.unlocking_failed') else _t('admin_calendar.locking_failed'))
else
growl.error(_t('admin_calendar.unlockable_because_reservations'))
### PRIVATE SCOPE ### ### PRIVATE SCOPE ###
## ##

View File

@ -256,6 +256,9 @@ Application.Controllers.controller "ShowMachineController", ['$scope', '$state',
$state.go('app.public.machines_list') $state.go('app.public.machines_list')
, (error)-> , (error)->
growl.warning(_t('the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users')) growl.warning(_t('the_machine_cant_be_deleted_because_it_is_already_reserved_by_some_users'))
## ##
# Callback to book a reservation for the current machine # Callback to book a reservation for the current machine
## ##
@ -412,6 +415,7 @@ Application.Controllers.controller "ReserveMachineController", ["$scope", '$stat
updateCalendar() updateCalendar()
## ##
# When modifying an already booked reservation, callback when the modification was successfully done. # When modifying an already booked reservation, callback when the modification was successfully done.
## ##

View File

@ -24,4 +24,7 @@ Application.Services.factory 'Availability', ["$resource", ($resource)->
isArray: true isArray: true
update: update:
method: 'PUT' method: 'PUT'
lock:
method: 'PUT'
url: '/api/availabilities/:id/lock'
] ]

View File

@ -360,6 +360,10 @@ body.container{
} }
.reservations-locked {
background-color: #f5f5f5;
}
.reservation-canceled { .reservation-canceled {
color: #606060; color: #606060;
border-radius: 0.2em; border-radius: 0.2em;

View File

@ -42,11 +42,12 @@
</a> </a>
<iframe name="export-frame" height="0" width="0" class="none"></iframe> <iframe name="export-frame" height="0" width="0" class="none"></iframe>
</div> </div>
<div class="widget panel b-a m m-t-lg">
<div class="widget panel b-a m m-t-lg" ng-if="availability">
<div class="panel-heading b-b small"> <div class="panel-heading b-b small">
<h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3> <h3 translate>{{ 'admin_calendar.ongoing_reservations' }}</h3>
</div> </div>
<div class="widget-content no-bg auto wrapper"> <div class="widget-content no-bg auto wrapper" ng-class="{'reservations-locked': availability.lock}">
<ul class="list-unstyled" ng-if="reservations.length > 0"> <ul class="list-unstyled" ng-if="reservations.length > 0">
<li ng-repeat="r in reservations" class="m-b-xs" ng-class="{'reservation-canceled':r.canceled_at}"> <li ng-repeat="r in reservations" class="m-b-xs" ng-class="{'reservation-canceled':r.canceled_at}">
{{r.user.name}} {{r.user.name}}
@ -55,25 +56,42 @@
<span class="btn btn-warning btn-xs" ng-click="cancelBooking(r)" ng-if="!r.canceled_at"><i class="fa fa-times red"></i></span> <span class="btn btn-warning btn-xs" ng-click="cancelBooking(r)" ng-if="!r.canceled_at"><i class="fa fa-times red"></i></span>
</li> </li>
</ul> </ul>
<div ng-if="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div> <div ng-show="reservations.length == 0" translate>{{ 'admin_calendar.no_reservations' }}</div>
<div class="m-t" ng-show="availability.lock"><i class="fa fa-ban"/> <span class="m-l-xs" translate>{{ 'admin_calendar.reservations_locked' }}</span></div>
</div>
</div>
<div class="widget panel b-a m m-t-lg" ng-if="availability.machine_ids.length > 0">
<div class="panel-heading b-b small">
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
</div>
<div class="widget-content no-bg auto wrapper">
<ul class="list-unstyled">
<li ng-repeat="m in machines" class="m-b-xs" ng-show="availability.machine_ids.indexOf(m.id) > -1">
{{m.name}}
<span class="btn btn-warning btn-xs" ng-click="removeMachine(m)" ng-if="availability.machine_ids.length > 1"><i class="fa fa-times red"></i></span>
</li>
</ul>
</div>
</div>
<div class="widget panel b-a m m-t-lg" ng-if="availability">
<div class="panel-heading b-b small">
<h3 translate>{{ 'admin_calendar.actions' }}</h3>
</div>
<div class="widget-content no-bg auto wrapper">
<button class="btn btn-default" ng-click="toggleLockReservations()">
<span ng-hide="availability.lock">
<i class="fa fa-stop" />
<span class="m-l-xs" translate>{{ 'admin_calendar.block_reservations' }}</span>
</span>
<span ng-show="availability.lock">
<i class="fa fa-play" />
<span class="m-l-xs" translate>{{ 'admin_calendar.allow_reservations' }}</span>
</span>
</button>
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-12 col-md-12 col-lg-3" ng-if="availability.machine_ids.length > 0"> </section>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3 translate>{{ 'admin_calendar.machines' }}</h3>
</div>
<div class="widget-content no-bg auto wrapper">
<ul class="list-unstyled">
<li ng-repeat="m in machines" class="m-b-xs" ng-show="availability.machine_ids.indexOf(m.id) > -1">
{{m.name}}
<span class="btn btn-warning btn-xs" ng-click="removeMachine(m)" ng-if="availability.machine_ids.length > 1"><i class="fa fa-times red"></i></span>
</li>
</ul>
</div>
</div>
</div>
</section>

View File

@ -2,7 +2,7 @@ class API::AvailabilitiesController < API::ApiController
include FablabConfiguration include FablabConfiguration
before_action :authenticate_user!, except: [:public] before_action :authenticate_user!, except: [:public]
before_action :set_availability, only: [:show, :update, :destroy, :reservations] before_action :set_availability, only: [:show, :update, :destroy, :reservations, :lock]
before_action :define_max_visibility, only: [:machine, :trainings, :spaces] before_action :define_max_visibility, only: [:machine, :trainings, :spaces]
respond_to :json respond_to :json
@ -21,22 +21,32 @@ class API::AvailabilitiesController < API::ApiController
def public def public
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start]) start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
@reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date) @reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user)
.where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
# request for 1 single day # request for 1 single day
if in_same_day(start_date, end_date) if in_same_day(start_date, end_date)
# trainings, events # trainings, events
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots).where(available_type: %w(training event)) @training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots).where(available_type: %w(training event))
.where('start_at >= ? AND end_at <= ?', start_date, end_date) .where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
# machines # machines
@machine_availabilities = Availability.includes(:tags, :machines).where(available_type: 'machines') @machine_availabilities = Availability.includes(:tags, :machines).where(available_type: 'machines')
.where('start_at >= ? AND end_at <= ?', start_date, end_date) .where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@machine_slots = [] @machine_slots = []
@machine_availabilities.each do |a| @machine_availabilities.each do |a|
a.machines.each do |machine| a.machines.each do |machine|
if params[:m] and params[:m].include?(machine.id.to_s) if params[:m] and params[:m].include?(machine.id.to_s)
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i| ((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: machine, title: machine.name) slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
machine: machine,
title: machine.name
)
slot = verify_machine_is_reserved(slot, @reservations, current_user, '') slot = verify_machine_is_reserved(slot, @reservations, current_user, '')
@machine_slots << slot @machine_slots << slot
end end
@ -47,6 +57,7 @@ class API::AvailabilitiesController < API::ApiController
# spaces # spaces
@space_availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space') @space_availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space')
.where('start_at >= ? AND end_at <= ?', start_date, end_date) .where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
if params[:s] if params[:s]
@space_availabilities.where(available_id: params[:s]) @space_availabilities.where(available_id: params[:s])
@ -57,7 +68,14 @@ class API::AvailabilitiesController < API::ApiController
space = a.spaces.first space = a.spaces.first
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i| ((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: space, title: space.name) slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: space,
title: space.name
)
slot = verify_space_is_reserved(slot, @reservations, current_user, '') slot = verify_space_is_reserved(slot, @reservations, current_user, '')
@space_slots << slot @space_slots << slot
end end
@ -69,6 +87,7 @@ class API::AvailabilitiesController < API::ApiController
else else
@availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots) @availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
.where('start_at >= ? AND end_at <= ?', start_date, end_date) .where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@availabilities.each do |a| @availabilities.each do |a|
if a.available_type == 'training' or a.available_type == 'event' if a.available_type == 'training' or a.available_type == 'event'
a = verify_training_event_is_reserved(a, @reservations, current_user) a = verify_training_event_is_reserved(a, @reservations, current_user)
@ -125,16 +144,27 @@ class API::AvailabilitiesController < API::ApiController
@slots = [] @slots = []
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now) @reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
if @user.is_admin? if @user.is_admin?
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND available_type = 'machines'", Time.now) @availabilities = @machine.availabilities.includes(:tags)
.where("end_at > ? AND available_type = 'machines'", Time.now)
.where(lock: false)
else else
end_at = @visi_max_other end_at = @visi_max_other
end_at = @visi_max_year if is_subscription_year(@user) end_at = @visi_max_year if is_subscription_year(@user)
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil])) @availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end end
@availabilities.each do |a| @availabilities.each do |a|
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i| ((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: @machine, title: '') slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
machine: @machine,
title: ''
)
slot = verify_machine_is_reserved(slot, @reservations, current_user, @current_user_role) slot = verify_machine_is_reserved(slot, @reservations, current_user, @current_user_role)
@slots << slot @slots << slot
end end
@ -167,12 +197,17 @@ class API::AvailabilitiesController < API::ApiController
# who made the request? # who made the request?
# 1) an admin (he can see all future availabilities) # 1) an admin (he can see all future availabilities)
if current_user.is_admin? if current_user.is_admin?
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines]).where('availabilities.start_at > ?', Time.now) @availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines])
.where('availabilities.start_at > ?', Time.now)
.where(lock: false)
# 2) an user (he cannot see availabilities further than 1 (or 3) months) # 2) an user (he cannot see availabilities further than 1 (or 3) months)
else else
end_at = @visi_max_year end_at = @visi_max_year
end_at = @visi_max_year if can_show_slot_plus_three_months(@user) end_at = @visi_max_year if can_show_slot_plus_three_months(@user)
@availabilities = @availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines]).where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil])) @availabilities = @availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines])
.where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end end
# finally, we merge the availabilities with the reservations # finally, we merge the availabilities with the reservations
@ -190,18 +225,32 @@ class API::AvailabilitiesController < API::ApiController
@current_user_role = current_user.is_admin? ? 'admin' : 'user' @current_user_role = current_user.is_admin? ? 'admin' : 'user'
@space = Space.friendly.find(params[:space_id]) @space = Space.friendly.find(params[:space_id])
@slots = [] @slots = []
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now) @reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id)
.includes(:slots, user: [:profile]).references(:slots, :user)
.where('slots.start_at > ?', Time.now)
if current_user.is_admin? if current_user.is_admin?
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND available_type = 'space'", Time.now) @availabilities = @space.availabilities.includes(:tags)
.where("end_at > ? AND available_type = 'space'", Time.now)
.where(lock: false)
else else
end_at = @visi_max_other end_at = @visi_max_other
end_at = @visi_max_year if is_subscription_year(@user) end_at = @visi_max_year if is_subscription_year(@user)
@availabilities = @space.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil])) @availabilities = @space.availabilities.includes(:tags)
.where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end end
@availabilities.each do |a| @availabilities.each do |a|
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i| ((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now if (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes, end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes, availability_id: a.id, availability: a, space: @space, title: '') slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: @space,
title: ''
)
slot = verify_space_is_reserved(slot, @reservations, @user, @current_user_role) slot = verify_space_is_reserved(slot, @reservations, @user, @current_user_role)
@slots << slot @slots << slot
end end
@ -235,6 +284,15 @@ class API::AvailabilitiesController < API::ApiController
end end
end end
def lock
authorize @availability
if @availability.update_attributes(lock: lock_params)
render :show, status: :ok, location: @availability
else
render json: @availability.errors, status: :unprocessable_entity
end
end
private private
def set_availability def set_availability
@availability = Availability.find(params[:id]) @availability = Availability.find(params[:id])
@ -245,6 +303,10 @@ class API::AvailabilitiesController < API::ApiController
:machines_attributes => [:id, :_destroy]) :machines_attributes => [:id, :_destroy])
end end
def lock_params
params.require(:lock)
end
def is_reserved_availability(availability, user_id) def is_reserved_availability(availability, user_id)
reserved_slots = [] reserved_slots = []
availability.slots.each do |s| availability.slots.each do |s|

View File

@ -0,0 +1,3 @@
# Raised when total of reservation isn't equal to the total of stripe's invoice
class InvoiceTotalDifferentError < StandardError
end

View File

@ -1,3 +0,0 @@
# Raised when total of reservation isnt equal total of strip's invoice
class InvoiceTotalDiffrentError < StandardError
end

View File

@ -0,0 +1,3 @@
# Raised when reserving on a locked availability
class LockedError < StandardError
end

View File

@ -47,6 +47,13 @@ class Reservation < ActiveRecord::Base
plan = nil plan = nil
end end
# check that none of the reserved availabilities was locked
slots.each do |slot|
if slot.availability.lock
raise LockedError
end
end
case reservable case reservable
@ -260,7 +267,7 @@ class Reservation < ActiveRecord::Base
# this function only check reservation total is equal strip invoice total when # this function only check reservation total is equal strip invoice total when
# pay only reservation not reservation + subscription # pay only reservation not reservation + subscription
#if !is_equal_reservation_total_and_stp_invoice_total(stp_invoice, coupon_code) #if !is_equal_reservation_total_and_stp_invoice_total(stp_invoice, coupon_code)
#raise InvoiceTotalDiffrentError #raise InvoiceTotalDifferentError
#end #end
stp_invoice.pay stp_invoice.pay
card.delete if card card.delete if card

View File

@ -1,5 +1,5 @@
class AvailabilityPolicy < ApplicationPolicy class AvailabilityPolicy < ApplicationPolicy
%w(index? show? create? update? destroy? reservations? export?).each do |action| %w(index? show? create? update? destroy? reservations? export? lock?).each do |action|
define_method action do define_method action do
user.is_admin? user.is_admin?
end end

View File

@ -6,11 +6,12 @@ json.array!(@availabilities) do |availability|
json.available_type availability.available_type json.available_type availability.available_type
json.machine_ids availability.machine_ids json.machine_ids availability.machine_ids
json.training_ids availability.training_ids json.training_ids availability.training_ids
json.backgroundColor 'white' json.backgroundColor !availability.lock ? 'white' : '#f5f5f5'
json.borderColor availability_border_color(availability) json.borderColor availability_border_color(availability)
json.tag_ids availability.tag_ids json.tag_ids availability.tag_ids
json.tags availability.tags do |t| json.tags availability.tags do |t|
json.id t.id json.id t.id
json.name t.name json.name t.name
end end
json.lock availability.lock
end end

View File

@ -11,3 +11,4 @@ json.tags @availability.tags do |t|
json.id t.id json.id t.id
json.name t.name json.name t.name
end end
json.lock @availability.lock

View File

@ -48,6 +48,17 @@ en:
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Unable to delete the slot {{START}} - {{END}} because it's already reserved by a member" # angular interpolation
you_should_select_at_least_a_machine: "You should select at least one machine on this slot." you_should_select_at_least_a_machine: "You should select at least one machine on this slot."
export_is_running_you_ll_be_notified_when_its_ready: "Export is running. You'll be notified when it's ready." export_is_running_you_ll_be_notified_when_its_ready: "Export is running. You'll be notified when it's ready."
actions: "Actions"
block_reservations: "Block reservations"
do_you_really_want_to_block_this_slot: "Do you really want to block new reservations on this slot? It will become invisible to users."
locking_success: "Slot successfully locked, it won't appear any longer in the user calendar"
locking_failed: "An error occurred. Slot locking has failed"
allow_reservations: "Allow reservations"
do_you_really_want_to_allow_reservations: "Do you really want to allow booking again on this slot? It will become visible for the users."
unlocking_success: "Slot successfully unlocked, it will appear again in the user calendar"
unlocking_failed: "An error occurred. Slot unlocking has failed"
reservations_locked: "Booking is blocked"
unlockable_because_reservations: "Unable to block booking on this slot because some uncancelled reservations exist on it."
project_elements: project_elements:
# management of the projects' components # management of the projects' components

View File

@ -48,6 +48,17 @@ fr:
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Le créneau {{START}} - {{END}} n'a pu être supprimé car il est déjà réservé par un membre" # angular interpolation
you_should_select_at_least_a_machine: "Vous devriez sélectionne au moins une machine pour ce créneau." you_should_select_at_least_a_machine: "Vous devriez sélectionne au moins une machine pour ce créneau."
export_is_running_you_ll_be_notified_when_its_ready: "L'export est en cours. Vous serez notifié lorsqu'il sera prêt." export_is_running_you_ll_be_notified_when_its_ready: "L'export est en cours. Vous serez notifié lorsqu'il sera prêt."
actions: "Actions"
block_reservations: "Bloquer les réservations"
do_you_really_want_to_block_this_slot: "Êtes vous sur de vouloir bloquer les nouvelles réservations sur ce créneau ? Il deviendra alors invisible pour les utilisateurs."
locking_success: "Le créneau a bien été verrouillé, il n'apparaitra plus dans le calendrier utilisateur"
locking_failed: "Une erreur est survenue. Le verrouillage du créneau a échoué"
allow_reservations: "Autoriser les réservations"
do_you_really_want_to_allow_reservations: "Êtes vous sur de vouloir autoriser de nouveau la prise de réservations sur ce créneau ? Il deviendra alors visible pour les utilisateurs."
unlocking_success: "Le créneau a bien été déverrouillé, il apparaîtra de nouveau dans le calendrier utilisateur"
unlocking_failed: "Une erreur est survenue. Le déverrouillage du créneau a échoué"
reservations_locked: "Réservations bloquées"
unlockable_because_reservations: "Impossible de bloquer les réservations sur ce créneau car il existe des réservations non annulées sur celui-ci."
project_elements: project_elements:
# gestion des éléments constituant les projets # gestion des éléments constituant les projets

View File

@ -48,6 +48,17 @@ pt:
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Não é possível deletar o slot {{START}} - {{END}} porque já foi reservado por um membro" # angular interpolation unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "Não é possível deletar o slot {{START}} - {{END}} porque já foi reservado por um membro" # angular interpolation
you_should_select_at_least_a_machine: "Você deveria selecionar a última máquina neste slot." you_should_select_at_least_a_machine: "Você deveria selecionar a última máquina neste slot."
export_is_running_you_ll_be_notified_when_its_ready: "A Exportação está em andamento. Você será notificado quando terminar." export_is_running_you_ll_be_notified_when_its_ready: "A Exportação está em andamento. Você será notificado quando terminar."
actions: "Ações"
block_reservations: "Impedir reservas"
do_you_really_want_to_block_this_slot: "Você realmente quer bloquear novas reservas neste slot? Isso se tornará invisível para os usuários."
locking_success: "Slot bloqueado com êxito, ela não aparecerá mais tempo no calendário do usuário"
locking_failed: "Um erro ocorreu. O bloqueio do slot falhou"
allow_reservations: "Permitir a reserva"
do_you_really_want_to_allow_reservations: "Você realmente quer permitir a reserva novamente neste slot? Será visível para os usuários."
unlocking_success: "Slot desbloqueado com sucesso, ele aparecerá novamente no calendário do usuário"
unlocking_failed: "Um erro ocorreu. O desbloqueio do slot falhou"
reservations_locked: "Reserva é bloqueado"
unlockable_because_reservations: "Não é possível bloquear a reserva neste slot porque existem algumas reservas não cancelados nele."
project_elements: project_elements:
# management of the projects' components # management of the projects' components

View File

@ -85,6 +85,7 @@ Rails.application.routes.draw do
get 'reservations', on: :member get 'reservations', on: :member
get 'public', on: :collection get 'public', on: :collection
get '/export_index', action: 'export_availabilities', on: :collection get '/export_index', action: 'export_availabilities', on: :collection
put ':id/lock', action: 'lock', on: :collection
end end
resources :groups, only: [:index, :create, :update, :destroy] resources :groups, only: [:index, :create, :update, :destroy]

View File

@ -0,0 +1,5 @@
class AddLockToAvailability < ActiveRecord::Migration
def change
add_column :availabilities, :lock, :boolean, default: false
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170227114634) do ActiveRecord::Schema.define(version: 20170906100906) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -79,6 +79,7 @@ ActiveRecord::Schema.define(version: 20170227114634) do
t.datetime "updated_at" t.datetime "updated_at"
t.integer "nb_total_places" t.integer "nb_total_places"
t.boolean "destroying", default: false t.boolean "destroying", default: false
t.boolean "lock", default: false
end end
create_table "availability_tags", force: :cascade do |t| create_table "availability_tags", force: :cascade do |t|