Skip to content

Commit

Permalink
Merge branch 'dev' for release 6.0.13
Browse files Browse the repository at this point in the history
  • Loading branch information
gnepud committed Aug 28, 2023
2 parents 1e862d5 + d48fc08 commit 5e6e4b8
Show file tree
Hide file tree
Showing 89 changed files with 3,630 additions and 2,727 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog Fab-manager

## v6.0.13 2023 August 28

- Fix a bug: unable to cancel a payment schedule
- adds reservation context feature (for machine, training, space)
- adds coupon in statistic export (for subscription, machine, training, space, event, order)
- [TODO DEPLOY] `rails db:seed`
- [TODO DEPLOY] `rails fablab:es:build_stats`
- [TODO DEPLOY] `rails fablab:maintenance:regenerate_statistics[2014,1]`

## v6.0.12 2023 August 14

- Fix a bug: event reserved places compute error
Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/project_categories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
class API::ProjectCategoriesController < ApplicationController
before_action :set_project_category, only: %i[update destroy]
before_action :authenticate_user!, only: %i[create update destroy]

def index
@project_categories = ProjectCategory.all
end
Expand Down
58 changes: 58 additions & 0 deletions app/controllers/api/reservation_contexts_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

# API Controller for resources of type ReservationContext
class API::ReservationContextsController < API::APIController
before_action :authenticate_user!, except: [:index]
before_action :set_reservation_context, only: %i[show update destroy]

def index
@reservation_contexts = ReservationContext.all
@reservation_contexts = @reservation_contexts.applicable_on(params[:applicable_on]) if params[:applicable_on].present?
@reservation_contexts = @reservation_contexts.order(:created_at)
end

def show; end

def create
authorize ReservationContext
@reservation_context = ReservationContext.new(reservation_context_params)
if @reservation_context.save
render :show, status: :created, location: @reservation_context
else
render json: @reservation_context.errors, status: :unprocessable_entity
end
end

def update
authorize ReservationContext
if @reservation_context.update(reservation_context_params)
render :show, status: :ok, location: @reservation_context
else
render json: @reservation_context.errors, status: :unprocessable_entity
end
end

def destroy
authorize ReservationContext
if @reservation_context.safe_destroy
head :no_content
else
render json: @reservation_context.errors, status: :unprocessable_entity
end
end

def applicable_on_values
authorize ReservationContext
render json: ReservationContext::APPLICABLE_ON, status: :ok
end

private

def set_reservation_context
@reservation_context = ReservationContext.find(params[:id])
end

def reservation_context_params
params.require(:reservation_context).permit(:name, applicable_on: [])
end
end
6 changes: 6 additions & 0 deletions app/controllers/api/subscriptions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ def payment_details

def cancel
authorize @subscription
payment_schedule = @subscription.original_payment_schedule
if payment_schedule
PaymentScheduleService.cancel(payment_schedule)
render :show, status: :ok, location: @subscription and return
end

if @subscription.expire
render :show, status: :ok, location: @subscription
else
Expand Down
10 changes: 10 additions & 0 deletions app/frontend/src/javascript/api/reservation_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ReservationContext } from '../models/reservation';

export default class ReservationContextAPI {
static async index (): Promise<Array<ReservationContext>> {
const res: AxiosResponse<Array<ReservationContext>> = await apiClient.get('/api/reservation_contexts');
return res?.data;
}
}
82 changes: 65 additions & 17 deletions app/frontend/src/javascript/controllers/admin/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
*/
'use strict';

