mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
Merge branch 'slot_is_only_subscriptions' into dev
This commit is contained in:
commit
a44bf85014
@ -1,5 +1,6 @@
|
|||||||
# Changelog Fab-manager
|
# Changelog Fab-manager
|
||||||
|
|
||||||
|
- Ability to configure reservation slot restricted for plan subscribers
|
||||||
- Ability to configure the policy (allow or prevent) for members booking a machine/formation/event slot, if they already have a reservation the same day at the same time
|
- Ability to configure the policy (allow or prevent) for members booking a machine/formation/event slot, if they already have a reservation the same day at the same time
|
||||||
- Ability to create and delete periodic calendar availabilities (recurrence)
|
- Ability to create and delete periodic calendar availabilities (recurrence)
|
||||||
- Ability to fully customize the home page
|
- Ability to fully customize the home page
|
||||||
|
@ -18,8 +18,8 @@
|
|||||||
* Controller used in the calendar management page
|
* Controller used in the calendar management page
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
|
Application.Controllers.controller('AdminCalendarController', ['$scope', '$state', '$uibModal', 'moment', 'Availability', 'Slot', 'Setting', 'Export', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', 'machinesPromise', 'plansPromise', 'groupsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Member', 'uiTourService',
|
||||||
function ($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
|
function ($scope, $state, $uibModal, moment, Availability, Slot, Setting, Export, growl, dialogs, bookingWindowStart, bookingWindowEnd, machinesPromise, plansPromise, groupsPromise, _t, uiCalendarConfig, CalendarConfig, Member, uiTourService) {
|
||||||
/* PRIVATE STATIC CONSTANTS */
|
/* PRIVATE STATIC CONSTANTS */
|
||||||
|
|
||||||
// The calendar is divided in slots of 30 minutes
|
// The calendar is divided in slots of 30 minutes
|
||||||
@ -164,6 +164,44 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a confirmation modal to remove a plan for the currently selected availability,
|
||||||
|
* @param plan {Object} must contain the machine ID and name
|
||||||
|
*/
|
||||||
|
$scope.removePlan = function (plan) {
|
||||||
|
// open a confirmation dialog
|
||||||
|
return dialogs.confirm({
|
||||||
|
resolve: {
|
||||||
|
object () {
|
||||||
|
return {
|
||||||
|
title: _t('app.admin.calendar.confirmation_required'),
|
||||||
|
msg: _t('app.admin.calendar.do_you_really_want_to_remove_PLAN_from_this_slot', { GENDER: getGender($scope.currentUser), PLAN: plan.name }) + ' ' +
|
||||||
|
_t('app.admin.calendar.this_will_prevent_any_new_reservation_on_this_slot_but_wont_cancel_those_existing') + '<br><strong>' +
|
||||||
|
_t('app.admin.calendar.beware_this_cannot_be_reverted') + '</strong>'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, function () {
|
||||||
|
// the admin has confirmed, remove the plan
|
||||||
|
const plans = _.drop($scope.availability.plan_ids, plan.id);
|
||||||
|
|
||||||
|
return Availability.update({ id: $scope.availability.id }, { availability: { plans_attributes: [{ id: plan.id, _destroy: true }] } }
|
||||||
|
, function (data, status) { // success
|
||||||
|
// update the plan_ids attribute
|
||||||
|
$scope.availability.plan_ids = data.plan_ids;
|
||||||
|
$scope.availability.plans = availabilityPlans();
|
||||||
|
uiCalendarConfig.calendars.calendar.fullCalendar('rerenderEvents');
|
||||||
|
// notify the admin
|
||||||
|
return growl.success(_t('app.admin.calendar.the_plan_was_successfully_removed_from_the_slot'));
|
||||||
|
}
|
||||||
|
, function (data, status) { // failed
|
||||||
|
growl.error(_t('app.admin.calendar.deletion_failed'));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to alert the admin that the export request was acknowledged and is
|
* Callback to alert the admin that the export request was acknowledged and is
|
||||||
* processing right now.
|
* processing right now.
|
||||||
@ -340,6 +378,26 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
} else { return 'other'; }
|
} else { return 'other'; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a list of plans classified by group
|
||||||
|
*
|
||||||
|
* @returns {array}
|
||||||
|
*/
|
||||||
|
var availabilityPlans = function() {
|
||||||
|
const plansClassifiedByGroup = [];
|
||||||
|
const _plans = _.filter(plansPromise, function (p) { return _.include($scope.availability.plan_ids, p.id) });
|
||||||
|
for (let group of Array.from(groupsPromise)) {
|
||||||
|
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||||
|
for (let plan of Array.from(_plans)) {
|
||||||
|
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
||||||
|
}
|
||||||
|
if (groupObj.plans.length > 0) {
|
||||||
|
plansClassifiedByGroup.push(groupObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return plansClassifiedByGroup;
|
||||||
|
};
|
||||||
|
|
||||||
// Triggered when the admin drag on the agenda to create a new reservable slot.
|
// Triggered when the admin drag on the agenda to create a new reservable slot.
|
||||||
// @see http://fullcalendar.io/docs/selection/select_callback/
|
// @see http://fullcalendar.io/docs/selection/select_callback/
|
||||||
//
|
//
|
||||||
@ -372,7 +430,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
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; }],
|
||||||
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }]
|
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
|
||||||
|
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
|
||||||
|
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
|
||||||
} });
|
} });
|
||||||
// when the modal is closed, we send the slot to the server for saving
|
// when the modal is closed, we send the slot to the server for saving
|
||||||
modalInstance.result.then(
|
modalInstance.result.then(
|
||||||
@ -389,7 +449,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
borderColor: availability.borderColor,
|
borderColor: availability.borderColor,
|
||||||
tag_ids: availability.tag_ids,
|
tag_ids: availability.tag_ids,
|
||||||
tags: availability.tags,
|
tags: availability.tags,
|
||||||
machine_ids: availability.machine_ids
|
machine_ids: availability.machine_ids,
|
||||||
|
plan_ids: availability.plan_ids
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@ -406,6 +467,7 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
|
|||||||
*/
|
*/
|
||||||
const calendarEventClickCb = function (event, jsEvent, view) {
|
const calendarEventClickCb = function (event, jsEvent, view) {
|
||||||
$scope.availability = event;
|
$scope.availability = event;
|
||||||
|
$scope.availability.plans = availabilityPlans();
|
||||||
|
|
||||||
if ($scope.availabilityDom) {
|
if ($scope.availabilityDom) {
|
||||||
$scope.availabilityDom.classList.remove("fc-selected")
|
$scope.availabilityDom.classList.remove("fc-selected")
|
||||||
@ -484,8 +546,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', 'growl', '_t',
|
Application.Controllers.controller('CreateEventModalController', ['$scope', '$uibModalInstance', '$sce', 'moment', 'start', 'end', 'machinesPromise', 'Availability', 'trainingsPromise', 'spacesPromise', 'tagsPromise', 'plansPromise', 'groupsPromise', 'growl', '_t',
|
||||||
function ($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, plansPromise, groupsPromise, growl, _t) {
|
||||||
// $uibModal parameter
|
// $uibModal parameter
|
||||||
$scope.start = start;
|
$scope.start = start;
|
||||||
|
|
||||||
@ -504,6 +566,21 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
// all tags list
|
// all tags list
|
||||||
$scope.tags = tagsPromise;
|
$scope.tags = tagsPromise;
|
||||||
|
|
||||||
|
$scope.isOnlySubscriptions = false;
|
||||||
|
$scope.selectedPlans = [];
|
||||||
|
$scope.selectedPlansBinding = {};
|
||||||
|
// list of plans, classified by group
|
||||||
|
$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 = [];
|
||||||
$scope.selectedMachinesBinding = {};
|
$scope.selectedMachinesBinding = {};
|
||||||
@ -553,6 +630,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
// localized name(s) of the selected tag(s)
|
// localized name(s) of the selected tag(s)
|
||||||
$scope.tagsName = '';
|
$scope.tagsName = '';
|
||||||
|
|
||||||
|
// localized name(s) of the selected plan(s)
|
||||||
|
$scope.plansName = '';
|
||||||
|
|
||||||
// make the duration available for display
|
// make the duration available for display
|
||||||
$scope.slotDuration = Fablab.slotDuration;
|
$scope.slotDuration = Fablab.slotDuration;
|
||||||
|
|
||||||
@ -584,6 +664,34 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds or removes the provided plan from the current slot
|
||||||
|
* @param plan {Object}
|
||||||
|
*/
|
||||||
|
$scope.toggleSelectPlan = function (plan) {
|
||||||
|
const index = $scope.selectedPlans.indexOf(plan);
|
||||||
|
if (index > -1) {
|
||||||
|
return $scope.selectedPlans.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
return $scope.selectedPlans.push(plan);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select/unselect all the plans
|
||||||
|
*/
|
||||||
|
$scope.toggleAllPlans = function() {
|
||||||
|
const count = $scope.selectedPlans.length;
|
||||||
|
$scope.selectedPlans = [];
|
||||||
|
$scope.selectedPlansBinding = {};
|
||||||
|
if (count == 0) {
|
||||||
|
plansPromise.forEach(function (plan) {
|
||||||
|
$scope.selectedPlans.push(plan);
|
||||||
|
$scope.selectedPlansBinding[plan.id] = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for the modal window validation: save the slot and closes the modal
|
* Callback for the modal window validation: save the slot and closes the modal
|
||||||
*/
|
*/
|
||||||
@ -603,6 +711,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
if ($scope.availability.is_recurrent) {
|
if ($scope.availability.is_recurrent) {
|
||||||
$scope.availability.occurrences = $scope.occurrences;
|
$scope.availability.occurrences = $scope.occurrences;
|
||||||
}
|
}
|
||||||
|
if ($scope.isOnlySubscriptions && $scope.selectedPlans.length > 0) {
|
||||||
|
$scope.availability.plan_ids = $scope.selectedPlans.map(function (p) { return p.id; });
|
||||||
|
}
|
||||||
return Availability.save(
|
return Availability.save(
|
||||||
{ availability: $scope.availability },
|
{ availability: $scope.availability },
|
||||||
function (availability) { $uibModalInstance.close(availability); }
|
function (availability) { $uibModalInstance.close(availability); }
|
||||||
@ -653,6 +764,14 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
$scope.selectedSpace = $scope.spaces[0];
|
$scope.selectedSpace = $scope.spaces[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when disable is only subscriptions option, reset all selected plans
|
||||||
|
$scope.$watch('isOnlySubscriptions', function(value) {
|
||||||
|
if (!value) {
|
||||||
|
$scope.selectedPlans = [];
|
||||||
|
$scope.selectedPlansBinding = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 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 Fablab.slotDuration 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.
|
||||||
@ -778,6 +897,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
|
|||||||
return $scope.availability.tag_ids.indexOf(t.id) > -1;
|
return $scope.availability.tag_ids.indexOf(t.id) > -1;
|
||||||
})
|
})
|
||||||
$scope.tagsName = localizedList(tags);
|
$scope.tagsName = localizedList(tags);
|
||||||
|
if ($scope.isOnlySubscriptions && $scope.selectedPlans.length > 0) {
|
||||||
|
$scope.plansName = localizedList($scope.selectedPlans);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const localizedList = function (items) {
|
const localizedList = function (items) {
|
||||||
|
@ -450,6 +450,8 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
|
|||||||
$scope.settings = settingsPromise;
|
$scope.settings = settingsPromise;
|
||||||
|
|
||||||
// list of plans, classified by group
|
// list of plans, classified by group
|
||||||
|
$scope.groups = groupsPromise;
|
||||||
|
$scope.plans = plansPromise;
|
||||||
$scope.plansClassifiedByGroup = [];
|
$scope.plansClassifiedByGroup = [];
|
||||||
for (let group of Array.from(groupsPromise)) {
|
for (let group of Array.from(groupsPromise)) {
|
||||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||||
|
@ -328,6 +328,8 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$stateP
|
|||||||
|
|
||||||
// list of plans, classified by group
|
// list of plans, classified by group
|
||||||
$scope.plansClassifiedByGroup = [];
|
$scope.plansClassifiedByGroup = [];
|
||||||
|
$scope.groups = groupsPromise;
|
||||||
|
$scope.plans = plansPromise;
|
||||||
for (let group of Array.from(groupsPromise)) {
|
for (let group of Array.from(groupsPromise)) {
|
||||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||||
for (let plan of Array.from(plansPromise)) {
|
for (let plan of Array.from(plansPromise)) {
|
||||||
|
@ -111,6 +111,8 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$sta
|
|||||||
{ member: {} };
|
{ member: {} };
|
||||||
|
|
||||||
// list of plans, classified by group
|
// list of plans, classified by group
|
||||||
|
$scope.groups = groupsPromise;
|
||||||
|
$scope.plans = plansPromise;
|
||||||
$scope.plansClassifiedByGroup = [];
|
$scope.plansClassifiedByGroup = [];
|
||||||
for (let group of Array.from(groupsPromise)) {
|
for (let group of Array.from(groupsPromise)) {
|
||||||
const groupObj = { id: group.id, name: group.name, plans: [] };
|
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||||
|
@ -23,6 +23,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
|||||||
plan: '=',
|
plan: '=',
|
||||||
planSelectionTime: '=',
|
planSelectionTime: '=',
|
||||||
settings: '=',
|
settings: '=',
|
||||||
|
plans: '=',
|
||||||
|
groups: '=',
|
||||||
onSlotAddedToCart: '=',
|
onSlotAddedToCart: '=',
|
||||||
onSlotRemovedFromCart: '=',
|
onSlotRemovedFromCart: '=',
|
||||||
onSlotStartToModify: '=',
|
onSlotStartToModify: '=',
|
||||||
@ -164,22 +166,55 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
|||||||
$scope.payCart = function () {
|
$scope.payCart = function () {
|
||||||
// first, we check that a user was selected
|
// first, we check that a user was selected
|
||||||
if (Object.keys($scope.user).length > 0) {
|
if (Object.keys($scope.user).length > 0) {
|
||||||
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
|
|
||||||
|
|
||||||
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
|
// check user was selected a plan if slot is restricted for subscriptions
|
||||||
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
|
const slotValidations = [];
|
||||||
if (!$scope.isAdmin() && (amountToPay > 0)) {
|
let slotNotValid;
|
||||||
if ($rootScope.fablabWithoutOnlinePayment) {
|
let slotNotValidError;
|
||||||
growl.error(_t('app.shared.cart.online_payment_disabled'));
|
$scope.events.reserved.forEach(function (slot) {
|
||||||
|
if (slot.plan_ids.length > 0) {
|
||||||
|
if (
|
||||||
|
($scope.selectedPlan && _.include(slot.plan_ids, $scope.selectedPlan.id)) ||
|
||||||
|
($scope.user.subscribed_plan && _.include(slot.plan_ids, $scope.user.subscribed_plan.id))
|
||||||
|
) {
|
||||||
|
slotValidations.push(true);
|
||||||
} else {
|
} else {
|
||||||
return payByStripe(reservation);
|
slotNotValid = slot;
|
||||||
}
|
if ($scope.selectedPlan && !_.include(slot.plan_ids, $scope.selectedPlan.id)) {
|
||||||
} else {
|
slotNotValidError = 'selectedPlanError';
|
||||||
if ($scope.isAdmin() || (amountToPay === 0)) {
|
}
|
||||||
return payOnSite(reservation);
|
if ($scope.user.subscribed_plan && !_.include(slot.plan_ids, $scope.user.subscribed_plan.id)) {
|
||||||
|
slotNotValidError = 'userPlanError';
|
||||||
|
}
|
||||||
|
if (!$scope.selectedPlan || !$scope.user.subscribed_plan) {
|
||||||
|
slotNotValidError = 'noPlanError';
|
||||||
|
}
|
||||||
|
slotValidations.push(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const hasPlanForSlot = slotValidations.every(function (a) { return a; });
|
||||||
|
if (!hasPlanForSlot) {
|
||||||
|
if (!$scope.isAdmin()) {
|
||||||
|
return growl.error(_t('app.shared.cart.slot_restrict_subscriptions_must_select_plan'));
|
||||||
|
} else {
|
||||||
|
const modalInstance = $uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: '<%= asset_path "shared/_reserve_slot_without_plan.html" %>',
|
||||||
|
size: 'md',
|
||||||
|
controller: 'ReserveSlotWithoutPlanController',
|
||||||
|
resolve: {
|
||||||
|
slot: function() { return slotNotValid; },
|
||||||
|
slotNotValidError: function() { return slotNotValidError; },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modalInstance.result.then(function(res) {
|
||||||
|
return paySlots();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return paySlots();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise we alert, this error musn't occur when the current user is not admin
|
// otherwise we alert, this error musn't occur when the current user is not admin
|
||||||
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
|
return growl.error(_t('app.shared.cart.please_select_a_member_first'));
|
||||||
@ -292,6 +327,27 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
|||||||
*/
|
*/
|
||||||
var slotSelectionChanged = function () {
|
var slotSelectionChanged = function () {
|
||||||
if ($scope.slot) {
|
if ($scope.slot) {
|
||||||
|
// build a list of plans if this slot is restricted for subscriptions
|
||||||
|
if ($scope.slot.plan_ids.length > 0) {
|
||||||
|
const _plans = _.filter($scope.plans, function (p) { return _.include($scope.slot.plan_ids, p.id) });
|
||||||
|
$scope.slot.plansGrouped = [];
|
||||||
|
$scope.slot.group_ids = [];
|
||||||
|
for (let group of Array.from($scope.groups)) {
|
||||||
|
const groupObj = { id: group.id, name: group.name, plans: [] };
|
||||||
|
for (let plan of Array.from(_plans)) {
|
||||||
|
if (plan.group_id === group.id) { groupObj.plans.push(plan); }
|
||||||
|
}
|
||||||
|
if (groupObj.plans.length > 0) {
|
||||||
|
if ($scope.isAdmin()) {
|
||||||
|
$scope.slot.plansGrouped.push(groupObj);
|
||||||
|
} else if ($scope.user.group_id === groupObj.id) {
|
||||||
|
$scope.slot.plansGrouped.push(groupObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.slot.group_ids = $scope.slot.plansGrouped.map(function(g) { return g.id; });
|
||||||
|
}
|
||||||
|
|
||||||
if (!$scope.slot.is_reserved && !$scope.events.modifiable && !$scope.slot.is_completed) {
|
if (!$scope.slot.is_reserved && !$scope.events.modifiable && !$scope.slot.is_completed) {
|
||||||
// slot is not reserved and we are not currently modifying a slot
|
// slot is not reserved and we are not currently modifying a slot
|
||||||
// -> can be added to cart or removed if already present
|
// -> can be added to cart or removed if already present
|
||||||
@ -638,6 +694,28 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
|
|||||||
return $scope.selectedPlan = null;
|
return $scope.selectedPlan = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to pay slots
|
||||||
|
*/
|
||||||
|
var paySlots = function() {
|
||||||
|
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
|
||||||
|
|
||||||
|
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
|
||||||
|
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
|
||||||
|
if (!$scope.isAdmin() && (amountToPay > 0)) {
|
||||||
|
if ($rootScope.fablabWithoutOnlinePayment) {
|
||||||
|
growl.error(_t('app.shared.cart.online_payment_disabled'));
|
||||||
|
} else {
|
||||||
|
return payByStripe(reservation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($scope.isAdmin() || (amountToPay === 0)) {
|
||||||
|
return payOnSite(reservation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// !!! MUST BE CALLED AT THE END of the directive
|
// !!! MUST BE CALLED AT THE END of the directive
|
||||||
return initialize();
|
return initialize();
|
||||||
}
|
}
|
||||||
@ -666,3 +744,25 @@ Application.Controllers.controller('ReserveSlotSameTimeController', ['$scope', '
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller used to alert admin reserve slot without plan
|
||||||
|
*/
|
||||||
|
Application.Controllers.controller('ReserveSlotWithoutPlanController', ['$scope', '$uibModalInstance', 'slot', 'slotNotValidError', 'growl', '_t',
|
||||||
|
function ($scope, $uibModalInstance, slot, slotNotValidError, growl, _t) {
|
||||||
|
$scope.slot = slot;
|
||||||
|
$scope.slotNotValidError = slotNotValidError;
|
||||||
|
/**
|
||||||
|
* Confirmation callback
|
||||||
|
*/
|
||||||
|
$scope.ok = function () {
|
||||||
|
$uibModalInstance.close({});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Cancellation callback
|
||||||
|
*/
|
||||||
|
$scope.cancel = function () {
|
||||||
|
$uibModalInstance.dismiss('cancel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
@ -584,7 +584,9 @@ angular.module('application.router', ['ui.router'])
|
|||||||
resolve: {
|
resolve: {
|
||||||
bookingWindowStart: ['Setting', function (Setting) { return Setting.get({ name: 'booking_window_start' }).$promise; }],
|
bookingWindowStart: ['Setting', function (Setting) { return Setting.get({ name: 'booking_window_start' }).$promise; }],
|
||||||
bookingWindowEnd: ['Setting', function (Setting) { return Setting.get({ name: 'booking_window_end' }).$promise; }],
|
bookingWindowEnd: ['Setting', function (Setting) { return Setting.get({ name: 'booking_window_end' }).$promise; }],
|
||||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }]
|
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||||
|
plansPromise: ['Plan', function (Plan) { return Plan.query().$promise; }],
|
||||||
|
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('app.admin.calendar.icalendar', {
|
.state('app.admin.calendar.icalendar', {
|
||||||
|
@ -90,6 +90,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="widget panel b-a m m-t-lg" ng-if="availability.plan_ids.length > 0">
|
||||||
|
<div class="panel-heading b-b small">
|
||||||
|
<h3 translate>{{ 'app.admin.calendar.plans' }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="widget-content no-bg auto wrapper">
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li ng-repeat="g in availability.plans" class="m-b-xs">
|
||||||
|
<div class="font-sbold">{{::g.name}}</div>
|
||||||
|
<ul class="m-n" ng-repeat="plan in g.plans">
|
||||||
|
<li>
|
||||||
|
{{::plan.name}}
|
||||||
|
<span class="btn btn-warning btn-xs" ng-click="removePlan(plan)" ><i class="fa fa-times red"></i></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="widget panel b-a m m-t-lg" ng-if="availability" >
|
<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>{{ 'app.admin.calendar.actions' }}</h3>
|
<h3 translate>{{ 'app.admin.calendar.actions' }}</h3>
|
||||||
|
@ -102,12 +102,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="m-t-sm">
|
||||||
|
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.restrict_this_slot_for_subscriptions_optional' }}</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="is_only_subscriptions" translate>{{ 'app.admin.calendar.enabled' }}</label>
|
||||||
|
<input bs-switch
|
||||||
|
ng-model="isOnlySubscriptions"
|
||||||
|
id="is_only_subscriptions"
|
||||||
|
type="checkbox"
|
||||||
|
class="form-control"
|
||||||
|
switch-on-text="{{ 'app.shared.buttons.yes' | translate }}"
|
||||||
|
switch-off-text="{{ 'app.shared.buttons.no' | translate }}"
|
||||||
|
switch-animate="true"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12" ng-show="isOnlySubscriptions">
|
||||||
|
<p class="font-sbold m-t-sm">{{ 'app.admin.calendar.select_some_plans' | translate }}</p>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-default pull-right m-t-n-xl" ng-click="toggleAllPlans()" translate>{{ selectedPlans.length == 0 ? 'app.admin.calendar.select_all' : 'app.admin.calendar.select_none' }}</button>
|
||||||
|
<div ng-repeat="group in plansClassifiedByGroup">
|
||||||
|
<div class="text-center font-sbold">{{::group.name}}</div>
|
||||||
|
<label class="checkbox m-l-md" ng-repeat="plan in group.plans">
|
||||||
|
<input type="checkbox" ng-click="toggleSelectPlan(plan)" ng-model="selectedPlansBinding[plan.id]"> {{::plan.name}}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body m-h" ng-show="step === 4">
|
<div class="modal-body m-h" ng-show="step === 4">
|
||||||
<div class="m-t-sm">
|
<div class="m-t-sm">
|
||||||
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.recurrence' }}</p>
|
<p class="text-center font-sbold" translate>{{ 'app.admin.calendar.recurrence' }}</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group">
|
<div class="form-group col-md-12">
|
||||||
<label for="is_recurrent" translate>{{ 'app.admin.calendar.enabled' }}</label>
|
<label for="is_recurrent" translate>{{ 'app.admin.calendar.enabled' }}</label>
|
||||||
<input bs-switch
|
<input bs-switch
|
||||||
ng-model="availability.is_recurrent"
|
ng-model="availability.is_recurrent"
|
||||||
@ -177,6 +206,10 @@
|
|||||||
<span class="underline" translate>{{ 'app.admin.calendar.labels' }}</span>
|
<span class="underline" translate>{{ 'app.admin.calendar.labels' }}</span>
|
||||||
<span ng-bind-html="tagsName"></span>
|
<span ng-bind-html="tagsName"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="m-t" ng-show="isOnlySubscriptions">
|
||||||
|
<span class="underline" translate>{{ 'app.admin.calendar.plans' }}</span>
|
||||||
|
<span ng-bind-html="plansName"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
plan="selectedPlan"
|
plan="selectedPlan"
|
||||||
plan-selection-time="planSelectionTime"
|
plan-selection-time="planSelectionTime"
|
||||||
settings="settings"
|
settings="settings"
|
||||||
|
plans="plans"
|
||||||
|
groups="groups"
|
||||||
on-slot-added-to-cart="markSlotAsAdded"
|
on-slot-added-to-cart="markSlotAsAdded"
|
||||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||||
on-slot-start-to-modify="markSlotAsModifying"
|
on-slot-start-to-modify="markSlotAsModifying"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l ">
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l ">
|
||||||
<section class="heading-title">
|
<section class="heading-title">
|
||||||
<h1 translate>{{ 'app.public.plans.subcriptions' }}</h1>
|
<h1 translate>{{ 'app.public.plans.subscriptions' }}</h1>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,8 +28,22 @@
|
|||||||
switch-animate="true"
|
switch-animate="true"
|
||||||
switch-readonly="{{slot.isValid}}"/>
|
switch-readonly="{{slot.isValid}}"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="alert alert-warning m-t-sm m-b-none" ng-show="slot.plan_ids.length > 0">
|
||||||
|
<div ng-show="slot.plansGrouped.length > 0">
|
||||||
|
<div class="font-sbold text-u-c" translate>{{ 'app.shared.cart.slot_restrict_plans' }}</div>
|
||||||
|
<div ng-repeat="group in slot.plansGrouped">
|
||||||
|
<div class="font-sbold">{{::group.name}}</div>
|
||||||
|
<ul class="m-n" ng-repeat="plan in group.plans">
|
||||||
|
<li>{{::plan.name}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ng-show="slot.plansGrouped.length === 0">
|
||||||
|
<div class="font-sbold text-u-c" translate>{{ 'app.shared.cart.slot_restrict_plans_of_others_groups' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div ng-hide="slot.plan_ids.length > 0 && slot.plansGrouped.length === 0">
|
||||||
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validateSlot(slot)" ng-if="!slot.isValid" translate>{{ 'app.shared.cart.confirm_this_slot' }}</button>
|
<button class="btn btn-valid btn-warning btn-block text-u-c r-b" ng-click="validateSlot(slot)" ng-if="!slot.isValid" translate>{{ 'app.shared.cart.confirm_this_slot' }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
22
app/assets/templates/shared/_reserve_slot_without_plan.html
Normal file
22
app/assets/templates/shared/_reserve_slot_without_plan.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<img ng-src="{{logoBlack.custom_asset_file_attributes.attachment_url}}" alt="{{logo.custom_asset_file_attributes.attachment}}" class="modal-logo"/>
|
||||||
|
<h1 ng-show="slotNotValidError === 'selectedPlanError'" translate>{{ 'app.shared.cart.selected_plan_dont_match_slot' }}</h1>
|
||||||
|
<h1 ng-show="slotNotValidError === 'userPlanError'" translate>{{ 'app.shared.cart.user_plan_dont_match_slot' }}</h1>
|
||||||
|
<h1 ng-show="slotNotValidError === 'noPlanError'" translate>{{ 'app.shared.cart.no_plan_match_slot' }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="font-sbold text-u-c">{{ 'app.shared.cart.datetime_to_time' | translate:{START_DATETIME:(slot.start | amDateFormat:'LLLL'), END_TIME:(slot.end | amDateFormat:'LT') } }}</div>
|
||||||
|
<div class="alert alert-warning m-t-sm m-b-none" ng-show="slot.plan_ids.length > 0">
|
||||||
|
<div class="font-sbold text-u-c" translate>{{ 'app.shared.cart.slot_restrict_plans' }}</div>
|
||||||
|
<div ng-repeat="group in slot.plansGrouped">
|
||||||
|
<div class="font-sbold">{{::group.name}}</div>
|
||||||
|
<ul class="m-n" ng-repeat="plan in group.plans">
|
||||||
|
<li>{{::plan.name}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-info" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||||
|
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||||
|
</div>
|
@ -35,6 +35,8 @@
|
|||||||
plan="selectedPlan"
|
plan="selectedPlan"
|
||||||
plan-selection-time="planSelectionTime"
|
plan-selection-time="planSelectionTime"
|
||||||
settings="settings"
|
settings="settings"
|
||||||
|
plans="plans"
|
||||||
|
groups="groups"
|
||||||
on-slot-added-to-cart="markSlotAsAdded"
|
on-slot-added-to-cart="markSlotAsAdded"
|
||||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||||
on-slot-start-to-modify="markSlotAsModifying"
|
on-slot-start-to-modify="markSlotAsModifying"
|
||||||
|
@ -47,6 +47,8 @@
|
|||||||
plan="selectedPlan"
|
plan="selectedPlan"
|
||||||
plan-selection-time="planSelectionTime"
|
plan-selection-time="planSelectionTime"
|
||||||
settings="settings"
|
settings="settings"
|
||||||
|
plans="plans"
|
||||||
|
groups="groups"
|
||||||
on-slot-added-to-cart="markSlotAsAdded"
|
on-slot-added-to-cart="markSlotAsAdded"
|
||||||
on-slot-removed-from-cart="markSlotAsRemoved"
|
on-slot-removed-from-cart="markSlotAsRemoved"
|
||||||
on-slot-start-to-modify="markSlotAsModifying"
|
on-slot-start-to-modify="markSlotAsModifying"
|
||||||
|
@ -148,8 +148,8 @@ 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,
|
||||||
machine_ids: [], training_ids: [], space_ids: [], tag_ids: [],
|
machine_ids: [], training_ids: [], space_ids: [], tag_ids: [], plan_ids: [],
|
||||||
machines_attributes: %i[id _destroy])
|
machines_attributes: %i[id _destroy], plans_attributes: %i[id _destroy])
|
||||||
end
|
end
|
||||||
|
|
||||||
def lock_params
|
def lock_params
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
# Eg. a 3D printer will be reservable on thursday from 9 to 11 pm
|
# Eg. a 3D printer will be reservable on thursday from 9 to 11 pm
|
||||||
# Availabilities may be subdivided into Slots (of 1h), for some types of reservables (eg. Machine)
|
# Availabilities may be subdivided into Slots (of 1h), for some types of reservables (eg. Machine)
|
||||||
class Availability < ActiveRecord::Base
|
class Availability < ActiveRecord::Base
|
||||||
|
|
||||||
# elastic initialisations
|
# elastic initialisations
|
||||||
include Elasticsearch::Model
|
include Elasticsearch::Model
|
||||||
index_name 'fablab'
|
index_name 'fablab'
|
||||||
@ -29,6 +28,10 @@ class Availability < ActiveRecord::Base
|
|||||||
has_many :tags, through: :availability_tags
|
has_many :tags, through: :availability_tags
|
||||||
accepts_nested_attributes_for :tags, allow_destroy: true
|
accepts_nested_attributes_for :tags, allow_destroy: true
|
||||||
|
|
||||||
|
has_many :plans_availabilities, dependent: :destroy
|
||||||
|
has_many :plans, through: :plans_availabilities
|
||||||
|
accepts_nested_attributes_for :plans, allow_destroy: true
|
||||||
|
|
||||||
scope :machines, -> { where(available_type: 'machines') }
|
scope :machines, -> { where(available_type: 'machines') }
|
||||||
scope :trainings, -> { includes(:trainings).where(available_type: 'training') }
|
scope :trainings, -> { includes(:trainings).where(available_type: 'training') }
|
||||||
scope :spaces, -> { includes(:spaces).where(available_type: 'space') }
|
scope :spaces, -> { includes(:spaces).where(available_type: 'space') }
|
||||||
@ -86,7 +89,7 @@ class Availability < ActiveRecord::Base
|
|||||||
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
|
((end_at - start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i * nb_total_places
|
||||||
end
|
end
|
||||||
|
|
||||||
def title(filter = {})
|
def title(filter = {})
|
||||||
@ -166,5 +169,4 @@ class Availability < ActiveRecord::Base
|
|||||||
|
|
||||||
errors.add(:machine_ids, I18n.t('availabilities.must_be_associated_with_at_least_1_machine'))
|
errors.add(:machine_ids, I18n.t('availabilities.must_be_associated_with_at_least_1_machine'))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -25,7 +25,6 @@ class Plan < ActiveRecord::Base
|
|||||||
after_create :create_statistic_type
|
after_create :create_statistic_type
|
||||||
after_create :set_name
|
after_create :set_name
|
||||||
|
|
||||||
|
|
||||||
validates :amount, :group, :base_name, presence: true
|
validates :amount, :group, :base_name, presence: true
|
||||||
validates :interval_count, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
|
validates :interval_count, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
|
||||||
validates :interval_count, numericality: { less_than: 13 }, if: proc { |plan| plan.interval == 'month' }
|
validates :interval_count, numericality: { less_than: 13 }, if: proc { |plan| plan.interval == 'month' }
|
||||||
|
6
app/models/plans_availability.rb
Normal file
6
app/models/plans_availability.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PlansAvailability < ActiveRecord::Base
|
||||||
|
belongs_to :plan
|
||||||
|
belongs_to :availability
|
||||||
|
end
|
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Reservation < ActiveRecord::Base
|
class Reservation < ActiveRecord::Base
|
||||||
include NotifyWith::NotificationAttachedObject
|
include NotifyWith::NotificationAttachedObject
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ class Reservation < ActiveRecord::Base
|
|||||||
validates_presence_of :reservable_id, :reservable_type
|
validates_presence_of :reservable_id, :reservable_type
|
||||||
validate :machine_not_already_reserved, if: -> { reservable.is_a?(Machine) }
|
validate :machine_not_already_reserved, if: -> { reservable.is_a?(Machine) }
|
||||||
validate :training_not_fully_reserved, if: -> { reservable.is_a?(Training) }
|
validate :training_not_fully_reserved, if: -> { reservable.is_a?(Training) }
|
||||||
|
validates_with ReservationSlotSubscriptionValidator
|
||||||
|
|
||||||
attr_accessor :plan_id, :subscription
|
attr_accessor :plan_id, :subscription
|
||||||
|
|
||||||
|
15
app/validators/reservation_slot_subscription_validator.rb
Normal file
15
app/validators/reservation_slot_subscription_validator.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ReservationSlotSubscriptionValidator < ActiveModel::Validator
|
||||||
|
def validate(record)
|
||||||
|
record.slots.each do |s|
|
||||||
|
unless s.availability.plan_ids.empty?
|
||||||
|
if record.user.subscribed_plan && s.availability.plan_ids.include?(record.user.subscribed_plan.id)
|
||||||
|
elsif s.availability.plan_ids.include?(record.plan_id)
|
||||||
|
else
|
||||||
|
record.errors[:slots] << 'slot is restrict for subscriptions'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.array!(@availabilities) do |availability|
|
json.array!(@availabilities) do |availability|
|
||||||
json.id availability.id
|
json.id availability.id
|
||||||
json.title availability.title
|
json.title availability.title
|
||||||
@ -15,4 +17,5 @@ json.array!(@availabilities) do |availability|
|
|||||||
json.name t.name
|
json.name t.name
|
||||||
end
|
end
|
||||||
json.lock availability.lock
|
json.lock availability.lock
|
||||||
|
json.plan_ids availability.plan_ids
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.array!(@slots) do |slot|
|
json.array!(@slots) do |slot|
|
||||||
json.id slot.id if slot.id
|
json.id slot.id if slot.id
|
||||||
json.can_modify slot.can_modify
|
json.can_modify slot.can_modify
|
||||||
@ -14,13 +16,16 @@ json.array!(@slots) do |slot|
|
|||||||
json.name slot.machine.name
|
json.name slot.machine.name
|
||||||
end
|
end
|
||||||
# the user who booked the slot ...
|
# the user who booked the slot ...
|
||||||
json.user do
|
if (@current_user_role == 'admin') && slot.reservation
|
||||||
json.id slot.reservation.user.id
|
json.user do
|
||||||
json.name slot.reservation.user.profile.full_name
|
json.id slot.reservation.user.id
|
||||||
end if @current_user_role == 'admin' and slot.reservation # ... if the slot was reserved
|
json.name slot.reservation.user.profile.full_name
|
||||||
|
end
|
||||||
|
end # ... if the slot was reserved
|
||||||
json.tag_ids slot.availability.tag_ids
|
json.tag_ids slot.availability.tag_ids
|
||||||
json.tags slot.availability.tags do |t|
|
json.tags slot.availability.tags do |t|
|
||||||
json.id t.id
|
json.id t.id
|
||||||
json.name t.name
|
json.name t.name
|
||||||
end
|
end
|
||||||
|
json.plan_ids slot.availability.plan_ids
|
||||||
end
|
end
|
||||||
|
@ -5,10 +5,11 @@ json.start_at @availability.start_at.iso8601
|
|||||||
json.end_at @availability.end_at.iso8601
|
json.end_at @availability.end_at.iso8601
|
||||||
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.backgroundColor 'white'
|
json.backgroundColor 'white'
|
||||||
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
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.array!(@slots) do |slot|
|
json.array!(@slots) do |slot|
|
||||||
json.id slot.id if slot.id
|
json.id slot.id if slot.id
|
||||||
json.can_modify slot.can_modify
|
json.can_modify slot.can_modify
|
||||||
@ -15,13 +17,16 @@ json.array!(@slots) do |slot|
|
|||||||
json.name slot.space.name
|
json.name slot.space.name
|
||||||
end
|
end
|
||||||
# the user who booked the slot ...
|
# the user who booked the slot ...
|
||||||
json.user do
|
if (@current_user_role == 'admin') && slot.reservation
|
||||||
json.id slot.reservation.user.id
|
json.user do
|
||||||
json.name slot.reservation.user.profile.full_name
|
json.id slot.reservation.user.id
|
||||||
end if @current_user_role == 'admin' and slot.reservation # ... if the slot was reserved
|
json.name slot.reservation.user.profile.full_name
|
||||||
|
end
|
||||||
|
end # ... if the slot was reserved
|
||||||
json.tag_ids slot.availability.tag_ids
|
json.tag_ids slot.availability.tag_ids
|
||||||
json.tags slot.availability.tags do |t|
|
json.tags slot.availability.tags do |t|
|
||||||
json.id t.id
|
json.id t.id
|
||||||
json.name t.name
|
json.name t.name
|
||||||
end
|
end
|
||||||
|
json.plan_ids slot.availability.plan_ids
|
||||||
end
|
end
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
json.array!(@availabilities) do |a|
|
json.array!(@availabilities) do |a|
|
||||||
json.id a.slot_id if a.slot_id
|
json.id a.slot_id if a.slot_id
|
||||||
if a.is_reserved
|
if a.is_reserved
|
||||||
@ -31,4 +33,5 @@ json.array!(@availabilities) do |a|
|
|||||||
json.id t.id
|
json.id t.id
|
||||||
json.name t.name
|
json.name t.name
|
||||||
end
|
end
|
||||||
|
json.plan_ids a.plan_ids
|
||||||
end
|
end
|
||||||
|
@ -32,6 +32,8 @@ en:
|
|||||||
beware_this_cannot_be_reverted: "Beware: this cannot be reverted."
|
beware_this_cannot_be_reverted: "Beware: this cannot be reverted."
|
||||||
the_machine_was_successfully_removed_from_the_slot: "The machine was successfully removed from the slot."
|
the_machine_was_successfully_removed_from_the_slot: "The machine was successfully removed from the slot."
|
||||||
deletion_failed: "Deletion failed."
|
deletion_failed: "Deletion failed."
|
||||||
|
do_you_really_want_to_remove_PLAN_from_this_slot: "Do you really want to remove \"{PLAN}\" from this slot?"
|
||||||
|
the_plan_was_successfully_removed_from_the_slot: "The plan was successfully removed from the slot."
|
||||||
DATE_slot: "{DATE} slot:"
|
DATE_slot: "{DATE} slot:"
|
||||||
what_kind_of_slot_do_you_want_to_create: "What kind of slot do you want to create?"
|
what_kind_of_slot_do_you_want_to_create: "What kind of slot do you want to create?"
|
||||||
training: "Training"
|
training: "Training"
|
||||||
@ -49,6 +51,9 @@ en:
|
|||||||
adjust_the_opening_hours: "Adjust the opening hours"
|
adjust_the_opening_hours: "Adjust the opening hours"
|
||||||
to_time: "to" # eg. from 18:00 to 21:00
|
to_time: "to" # eg. from 18:00 to 21:00
|
||||||
restrict_this_slot_with_labels_optional: "Restrict this slot with labels (optional)"
|
restrict_this_slot_with_labels_optional: "Restrict this slot with labels (optional)"
|
||||||
|
restrict_this_slot_for_subscriptions_optional: "Restrict this slot for subscription users (optional)"
|
||||||
|
select_some_plans: "Select some plans"
|
||||||
|
plans: "Plan(s):"
|
||||||
recurrence: "Recurrence"
|
recurrence: "Recurrence"
|
||||||
enabled: "Enabled"
|
enabled: "Enabled"
|
||||||
period: "Period"
|
period: "Period"
|
||||||
|
@ -32,6 +32,8 @@ es:
|
|||||||
beware_this_cannot_be_reverted: "Beware: esto no puede ser revertido."
|
beware_this_cannot_be_reverted: "Beware: esto no puede ser revertido."
|
||||||
the_machine_was_successfully_removed_from_the_slot: "La máquina se eliminó correctamente de la ranura."
|
the_machine_was_successfully_removed_from_the_slot: "La máquina se eliminó correctamente de la ranura."
|
||||||
deletion_failed: "Fallo al borrar."
|
deletion_failed: "Fallo al borrar."
|
||||||
|
do_you_really_want_to_remove_PLAN_from_this_slot: "Do you really want to remove \"{PLAN}\" from this slot?"
|
||||||
|
the_plan_was_successfully_removed_from_the_slot: "The plan was successfully removed from the slot."
|
||||||
DATE_slot: "{DATE} espacio:"
|
DATE_slot: "{DATE} espacio:"
|
||||||
what_kind_of_slot_do_you_want_to_create: "¿Qué tipo de horario desea crear?"
|
what_kind_of_slot_do_you_want_to_create: "¿Qué tipo de horario desea crear?"
|
||||||
training: "Formación"
|
training: "Formación"
|
||||||
@ -49,6 +51,9 @@ es:
|
|||||||
adjust_the_opening_hours: "Ajustar el horario de apertura"
|
adjust_the_opening_hours: "Ajustar el horario de apertura"
|
||||||
to_time: a
|
to_time: a
|
||||||
restrict_this_slot_with_labels_optional: "Restringir este horario con etiquetas (opcional)"
|
restrict_this_slot_with_labels_optional: "Restringir este horario con etiquetas (opcional)"
|
||||||
|
restrict_this_slot_for_subscriptions_optional: "Restrict this slot for subscription users (optional)"
|
||||||
|
select_some_plans: "Select some plans"
|
||||||
|
plans: "Plan(s):"
|
||||||
recurrence: "Recurrencia"
|
recurrence: "Recurrencia"
|
||||||
enabled: "Activa"
|
enabled: "Activa"
|
||||||
period: "Período"
|
period: "Período"
|
||||||
|
@ -32,6 +32,8 @@ fr:
|
|||||||
beware_this_cannot_be_reverted: "Attention : ceci n'est pas réversible."
|
beware_this_cannot_be_reverted: "Attention : ceci n'est pas réversible."
|
||||||
the_machine_was_successfully_removed_from_the_slot: "La machine a bien été supprimée du créneau."
|
the_machine_was_successfully_removed_from_the_slot: "La machine a bien été supprimée du créneau."
|
||||||
deletion_failed: "La suppression a échouée."
|
deletion_failed: "La suppression a échouée."
|
||||||
|
do_you_really_want_to_remove_PLAN_from_this_slot: "Êtes-vous {GENDER, select, female{sûre} other{sûr}} de vouloir retirer \"{PLAN}\" de ce créneau ?"
|
||||||
|
the_plan_was_successfully_removed_from_the_slot: "Le formule d'abonnement a bien été supprimée du créneau."
|
||||||
DATE_slot: "Créneau du {DATE} :"
|
DATE_slot: "Créneau du {DATE} :"
|
||||||
what_kind_of_slot_do_you_want_to_create: "Quel type de créneau voulez-vous créer ?"
|
what_kind_of_slot_do_you_want_to_create: "Quel type de créneau voulez-vous créer ?"
|
||||||
training: "Formation"
|
training: "Formation"
|
||||||
@ -49,6 +51,9 @@ fr:
|
|||||||
adjust_the_opening_hours: "Ajuster l'horaire"
|
adjust_the_opening_hours: "Ajuster l'horaire"
|
||||||
to_time: "à" # eg. from 18:00 to 21:00
|
to_time: "à" # eg. from 18:00 to 21:00
|
||||||
restrict_this_slot_with_labels_optional: "Restreindre ce créneau avec des étiquettes (optionnel)"
|
restrict_this_slot_with_labels_optional: "Restreindre ce créneau avec des étiquettes (optionnel)"
|
||||||
|
restrict_this_slot_for_subscriptions_optional: "Restreindre ce créneau pour les abonnements (optionnel)"
|
||||||
|
select_some_plans: "Sélectionnez des formules d'abonnement"
|
||||||
|
plans: "Abonnement(s):"
|
||||||
recurrence: "Récurrence"
|
recurrence: "Récurrence"
|
||||||
enabled: "Activée"
|
enabled: "Activée"
|
||||||
period: "Période"
|
period: "Période"
|
||||||
|
@ -32,6 +32,8 @@ pt:
|
|||||||
beware_this_cannot_be_reverted: "Cuidado: isso não pode ser revertido."
|
beware_this_cannot_be_reverted: "Cuidado: isso não pode ser revertido."
|
||||||
the_machine_was_successfully_removed_from_the_slot: "A máquina foi removida com sucesso desse slot."
|
the_machine_was_successfully_removed_from_the_slot: "A máquina foi removida com sucesso desse slot."
|
||||||
deletion_failed: "Falha ao deletar."
|
deletion_failed: "Falha ao deletar."
|
||||||
|
do_you_really_want_to_remove_PLAN_from_this_slot: "Do you really want to remove \"{PLAN}\" from this slot?"
|
||||||
|
the_plan_was_successfully_removed_from_the_slot: "The plan was successfully removed from the slot."
|
||||||
DATE_slot: "{DATE} slot:"
|
DATE_slot: "{DATE} slot:"
|
||||||
what_kind_of_slot_do_you_want_to_create: "Qual tipo de slot você deseja criar?"
|
what_kind_of_slot_do_you_want_to_create: "Qual tipo de slot você deseja criar?"
|
||||||
training: "Treinamento"
|
training: "Treinamento"
|
||||||
@ -49,6 +51,9 @@ pt:
|
|||||||
adjust_the_opening_hours: "Ajustar o horário de funcionamento"
|
adjust_the_opening_hours: "Ajustar o horário de funcionamento"
|
||||||
to_time: "ás" # eg. from 18:00 to 21:00
|
to_time: "ás" # eg. from 18:00 to 21:00
|
||||||
restrict_this_slot_with_labels_optional: "Restrinja este slot com etiquetas (opcional)"
|
restrict_this_slot_with_labels_optional: "Restrinja este slot com etiquetas (opcional)"
|
||||||
|
restrict_this_slot_for_subscriptions_optional: "Restrict this slot for subscription users (optional)"
|
||||||
|
select_some_plans: "Select some plans"
|
||||||
|
plans: "Plan(s):"
|
||||||
recurrence: "Recurrence"
|
recurrence: "Recurrence"
|
||||||
enabled: "Enabled"
|
enabled: "Enabled"
|
||||||
period: "Period"
|
period: "Period"
|
||||||
|
@ -229,7 +229,7 @@ ach:
|
|||||||
the_training_cant_be_deleted_because_it_is_already_reserved_by_some_users: "crwdns9167:0crwdne9167:0"
|
the_training_cant_be_deleted_because_it_is_already_reserved_by_some_users: "crwdns9167:0crwdne9167:0"
|
||||||
plans:
|
plans:
|
||||||
#summary of the subscriptions
|
#summary of the subscriptions
|
||||||
subcriptions: "crwdns9169:0crwdne9169:0"
|
subscriptions: "crwdns9169:0crwdne9169:0"
|
||||||
i_choose_that_plan: "crwdns9171:0crwdne9171:0"
|
i_choose_that_plan: "crwdns9171:0crwdne9171:0"
|
||||||
i_subscribe_online: "crwdns9173:0crwdne9173:0"
|
i_subscribe_online: "crwdns9173:0crwdne9173:0"
|
||||||
i_already_subscribed: "crwdns9175:0crwdne9175:0"
|
i_already_subscribed: "crwdns9175:0crwdne9175:0"
|
||||||
|
@ -257,7 +257,7 @@ en:
|
|||||||
|
|
||||||
plans:
|
plans:
|
||||||
# summary of the subscriptions
|
# summary of the subscriptions
|
||||||
subcriptions: "Subscriptions"
|
subscriptions: "Subscriptions"
|
||||||
i_choose_that_plan: "I choose that plan"
|
i_choose_that_plan: "I choose that plan"
|
||||||
i_subscribe_online: "I subscribe online"
|
i_subscribe_online: "I subscribe online"
|
||||||
i_already_subscribed: "I already subscribed"
|
i_already_subscribed: "I already subscribed"
|
||||||
|
@ -257,7 +257,7 @@ es:
|
|||||||
|
|
||||||
plans:
|
plans:
|
||||||
# summary of the subscriptions
|
# summary of the subscriptions
|
||||||
subcriptions: "Suscripciones"
|
subscriptions: "Suscripciones"
|
||||||
i_choose_that_plan: "Elijo este plan"
|
i_choose_that_plan: "Elijo este plan"
|
||||||
i_subscribe_online: "Suscribirme online"
|
i_subscribe_online: "Suscribirme online"
|
||||||
i_already_subscribed: "Ya me he suscrito"
|
i_already_subscribed: "Ya me he suscrito"
|
||||||
|
@ -257,7 +257,7 @@ fr:
|
|||||||
|
|
||||||
plans:
|
plans:
|
||||||
# page récapitulative des abonnements
|
# page récapitulative des abonnements
|
||||||
subcriptions: "Les abonnements"
|
subscriptions: "Les abonnements"
|
||||||
i_choose_that_plan: "Je choisis cette formule"
|
i_choose_that_plan: "Je choisis cette formule"
|
||||||
i_subscribe_online: "Je m'abonne en ligne"
|
i_subscribe_online: "Je m'abonne en ligne"
|
||||||
i_already_subscribed: "Je suis déjà abonné"
|
i_already_subscribed: "Je suis déjà abonné"
|
||||||
|
@ -257,7 +257,7 @@ pt:
|
|||||||
|
|
||||||
plans:
|
plans:
|
||||||
# summary of the subscriptions
|
# summary of the subscriptions
|
||||||
subcriptions: "Assinaturas"
|
subscriptions: "Assinaturas"
|
||||||
i_choose_that_plan: "Eu escolho esse plano"
|
i_choose_that_plan: "Eu escolho esse plano"
|
||||||
i_subscribe_online: "Me inscrever online"
|
i_subscribe_online: "Me inscrever online"
|
||||||
i_already_subscribed: "Eu já estou inscrito"
|
i_already_subscribed: "Eu já estou inscrito"
|
||||||
|
@ -439,6 +439,12 @@ en:
|
|||||||
a_problem_occurred_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later."
|
a_problem_occurred_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later."
|
||||||
none: "None"
|
none: "None"
|
||||||
online_payment_disabled: "Online payment is not available. Please contact the Fablab reception directly."
|
online_payment_disabled: "Online payment is not available. Please contact the Fablab reception directly."
|
||||||
|
slot_restrict_plans: "This slot is restricted for the plans below:"
|
||||||
|
slot_restrict_subscriptions_must_select_plan: "The slot is restricted for the subscribers. Please select a plan first."
|
||||||
|
slot_restrict_plans_of_others_groups: "The slot is restricted for the subscribers of others groups."
|
||||||
|
selected_plan_dont_match_slot: "Selected plan dont match this slot"
|
||||||
|
user_plan_dont_match_slot: "User subscribed plan dont match this slot"
|
||||||
|
no_plan_match_slot: "You dont have any plan to match this slot"
|
||||||
slot_at_same_time: "Conflict with others reservations"
|
slot_at_same_time: "Conflict with others reservations"
|
||||||
do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time"
|
do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time"
|
||||||
unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time."
|
unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time."
|
||||||
|
@ -439,6 +439,12 @@ es:
|
|||||||
a_problem_occurred_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later."
|
a_problem_occurred_during_the_payment_process_please_try_again_later: "A problem occurred during the payment process. Please try again later."
|
||||||
none: "Ninguno"
|
none: "Ninguno"
|
||||||
online_payment_disabled: "El pago en línea no está disponible. Póngase en contacto directamente con la recepción de Fablab."
|
online_payment_disabled: "El pago en línea no está disponible. Póngase en contacto directamente con la recepción de Fablab."
|
||||||
|
slot_restrict_plans: "This slot is restricted for the plans below:"
|
||||||
|
slot_restrict_subscriptions_must_select_plan: "The slot is restricted for the subscribers. Please select a plan first."
|
||||||
|
slot_restrict_plans_of_others_groups: "The slot is restricted for the subscribers of others groups."
|
||||||
|
selected_plan_dont_match_slot: "Selected plan dont match this slot"
|
||||||
|
user_plan_dont_match_slot: "User subscribed plan dont match this slot"
|
||||||
|
no_plan_match_slot: "You dont have any plan to match this slot"
|
||||||
slot_at_same_time: "Conflict with others reservations"
|
slot_at_same_time: "Conflict with others reservations"
|
||||||
do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time"
|
do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time"
|
||||||
unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time."
|
unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time."
|
||||||
|
@ -439,6 +439,12 @@ fr:
|
|||||||
a_problem_occurred_during_the_payment_process_please_try_again_later: "Il y a eu un problème lors de la procédure de paiement. Veuillez réessayer plus tard."
|
a_problem_occurred_during_the_payment_process_please_try_again_later: "Il y a eu un problème lors de la procédure de paiement. Veuillez réessayer plus tard."
|
||||||
none: "Aucune"
|
none: "Aucune"
|
||||||
online_payment_disabled: "Le payment par carte bancaire n'est pas disponible. Merci de contacter directement l'accueil du Fablab."
|
online_payment_disabled: "Le payment par carte bancaire n'est pas disponible. Merci de contacter directement l'accueil du Fablab."
|
||||||
|
slot_restrict_plans: "Ce créneau est restreint pour les formules d'abonnement ci-desous:"
|
||||||
|
slot_restrict_subscriptions_must_select_plan: "Le créneau est restreint pour les abonnés. Veuillez tout d'abord sélectionner une formule d'abonnement"
|
||||||
|
slot_restrict_plans_of_others_groups: "Ce créneau est restreint pour les abonnés d'autres groupes."
|
||||||
|
selected_plan_dont_match_slot: "L'abonnement sélectionné ne correspondent pas ce créneau"
|
||||||
|
user_plan_dont_match_slot: "L'abonnement du membre ne correspondent pas ce créneau"
|
||||||
|
no_plan_match_slot: "Aucun abonnement correspondent ce créneau"
|
||||||
slot_at_same_time: "Conflit avec d'autres réservations"
|
slot_at_same_time: "Conflit avec d'autres réservations"
|
||||||
do_you_really_want_to_book_slot_at_same_time: "Êtes-vous sûr de réserver ce créneau ? D'autres réservations ont lieu en même temps"
|
do_you_really_want_to_book_slot_at_same_time: "Êtes-vous sûr de réserver ce créneau ? D'autres réservations ont lieu en même temps"
|
||||||
unable_to_book_slot_because_really_have_reservation_at_same_time: "Impossible de réserver ce créneau car les réservations ci-dessous ont lieu en même temps."
|
unable_to_book_slot_because_really_have_reservation_at_same_time: "Impossible de réserver ce créneau car les réservations ci-dessous ont lieu en même temps."
|
||||||
|
@ -439,6 +439,12 @@ pt:
|
|||||||
a_problem_occurred_during_the_payment_process_please_try_again_later: "Um problema ocorreu durante o processo de pagamento. Por favor tente novamente mais tarde."
|
a_problem_occurred_during_the_payment_process_please_try_again_later: "Um problema ocorreu durante o processo de pagamento. Por favor tente novamente mais tarde."
|
||||||
none: "Vazio"
|
none: "Vazio"
|
||||||
online_payment_disabled: "O pagamento online não está disponível. Entre em contato diretamente com a recepção do Fablab."
|
online_payment_disabled: "O pagamento online não está disponível. Entre em contato diretamente com a recepção do Fablab."
|
||||||
|
slot_restrict_plans: "This slot is restricted for the plans below:"
|
||||||
|
slot_restrict_subscriptions_must_select_plan: "The slot is restricted for the subscribers. Please select a plan first."
|
||||||
|
slot_restrict_plans_of_others_groups: "The slot is restricted for the subscribers of others groups."
|
||||||
|
selected_plan_dont_match_slot: "Selected plan dont match this slot"
|
||||||
|
user_plan_dont_match_slot: "User subscribed plan dont match this slot"
|
||||||
|
no_plan_match_slot: "You dont have any plan to match this slot"
|
||||||
slot_at_same_time: "Conflict with others reservations"
|
slot_at_same_time: "Conflict with others reservations"
|
||||||
do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time"
|
do_you_really_want_to_book_slot_at_same_time: "Do you really want to book this slot? Other bookings take place at the same time"
|
||||||
unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time."
|
unable_to_book_slot_because_really_have_reservation_at_same_time: "Unable to book this slot because the following reservation occurs at the same time."
|
||||||
|
10
db/migrate/20200206132857_create_plans_availabilities.rb
Normal file
10
db/migrate/20200206132857_create_plans_availabilities.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreatePlansAvailabilities < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :plans_availabilities do |t|
|
||||||
|
t.belongs_to :plan, index: true
|
||||||
|
t.belongs_to :availability, index: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -465,6 +465,14 @@ ActiveRecord::Schema.define(version: 20200218092221) do
|
|||||||
|
|
||||||
add_index "plans", ["group_id"], name: "index_plans_on_group_id", using: :btree
|
add_index "plans", ["group_id"], name: "index_plans_on_group_id", using: :btree
|
||||||
|
|
||||||
|
create_table "plans_availabilities", force: :cascade do |t|
|
||||||
|
t.integer "plan_id"
|
||||||
|
t.integer "availability_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "plans_availabilities", ["availability_id"], name: "index_plans_availabilities_on_availability_id", using: :btree
|
||||||
|
add_index "plans_availabilities", ["plan_id"], name: "index_plans_availabilities_on_plan_id", using: :btree
|
||||||
|
|
||||||
create_table "price_categories", force: :cascade do |t|
|
create_table "price_categories", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.text "conditions"
|
t.text "conditions"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user