mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
Merge branch 'recurrence' into dev
This commit is contained in:
commit
71d7b68218
@ -9,6 +9,7 @@
|
||||
- Fix a security issue: fixed [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57)
|
||||
- [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details)
|
||||
- [TODO DEPLOY] -> (only dev) `bundle install`
|
||||
- [TODO DEPLOY] `rake db:migrate`
|
||||
|
||||
## v4.2.4 2019 October 30
|
||||
|
||||
|
@ -300,7 +300,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
end () { return end; },
|
||||
machinesPromise: ['Machine', function (Machine) { return Machine.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; }],
|
||||
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }]
|
||||
} });
|
||||
// when the modal is closed, we send the slot to the server for saving
|
||||
modalInstance.result.then(
|
||||
@ -378,8 +379,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
||||
/**
|
||||
* Controller used in the slot creation modal window
|
||||
*/
|
||||
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', 'moment', 'start', 'end', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'Tag', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, Tag, growl, _t) {
|
||||
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'growl', '_t',
|
||||
function ($scope, $uibModalInstance, $sce, moment, start, end, machinesPromise, Availability, trainingsPromise, spacesPromise, tagsPromise, growl, _t) {
|
||||
// $uibModal parameter
|
||||
$scope.start = start;
|
||||
|
||||
@ -395,6 +396,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
// spaces list
|
||||
$scope.spaces = spacesPromise.filter(function (s) { return !s.disabled; });
|
||||
|
||||
// all tags list
|
||||
$scope.tags = tagsPromise;
|
||||
|
||||
// machines associated with the created slot
|
||||
$scope.selectedMachines = [];
|
||||
|
||||
@ -426,9 +430,23 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
$scope.availability = {
|
||||
start_at: start,
|
||||
end_at: end,
|
||||
available_type: 'machines' // default
|
||||
available_type: 'machines', // default
|
||||
tag_ids: [],
|
||||
is_recurrent: false,
|
||||
period: 'week',
|
||||
nb_periods: 1,
|
||||
end_date: undefined // recurrence end
|
||||
};
|
||||
|
||||
// recurrent slots
|
||||
$scope.occurrences = [];
|
||||
|
||||
// localized name(s) of the reservable item(s)
|
||||
$scope.reservableName = '';
|
||||
|
||||
// localized name(s) of the selected tag(s)
|
||||
$scope.tagsName = '';
|
||||
|
||||
/**
|
||||
* Adds or removes the provided machine from the current slot
|
||||
* @param machine {Object}
|
||||
@ -458,9 +476,13 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
} else if ($scope.availability.available_type === 'space') {
|
||||
$scope.availability.space_ids = [$scope.selectedSpace.id];
|
||||
}
|
||||
if ($scope.availability.is_recurrent) {
|
||||
$scope.availability.occurrences = $scope.occurrences;
|
||||
}
|
||||
return Availability.save(
|
||||
{ availability: $scope.availability }
|
||||
, function (availability) { $uibModalInstance.close(availability); });
|
||||
{ availability: $scope.availability },
|
||||
function (availability) { $uibModalInstance.close(availability); }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -468,6 +490,8 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
*/
|
||||
$scope.next = function () {
|
||||
if ($scope.step === 1) { $scope.setNbTotalPlaces(); }
|
||||
if ($scope.step === 2) { return validateSelection(); }
|
||||
if ($scope.step === 4) { return validateRecurrence(); }
|
||||
return $scope.step++;
|
||||
};
|
||||
|
||||
@ -505,8 +529,6 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
$scope.selectedSpace = $scope.spaces[0];
|
||||
}
|
||||
|
||||
Tag.query().$promise.then(function (data) { $scope.tags = data; });
|
||||
|
||||
// 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
|
||||
// can configure any duration as it does not matters.
|
||||
@ -534,8 +556,8 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
end.add(moment(newValue).diff(oldValue), 'milliseconds');
|
||||
$scope.end = end.toDate();
|
||||
} else { // for training availabilities
|
||||
// prevent the admin from setting the begining after the and
|
||||
if (moment(newValue).add(1, 'hour').isAfter($scope.end)) {
|
||||
// prevent the admin from setting the beginning after the end
|
||||
if (moment(newValue).add(Fablab.slotDuration, 'minutes').isAfter($scope.end)) {
|
||||
$scope.start = oldValue;
|
||||
}
|
||||
}
|
||||
@ -545,7 +567,7 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
|
||||
// Maintain consistency between the end time and the date object in the availability object
|
||||
return $scope.$watch('end', function (newValue, oldValue, scope) {
|
||||
// we prevent the admin from setting the end of the availability before its begining
|
||||
// we prevent the admin from setting the end of the availability before its beginning
|
||||
if (moment($scope.start).add(Fablab.slotDuration, 'minutes').isAfter(newValue)) {
|
||||
$scope.end = oldValue;
|
||||
}
|
||||
@ -554,6 +576,95 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that a machine or more was/were selected before continuing to step 3 (adjust time + tags)
|
||||
*/
|
||||
const validateSelection = function () {
|
||||
if ($scope.availability.available_type === 'machines') {
|
||||
if ($scope.selectedMachines.length === 0) {
|
||||
return growl.error(_t('admin_calendar.you_should_select_at_least_a_machine'));
|
||||
}
|
||||
}
|
||||
$scope.step++;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that the recurrence parameters were correctly set before continuing to step 5 (summary)
|
||||
*/
|
||||
const validateRecurrence = function () {
|
||||
if ($scope.availability.is_recurrent) {
|
||||
if (!$scope.availability.period) {
|
||||
return growl.error(_t('admin_calendar.select_period'));
|
||||
}
|
||||
if (!$scope.availability.nb_periods) {
|
||||
return growl.error(_t('admin_calendar.select_nb_period'));
|
||||
}
|
||||
if (!$scope.availability.end_date) {
|
||||
return growl.error(_t('admin_calendar.select_end_date'));
|
||||
}
|
||||
}
|
||||
// settings are ok
|
||||
computeOccurrences();
|
||||
computeNames();
|
||||
$scope.step++;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the various occurrences of the availability, according to the recurrence settings
|
||||
*/
|
||||
const computeOccurrences = function () {
|
||||
$scope.occurrences = [];
|
||||
|
||||
if ($scope.availability.is_recurrent) {
|
||||
const date = moment($scope.availability.start_at);
|
||||
const diff = moment($scope.availability.end_at).diff($scope.availability.start_at);
|
||||
const end = moment($scope.availability.end_date).endOf('day');
|
||||
while (date.isBefore(end)) {
|
||||
const occur_end = moment(date).add(diff, 'ms');
|
||||
$scope.occurrences.push({
|
||||
start_at: date.toDate(),
|
||||
end_at: occur_end.toDate()
|
||||
});
|
||||
date.add($scope.availability.nb_periods, $scope.availability.period);
|
||||
}
|
||||
} else {
|
||||
$scope.occurrences.push({
|
||||
start_at: $scope.availability.start_at,
|
||||
end_at: $scope.availability.end_at
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const computeNames = function () {
|
||||
$scope.reservableName = '';
|
||||
switch ($scope.availability.available_type) {
|
||||
case 'machines':
|
||||
$scope.reservableName = localizedList($scope.selectedMachines)
|
||||
break;
|
||||
case 'training':
|
||||
$scope.reservableName = `<strong>${$scope.selectedTraining.name}</strong>`;
|
||||
break;
|
||||
case 'space':
|
||||
$scope.reservableName = `<strong>${$scope.selectedSpace.name}</strong>`;
|
||||
break;
|
||||
default:
|
||||
$scope.reservableName = `<span class="warning">${_t("admin_calendar.none")}</span>`;
|
||||
}
|
||||
const tags = $scope.tags.filter(function (t) {
|
||||
return $scope.availability.tag_ids.indexOf(t.id) > -1;
|
||||
})
|
||||
$scope.tagsName = localizedList(tags);
|
||||
}
|
||||
|
||||
const localizedList = function (items) {
|
||||
if (items.length === 0) return `<span class="text-gray text-italic">${_t("admin_calendar.none")}</span>`;
|
||||
|
||||
const names = items.map(function (i) { return $sce.trustAsHtml(`<strong>${i.name}</strong>`); });
|
||||
if (items.length > 1) return names.slice(0, -1).join(', ') + ` ${_t('and')} ` + names[names.length - 1];
|
||||
|
||||
return names[0];
|
||||
}
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the controller
|
||||
return initialize();
|
||||
}
|
||||
|
@ -93,12 +93,88 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step < 3">
|
||||
<div class="modal-body m-h" ng-show="step === 4">
|
||||
<div class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.recurrence' }}</p>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="is_recurrent" translate>{{ 'admin_calendar.enabled' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="availability.is_recurrent"
|
||||
id="is_recurrent"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"/>
|
||||
<input type="hidden" name="availability[is_recurrent]" value="{{availability.is_recurrent}}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="period">{{ 'admin_calendar.period' | translate }}</label>
|
||||
<select id="period"
|
||||
name="period"
|
||||
class="form-control"
|
||||
ng-model="availability.period"
|
||||
ng-required="availability.is_recurrent"
|
||||
ng-disabled="!availability.is_recurrent">
|
||||
<option value="week" ng-selected="availability.period == 'week'" translate>{{ 'admin_calendar.week' }}</option>
|
||||
<option value="month" ng-selected="availability.period == 'month'" translate>{{ 'admin_calendar.month' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="nb_periods">{{ 'admin_calendar.number_of_periods' | translate }}</label>
|
||||
<input id="nb_periods"
|
||||
name="nb_periods"
|
||||
class="form-control"
|
||||
ng-model="availability.nb_periods"
|
||||
type="number"
|
||||
ng-required="availability.is_recurrent"
|
||||
ng-disabled="!availability.is_recurrent" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="end_date">{{ 'admin_calendar.end_date' | translate }}</label>
|
||||
<input id="end_date"
|
||||
name="end_date"
|
||||
class="form-control"
|
||||
ng-model="availability.end_date"
|
||||
type="date"
|
||||
ng-required="availability.is_recurrent"
|
||||
ng-disabled="!availability.is_recurrent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body m-h" ng-show="step === 5">
|
||||
<div class="m-t-sm">
|
||||
<p class="text-center font-sbold" translate>{{ 'admin_calendar.summary' }}</p>
|
||||
<div class="row">
|
||||
<span>{{ 'admin_calendar.about_to_create' | translate:{NUMBER:occurrences.length,TYPE:availability.available_type}:'messageformat'}}</span>
|
||||
<ul>
|
||||
<li ng-repeat="slot in occurrences">{{slot.start_at | amDateFormat:'L LT'}} - {{slot.end_at | amDateFormat:'LT'}}</li>
|
||||
</ul>
|
||||
<div>
|
||||
<span class="underline" translate>{{ 'admin_calendar.reservable' }}</span>
|
||||
<span ng-bind-html="reservableName"></span>
|
||||
</div>
|
||||
<div class="m-t">
|
||||
<span class="underline" translate>{{ 'admin_calendar.labels' }}</span>
|
||||
<span ng-bind-html="tagsName"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step < 5">
|
||||
<button class="btn btn-info" ng-click="previous()" ng-disabled="step === 1" translate>{{ 'admin_calendar.previous' }}</button>
|
||||
<button class="btn btn-info" ng-click="next()" translate>{{ 'admin_calendar.next' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
</div>
|
||||
<div class="modal-footer" ng-show="step === 3">
|
||||
<div class="modal-footer" ng-show="step === 5">
|
||||
<button class="btn btn-info" ng-click="previous()" translate>{{ 'admin_calendar.previous' }}</button>
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'cancel' }}</button>
|
||||
|
@ -48,6 +48,10 @@ class API::AvailabilitiesController < API::ApiController
|
||||
authorize Availability
|
||||
@availability = Availability.new(availability_params)
|
||||
if @availability.save
|
||||
if params[:availability][:occurrences]
|
||||
service = Availabilities::CreateAvailabilitiesService.new
|
||||
service.create(@availability, params[:availability][:occurrences])
|
||||
end
|
||||
render :show, status: :created, location: @availability
|
||||
else
|
||||
render json: @availability.errors, status: :unprocessable_entity
|
||||
@ -140,6 +144,7 @@ class API::AvailabilitiesController < API::ApiController
|
||||
|
||||
def availability_params
|
||||
params.require(:availability).permit(:start_at, :end_at, :available_type, :machine_ids, :training_ids, :nb_total_places,
|
||||
:is_recurrent, :period, :nb_periods, :end_date,
|
||||
machine_ids: [], training_ids: [], space_ids: [], tag_ids: [],
|
||||
machines_attributes: %i[id _destroy])
|
||||
end
|
||||
|
@ -36,7 +36,7 @@ class Availability < ActiveRecord::Base
|
||||
attr_accessor :is_reserved, :slot_id, :can_modify
|
||||
|
||||
validates :start_at, :end_at, presence: true
|
||||
validate :length_must_be_1h_minimum, unless: proc { end_at.blank? or start_at.blank? }
|
||||
validate :length_must_be_slot_multiple, unless: proc { end_at.blank? or start_at.blank? }
|
||||
validate :should_be_associated
|
||||
|
||||
## elastic callbacks
|
||||
@ -155,8 +155,10 @@ class Availability < ActiveRecord::Base
|
||||
|
||||
private
|
||||
|
||||
def length_must_be_1h_minimum
|
||||
errors.add(:end_at, I18n.t('availabilities.must_be_at_least_1_hour_after_the_start_date')) if end_at < (start_at + 1.hour)
|
||||
def length_must_be_slot_multiple
|
||||
if end_at < (start_at + Rails.application.secrets.slot_duration.minutes)
|
||||
errors.add(:end_at, I18n.t('availabilities.length_must_be_slot_multiple', MIN: Rails.application.secrets.slot_duration))
|
||||
end
|
||||
end
|
||||
|
||||
def should_be_associated
|
||||
|
27
app/services/availabilities/create_availabilities_service.rb
Normal file
27
app/services/availabilities/create_availabilities_service.rb
Normal file
@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides helper methods toi create an Availability with multiple occurrences
|
||||
class Availabilities::CreateAvailabilitiesService
|
||||
def create(availability, occurrences = [])
|
||||
availability.update_attributes(occurrence_id: availability.id)
|
||||
|
||||
occurrences.each do |o|
|
||||
next if availability.start_at == o[:start_at] && availability.end_at == o[:end_at]
|
||||
|
||||
Availability.new(
|
||||
start_at: o[:start_at],
|
||||
end_at: o[:end_at],
|
||||
available_type: availability.available_type,
|
||||
is_recurrent: availability.is_recurrent,
|
||||
period: availability.period,
|
||||
nb_periods: availability.nb_periods,
|
||||
end_date: availability.end_date,
|
||||
occurrence_id: availability.occurrence_id,
|
||||
machine_ids: availability.machine_ids,
|
||||
training_ids: availability.training_ids,
|
||||
space_ids: availability.space_ids,
|
||||
tag_ids: availability.tag_ids
|
||||
).save!
|
||||
end
|
||||
end
|
||||
end
|
@ -44,6 +44,21 @@ en:
|
||||
adjust_the_opening_hours: "Adjust the opening hours"
|
||||
to_time: "to" # context: time. eg. "from 18:00 to 21:00"
|
||||
restrict_this_slot_with_labels_optional: "Restrict this slot with labels (optional)"
|
||||
recurrence: "Recurrence"
|
||||
enabled: "Enabled"
|
||||
period: "Period"
|
||||
week: "Week"
|
||||
month: "Month"
|
||||
number_of_periods: "Number of periods"
|
||||
end_date: "End date"
|
||||
summary: "Summary"
|
||||
select_period: "Please select a period 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"
|
||||
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}}:" # messageFormat interpolation
|
||||
reservable: "Reservable(s):"
|
||||
labels: "Label(s):"
|
||||
none: "None"
|
||||
the_slot_START-END_has_been_successfully_deleted: "The slot {{START}} - {{END}} has been successfully deleted" # 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."
|
||||
|
@ -44,6 +44,21 @@ es:
|
||||
adjust_the_opening_hours: "Ajustar el horario de apertura"
|
||||
to_time: "to" # context: hora. ej. "de 18:00 a 21:00"
|
||||
restrict_this_slot_with_labels_optional: "Restringir esta ranura con etiquetas (opcional)"
|
||||
recurrence: "Recurrence" # translation_missing
|
||||
enabled: "Enabled" # translation_missing
|
||||
period: "Period" # translation_missing
|
||||
week: "Week" # translation_missing
|
||||
month: "Month" # translation_missing
|
||||
number_of_periods: "Number of periods" # translation_missing
|
||||
end_date: "End date" # translation_missing
|
||||
summary: "Summary" # translation_missing
|
||||
select_period: "Please select a period for the recurrence" # translation_missing
|
||||
select_nb_period: "Please select a number of periods for the recurrence" # translation_missing
|
||||
select_end_date: "Please select the date of the last occurrence" # translation_missing
|
||||
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}}:" # messageFormat interpolation # translation_missing
|
||||
reservable: "Reservable(s):" # translation_missing
|
||||
labels: "Etiqueta(s):"
|
||||
none: "Ninguna"
|
||||
the_slot_START-END_has_been_successfully_deleted: "La ranura {{START}} - {{END}} se ha eliminado correctamente" # angular interpolation
|
||||
unable_to_delete_the_slot_START-END_because_it_s_already_reserved_by_a_member: "No se puede eliminar la ranura {{START}} - {{END}} porque ya está reservada por un miembror" # angular interpolation
|
||||
you_should_select_at_least_a_machine: "Debe seleccionar al menos una máquina en esta ranura."
|
||||
|
@ -44,6 +44,21 @@ fr:
|
||||
adjust_the_opening_hours: "Ajuster l'horaire"
|
||||
to_time: "à" # context: time. eg. "from 18:00 to 21:00"
|
||||
restrict_this_slot_with_labels_optional: "Restreindre ce créneau avec des étiquettes (optionnel)"
|
||||
recurrence: "Récurrence"
|
||||
enabled: "Activée"
|
||||
period: "Période"
|
||||
week: "Semaine"
|
||||
month: "Mois"
|
||||
number_of_periods: "Nombre de périodes"
|
||||
end_date: "Date de fin"
|
||||
summary: "Récapitulatif"
|
||||
select_period: "Veuillez choisir une période 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"
|
||||
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 :" # messageFormat interpolation
|
||||
reservable: "Réservable(s) :"
|
||||
labels: "Étiquette(s) :"
|
||||
none: "Aucune"
|
||||
the_slot_START-END_has_been_successfully_deleted: "Le créneau {{START}} - {{END}} a bien été supprimé" # 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électionner au moins une machine pour ce créneau."
|
||||
|
@ -44,6 +44,21 @@ pt:
|
||||
adjust_the_opening_hours: "Ajustar o horário de funcionamento"
|
||||
to_time: "ás" # context: time. eg. "from 18:00 to 21:00"
|
||||
restrict_this_slot_with_labels_optional: "Restrinja este slot com etiquetas (opcional)"
|
||||
recurrence: "Recurrence" # translation_missing
|
||||
enabled: "Enabled" # translation_missing
|
||||
period: "Period" # translation_missing
|
||||
week: "Week" # translation_missing
|
||||
month: "Month" # translation_missing
|
||||
number_of_periods: "Number of periods" # translation_missing
|
||||
end_date: "End date" # translation_missing
|
||||
summary: "Summary" # translation_missing
|
||||
select_period: "Please select a period for the recurrence" # translation_missing
|
||||
select_nb_period: "Please select a number of periods for the recurrence" # translation_missing
|
||||
select_end_date: "Please select the date of the last occurrence" # translation_missing
|
||||
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}}:" # messageFormat interpolation # translation_missing
|
||||
reservable: "Reservable(s) :" # translation_missing
|
||||
labels: "Etiqueta(s):"
|
||||
none: "Nenhuma"
|
||||
the_slot_START-END_has_been_successfully_deleted: "O slot {{START}} - {{END}} foi deletado com sucesso" # 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."
|
||||
|
@ -79,6 +79,7 @@ en:
|
||||
to_date: "to" # context: date. eg: "from 01/01 to 01/05"
|
||||
to_time: "to" # context: time. eg. "from 18:00 to 21:00"
|
||||
or: "or"
|
||||
and: "and"
|
||||
change_my_data: "Change my data"
|
||||
sync_my_profile: "Sync my profile"
|
||||
once_your_data_are_up_to_date_: "Once your data are up to date,"
|
||||
|
@ -79,6 +79,7 @@ es:
|
||||
to_date: "Hasta" # context: date. eg: "Hasta 01/01 to 01/05"
|
||||
to_time: "Hasta" # context: time. eg. "Hasta 18:00 to 21:00"
|
||||
or: "ó"
|
||||
and: "y"
|
||||
change_my_data: "Cambiar mis datos"
|
||||
sync_my_profile: "Sincronizar mi perfil"
|
||||
once_your_data_are_up_to_date_: "Una vez sus datos hayan sido actualizados,"
|
||||
|
@ -79,6 +79,7 @@ fr:
|
||||
to_date: "au" # context: date. eg: "from 01/01 to 01/05"
|
||||
to_time: "à" # context: time. eg. "from 18:00 to 21:00"
|
||||
or: "ou"
|
||||
and: "et"
|
||||
change_my_data: "Modifier mes données"
|
||||
sync_my_profile: "Synchroniser mon profil"
|
||||
once_your_data_are_up_to_date_: "Une fois vos données à jour,"
|
||||
|
@ -79,6 +79,7 @@ pt:
|
||||
to_date: "até" # context: date. eg: "from 01/01 to 01/05"
|
||||
to_time: "até" # context: time. eg. "from 18:00 to 21:00"
|
||||
or: "ou"
|
||||
and: "e"
|
||||
change_my_data: "Alterar meus dados"
|
||||
sync_my_profile: "Sincronizar meu perfil"
|
||||
once_your_data_are_up_to_date_: "Uma vez que seus dados estão atualizados,"
|
||||
|
@ -62,7 +62,7 @@ en:
|
||||
# availability slots in the calendar
|
||||
not_available: "Not available"
|
||||
i_ve_reserved: "I've reserved"
|
||||
must_be_at_least_1_hour_after_the_start_date: "must be at least 1 hour after the start date"
|
||||
length_must_be_slot_multiple: "must be at least %{MIN} minutes after the start date"
|
||||
must_be_associated_with_at_least_1_machine: "must be associated with at least 1 machine"
|
||||
|
||||
members:
|
||||
|
@ -62,7 +62,7 @@ es:
|
||||
# availability slots in the calendar
|
||||
not_available: "No disponible"
|
||||
i_ve_reserved: "He reservado"
|
||||
must_be_at_least_1_hour_after_the_start_date: "Debe ser al menos 1 hora después de la fecha de inicio"
|
||||
length_must_be_slot_multiple: "Debe ser al menos %{MIN} minutos después de la fecha de inicio"
|
||||
must_be_associated_with_at_least_1_machine: "debe estar asociado con al menos 1 máquina"
|
||||
|
||||
members:
|
||||
|
@ -62,7 +62,7 @@ fr:
|
||||
# créneaux de disponibilité dans le calendrier
|
||||
not_available: "Non disponible"
|
||||
i_ve_reserved: "J'ai réservé"
|
||||
must_be_at_least_1_hour_after_the_start_date: "doit être au moins 1 heure après la date de début"
|
||||
length_must_be_slot_multiple: "doit être au moins %{MIN} minutes après la date de début"
|
||||
must_be_associated_with_at_least_1_machine: "doit être associé avec au moins 1 machine"
|
||||
|
||||
members:
|
||||
|
@ -62,7 +62,7 @@ pt:
|
||||
# availability slots in the calendar
|
||||
not_available: "Não disponível "
|
||||
i_ve_reserved: "Eu reservei"
|
||||
must_be_at_least_1_hour_after_the_start_date: "deve ser pelo menos 1 hora após a data de início"
|
||||
length_must_be_slot_multiple: "deve ser pelo menos %{MIN} minutos após a data de início"
|
||||
must_be_associated_with_at_least_1_machine: "deve estar associada a pelo menos uma máquina"
|
||||
|
||||
members:
|
||||
|
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# From this migration, we store recurrence info into the availability object, the availability can be linked to others, which are
|
||||
# its "children".
|
||||
class AddRecurrenceToAvailabilities < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :availabilities, :is_recurrent, :boolean
|
||||
add_column :availabilities, :occurrence_id, :integer
|
||||
add_column :availabilities, :period, :string
|
||||
add_column :availabilities, :nb_periods, :integer
|
||||
add_column :availabilities, :end_date, :datetime
|
||||
end
|
||||
end
|
@ -11,12 +11,12 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20190924140726) do
|
||||
ActiveRecord::Schema.define(version: 20191113103352) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
enable_extension "unaccent"
|
||||
enable_extension "pg_trgm"
|
||||
enable_extension "unaccent"
|
||||
|
||||
create_table "abuses", force: :cascade do |t|
|
||||
t.integer "signaled_id"
|
||||
@ -92,6 +92,11 @@ ActiveRecord::Schema.define(version: 20190924140726) do
|
||||
t.integer "nb_total_places"
|
||||
t.boolean "destroying", default: false
|
||||
t.boolean "lock", default: false
|
||||
t.boolean "is_recurrent"
|
||||
t.integer "occurrence_id"
|
||||
t.string "period"
|
||||
t.integer "nb_periods"
|
||||
t.datetime "end_date"
|
||||
end
|
||||
|
||||
create_table "availability_tags", force: :cascade do |t|
|
||||
|
Loading…
x
Reference in New Issue
Block a user