Application.Controllers.controller('SettingsController', ['$scope', '$rootScope', '$filter', '$uibModal', 'dialogs', 'Setting', 'growl', 'settingsPromise', 'privacyDraftsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'profileImageFile', 'CSRF', '_t', 'Member', 'uiTourService',
function ($scope, $rootScope, $filter, $uibModal, dialogs, Setting, growl, settingsPromise, privacyDraftsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, profileImageFile, CSRF, _t, Member, uiTourService) {
Application.Controllers.controller('SettingsController', ['$scope', '$rootScope', '$filter', '$uibModal', 'dialogs', 'Setting', 'growl', 'settingsPromise', 'privacyDraftsPromise', 'cgvFile', 'cguFile', 'logoFile', 'logoBlackFile', 'faviconFile', 'profileImageFile', 'reservationContextsPromise', 'reservationContextApplicableOnValuesPromise', 'CSRF', '_t', 'Member', 'uiTourService', 'ReservationContext',
function ($scope, $rootScope, $filter, $uibModal, dialogs, Setting, growl, settingsPromise, privacyDraftsPromise, cgvFile, cguFile, logoFile, logoBlackFile, faviconFile, profileImageFile, reservationContextsPromise, reservationContextApplicableOnValuesPromise, CSRF, _t, Member, uiTourService, ReservationContext) {
/* PUBLIC SCOPE */

// timepickers steps configuration
Expand Down Expand Up @@ -76,13 +76,19 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
$scope.mainColorSetting = { name: 'main_color', value: settingsPromise.main_color };
$scope.secondColorSetting = { name: 'secondary_color', value: settingsPromise.secondary_color };
$scope.nameGenre = { name: 'name_genre', value: settingsPromise.name_genre };
$scope.reservationContextFeature = { name: 'reservation_context_feature', value: settingsPromise.reservation_context_feature };
$scope.cguFile = cguFile.custom_asset;
$scope.cgvFile = cgvFile.custom_asset;
$scope.customLogo = logoFile.custom_asset;
$scope.customLogoBlack = logoBlackFile.custom_asset;
$scope.customFavicon = faviconFile.custom_asset;
$scope.profileImage = profileImageFile.custom_asset;

// necessary initialisation for reservation context
$scope.reservationContexts = reservationContextsPromise;
$scope.reservationContextApplicableOnValues = reservationContextApplicableOnValuesPromise;
$scope.newReservationContext = null;

// By default, we display the currently published privacy policy
$scope.privacyPolicy = {
version: null,
Expand Down Expand Up @@ -314,21 +320,6 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
$scope.codeMirrorEditor = editor;
};

/**
* Shows a success message forwarded from a child react component
*/
$scope.onSuccess = function (message) {
growl.success(message);
};

/**
* Callback triggered by react components
*/
$scope.onError = function (message) {
console.error(message);
growl.error(message);
};

/**
* Options for allow/prevent book overlapping slots: which kind of slots are used in the overlapping computation
*/
Expand Down Expand Up @@ -476,6 +467,63 @@ Application.Controllers.controller('SettingsController', ['$scope', '$rootScope'
growl.error(message);
};

// Functions for reservation context feature

$scope.onReservationContextFeatureChange = function (value) {
$scope.reservationContextFeature.value = value;
};

$scope.saveReservationContext = function (data, id) {
if (id != null) {
return ReservationContext.update({ id }, data);
} else {
return ReservationContext.save(data, function (resp) { $scope.reservationContexts[$scope.reservationContexts.length - 1].id = resp.id; });
}
};

$scope.removeReservationContext = function (index) {
if ($scope.reservationContexts[index].related_to > 0) {
growl.error(_t('app.admin.settings.unable_to_delete_reservation_context_already_related_to_reservations'));
return false;
}
return dialogs.confirm({
resolve: {
object () {
return {
title: _t('app.admin.settings.confirmation_required'),
msg: _t('app.admin.settings.do_you_really_want_to_delete_this_reservation_context')
};
}
}
}
, function () { // delete confirmed
ReservationContext.delete($scope.reservationContexts[index], null, function () { $scope.reservationContexts.splice(index, 1); }
, function () { growl.error(_t('app.admin.settings.unable_to_delete_reservation_context_an_error_occured')); });
});
};

$scope.addReservationContext = function () {
$scope.newReservationContext = {
name: '',
related_to: 0
};
return $scope.reservationContexts.push($scope.newReservationContext);
};

$scope.cancelReservationContext = function (rowform, index) {
if ($scope.reservationContexts[index].id != null) {
return rowform.$cancel();
} else {
return $scope.reservationContexts.splice(index, 1);
}
};

$scope.translateApplicableOnValue = function (value) {
if (!value) { return; }
if (angular.isArray(value)) { return value.map(v => _t(`app.admin.reservation_contexts.${v}`)).join(', '); }
return _t(`app.admin.reservation_contexts.${value}`);
};

/* PRIVATE SCOPE */

/**
Expand Down
39 changes: 26 additions & 13 deletions app/frontend/src/javascript/controllers/admin/statistics.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/
'use strict';

Application.Controllers.controller('StatisticsController', ['$scope', '$state', '$transitions', '$rootScope', '$uibModal', 'es', 'Member', '_t', 'membersPromise', 'statisticsPromise', 'uiTourService', 'settingsPromise',
function ($scope, $state, $transitions, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise, uiTourService, settingsPromise) {
Application.Controllers.controller('StatisticsController', ['$scope', '$state', '$transitions', '$rootScope', '$uibModal', 'es', 'Member', '_t', 'membersPromise', 'statisticsPromise', 'uiTourService', 'settingsPromise', 'reservationContextsPromise', 'reservationContextApplicableOnValuesPromise',
function ($scope, $state, $transitions, $rootScope, $uibModal, es, Member, _t, membersPromise, statisticsPromise, uiTourService, settingsPromise, reservationContextsPromise, reservationContextApplicableOnValuesPromise) {
/* PRIVATE STATIC CONSTANTS */

// search window size
Expand Down Expand Up @@ -133,6 +133,18 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
}
};

$scope.reservationContextFeatureEnabled = settingsPromise.reservation_context_feature === 'true';

$scope.reservationContexts = reservationContextsPromise;

$scope.reservationContexts = reservationContextsPromise.filter(rc => rc.applicable_on.length > 0);

$scope.reservationContextApplicableOnValues = reservationContextApplicableOnValuesPromise;

$scope.reservationContextIsApplicable = function (esTypeKey) {
return $scope.reservationContextApplicableOnValues.includes(esTypeKey);
};

/**
* Return a localized name for the given field
*/
Expand Down Expand Up @@ -220,17 +232,9 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
*/
$scope.formatDate = function (date) { return moment(date).format('LL'); };

/**
* Parse the sex and return a user-friendly string
* @param sex {string} 'male' | 'female'
*/
$scope.formatSex = function (sex) {
if (sex === 'male') {
return _t('app.admin.statistics.man');
}
if (sex === 'female') {
return _t('app.admin.statistics.woman');
}
$scope.formatReservationContext = function (id) {
if (id === null || id === undefined) { return; }
return $scope.reservationContexts.find(rc => rc.id === id).name;
};

/**
Expand Down Expand Up @@ -645,6 +649,15 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state',
{ key: 'ca', label: _t('app.admin.statistics.revenue'), values: ['input_number'] }
];

if ($scope.reservationContextFeatureEnabled && $scope.reservationContextIsApplicable($scope.selectedIndex.es_type_key)) {
const reservationContextValues = $scope.reservationContexts.map((rc) => {
return { key: rc.id, label: rc.name };
});
$scope.filters.push({
key: 'reservationContextId', label: _t('app.admin.statistics.reservation_context'), values: reservationContextValues
});
}

// if no plans were created, there's no types for statisticIndex=subscriptions
if ($scope.type.active) {
$scope.filters.splice(4, 0, { key: 'subType', label: _t('app.admin.statistics.type'), values: $scope.type.active.subtypes });
Expand Down
11 changes: 9 additions & 2 deletions app/frontend/src/javascript/controllers/machines.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ Application.Controllers.controller('ShowMachineController', ['$scope', '$state',
* This controller workflow is pretty similar to the trainings reservation controller.
*/

Application.Controllers.controller('ReserveMachineController', ['$scope', '$transition$', '_t', 'moment', 'Auth', '$timeout', 'Member', 'Availability', 'plansPromise', 'groupsPromise', 'machinePromise', 'settingsPromise', 'uiCalendarConfig', 'CalendarConfig', 'Reservation', 'growl', 'helpers', 'AuthService',
function ($scope, $transition$, _t, moment, Auth, $timeout, Member, Availability, plansPromise, groupsPromise, machinePromise, settingsPromise, uiCalendarConfig, CalendarConfig, Reservation, growl, helpers, AuthService) {
Application.Controllers.controller('ReserveMachineController', ['$scope', '$transition$', '_t', 'moment', 'Auth', '$timeout', 'Member', 'Availability', 'plansPromise', 'groupsPromise', 'machinePromise', 'settingsPromise', 'reservationContextsPromise', 'uiCalendarConfig', 'CalendarConfig', 'Reservation', 'growl', 'helpers', 'AuthService',
function ($scope, $transition$, _t, moment, Auth, $timeout, Member, Availability, plansPromise, groupsPromise, machinePromise, settingsPromise, reservationContextsPromise, uiCalendarConfig, CalendarConfig, Reservation, growl, helpers, AuthService) {
/* PRIVATE STATIC CONSTANTS */

// Slot free to be booked
Expand Down Expand Up @@ -445,6 +445,13 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$tran
// will be set to a Promise and resolved after the payment is sone
$scope.afterPaymentPromise = null;

// reservation contexts stuff
if (settingsPromise.reservation_context_feature === 'true') {
$scope.reservationContexts = reservationContextsPromise;
} else {
$scope.reservationContexts = [];
}

// fullCalendar (v2) configuration
$scope.calendarConfig = CalendarConfig({
minTime: moment.duration(moment.utc(settingsPromise.booking_window_start.match(/\d{4}-\d{2}-\d{2}(?: |T)\d{2}:\d{2}:\d{2}/)[0]).format('HH:mm:ss')),
Expand Down
11 changes: 9 additions & 2 deletions app/frontend/src/javascript/controllers/spaces.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ Application.Controllers.controller('ShowSpaceController', ['$scope', '$state', '
* per slots.
*/

Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transition$', 'Auth', '$timeout', 'Availability', 'Member', 'plansPromise', 'groupsPromise', 'settingsPromise', 'spacePromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Reservation', 'helpers', 'AuthService',
function ($scope, $transition$, Auth, $timeout, Availability, Member, plansPromise, groupsPromise, settingsPromise, spacePromise, _t, uiCalendarConfig, CalendarConfig, Reservation, helpers, AuthService) {
Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transition$', 'Auth', '$timeout', 'Availability', 'Member', 'plansPromise', 'groupsPromise', 'settingsPromise', 'spacePromise', 'reservationContextsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Reservation', 'helpers', 'AuthService',
function ($scope, $transition$, Auth, $timeout, Availability, Member, plansPromise, groupsPromise, settingsPromise, spacePromise, reservationContextsPromise, _t, uiCalendarConfig, CalendarConfig, Reservation, helpers, AuthService) {
/* PRIVATE STATIC CONSTANTS */

// Color of the selected event backgound
Expand Down Expand Up @@ -407,6 +407,13 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transi
// Global config: is the user validation required ?
$scope.enableUserValidationRequired = settingsPromise.user_validation_required === 'true';

// reservation contexts stuff
if (settingsPromise.reservation_context_feature === 'true') {
$scope.reservationContexts = reservationContextsPromise;
} else {
$scope.reservationContexts = [];
}

/**
* Change the last selected slot's appearance to looks like 'added to cart'
*/
Expand Down
11 changes: 9 additions & 2 deletions app/frontend/src/javascript/controllers/trainings.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ Application.Controllers.controller('ShowTrainingController', ['$scope', '$state'
* training can be reserved during the reservation process (the shopping cart may contain only one training and a subscription).
*/

Application.Controllers.controller('ReserveTrainingController', ['$scope', '$transition$', 'Auth', 'AuthService', '$timeout', 'Availability', 'Member', 'plansPromise', 'groupsPromise', 'settingsPromise', 'trainingPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Reservation', 'helpers',
function ($scope, $transition$, Auth, AuthService, $timeout, Availability, Member, plansPromise, groupsPromise, settingsPromise, trainingPromise, _t, uiCalendarConfig, CalendarConfig, Reservation, helpers) {
Application.Controllers.controller('ReserveTrainingController', ['$scope', '$transition$', 'Auth', 'AuthService', '$timeout', 'Availability', 'Member', 'plansPromise', 'groupsPromise', 'settingsPromise', 'trainingPromise', 'reservationContextsPromise', '_t', 'uiCalendarConfig', 'CalendarConfig', 'Reservation', 'helpers',
function ($scope, $transition$, Auth, AuthService, $timeout, Availability, Member, plansPromise, groupsPromise, settingsPromise, trainingPromise, reservationContextsPromise, _t, uiCalendarConfig, CalendarConfig, Reservation, helpers) {
/* PRIVATE STATIC CONSTANTS */

// Color of the selected event backgound
Expand Down Expand Up @@ -180,6 +180,13 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$tra
// Global config: is the user validation required ?
$scope.enableUserValidationRequired = settingsPromise.user_validation_required === 'true';

// reservation contexts stuff
if (settingsPromise.reservation_context_feature === 'true') {
$scope.reservationContexts = reservationContextsPromise;
} else {
$scope.reservationContexts = [];
}

/**
* Change the last selected slot's appearance to looks like 'added to cart'
*/
Expand Down
Loading

0 comments on commit 5e6e4b8

Please sign in to comment.