diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57ebfbbb7..183d8b00c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
# Changelog Fab Manager
+- Ability to configure reservation slot restrict for plans
- Ability to create and delete periodic calendar availabilities (recurrence)
- Ability to fully customize the home page
- Automated setup assistant
@@ -140,7 +141,7 @@
## v4.0.4 2019 August 14
- Fix a bug: #140 VAT rate is erroneous in invoices.
- Note: this bug was introduced in v4.0.3 and requires (if you are on v4.0.3) to regenerate the invoices since August 1st (if
+ Note: this bug was introduced in v4.0.3 and requires (if you are on v4.0.3) to regenerate the invoices since August 1st (if
- [TODO DEPLOY] `rake fablab:maintenance:regenerate_invoices[2019,8]`
## v4.0.3 2019 August 01
@@ -184,7 +185,7 @@
- Refactored user's profile to keep invoicing data after an user was deleted
- Refactored user's profile to keep statistical data after an user was deleted
- Ability to delete an user (fixes #129 and #120)
-- Ask user acceptance before deposing analytics cookies
+- Ask user acceptance before deposing analytics cookies
- Fix a bug: (spanish) some translations are not loaded correctly
- Fix a bug: some users may not appear in the admin's general listing
- Fix a bug: Availabilities export report an erroneous number of reservations for machine availabilities (#131)
@@ -381,8 +382,8 @@
- Fix a security issue: sprockets < 2.12.5 has a security vulnerability as described in [CVE-2018-3760](https://nvd.nist.gov/vuln/detail/CVE-2018-3760)
- Ensure elasticSearch indices are started with green status on new installations
- Refactored User.to_json to remove code duplication
-- Fixed syntax and typos in README
-- [TODO DEPLOY] **IMPORTANT** Please read [elastic_upgrade.md](doc/elastic_upgrade.md) for instructions on upgrading ElasticSearch.
+- Fixed syntax and typos in README
+- [TODO DEPLOY] **IMPORTANT** Please read [elastic_upgrade.md](doc/elastic_upgrade.md) for instructions on upgrading ElasticSearch.
- [TODO DEPLOY] `rake fablab:fix:categories_slugs`
- [TODO DEPLOY] -> (only dev) `bundle install`
- [TODO DEPLOY] `rake db:seed`
@@ -395,7 +396,7 @@
- Set Stripe API version, all fab-managers has to use this version because codebase relies on it
- Fix a security issue: OmniAuth < 1.3.2 has a security vulnerability described in [CVE-2017-18076](https://nvd.nist.gov/vuln/detail/CVE-2017-18076)
- Fix a security issue: rack-protection < 1.5.5 has a security vulnerability described in [CVE-2018-1000119](https://nvd.nist.gov/vuln/detail/CVE-2018-1000119)
-- Fix a security issue: http gem < 0.7.3 has a security vulnerability described in [CVE-2015-1828](https://nvd.nist.gov/vuln/detail/CVE-2015-1828), updates twitter gem as a dependency
+- Fix a security issue: http gem < 0.7.3 has a security vulnerability described in [CVE-2015-1828](https://nvd.nist.gov/vuln/detail/CVE-2015-1828), updates twitter gem as a dependency
## v2.6.3 2018 January 2
@@ -451,12 +452,12 @@
## v2.5.13 2017 September 11
-- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with recursive events which the initial event was deleted
+- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with recursive events which the initial event was deleted
## v2.5.12 2017 September 11
- Fix a bug: Long words overflow from homepage's events blocks
-- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with non-recursive events
+- Fix a bug: ActiveRecord::RecordNotFound when running rake task fix:recursive_events_over_DST with non-recursive events
## v2.5.11 2017 September 7
diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb
index d822d5521..4244e7878 100644
--- a/app/assets/javascripts/controllers/admin/calendar.js.erb
+++ b/app/assets/javascripts/controllers/admin/calendar.js.erb
@@ -297,7 +297,9 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
trainingsPromise: ['Training', function (Training) { return Training.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
modalInstance.result.then(
@@ -394,8 +396,8 @@ Application.Controllers.controller('AdminCalendarController', ['$scope', '$state
/**
* 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',
- function ($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, plansPromise, groupsPromise, growl, _t) {
// $uibModal parameter
$scope.start = start;
@@ -414,6 +416,21 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
// all tags list
$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
$scope.selectedMachines = [];
$scope.selectedMachinesBinding = {};
@@ -463,6 +480,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
// localized name(s) of the selected tag(s)
$scope.tagsName = '';
+ // localized name(s) of the selected plan(s)
+ $scope.plansName = '';
+
/**
* Adds or removes the provided machine from the current slot
* @param machine {Object}
@@ -491,6 +511,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.selectedMachines.splice(index, 1);
+ } else {
+ return $scope.selectedMachines.push(machine);
+ }
+ };
+
+ /**
+ * 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
*/
@@ -510,6 +558,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
if ($scope.availability.is_recurrent) {
$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(
{ availability: $scope.availability },
function (availability) { $uibModalInstance.close(availability); }
@@ -560,6 +611,14 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
$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
// 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.
@@ -685,6 +744,9 @@ Application.Controllers.controller('CreateEventModalController', ['$scope', '$ui
return $scope.availability.tag_ids.indexOf(t.id) > -1;
})
$scope.tagsName = localizedList(tags);
+ if ($scope.isOnlySubscriptions && $scope.selectedPlans.length > 0) {
+ $scope.plansName = localizedList($scope.selectedPlans);
+ }
}
const localizedList = function (items) {
diff --git a/app/assets/javascripts/controllers/machines.js.erb b/app/assets/javascripts/controllers/machines.js.erb
index 733b1dca1..2ae5aebca 100644
--- a/app/assets/javascripts/controllers/machines.js.erb
+++ b/app/assets/javascripts/controllers/machines.js.erb
@@ -367,6 +367,8 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
$scope.settings = settingsPromise;
// list of plans, classified by group
+ $scope.groups = groupsPromise;
+ $scope.plans = plansPromise;
$scope.plansClassifiedByGroup = [];
for (let group of Array.from(groupsPromise)) {
const groupObj = { id: group.id, name: group.name, plans: [] };
diff --git a/app/assets/javascripts/directives/cart.js.erb b/app/assets/javascripts/directives/cart.js.erb
index ff2c4a810..cbe5a6980 100644
--- a/app/assets/javascripts/directives/cart.js.erb
+++ b/app/assets/javascripts/directives/cart.js.erb
@@ -23,6 +23,8 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
plan: '=',
planSelectionTime: '=',
settings: '=',
+ plans: '=',
+ groups: '=',
onSlotAddedToCart: '=',
onSlotRemovedFromCart: '=',
onSlotStartToModify: '=',
@@ -134,6 +136,27 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
$scope.payCart = function () {
// first, we check that a user was selected
if (Object.keys($scope.user).length > 0) {
+
+ // check user was selected a plan if slot is restricted for subcriptions
+ const slotHasPlan = [];
+ $scope.events.reserved.forEach(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))
+ ) {
+ slotHasPlan.push(true);
+ } else {
+ slotHasPlan.push(false);
+ }
+ }
+ });
+ const hasPlanForSlot = slotHasPlan.every(a => a);
+ if (!hasPlanForSlot) {
+ return growl.error(_t('app.shared.cart.slot_restrict_subcriptions_must_select_plan'));
+ }
+
+
const reservation = mkReservation($scope.user, $scope.events.reserved, $scope.selectedPlan);
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
@@ -262,6 +285,21 @@ Application.Directives.directive('cart', [ '$rootScope', '$uibModal', 'dialogs',
*/
var slotSelectionChanged = function () {
if ($scope.slot) {
+ // build a list of plans if this slot is restricted for subcriptions
+ if ($scope.slot.plan_ids.length > 0) {
+ const _plans = _.filter($scope.plans, p => _.include($scope.slot.plan_ids, p.id));
+ $scope.slot.plansGrouped = [];
+ 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) {
+ $scope.slot.plansGrouped.push(groupObj);
+ }
+ }
+ }
+
if (!$scope.slot.is_reserved && !$scope.events.modifiable && !$scope.slot.is_completed) {
// slot is not reserved and we are not currently modifying a slot
// -> can be added to cart or removed if already present
diff --git a/app/assets/templates/admin/calendar/eventModal.html.erb b/app/assets/templates/admin/calendar/eventModal.html.erb
index 3b7f245c1..6773e1e78 100644
--- a/app/assets/templates/admin/calendar/eventModal.html.erb
+++ b/app/assets/templates/admin/calendar/eventModal.html.erb
@@ -93,12 +93,41 @@
+