mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-20 09:52:19 +01:00
Ability to define, per availability, a custom duration for the reservation slots
This commit is contained in:
parent
9735bd298e
commit
ff75a96ecc
@ -1,5 +1,7 @@
|
|||||||
# Changelog Fab-manager
|
# Changelog Fab-manager
|
||||||
|
|
||||||
|
- Ability to define, per availability, a custom duration for the reservation slots
|
||||||
|
|
||||||
## v4.3.4 2020 April 14
|
## v4.3.4 2020 April 14
|
||||||
|
|
||||||
- Improved version check
|
- Improved version check
|
||||||
|
@ -417,8 +417,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>',
|
templateUrl: '<%= asset_path "admin/calendar/eventModal.html" %>',
|
||||||
controller: 'CreateEventModalController',
|
controller: 'CreateEventModalController',
|
||||||
resolve: {
|
resolve: {
|
||||||
start () { return start; },
|
start() { return start; },
|
||||||
end () { return end; },
|
end() { return end; },
|
||||||
|
slots() { return Math.ceil(slots); },
|
||||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||||
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
|
trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }],
|
||||||
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
|
spacesPromise: ['Space', function (Space) { return Space.query().$promise; }],
|
||||||
@ -526,8 +527,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
/**
|
/**
|
||||||
* Controller used in the slot creation modal window
|
* Controller used in the slot creation modal window
|
||||||
*/
|
*/
|
||||||
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'plansPromise', 'groupsPromise', 'growl', '_t',
|
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'slots', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'plansPromise', 'groupsPromise', 'growl', '_t',
|
||||||
function ($scope, $uibModalInstance, $sce, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, tagsPromise, plansPromise, groupsPromise, growl, _t) {
|
function ($scope, $uibModalInstance, $sce, moment, start, end, slots, machinesPromise, Availability, trainingsPromise, spacesPromise, tagsPromise, plansPromise, groupsPromise, growl, _t) {
|
||||||
// $uibModal parameter
|
// $uibModal parameter
|
||||||
$scope.start = start;
|
$scope.start = start;
|
||||||
|
|
||||||
@ -551,15 +552,6 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
$scope.selectedPlansBinding = {};
|
$scope.selectedPlansBinding = {};
|
||||||
// list of plans, classified by group
|
// list of plans, classified by group
|
||||||
$scope.plansClassifiedByGroup = [];
|
$scope.plansClassifiedByGroup = [];
|
||||||
for (let group of Array.from(groupsPromise)) {
|
|
||||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
|
||||||
for (let plan of Array.from(plansPromise)) {
|
|
||||||
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
|
||||||
}
|
|
||||||
if (groupObj.plans.length > 0) {
|
|
||||||
$scope.plansClassifiedByGroup.push(groupObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// machines associated with the created slot
|
// machines associated with the created slot
|
||||||
$scope.selectedMachines = [];
|
$scope.selectedMachines = [];
|
||||||
@ -598,7 +590,8 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
is_recurrent: false,
|
is_recurrent: false,
|
||||||
period: 'week',
|
period: 'week',
|
||||||
nb_periods: 1,
|
nb_periods: 1,
|
||||||
end_date: undefined // recurrence end
|
end_date: undefined, // recurrence end
|
||||||
|
slot_duration: Fablab.slotDuration
|
||||||
};
|
};
|
||||||
|
|
||||||
// recurrent slots
|
// recurrent slots
|
||||||
@ -613,9 +606,6 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
// localized name(s) of the selected plan(s)
|
// localized name(s) of the selected plan(s)
|
||||||
$scope.plansName = '';
|
$scope.plansName = '';
|
||||||
|
|
||||||
// make the duration available for display
|
|
||||||
$scope.slotDuration = Fablab.slotDuration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds or removes the provided machine from the current slot
|
* Adds or removes the provided machine from the current slot
|
||||||
* @param machine {Object}
|
* @param machine {Object}
|
||||||
@ -731,6 +721,13 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test if the current availability type is divided in slots
|
||||||
|
*/
|
||||||
|
$scope.isTypeDivided = function () {
|
||||||
|
return isTypeDivided($scope.availability.available_type);
|
||||||
|
}
|
||||||
|
|
||||||
/* PRIVATE SCOPE */
|
/* PRIVATE SCOPE */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -752,21 +749,39 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// group plans by Group
|
||||||
|
for (let group of Array.from(groupsPromise)) {
|
||||||
|
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||||
|
for (let plan of Array.from(plansPromise)) {
|
||||||
|
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
||||||
|
}
|
||||||
|
if (groupObj.plans.length > 0) {
|
||||||
|
$scope.plansClassifiedByGroup.push(groupObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the slot duration changes, we increment the availability to match the value
|
||||||
|
$scope.$watch('availability.slot_duration', function (newValue, oldValue, scope) {
|
||||||
|
start = moment($scope.start);
|
||||||
|
start.add(newValue * slots, 'minutes');
|
||||||
|
$scope.end = start.toDate();
|
||||||
|
});
|
||||||
|
|
||||||
// When we configure a machine/space availability, do not let the user change the end time, as the total
|
// When we configure a machine/space availability, do not let the user change the end time, as the total
|
||||||
// time must be dividable by Fablab.slotDuration minutes (base slot duration). For training availabilities, the user
|
// time must be dividable by $scope.availability.slot_duration minutes (base slot duration). For training availabilities, the user
|
||||||
// can configure any duration as it does not matters.
|
// can configure any duration as it does not matters.
|
||||||
$scope.$watch('availability.available_type', function (newValue, oldValue, scope) {
|
$scope.$watch('availability.available_type', function (newValue, oldValue, scope) {
|
||||||
if ((newValue === 'machines') || (newValue === 'space')) {
|
if (isTypeDivided(newValue)) {
|
||||||
$scope.endDateReadOnly = true;
|
$scope.endDateReadOnly = true;
|
||||||
const slots = Math.trunc(($scope.end.valueOf() - $scope.start.valueOf()) / (60 * 1000)) / Fablab.slotDuration;
|
const slots = Math.trunc(($scope.end.valueOf() - $scope.start.valueOf()) / (60 * 1000)) / $scope.availability.slot_duration;
|
||||||
if (!Number.isInteger(slots)) {
|
if (!Number.isInteger(slots)) {
|
||||||
// otherwise, round it to upper decimal
|
// otherwise, round it to upper decimal
|
||||||
const upper = Math.ceil(slots) * Fablab.slotDuration;
|
const upper = Math.ceil(slots) * $scope.availability.slot_duration;
|
||||||
$scope.end = moment($scope.start).add(upper, 'minutes').toDate();
|
$scope.end = moment($scope.start).add(upper, 'minutes').toDate();
|
||||||
}
|
}
|
||||||
return $scope.availability.end_at = $scope.end;
|
$scope.availability.end_at = $scope.end;
|
||||||
} else {
|
} else {
|
||||||
return $scope.endDateReadOnly = false;
|
$scope.endDateReadOnly = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -774,13 +789,13 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
// maintain the relative length of the slot (ie. change the end time accordingly)
|
// maintain the relative length of the slot (ie. change the end time accordingly)
|
||||||
$scope.$watch('start', function (newValue, oldValue, scope) {
|
$scope.$watch('start', function (newValue, oldValue, scope) {
|
||||||
// for machine or space availabilities, adjust the end time
|
// for machine or space availabilities, adjust the end time
|
||||||
if (($scope.availability.available_type === 'machines') || ($scope.availability.available_type === 'space')) {
|
if ($scope.isTypeDivided()) {
|
||||||
end = moment($scope.end);
|
end = moment($scope.end);
|
||||||
end.add(moment(newValue).diff(oldValue), 'milliseconds');
|
end.add(moment(newValue).diff(oldValue), 'milliseconds');
|
||||||
$scope.end = end.toDate();
|
$scope.end = end.toDate();
|
||||||
} else { // for training availabilities
|
} else { // for training availabilities
|
||||||
// prevent the admin from setting the beginning after the end
|
// prevent the admin from setting the beginning after the end
|
||||||
if (moment(newValue).add(Fablab.slotDuration, 'minutes').isAfter($scope.end)) {
|
if (moment(newValue).add($scope.availability.slot_duration, 'minutes').isAfter($scope.end)) {
|
||||||
$scope.start = oldValue;
|
$scope.start = oldValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -791,7 +806,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
// Maintain consistency between the end time and the date object in the availability object
|
// Maintain consistency between the end time and the date object in the availability object
|
||||||
$scope.$watch('end', function (newValue, oldValue, scope) {
|
$scope.$watch('end', function (newValue, oldValue, scope) {
|
||||||
// we prevent the admin from setting the end of the availability before its beginning
|
// we prevent the admin from setting the end of the availability before its beginning
|
||||||
if (moment($scope.start).add(Fablab.slotDuration, 'minutes').isAfter(newValue)) {
|
if (moment($scope.start).add($scope.availability.slot_duration, 'minutes').isAfter(newValue)) {
|
||||||
$scope.end = oldValue;
|
$scope.end = oldValue;
|
||||||
}
|
}
|
||||||
// update availability object
|
// update availability object
|
||||||
@ -799,6 +814,13 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test if the provided availability type is divided in slots
|
||||||
|
*/
|
||||||
|
const isTypeDivided = function (type) {
|
||||||
|
return ((type === 'machines') || (type === 'space'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates that a machine or more was/were selected before continuing to step 3 (adjust time + tags)
|
* Validates that a machine or more was/were selected before continuing to step 3 (adjust time + tags)
|
||||||
*/
|
*/
|
||||||
|
@ -75,6 +75,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" ng-show="step === 3">
|
<div class="modal-body" ng-show="step === 3">
|
||||||
|
<div id="slotDuration" class="m-t-sm" ng-show="isTypeDivided()">
|
||||||
|
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.divide_this_availability' }}</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control" ng-model="availability.slot_duration" step="5" />
|
||||||
|
<span class="input-group-addon" translate>{{ 'app.admin.calendar.minutes' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="timeAdjust" class="m-t-sm">
|
<div id="timeAdjust" class="m-t-sm">
|
||||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.adjust_the_opening_hours' }}</p>
|
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.adjust_the_opening_hours' }}</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -204,7 +211,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="alert alert-info text-xs">
|
<div class="alert alert-info text-xs">
|
||||||
<i class="fa fa-lightbulb-o m-r" aria-hidden="true"></i>
|
<i class="fa fa-lightbulb-o m-r" aria-hidden="true"></i>
|
||||||
<span translate translate-values="{DURATION: slotDuration, COUNT: occurrences.length}"> {{ 'app.admin.calendar.divided_in_slots' }}</span>
|
<span translate translate-values="{DURATION: availability.slot_duration, COUNT: occurrences.length}"> {{ 'app.admin.calendar.divided_in_slots' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="underline" translate>{{ 'app.admin.calendar.reservable' }}</span>
|
<span class="underline" translate>{{ 'app.admin.calendar.reservable' }}</span>
|
||||||
|
@ -147,7 +147,7 @@ class API::AvailabilitiesController < API::ApiController
|
|||||||
|
|
||||||
def availability_params
|
def availability_params
|
||||||
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places,
|
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places,
|
||||||
:is_recurrent, :period, :nb_periods, :end_date,
|
:is_recurrent, :period, :nb_periods, :end_date, :slot_duration,
|
||||||
machine_ids: [], training_ids: [], space_ids: [], tag_ids: [], plan_ids: [],
|
machine_ids: [], training_ids: [], space_ids: [], tag_ids: [], plan_ids: [],
|
||||||
machines_attributes: %i[id _destroy], plans_attributes: %i[id _destroy])
|
machines_attributes: %i[id _destroy], plans_attributes: %i[id _destroy])
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# API Controller for resources of type Slot
|
# API Controller for resources of type Slot
|
||||||
# Slots are used to cut Availabilities into reservable slots of ApplicationHelper::SLOT_DURATION minutes
|
# Slots are used to cut Availabilities into reservable slots. The duration of these slots is configured per
|
||||||
|
# availability by Availability.slot_duration, or otherwise globally by ApplicationHelper::SLOT_DURATION minutes
|
||||||
class API::SlotsController < API::ApiController
|
class API::SlotsController < API::ApiController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_slot, only: %i[update cancel]
|
before_action :set_slot, only: %i[update cancel]
|
||||||
|
@ -89,7 +89,8 @@ class Availability < ApplicationRecord
|
|||||||
def available_space_places
|
def available_space_places
|
||||||
return unless available_type == 'space'
|
return unless available_type == 'space'
|
||||||
|
|
||||||
((end_at - start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i * nb_total_places
|
duration = slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
|
((end_at - start_at) / duration.minutes).to_i * nb_total_places
|
||||||
end
|
end
|
||||||
|
|
||||||
def title(filter = {})
|
def title(filter = {})
|
||||||
@ -159,9 +160,10 @@ class Availability < ApplicationRecord
|
|||||||
private
|
private
|
||||||
|
|
||||||
def length_must_be_slot_multiple
|
def length_must_be_slot_multiple
|
||||||
return unless end_at < (start_at + ApplicationHelper::SLOT_DURATION.minutes)
|
duration = slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
|
return unless end_at < (start_at + duration.minutes)
|
||||||
|
|
||||||
errors.add(:end_at, I18n.t('availabilities.length_must_be_slot_multiple', MIN: ApplicationHelper::SLOT_DURATION))
|
errors.add(:end_at, I18n.t('availabilities.length_must_be_slot_multiple', MIN: duration))
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_be_associated
|
def should_be_associated
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Time range of duration defined by ApplicationHelper::SLOT_DURATION, slicing an Availability.
|
# Time range, slicing an Availability.
|
||||||
|
# Its duration is defined by globally by ApplicationHelper::SLOT_DURATION but can be overridden per availability
|
||||||
# During a slot a Reservation is possible
|
# During a slot a Reservation is possible
|
||||||
# Only reserved slots are persisted in DB, others are instantiated on the fly
|
# Only reserved slots are persisted in DB, others are instantiated on the fly
|
||||||
class Slot < ApplicationRecord
|
class Slot < ApplicationRecord
|
||||||
|
@ -17,12 +17,13 @@ class Availabilities::AvailabilitiesService
|
|||||||
|
|
||||||
slots = []
|
slots = []
|
||||||
availabilities.each do |a|
|
availabilities.each do |a|
|
||||||
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
slot_duration = a.slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > DateTime.current || user.admin?
|
((a.end_at - a.start_at) / slot_duration.minutes).to_i.times do |i|
|
||||||
|
next unless (a.start_at + (i * slot_duration).minutes) > DateTime.current || user.admin?
|
||||||
|
|
||||||
slot = Slot.new(
|
slot = Slot.new(
|
||||||
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
|
start_at: a.start_at + (i * slot_duration).minutes,
|
||||||
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
end_at: a.start_at + (i * slot_duration).minutes + slot_duration.minutes,
|
||||||
availability_id: a.id,
|
availability_id: a.id,
|
||||||
availability: a,
|
availability: a,
|
||||||
machine: machine,
|
machine: machine,
|
||||||
@ -43,12 +44,13 @@ class Availabilities::AvailabilitiesService
|
|||||||
|
|
||||||
slots = []
|
slots = []
|
||||||
availabilities.each do |a|
|
availabilities.each do |a|
|
||||||
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
slot_duration = a.slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > DateTime.current || user.admin?
|
((a.end_at - a.start_at) / slot_duration.minutes).to_i.times do |i|
|
||||||
|
next unless (a.start_at + (i * slot_duration).minutes) > DateTime.current || user.admin?
|
||||||
|
|
||||||
slot = Slot.new(
|
slot = Slot.new(
|
||||||
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
|
start_at: a.start_at + (i * slot_duration).minutes,
|
||||||
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
end_at: a.start_at + (i * slot_duration).minutes + slot_duration.minutes,
|
||||||
availability_id: a.id,
|
availability_id: a.id,
|
||||||
availability: a,
|
availability: a,
|
||||||
space: space,
|
space: space,
|
||||||
|
@ -15,13 +15,14 @@ class Availabilities::PublicAvailabilitiesService
|
|||||||
.where(lock: false)
|
.where(lock: false)
|
||||||
slots = []
|
slots = []
|
||||||
availabilities.each do |a|
|
availabilities.each do |a|
|
||||||
|
slot_duration = a.slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
a.machines.each do |machine|
|
a.machines.each do |machine|
|
||||||
next unless machine_ids&.include?(machine.id.to_s)
|
next unless machine_ids&.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) / slot_duration.minutes).to_i.times do |i|
|
||||||
slot = Slot.new(
|
slot = Slot.new(
|
||||||
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
|
start_at: a.start_at + (i * slot_duration).minutes,
|
||||||
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
end_at: a.start_at + (i * slot_duration).minutes + slot_duration.minutes,
|
||||||
availability_id: a.id,
|
availability_id: a.id,
|
||||||
availability: a,
|
availability: a,
|
||||||
machine: machine,
|
machine: machine,
|
||||||
@ -45,13 +46,14 @@ class Availabilities::PublicAvailabilitiesService
|
|||||||
|
|
||||||
slots = []
|
slots = []
|
||||||
availabilities.each do |a|
|
availabilities.each do |a|
|
||||||
|
slot_duration = a.slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
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) / slot_duration.minutes).to_i.times do |i|
|
||||||
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > DateTime.current
|
next unless (a.start_at + (i * slot_duration).minutes) > DateTime.current
|
||||||
|
|
||||||
slot = Slot.new(
|
slot = Slot.new(
|
||||||
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
|
start_at: a.start_at + (i * slot_duration).minutes,
|
||||||
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
|
end_at: a.start_at + (i * slot_duration).minutes + slot_duration.minutes,
|
||||||
availability_id: a.id,
|
availability_id: a.id,
|
||||||
availability: a,
|
availability: a,
|
||||||
space: space,
|
space: space,
|
||||||
|
@ -5,6 +5,7 @@ json.array!(@availabilities) do |availability|
|
|||||||
json.title availability.title
|
json.title availability.title
|
||||||
json.start availability.start_at.iso8601
|
json.start availability.start_at.iso8601
|
||||||
json.end availability.end_at.iso8601
|
json.end availability.end_at.iso8601
|
||||||
|
json.slot_duration availability.slot_duration
|
||||||
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
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
json.extract! @availability, :id, :title, :lock, :is_recurrent, :occurrence_id, :period, :nb_periods, :end_date
|
json.extract! @availability, :id, :title, :lock, :is_recurrent, :occurrence_id, :period, :nb_periods, :end_date
|
||||||
json.start_at @availability.start_at.iso8601
|
json.start_at @availability.start_at.iso8601
|
||||||
json.end_at @availability.end_at.iso8601
|
json.end_at @availability.end_at.iso8601
|
||||||
|
json.slot_duration @availability.slot_duration
|
||||||
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.plan_ids @availability.plan_ids
|
json.plan_ids @availability.plan_ids
|
||||||
|
@ -16,10 +16,11 @@ wb.add_worksheet(name: t('export_availabilities.machines')) do |sheet|
|
|||||||
|
|
||||||
# data rows
|
# data rows
|
||||||
@availabilities.where(available_type: 'machines').order(:start_at).each do |a|
|
@availabilities.where(available_type: 'machines').order(:start_at).each do |a|
|
||||||
|
slot_duration = a.slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
a.machines.each do |m|
|
a.machines.each do |m|
|
||||||
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
((a.end_at - a.start_at) / slot_duration.minutes).to_i.times do |i|
|
||||||
start_at = a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes
|
start_at = a.start_at + (i * slot_duration).minutes
|
||||||
end_at = a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes
|
end_at = a.start_at + (i * slot_duration).minutes + slot_duration.minutes
|
||||||
reservations = 0
|
reservations = 0
|
||||||
if a.slots&.map(&:start_at)&.include? start_at
|
if a.slots&.map(&:start_at)&.include? start_at
|
||||||
reservations = Reservation.where(reservable: m).includes(:slots).where('slots.id' => a.slots, 'slots.start_at' => start_at).count
|
reservations = Reservation.where(reservable: m).includes(:slots).where('slots.id' => a.slots, 'slots.start_at' => start_at).count
|
||||||
@ -83,9 +84,10 @@ if Rails.application.secrets.fablab_without_spaces != 'false'
|
|||||||
|
|
||||||
# data rows
|
# data rows
|
||||||
@availabilities.where(available_type: 'space').order(:start_at).each do |a|
|
@availabilities.where(available_type: 'space').order(:start_at).each do |a|
|
||||||
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
|
slot_duration = a.slot_duration || ApplicationHelper::SLOT_DURATION
|
||||||
start_at = a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes
|
((a.end_at - a.start_at) / slot_duration.minutes).to_i.times do |i|
|
||||||
end_at = a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes
|
start_at = a.start_at + (i * slot_duration).minutes
|
||||||
|
end_at = a.start_at + (i * slot_duration).minutes + slot_duration.minutes
|
||||||
reservations = a.slots.where(start_at: start_at).count
|
reservations = a.slots.where(start_at: start_at).count
|
||||||
|
|
||||||
data = [
|
data = [
|
||||||
|
@ -65,7 +65,7 @@ en:
|
|||||||
select_nb_period: "Please select a number of periods for the recurrence"
|
select_nb_period: "Please select a number of periods for the recurrence"
|
||||||
select_end_date: "Please select the date of the last occurrence"
|
select_end_date: "Please select the date of the last occurrence"
|
||||||
about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}:"
|
about_to_create: "You are about to create the following {TYPE, select, machines{machine} training{training} space{space} other{other}} {NUMBER, plural, one{slot} other{slots}}:"
|
||||||
divided_in_slots: "{COUNT, plural, =1{This slot} other{These slots}} will be open for booking in {DURATION}-minutes increments. Contact your system administrator to change this setting."
|
divided_in_slots: "{COUNT, plural, =1{This slot} other{These slots}} will be open for booking in {DURATION}-minutes increments."
|
||||||
reservable: "Reservable(s):"
|
reservable: "Reservable(s):"
|
||||||
labels: "Label(s):"
|
labels: "Label(s):"
|
||||||
none: "None"
|
none: "None"
|
||||||
@ -98,6 +98,8 @@ en:
|
|||||||
legend: "Legend"
|
legend: "Legend"
|
||||||
and: "and"
|
and: "and"
|
||||||
external_sync: "Calendar synchronization"
|
external_sync: "Calendar synchronization"
|
||||||
|
divide_this_availability: "Divide this availability in slots of"
|
||||||
|
minutes: "minutes"
|
||||||
# import external iCal calendar
|
# import external iCal calendar
|
||||||
icalendar:
|
icalendar:
|
||||||
icalendar_import: "iCalendar import"
|
icalendar_import: "iCalendar import"
|
||||||
|
@ -65,7 +65,7 @@ fr:
|
|||||||
select_nb_period: "Veuillez choisir un nombre de périodes pour la récurrence"
|
select_nb_period: "Veuillez choisir un nombre de périodes pour la récurrence"
|
||||||
select_end_date: "Veuillez choisir la date de dernière occurrence"
|
select_end_date: "Veuillez choisir la date de dernière occurrence"
|
||||||
about_to_create: "Vous vous apprêtez à créer {NUMBER, plural, one{le créneau} other{les créneaux}} {TYPE, select, machines{machine} training{formation} space{espace} other{autre}} suivant :"
|
about_to_create: "Vous vous apprêtez à créer {NUMBER, plural, one{le créneau} other{les créneaux}} {TYPE, select, machines{machine} training{formation} space{espace} other{autre}} suivant :"
|
||||||
divided_in_slots: "{COUNT, plural, =1{Ce créneau sera proposé} other{Ces créneaux seront proposés}} à la réservation par tranches de {DURATION} minutes. Contactez votre administrateur système pour modifier ce paramètre."
|
divided_in_slots: "{COUNT, plural, =1{Ce créneau sera proposé} other{Ces créneaux seront proposés}} à la réservation par tranches de {DURATION} minutes."
|
||||||
reservable: "Réservable(s) :"
|
reservable: "Réservable(s) :"
|
||||||
labels: "Étiquette(s) :"
|
labels: "Étiquette(s) :"
|
||||||
none: "Aucune"
|
none: "Aucune"
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal:true
|
||||||
|
|
||||||
|
# From this migration any availability can override the default SLOT_DURATION value for its own slots
|
||||||
|
class AddSlotDurationToAvailability < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :availabilities, :slot_duration, :integer
|
||||||
|
end
|
||||||
|
end
|
25
db/schema.rb
25
db/schema.rb
@ -10,7 +10,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: 2020_04_08_101654) do
|
ActiveRecord::Schema.define(version: 2020_04_15_141809) 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 "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
@ -18,8 +18,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
enable_extension "unaccent"
|
enable_extension "unaccent"
|
||||||
|
|
||||||
create_table "abuses", id: :serial, force: :cascade do |t|
|
create_table "abuses", id: :serial, force: :cascade do |t|
|
||||||
t.integer "signaled_id"
|
|
||||||
t.string "signaled_type"
|
t.string "signaled_type"
|
||||||
|
t.integer "signaled_id"
|
||||||
t.string "first_name"
|
t.string "first_name"
|
||||||
t.string "last_name"
|
t.string "last_name"
|
||||||
t.string "email"
|
t.string "email"
|
||||||
@ -48,8 +48,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
t.string "locality"
|
t.string "locality"
|
||||||
t.string "country"
|
t.string "country"
|
||||||
t.string "postal_code"
|
t.string "postal_code"
|
||||||
t.integer "placeable_id"
|
|
||||||
t.string "placeable_type"
|
t.string "placeable_type"
|
||||||
|
t.integer "placeable_id"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
end
|
end
|
||||||
@ -63,8 +63,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
create_table "assets", id: :serial, force: :cascade do |t|
|
create_table "assets", id: :serial, force: :cascade do |t|
|
||||||
t.integer "viewable_id"
|
|
||||||
t.string "viewable_type"
|
t.string "viewable_type"
|
||||||
|
t.integer "viewable_id"
|
||||||
t.string "attachment"
|
t.string "attachment"
|
||||||
t.string "type"
|
t.string "type"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
@ -94,6 +94,7 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
t.string "period"
|
t.string "period"
|
||||||
t.integer "nb_periods"
|
t.integer "nb_periods"
|
||||||
t.datetime "end_date"
|
t.datetime "end_date"
|
||||||
|
t.integer "slot_duration"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "availability_tags", id: :serial, force: :cascade do |t|
|
create_table "availability_tags", id: :serial, force: :cascade do |t|
|
||||||
@ -131,8 +132,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
create_table "credits", id: :serial, force: :cascade do |t|
|
create_table "credits", id: :serial, force: :cascade do |t|
|
||||||
t.integer "creditable_id"
|
|
||||||
t.string "creditable_type"
|
t.string "creditable_type"
|
||||||
|
t.integer "creditable_id"
|
||||||
t.integer "plan_id"
|
t.integer "plan_id"
|
||||||
t.integer "hours"
|
t.integer "hours"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
@ -284,8 +285,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
create_table "invoices", id: :serial, force: :cascade do |t|
|
create_table "invoices", id: :serial, force: :cascade do |t|
|
||||||
t.integer "invoiced_id"
|
|
||||||
t.string "invoiced_type"
|
t.string "invoiced_type"
|
||||||
|
t.integer "invoiced_id"
|
||||||
t.string "stp_invoice_id"
|
t.string "stp_invoice_id"
|
||||||
t.integer "total"
|
t.integer "total"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
@ -348,15 +349,15 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
|
|
||||||
create_table "notifications", id: :serial, force: :cascade do |t|
|
create_table "notifications", id: :serial, force: :cascade do |t|
|
||||||
t.integer "receiver_id"
|
t.integer "receiver_id"
|
||||||
t.integer "attached_object_id"
|
|
||||||
t.string "attached_object_type"
|
t.string "attached_object_type"
|
||||||
|
t.integer "attached_object_id"
|
||||||
t.integer "notification_type_id"
|
t.integer "notification_type_id"
|
||||||
t.boolean "is_read", default: false
|
t.boolean "is_read", default: false
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.string "receiver_type"
|
t.string "receiver_type"
|
||||||
t.boolean "is_send", default: false
|
t.boolean "is_send", default: false
|
||||||
t.jsonb "meta_data", default: {}
|
t.jsonb "meta_data", default: "{}"
|
||||||
t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id"
|
t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id"
|
||||||
t.index ["receiver_id"], name: "index_notifications_on_receiver_id"
|
t.index ["receiver_id"], name: "index_notifications_on_receiver_id"
|
||||||
end
|
end
|
||||||
@ -456,8 +457,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
create_table "prices", id: :serial, force: :cascade do |t|
|
create_table "prices", id: :serial, force: :cascade do |t|
|
||||||
t.integer "group_id"
|
t.integer "group_id"
|
||||||
t.integer "plan_id"
|
t.integer "plan_id"
|
||||||
t.integer "priceable_id"
|
|
||||||
t.string "priceable_type"
|
t.string "priceable_type"
|
||||||
|
t.integer "priceable_id"
|
||||||
t.integer "amount"
|
t.integer "amount"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
@ -564,8 +565,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
t.text "message"
|
t.text "message"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.integer "reservable_id"
|
|
||||||
t.string "reservable_type"
|
t.string "reservable_type"
|
||||||
|
t.integer "reservable_id"
|
||||||
t.integer "nb_reserve_places"
|
t.integer "nb_reserve_places"
|
||||||
t.integer "statistic_profile_id"
|
t.integer "statistic_profile_id"
|
||||||
t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id"
|
t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id"
|
||||||
@ -574,8 +575,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
|
|
||||||
create_table "roles", id: :serial, force: :cascade do |t|
|
create_table "roles", id: :serial, force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.integer "resource_id"
|
|
||||||
t.string "resource_type"
|
t.string "resource_type"
|
||||||
|
t.integer "resource_id"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
|
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
|
||||||
@ -866,8 +867,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_101654) do
|
|||||||
|
|
||||||
create_table "wallet_transactions", id: :serial, force: :cascade do |t|
|
create_table "wallet_transactions", id: :serial, force: :cascade do |t|
|
||||||
t.integer "wallet_id"
|
t.integer "wallet_id"
|
||||||
t.integer "transactable_id"
|
|
||||||
t.string "transactable_type"
|
t.string "transactable_type"
|
||||||
|
t.integer "transactable_id"
|
||||||
t.string "transaction_type"
|
t.string "transaction_type"
|
||||||
t.integer "amount"
|
t.integer "amount"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user